Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ Stable versions

4.2.1 (?):
Changes by Alice Rowan
- Support 24-bit and 32-bit output for the PulseAudio, ALSA,
WAV, file drivers (requires libxmp 4.7.0+) (FIXME).
- Use XMP_MAX_SRATE as the limit for -f instead of 48000
(requires libxmp 4.7.0+).
- Report pan value "---" for instruments/samples without
a default panning value (sub->pan < 0).
- Don't unmute muted IT channels unless explicitly unmuted by
the -S/--solo option.
- Use XMP_MAX_SRATE as the limit for -f instead of 48000.
- wav: fix incorrect RIFF length in output.
- wav: add terminal pad byte after odd length data chunks.
- file: fix incorrect handling of -Dendian ("big" would output
little endian, anything else would output big endian).
- pulseaudio: support 8-bit output.

4.2.0 (20230615):
Changes by Özkan Sezer:
Expand Down
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ directly to cmatsuoka@gmail.com.
LICENSE

Extended Module Player
Copyright (C) 1996-2022 Claudio Matsuoka and Hipolito Carraro Jr
Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Expand Down
6 changes: 6 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@

#include <xmp.h>

/* Allow building with <4.7.0 for now... */
#if XMP_VERCODE < 0x040700
#define XMP_FORMAT_32BIT (1 << 3)
#endif

struct player_mode {
const char *name;
const char *desc;
Expand All @@ -33,6 +38,7 @@ struct options {
int amplify; /* amplification factor */
int rate; /* sampling rate */
int format; /* sample format */
int format_downmix; /* XMP_FORMAT_32BIT may require downmix to 24 */
int max_time; /* max. replay time */
int mix; /* channel separation */
int defpan; /* default pan */
Expand Down
17 changes: 13 additions & 4 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Extended Module Player
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
*
* This file is part of the Extended Module Player and is distributed
* under the terms of the GNU General Public License. See the COPYING
Expand Down Expand Up @@ -335,13 +335,22 @@ int main(int argc, char **argv)
}

if (opt.verbose > 0) {
int format_bits;
if (opt.format & XMP_FORMAT_32BIT) {
format_bits = opt.format_downmix == 24 ? 24 : 32;
} else if (~opt.format & XMP_FORMAT_8BIT) {
format_bits = 16;
} else {
format_bits = 8;
}

report("Extended Module Player " VERSION "\n"
"Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr\n");
"Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr\n");

report("Using %s\n", sound->description());

report("Mixer set to %d Hz, %dbit, %s%s%s\n", opt.rate,
opt.format & XMP_FORMAT_8BIT ? 8 : 16,
report("Mixer set to %d Hz, %dbit, %s%s%s%s\n", opt.rate, format_bits,
opt.format & XMP_FORMAT_UNSIGNED ? "unsigned " : "signed ",
opt.interp == XMP_INTERP_LINEAR ? "linear interpolated " :
opt.interp == XMP_INTERP_SPLINE ? "cubic spline interpolated " : "",
opt.format & XMP_FORMAT_MONO ? "mono" : "stereo",
Expand Down
23 changes: 19 additions & 4 deletions src/options.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Extended Module Player
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
*
* This file is part of the Extended Module Player and is distributed
* under the terms of the GNU General Public License. See the COPYING
Expand Down Expand Up @@ -96,7 +96,7 @@ static void usage(char *s, struct options *options)
"\nMixer options:\n"
" -A --amiga Use Paula simulation mixer in Amiga formats\n"
" -a --amplify {0|1|2|3} Amplification factor: 0=Normal, 1=x2, 2=x4, 3=x8\n"
" -b --bits {8|16} Software mixer resolution (8 or 16 bits)\n"
" -b --bits {8|16|24|32} Software mixer resolution (8, 16, 24, or 32 bits)\n"
" -c --stdout Mix the module to stdout\n"
" -f --frequency rate Sampling rate in hertz (default 44100)\n"
" -i --interpolation {nearest|linear|spline}\n"
Expand Down Expand Up @@ -190,8 +190,19 @@ void get_options(int argc, char **argv, struct options *options)
options->amplify = atoi(optarg);
break;
case 'b':
if (atoi(optarg) == 8) {
options->format &= ~(XMP_FORMAT_8BIT | XMP_FORMAT_32BIT);
options->format_downmix = 0;
switch (atoi(optarg)) {
case 8:
options->format |= XMP_FORMAT_8BIT;
break;
case 24:
options->format |= XMP_FORMAT_32BIT;
options->format_downmix = 24;
break;
case 32:
options->format |= XMP_FORMAT_32BIT;
break;
}
break;
case 'C':
Expand Down Expand Up @@ -387,7 +398,11 @@ void get_options(int argc, char **argv, struct options *options)

if (xmp_vercode < 0x040700) {
if (options->rate > 48000)
options->rate = 48000; /* Old max. rate 48kHz */
options->rate = 48000; /* Old max. rate 48 kHz */

options->format &= ~XMP_FORMAT_32BIT;
options->format_downmix = 0;

}

/* apply guess if no driver selected */
Expand Down
105 changes: 97 additions & 8 deletions src/sound.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Extended Module Player
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
*
* This file is part of the Extended Module Player and is distributed
* under the terms of the GNU General Public License. See the COPYING
Expand Down Expand Up @@ -101,16 +101,105 @@ const struct sound_driver *select_sound_driver(struct options *options)
return NULL;
}

/* Convert little-endian 16 bit samples to big-endian */
void convert_endian(unsigned char *p, int l)
/* Downmix 32-bit to 24-bit aligned (in-place) */
void downmix_32_to_24_aligned(unsigned char *buffer, int buffer_bytes)
{
/* Note: most 24-bit support ignores the high byte.
* sndio, however, expects 24-bit to be sign extended. */
int *buf32 = (int *)buffer;
int i;

for (i = 0; i < buffer_bytes; i += 4) {
*buf32 >>= 8;
buf32++;
}
}

/* Downmix 32-bit to 24-bit packed (in-place).
* Returns the new number of useful bytes in the buffer. */
int downmix_32_to_24_packed(unsigned char *buffer, int buffer_bytes)
{
unsigned char *out = buffer;
int buffer_samples = buffer_bytes >> 2;
int i;

/* Big endian 32-bit (22 11 00 XX) -> 24-bit (22 11 00)
* Little endian 32-bit (XX 00 11 22) -> 24-bit (00 11 22)
* Skip the first byte for little endian to allow reusing the same loop.
*/
if (!is_big_endian()) {
buffer++;
}

for (i = 0; i < buffer_samples; i++) {
out[0] = buffer[0];
out[1] = buffer[1];
out[2] = buffer[2];
out += 3;
buffer += 4;
}

return buffer_samples * 3;
}


/* Convert native endian 16-bit samples for file IO */
void convert_endian_16bit(unsigned char *buffer, int buffer_bytes)
{
unsigned char b;
int i;

for (i = 0; i < l; i++) {
b = p[0];
p[0] = p[1];
p[1] = b;
p += 2;
for (i = 0; i < buffer_bytes; i += 2) {
b = buffer[0];
buffer[0] = buffer[1];
buffer[1] = b;
buffer += 2;
}
}

/* Convert native endian 24-bit unaligned samples for file IO */
void convert_endian_24bit(unsigned char *buffer, int buffer_bytes)
{
unsigned char b;
int i;

for (i = 0; i < buffer_bytes; i += 3) {
b = buffer[0];
buffer[0] = buffer[2];
buffer[2] = b;
buffer += 3;
}
}

/* Convert native endian 32-bit samples for file IO */
void convert_endian_32bit(unsigned char *buffer, int buffer_bytes)
{
unsigned char a, b;
int i;

for (i = 0; i < buffer_bytes; i += 4) {
a = buffer[0];
b = buffer[1];
buffer[0] = buffer[3];
buffer[1] = buffer[2];
buffer[2] = b;
buffer[3] = a;
buffer += 4;
}
}

/* Convert native endian 16-bit, 24-bit, or 32-bit samples for file IO */
void convert_endian(unsigned char *buffer, int buffer_bytes, int bits)
{
switch (bits) {
case 16:
convert_endian_16bit(buffer, buffer_bytes);
break;
case 24:
convert_endian_24bit(buffer, buffer_bytes);
break;
case 32:
convert_endian_32bit(buffer, buffer_bytes);
break;
}
}
50 changes: 48 additions & 2 deletions src/sound.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,59 @@ extern const struct sound_driver *const sound_driver_list[];
#define chkparm2(x,y,z,w) { if (!strcmp(s, x)) { \
if (2 > sscanf(token, y, z, w)) parm_error(); } }

static inline int is_big_endian(void) {
static inline int is_big_endian(void)
{
unsigned short w = 0x00ff;
return (*(char *)&w == 0x00);
}

static inline int get_bits_from_format(const struct options *options)
{
if ((options->format & XMP_FORMAT_32BIT) && options->format_downmix != 24) {
return 32;
} else if (options->format & XMP_FORMAT_32BIT) {
return 24;
} else if (~options->format & XMP_FORMAT_8BIT) {
return 16;
} else {
return 8;
}
}

static inline int get_signed_from_format(const struct options *options)
{
return !(options->format & XMP_FORMAT_UNSIGNED);
}

static inline void update_format(struct options *options, int bits, int is_signed)
{
options->format &= ~(XMP_FORMAT_32BIT | XMP_FORMAT_8BIT | XMP_FORMAT_UNSIGNED);
options->format_downmix = 0;

switch (bits) {
case 8:
options->format |= XMP_FORMAT_8BIT;
break;
case 24:
options->format |= XMP_FORMAT_32BIT;
options->format_downmix = 24;
break;
case 32:
options->format |= XMP_FORMAT_32BIT;
break;
}
if (!is_signed) {
options->format |= XMP_FORMAT_UNSIGNED;
}
}

void init_sound_drivers(void);
const struct sound_driver *select_sound_driver(struct options *);
void convert_endian(unsigned char *, int);
void downmix_32_to_24_aligned(unsigned char *, int);
int downmix_32_to_24_packed(unsigned char *, int);
void convert_endian_16bit(unsigned char *, int);
void convert_endian_24bit(unsigned char *, int);
void convert_endian_32bit(unsigned char *, int);
void convert_endian(unsigned char *, int, int);

#endif
1 change: 1 addition & 0 deletions src/sound_ahi.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ static int init(struct options *options) {
if (AHIBuf[1]) {
/* driver is initialized before calling libxmp, so this is OK : */
options->format &= ~XMP_FORMAT_UNSIGNED;/* no unsigned with AHI */
options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/sound_aiff.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ static int init(struct options *options)
} else {
fd = stdout;
}
options->format &= ~XMP_FORMAT_32BIT;

fwrite(hed, 1, 54, fd);

Expand All @@ -107,8 +108,8 @@ static int init(struct options *options)

static void play(void *b, int len)
{
if (swap_endian && bits == 16) {
convert_endian((unsigned char *)b, len);
if (swap_endian) {
convert_endian((unsigned char *)b, len, bits);
}
fwrite(b, 1, len, fd);
size += len;
Expand Down
4 changes: 3 additions & 1 deletion src/sound_aix.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Extended Module Player
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
*
* This file is part of the Extended Module Player and is distributed
* under the terms of the GNU General Public License. See the COPYING
Expand Down Expand Up @@ -83,6 +83,8 @@ static int init(struct options *options)
close(audio_fd);
return -1;
}
options->format &= ~XMP_FORMAT_32BIT;

return 0;
}

Expand Down
Loading