diff --git a/Changelog b/Changelog
index a3e9f631..4bc1add8 100644
--- a/Changelog
+++ b/Changelog
@@ -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:
diff --git a/README b/README
index 8b4e9d11..fdcbdd2a 100644
--- a/README
+++ b/README
@@ -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
diff --git a/src/common.h b/src/common.h
index 6df19f8d..aac5154b 100644
--- a/src/common.h
+++ b/src/common.h
@@ -22,6 +22,11 @@
#include
+/* 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;
@@ -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 */
diff --git a/src/main.c b/src/main.c
index 780959c7..275d607d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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
@@ -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",
diff --git a/src/options.c b/src/options.c
index 27fb9045..856f64d4 100644
--- a/src/options.c
+++ b/src/options.c
@@ -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
@@ -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"
@@ -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':
@@ -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 */
diff --git a/src/sound.c b/src/sound.c
index 2bacd6fb..1c0a94b5 100644
--- a/src/sound.c
+++ b/src/sound.c
@@ -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
@@ -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;
}
}
diff --git a/src/sound.h b/src/sound.h
index b8eaa6ff..297379c7 100644
--- a/src/sound.h
+++ b/src/sound.h
@@ -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
diff --git a/src/sound_ahi.c b/src/sound_ahi.c
index 0f3efb61..7b7bbae9 100644
--- a/src/sound_ahi.c
+++ b/src/sound_ahi.c
@@ -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;
}
}
diff --git a/src/sound_aiff.c b/src/sound_aiff.c
index a25c09ae..e7c01008 100644
--- a/src/sound_aiff.c
+++ b/src/sound_aiff.c
@@ -99,6 +99,7 @@ static int init(struct options *options)
} else {
fd = stdout;
}
+ options->format &= ~XMP_FORMAT_32BIT;
fwrite(hed, 1, 54, fd);
@@ -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;
diff --git a/src/sound_aix.c b/src/sound_aix.c
index b607a951..224e15d0 100644
--- a/src/sound_aix.c
+++ b/src/sound_aix.c
@@ -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
@@ -83,6 +83,8 @@ static int init(struct options *options)
close(audio_fd);
return -1;
}
+ options->format &= ~XMP_FORMAT_32BIT;
+
return 0;
}
diff --git a/src/sound_alsa.c b/src/sound_alsa.c
index 89050cf5..94df0e41 100644
--- a/src/sound_alsa.c
+++ b/src/sound_alsa.c
@@ -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
@@ -11,6 +11,7 @@
#include "sound.h"
static snd_pcm_t *pcm_handle;
+static int downmix_24;
static int init(struct options *options)
{
@@ -39,12 +40,20 @@ static int init(struct options *options)
}
channels = format & XMP_FORMAT_MONO ? 1 : 2;
- if (format & XMP_FORMAT_UNSIGNED) {
- fmt = format & XMP_FORMAT_8BIT ?
- SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_U16;
+ downmix_24 = 0;
+ if ((format & XMP_FORMAT_32BIT) && options->format_downmix != 24) {
+ fmt = format & XMP_FORMAT_UNSIGNED ?
+ SND_PCM_FORMAT_U32 : SND_PCM_FORMAT_S32;
+ } else if ((format & XMP_FORMAT_32BIT) && options->format_downmix == 24) {
+ fmt = format & XMP_FORMAT_UNSIGNED ?
+ SND_PCM_FORMAT_U24 : SND_PCM_FORMAT_S24;
+ downmix_24 = 1;
+ } else if (~format & XMP_FORMAT_8BIT) {
+ fmt = format & XMP_FORMAT_UNSIGNED ?
+ SND_PCM_FORMAT_U16 : SND_PCM_FORMAT_S16;
} else {
- fmt = format & XMP_FORMAT_8BIT ?
- SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_S16;
+ fmt = format & XMP_FORMAT_UNSIGNED ?
+ SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S8;
}
snd_pcm_hw_params_alloca(&hwparams);
@@ -84,6 +93,10 @@ static void play(void *b, int i)
{
int frames = snd_pcm_bytes_to_frames(pcm_handle, i);
+ if (downmix_24) {
+ downmix_32_to_24_aligned((unsigned char *)b, i);
+ }
+
if (snd_pcm_writei(pcm_handle, b, frames) < 0) {
snd_pcm_prepare(pcm_handle);
}
diff --git a/src/sound_alsa05.c b/src/sound_alsa05.c
index a6dbf486..9ef8ad2d 100644
--- a/src/sound_alsa05.c
+++ b/src/sound_alsa05.c
@@ -159,6 +159,7 @@ static int init(struct options *options)
printf("Unable to setup channel: %s\n", snd_strerror(rc));
return -1;
}
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/sound_beos.cpp b/src/sound_beos.cpp
index 95ae6077..70b9fa2f 100644
--- a/src/sound_beos.cpp
+++ b/src/sound_beos.cpp
@@ -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
@@ -121,7 +121,7 @@ static int read_buffer(unsigned char *data, int len)
return len;
}
-static void render_proc(void *theCookie, void *buffer, size_t req,
+static void render_proc(void *theCookie, void *buffer, size_t req,
const media_raw_audio_format &format)
{
size_t amt;
@@ -161,6 +161,7 @@ static int init(struct options *options)
player = new BSoundPlayer(&fmt, "xmp output", render_proc);
+ options->format &= ~ XMP_FORMAT_32BIT;
return 0;
}
@@ -182,7 +183,7 @@ static void play(void *b, int i)
}
if (paused) {
- player->Start();
+ player->Start();
player->SetHasData(true);
paused = 0;
}
@@ -190,7 +191,7 @@ static void play(void *b, int i)
static void deinit(void)
{
- player->Stop();
+ player->Stop();
be_app->Lock();
be_app->Quit();
}
diff --git a/src/sound_bsd.c b/src/sound_bsd.c
index 11b1d0db..93c0d3d9 100644
--- a/src/sound_bsd.c
+++ b/src/sound_bsd.c
@@ -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
@@ -60,6 +60,7 @@ static int init(struct options *options)
close(audio_fd);
return -1;
}
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/sound_coreaudio.c b/src/sound_coreaudio.c
index aa47e4c7..67679fb2 100644
--- a/src/sound_coreaudio.c
+++ b/src/sound_coreaudio.c
@@ -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
@@ -208,6 +208,7 @@ static int init(struct options *options)
kAudioUnitScope_Input, 0, &rc, sizeof(rc))))
goto err2;
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
err2:
diff --git a/src/sound_dart.c b/src/sound_dart.c
index 57d92a49..97022076 100644
--- a/src/sound_dart.c
+++ b/src/sound_dart.c
@@ -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
@@ -152,6 +152,7 @@ static int init(struct options *options)
memset(MixBuffers[1].pBuffer, /*32767 */ 0, bsize);
MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, MixBuffers, 2);
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/sound_file.c b/src/sound_file.c
index a794a07c..52507e17 100644
--- a/src/sound_file.c
+++ b/src/sound_file.c
@@ -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
@@ -11,6 +11,7 @@
#include "sound.h"
static FILE *fd;
+static int bits;
static long size;
static int swap_endian;
@@ -18,11 +19,19 @@ static int init(struct options *options)
{
char **parm = options->driver_parm;
+ if (options->format & XMP_FORMAT_32BIT) {
+ bits = options->format_downmix == 24 ? 24 : 32;
+ } else if (~options->format & XMP_FORMAT_8BIT) {
+ bits = 16;
+ } else {
+ bits = 8;
+ }
+
swap_endian = 0;
parm_init(parm);
chkparm1("endian",
- swap_endian = (is_big_endian() ^ strcmp(token, "big")));
+ swap_endian = (is_big_endian() ^ !strcmp(token, "big")));
parm_end();
if (options->out_file == NULL) {
@@ -42,8 +51,11 @@ static int init(struct options *options)
static void play(void *b, int len)
{
+ if (bits == 24) {
+ len = downmix_32_to_24_packed((unsigned char *)b, len);
+ }
if (swap_endian) {
- convert_endian((unsigned char *)b, len);
+ convert_endian((unsigned char *)b, len, bits);
}
fwrite(b, 1, len, fd);
size += len;
diff --git a/src/sound_hpux.c b/src/sound_hpux.c
index d7fc02ba..395060b7 100644
--- a/src/sound_hpux.c
+++ b/src/sound_hpux.c
@@ -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
@@ -101,6 +101,7 @@ static int init(struct options *options)
ioctl(audio_fd, AUDIO_SET_TXBUFSIZE, bsize);
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
err1:
diff --git a/src/sound_netbsd.c b/src/sound_netbsd.c
index ff9892e8..e0dd505a 100644
--- a/src/sound_netbsd.c
+++ b/src/sound_netbsd.c
@@ -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
@@ -22,14 +22,76 @@
static int audio_fd;
static int audioctl_fd;
+static const struct options *opts;
+#define MATCH_ENCODING(s, r) \
+ ((r) == AUDIO_ENCODING_ULINEAR ? \
+ ((s) == AUDIO_ENCODING_ULINEAR || (s) == native_unsigned) : \
+ ((s) == AUDIO_ENCODING_SLINEAR || (s) == native_signed))
+
+#define ANY_LINEAR(s) \
+ ((s) == AUDIO_ENCODING_ULINEAR || (s) == native_unsigned || \
+ (s) == AUDIO_ENCODING_SLINEAR || (s) == native_signed)
+
+static void to_fmt(int *precision, int *encoding, const struct options *options)
+{
+ /* The NE defines only exist in kernel space for some reason... */
+ int native_signed = is_big_endian() ? AUDIO_ENCODING_SLINEAR_BE :
+ AUDIO_ENCODING_SLINEAR_LE;
+ int native_unsigned = is_big_endian() ? AUDIO_ENCODING_ULINEAR_BE :
+ AUDIO_ENCODING_ULINEAR_LE;
+
+ audio_encoding_t enc;
+ int match_precision = -1;
+ int match_encoding = -1;
+ int depth, uns, i;
+
+ depth = get_bits_from_format(options);
+ uns = get_signed_from_format(options) ? AUDIO_ENCODING_SLINEAR :
+ AUDIO_ENCODING_ULINEAR;
+
+ memset(&enc, 0, sizeof(enc));
+
+ for (i = 0; i < 100; i++) {
+ enc.index = i;
+ if (ioctl(audioctl_fd, AUDIO_GETENC, &enc) < 0) {
+ break;
+ }
+ if (enc.precision < depth ||
+ (enc.precision > depth && match_precision == depth) ||
+ !ANY_LINEAR(enc.encoding)) {
+ continue;
+ }
+ if (enc.precision == depth || MATCH_ENCODING(enc.encoding, uns)) {
+ match_precision = depth;
+ match_encoding = uns;
+ }
+ if (enc.precision == depth && MATCH_ENCODING(enc.encoding, uns)) {
+ break;
+ }
+ }
+ if (match_precision >= 0) {
+ *precision = match_precision;
+ *encoding = match_encoding;
+ }
+}
+
+static int is_signed(int encoding)
+{
+ return encoding == AUDIO_ENCODING_SLINEAR ||
+ encoding == AUDIO_ENCODING_SLINEAR_BE ||
+ encoding == AUDIO_ENCODING_SLINEAR_LE;
+}
+
static int init(struct options *options)
{
char **parm = options->driver_parm;
audio_info_t ainfo;
int gain = 128;
int bsize = 32 * 1024;
+ int precision = 16;
+ int encoding = AUDIO_ENCODING_SLINEAR;
parm_init(parm);
chkparm1("gain", gain = strtoul(token, NULL, 0));
@@ -59,6 +121,8 @@ static int init(struct options *options)
return -1;
}
+ to_fmt(&precision, &encoding, options);
+
close(audioctl_fd);
if (gain < AUDIO_MIN_GAIN)
@@ -71,17 +135,8 @@ static int init(struct options *options)
ainfo.mode = AUMODE_PLAY_ALL;
ainfo.play.sample_rate = options->rate;
ainfo.play.channels = options->format & XMP_FORMAT_MONO ? 1 : 2;
-
- if (options->format & XMP_FORMAT_8BIT) {
- ainfo.play.precision = 8;
- ainfo.play.encoding = AUDIO_ENCODING_ULINEAR;
- options->format |= XMP_FORMAT_UNSIGNED;
- } else {
- ainfo.play.precision = 16;
- ainfo.play.encoding = AUDIO_ENCODING_SLINEAR;
- options->format &= ~XMP_FORMAT_UNSIGNED;
- }
-
+ ainfo.play.precision = precision;
+ ainfo.play.encoding = encoding;
ainfo.play.gain = gain;
ainfo.play.buffer_size = bsize;
ainfo.blocksize = 0;
@@ -91,6 +146,9 @@ static int init(struct options *options)
return -1;
}
+ update_format(options, precision, is_signed(encoding));
+ opts = options;
+
return 0;
}
@@ -98,6 +156,10 @@ static void play(void *b, int i)
{
int j;
+ if (opts->format_downmix == 24) {
+ downmix_32_to_24_aligned(b, i);
+ }
+
while (i) {
if ((j = write(audio_fd, b, i)) > 0) {
i -= j;
diff --git a/src/sound_oss.c b/src/sound_oss.c
index 463744c8..8676f8e7 100644
--- a/src/sound_oss.c
+++ b/src/sound_oss.c
@@ -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
@@ -40,21 +40,33 @@ static int audio_fd;
static int fragnum, fragsize;
static int do_sync = 1;
+static const struct options *opts;
static const char *desc_default = "OSS PCM audio";
static char descbuf[80] = {0};
-static int to_fmt(int format)
+static int to_fmt(const struct options *options)
{
int fmt;
- if (format & XMP_FORMAT_8BIT)
+ switch (get_bits_from_format(options)) {
+#if defined(AFMT_S32_NE)
+ case 32:
+ return AFMT_S32_NE;
+#endif
+#if defined(AFMT_S24_NE)
+ case 24:
+ return AFMT_S24_NE;
+#endif
+ case 8:
fmt = AFMT_U8 | AFMT_S8;
- else {
+ break;
+ default:
fmt = AFMT_S16_NE | AFMT_U16_NE;
+ break;
}
- if (format & XMP_FORMAT_UNSIGNED)
+ if (!get_signed_from_format(options))
fmt &= AFMT_U8 | AFMT_U16_LE | AFMT_U16_BE;
else
fmt &= AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE;
@@ -62,22 +74,32 @@ static int to_fmt(int format)
return fmt;
}
-static int from_fmt(int fmt)
+static void from_fmt(struct options *options, int fmt)
{
- int format = 0;
+ int bits = 16;
+ int sgn = 1;
+#if defined(AFMT_S32_NE)
+ if (fmt == AFMT_S32_NE) {
+ bits = 32;
+ } else
+#endif
+#if defined(AFMT_S24_NE)
+ if (fmt == AFMT_S24_NE) {
+ bits = 24;
+ } else
+#endif
if (!(fmt & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE))) {
- format |= XMP_FORMAT_8BIT;
+ bits = 8;
}
if (fmt & (AFMT_U8 | AFMT_U16_LE | AFMT_U16_BE)) {
- format |= XMP_FORMAT_UNSIGNED;
+ sgn = 0;
}
-
- return format;
+ update_format(options, bits, sgn);
}
-static void setaudio(int *rate, int *format)
+static void setaudio(struct options *options)
{
static int fragset = 0;
int frag = 0;
@@ -85,19 +107,19 @@ static void setaudio(int *rate, int *format)
frag = (fragnum << 16) + fragsize;
- fmt = to_fmt(*format);
+ fmt = to_fmt(options);
ioctl(audio_fd, SNDCTL_DSP_SETFMT, &fmt);
- *format = from_fmt(fmt);
+ from_fmt(options, fmt);
- fmt = !(*format & XMP_FORMAT_MONO);
+ fmt = !(options->format & XMP_FORMAT_MONO);
ioctl(audio_fd, SNDCTL_DSP_STEREO, &fmt);
if (fmt) {
- *format &= ~XMP_FORMAT_MONO;
+ options->format &= ~XMP_FORMAT_MONO;
} else {
- *format |= XMP_FORMAT_MONO;
+ options->format |= XMP_FORMAT_MONO;
}
- ioctl(audio_fd, SNDCTL_DSP_SPEED, rate);
+ ioctl(audio_fd, SNDCTL_DSP_SPEED, &options->rate);
/* Set the fragments only once */
if (!fragset) {
@@ -140,7 +162,8 @@ static int init(struct options *options)
if (audio_fd < 0)
return -1;
- setaudio(&options->rate, &options->format);
+ setaudio(options);
+ opts = options;
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == 0) {
snprintf(descbuf, sizeof(descbuf), "%s [%d fragments of %d bytes]",
@@ -158,6 +181,10 @@ static void play(void *b, int i)
{
int j;
+ if (opts->format_downmix == 24) {
+ downmix_32_to_24_aligned(b, i);
+ }
+
while (i) {
if ((j = write(audio_fd, b, i)) > 0) {
i -= j;
diff --git a/src/sound_pulseaudio.c b/src/sound_pulseaudio.c
index 4c9e8fbc..3ac0b729 100644
--- a/src/sound_pulseaudio.c
+++ b/src/sound_pulseaudio.c
@@ -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
@@ -12,16 +12,45 @@
#include "sound.h"
static pa_simple *s;
+static int downmix_24;
static int init(struct options *options)
{
pa_sample_spec ss;
+ int format = -1;
int error;
- options->format &= ~(XMP_FORMAT_UNSIGNED | XMP_FORMAT_8BIT);
+ downmix_24 = 0;
+ if ((options->format & XMP_FORMAT_32BIT) && options->format_downmix == 24) {
+#ifdef PA_SAMPLE_S24_32NE
+ format = PA_SAMPLE_S24_32NE;
+ downmix_24 = 1;
+#else
+ options->format_downmix = 0;
+#endif
+ }
+
+ if (format < 0 && (options->format & XMP_FORMAT_32BIT)) {
+#ifdef PA_SAMPLE_S32NE
+ format = PA_SAMPLE_S32NE;
+#else
+ options->format &= ~XMP_FORMAT_32BIT;
+#endif
+ }
+
+ if (format < 0 && (options->format & XMP_FORMAT_8BIT)) {
+ format = PA_SAMPLE_U8;
+ options->format |= XMP_FORMAT_UNSIGNED;
+ } else {
+ options->format &= ~XMP_FORMAT_UNSIGNED;
+ }
- ss.format = PA_SAMPLE_S16NE;
+ if (format < 0) {
+ format = PA_SAMPLE_S16NE;
+ }
+
+ ss.format = format;
ss.channels = options->format & XMP_FORMAT_MONO ? 1 : 2;
ss.rate = options->rate;
@@ -47,6 +76,10 @@ static void play(void *b, int i)
{
int j, error;
+ if (downmix_24) {
+ downmix_32_to_24_aligned(b, i);
+ }
+
do {
if ((j = pa_simple_write(s, b, i, &error)) > 0) {
i -= j;
diff --git a/src/sound_qnx.c b/src/sound_qnx.c
index 009aa79f..7495039b 100644
--- a/src/sound_qnx.c
+++ b/src/sound_qnx.c
@@ -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
@@ -66,6 +66,7 @@ static int init(struct options *options)
goto error;
}
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
error:
diff --git a/src/sound_sb.c b/src/sound_sb.c
index fd2a41fc..36206906 100644
--- a/src/sound_sb.c
+++ b/src/sound_sb.c
@@ -1,5 +1,5 @@
/* Extended Module Player
- * Copyright (C) 1996-2021 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
@@ -113,6 +113,7 @@ static int init(struct options *options)
fprintf(stderr, "Sound Blaster: DMA start failed.\n");
return -1;
}
+ options->format &= ~XMP_FORMAT_32BIT;
printf("Sound Blaster %s or compatible (%d bit, %s, %u Hz)\n", card, bits, mode, options->rate);
diff --git a/src/sound_sgi.c b/src/sound_sgi.c
index ed2c0147..436d3ff7 100644
--- a/src/sound_sgi.c
+++ b/src/sound_sgi.c
@@ -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
@@ -132,6 +132,7 @@ static int init(struct options *options)
if ((audio_port = ALopenport("xmp", "w", config)) == 0)
return -1;
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/sound_sndio.c b/src/sound_sndio.c
index 14b37dfd..db740238 100644
--- a/src/sound_sndio.c
+++ b/src/sound_sndio.c
@@ -21,6 +21,7 @@
#include "sound.h"
static struct sio_hdl *hdl;
+static const struct options *opts;
static int init(struct options *options)
{
@@ -38,15 +39,8 @@ static int init(struct options *options)
par.le = SIO_LE_NATIVE;
par.appbufsz = par.rate / 4;
- if (options->format & XMP_FORMAT_8BIT) {
- par.bits = 8;
- par.sig = 0;
- options->format |= XMP_FORMAT_UNSIGNED;
- } else {
- par.bits = 16;
- par.sig = 1;
- options->format &= ~XMP_FORMAT_UNSIGNED;
- }
+ par.bits = get_bits_from_format(options);
+ par.sig = get_signed_from_format(options);
askpar = par;
if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) {
@@ -54,9 +48,8 @@ static int init(struct options *options)
goto error;
}
- if ((par.bits == 16 && par.le != askpar.le) ||
- par.bits != askpar.bits ||
- par.sig != askpar.sig ||
+ if ((par.bits > 8 && par.le != askpar.le) ||
+ (par.bits != 8 && par.bits != 16 && par.bits != 24 && par.bits != 32) ||
par.pchan != askpar.pchan ||
(par.rate * 1000 < askpar.rate * 995) ||
(par.rate * 1000 > askpar.rate * 1005)) {
@@ -68,6 +61,10 @@ static int init(struct options *options)
fprintf(stderr, "%s: failed to start audio device\n", __func__);
goto error;
}
+
+ update_format(options, par.bits, par.sig);
+ opts = options;
+
return 0;
error:
@@ -83,6 +80,10 @@ static void deinit(void)
static void play(void *buf, int len)
{
+ if (opts->format_downmix == 24) {
+ downmix_32_to_24_aligned(buf, len);
+ }
+
if (buf != NULL) {
sio_write(hdl, buf, len);
}
diff --git a/src/sound_solaris.c b/src/sound_solaris.c
index 275c71de..5f32e983 100644
--- a/src/sound_solaris.c
+++ b/src/sound_solaris.c
@@ -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
@@ -137,6 +137,7 @@ static int init(struct options *options)
/* sound_solaris.description = "Solaris CS4231 PCM audio"; */
}
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/sound_wav.c b/src/sound_wav.c
index 0aa78fe0..6c4be460 100644
--- a/src/sound_wav.c
+++ b/src/sound_wav.c
@@ -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
@@ -10,10 +10,18 @@
#include
#include "sound.h"
+#define XMP_WAVE_FORMAT_PCM 0x0001
+#define XMP_WAVE_FORMAT_EXTENSIBLE 0xfffe
+
+static const char subformat[] = {
+ "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71"
+};
+
static FILE *fd;
-static int format_16bit;
+static int bits_per_sample;
static int swap_endian;
static long size;
+static long data_size_offset;
static void write_16l(FILE *f, unsigned short v)
{
@@ -47,7 +55,7 @@ static int init(struct options *options)
{
unsigned short chan;
unsigned int sampling_rate, bytes_per_second;
- unsigned short bytes_per_frame, bits_per_sample;
+ unsigned short bytes_per_frame;
swap_endian = is_big_endian();
@@ -70,27 +78,47 @@ static int init(struct options *options)
chan = options->format & XMP_FORMAT_MONO ? 1 : 2;
sampling_rate = options->rate;
- bits_per_sample = options->format & XMP_FORMAT_8BIT ? 8 : 16;
+ if (options->format & XMP_FORMAT_32BIT) {
+ bits_per_sample = options->format_downmix == 24 ? 24 : 32;
+ } else if (~options->format & XMP_FORMAT_8BIT) {
+ bits_per_sample = 16;
+ } else {
+ bits_per_sample = 8;
+ }
+
if (bits_per_sample == 8) {
options->format |= XMP_FORMAT_UNSIGNED;
- format_16bit = 0;
} else {
options->format &= ~XMP_FORMAT_UNSIGNED;
- format_16bit = 1;
}
bytes_per_frame = chan * bits_per_sample / 8;
bytes_per_second = sampling_rate * bytes_per_frame;
fwrite("fmt ", 1, 4, fd);
- write_32l(fd, 16);
- write_16l(fd, 1);
+ if (bits_per_sample > 16) {
+ write_32l(fd, 40);
+ write_16l(fd, XMP_WAVE_FORMAT_EXTENSIBLE);
+ data_size_offset = 64;
+ } else {
+ write_32l(fd, 16);
+ write_16l(fd, XMP_WAVE_FORMAT_PCM);
+ data_size_offset = 40;
+ }
write_16l(fd, chan);
write_32l(fd, sampling_rate);
write_32l(fd, bytes_per_second);
write_16l(fd, bytes_per_frame);
write_16l(fd, bits_per_sample);
+ if (bits_per_sample > 16) {
+ write_16l(fd, 22); /* size of extension */
+ write_16l(fd, bits_per_sample); /* valid bits per sample */
+ write_32l(fd, 0); /* chn position mask */
+ write_16l(fd, XMP_WAVE_FORMAT_PCM); /* subformat code */
+ fwrite(subformat, 1, 14, fd); /* subformat suffix */
+ }
+
fwrite("data", 1, 4, fd);
write_32l(fd, 0); /* will be written when finished */
@@ -101,8 +129,11 @@ static int init(struct options *options)
static void play(void *b, int len)
{
- if (swap_endian && format_16bit) {
- convert_endian((unsigned char *)b, len);
+ if (bits_per_sample == 24) {
+ len = downmix_32_to_24_packed((unsigned char *)b, len);
+ }
+ if (swap_endian) {
+ convert_endian((unsigned char *)b, len, bits_per_sample);
}
fwrite(b, 1, len, fd);
size += len;
@@ -110,11 +141,18 @@ static void play(void *b, int len)
static void deinit(void)
{
- if (fseek(fd, 40, SEEK_SET) == 0) {
+ /* Pad chunks to word boundaries, even at the end of the file. */
+ int pad = 0;
+ if (size & 1) {
+ fputc(0, fd);
+ pad = 1;
+ }
+
+ if (fseek(fd, data_size_offset, SEEK_SET) == 0) {
write_32l(fd, size);
}
if (fseek(fd, 4, SEEK_SET) == 0) {
- write_32l(fd, size + 40);
+ write_32l(fd, size + pad + (data_size_offset + 4 - 8));
}
if (fd && fd != stdout) {
diff --git a/src/sound_win32.c b/src/sound_win32.c
index 222bead2..c81ccdc4 100644
--- a/src/sound_win32.c
+++ b/src/sound_win32.c
@@ -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
@@ -120,6 +120,7 @@ static int init(struct options *options)
freebuffer = nextbuffer = 0;
+ options->format &= ~XMP_FORMAT_32BIT;
return 0;
}
diff --git a/src/xmp.1 b/src/xmp.1
index b3488594..c521c5bd 100644
--- a/src/xmp.1
+++ b/src/xmp.1
@@ -59,8 +59,8 @@ factors may cause distorted or noisy output\&.
.IP "\fB\-A, \-\-amiga\fP"
Use a mixer which models the sound of an Amiga 500\&, with or without the led filter\&.
.IP "\fB\-b, \-\-bits\fP \fIbits\fP"
-Set the software mixer resolution (8 or 16 bits)\&. If omitted,
-The audio device will be opened at the highest resolution available\&.
+Set the software mixer resolution (8, 16, 24, or 32 bits)\&. If omitted,
+The audio device will be opened at 16-bit resolution\&.
.IP "\fB\-C, \-\-show\-comments\fP"
Display module comment text, if any\&.
.IP "\fB\-c, \-\-stdout\fP"