diff --git a/Changelog b/Changelog
index 77cd7a92..7a6fd971 100644
--- a/Changelog
+++ b/Changelog
@@ -3,12 +3,25 @@ Stable versions
4.2.1 (?):
Changes by Alice Rowan
+ - Support 24-bit and 32-bit output (requires libxmp 4.7.0+):
+ ALSA, BeOS/Haiku, CoreAudio, NetBSD, OSS, PulseAudio,
+ sndio, WinMM, AIFF, file, WAV.
+ - 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.
- Haiku: Fix configure C++11 detection error message.
+ - ALSA: fix 8-bit and unsigned format mismatches after init.
+ - BeOS/Haiku: support 8-bit unsigned, strip unsigned otherwise.
+ - NetBSD: find the nearest match for the requested format.
+ - PulseAudio: support 8-bit output.
+ - WinMM: support 8-bit unsigned, strip unsigned otherwise.
+ - file: fix incorrect handling of -Dendian ("big" would output
+ little endian, anything else would output big endian).
+ - wav: fix incorrect RIFF length in output.
+ - wav: add terminal pad byte after odd length data chunks.
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 31dc2579..76080979 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..f1694dfe 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
@@ -336,15 +336,16 @@ int main(int argc, char **argv)
if (opt.verbose > 0) {
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,
+ get_bits_from_format(&opt),
+ get_signed_from_format(&opt) ? "signed " : "unsigned ",
opt.interp == XMP_INTERP_LINEAR ? "linear interpolated " :
opt.interp == XMP_INTERP_SPLINE ? "cubic spline interpolated " : "",
- opt.format & XMP_FORMAT_MONO ? "mono" : "stereo",
+ get_channels_from_format(&opt) == 1 ? "mono" : "stereo",
opt.dsp & XMP_DSP_LOWPASS ? "" : " (no filter)");
report("Press 'h' for help\n\n");
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..ff463645 100644
--- a/src/sound.h
+++ b/src/sound.h
@@ -58,13 +58,79 @@ 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 int get_channels_from_format(const struct options *options)
+{
+ return options->format & XMP_FORMAT_MONO ? 1 : 2;
+}
+
+static inline void update_format_bits(struct options *options, int bits)
+{
+ options->format &= ~(XMP_FORMAT_32BIT | XMP_FORMAT_8BIT);
+ 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;
+ }
+}
+
+static inline void update_format_signed(struct options *options, int is_signed)
+{
+ if (!is_signed) {
+ options->format |= XMP_FORMAT_UNSIGNED;
+ } else {
+ options->format &= ~XMP_FORMAT_UNSIGNED;
+ }
+}
+
+static inline void update_format_channels(struct options *options, int channels)
+{
+ if (channels == 1) {
+ options->format |= XMP_FORMAT_MONO;
+ } else {
+ options->format &= ~XMP_FORMAT_MONO;
+ }
+}
+
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..3b98ee0b 100644
--- a/src/sound_aiff.c
+++ b/src/sound_aiff.c
@@ -26,7 +26,7 @@ static int swap_endian;
static long size;
-static void ulong2extended(unsigned long in, extended *ex)
+static void ulong2extended(unsigned long in, extended *ex)
{
int exponent = 31 + 16383;
@@ -53,29 +53,29 @@ static void write32b(FILE *f, unsigned long w)
write8(f, w & 0x000000ff);
}
-static int init(struct options *options)
+static int init(struct options *options)
{
char hed[54] = {
'F', 'O', 'R', 'M', 0, 0, 0, 0,
'A', 'I', 'F', 'F',
- /* COMM chunk */
+ /* COMM chunk */
'C', 'O', 'M', 'M', 0, 0, 0, 18,
- 0, 0, /* channels */
- 0, 0, 0, 0, /* frames */
- 0, 0, /* bits */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* rate (extended format) */
-
- /* SSND chunk */
+ 0, 0, /* channels */
+ 0, 0, 0, 0, /* frames */
+ 0, 0, /* bits */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* rate (extended format) */
+
+ /* SSND chunk */
'S', 'S', 'N', 'D', 0, 0, 0, 0,
- 0, 0, 0, 0, /* offset */
- 0, 0, 0, 0 /* block size */
+ 0, 0, 0, 0, /* offset */
+ 0, 0, 0, 0 /* block size */
};
extended ex;
swap_endian = !is_big_endian();
- channels = options->format & XMP_FORMAT_MONO ? 1 : 2;
- bits = options->format & XMP_FORMAT_8BIT ? 8 : 16;
+ channels = get_channels_from_format(options);
+ bits = get_bits_from_format(options);
size = 0;
ulong2extended(options->rate, &ex);
@@ -99,22 +99,26 @@ static int init(struct options *options)
} else {
fd = stdout;
}
+ update_format_signed(options, 1);
fwrite(hed, 1, 54, fd);
return 0;
}
-static void play(void *b, int len)
+static void play(void *b, int len)
{
- if (swap_endian && bits == 16) {
- convert_endian((unsigned char *)b, len);
+ if (bits == 24) {
+ len = downmix_32_to_24_packed((unsigned char *)b, len);
+ }
+ if (swap_endian) {
+ convert_endian((unsigned char *)b, len, bits);
}
fwrite(b, 1, len, fd);
size += len;
}
-static void deinit(void)
+static void deinit(void)
{
if (size > 54) {
if (fseek(fd, 4, SEEK_SET) == 0) { /* FORM chunk size */
@@ -137,18 +141,18 @@ static void deinit(void)
fd = NULL;
}
-static void flush(void)
+static void flush(void)
{
if (fd) {
fflush(fd);
}
}
-static void onpause(void)
+static void onpause(void)
{
}
-static void onresume(void)
+static void onresume(void)
{
}
@@ -166,5 +170,5 @@ const struct sound_driver sound_aiff = {
play,
flush,
onpause,
- onresume
+ onresume
};
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..e097affb 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 bits;
static int init(struct options *options)
{
@@ -23,7 +24,7 @@ static int init(struct options *options)
unsigned int ptime = 50000; /* 50ms */
const char *card_name = "default";
unsigned int rate = options->rate;
- int format = options->format;
+ int sgn;
parm_init(parm);
chkparm1("buffer", btime = 1000 * strtoul(token, NULL, 0));
@@ -38,13 +39,24 @@ static int init(struct options *options)
return -1;
}
- 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;
- } else {
- fmt = format & XMP_FORMAT_8BIT ?
- SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_S16;
+ bits = get_bits_from_format(options);
+ sgn = get_signed_from_format(options);
+ channels = get_channels_from_format(options);
+
+ switch (bits) {
+ case 8:
+ fmt = sgn ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
+ break;
+ case 16:
+ default:
+ fmt = sgn ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U16;
+ break;
+ case 24:
+ fmt = sgn ? SND_PCM_FORMAT_S24 : SND_PCM_FORMAT_U24;
+ break;
+ case 32:
+ fmt = sgn ? SND_PCM_FORMAT_S32 : SND_PCM_FORMAT_U32;
+ break;
}
snd_pcm_hw_params_alloca(&hwparams);
@@ -68,14 +80,42 @@ static int init(struct options *options)
return -1;
}
- if (channels == 1) {
- format |= XMP_FORMAT_MONO;
- } else {
- format &= ~XMP_FORMAT_MONO;
+ snd_pcm_hw_params_get_format(hwparams, &fmt);
+ switch (fmt) {
+ case SND_PCM_FORMAT_S8:
+ case SND_PCM_FORMAT_U8:
+ bits = 8;
+ break;
+ case SND_PCM_FORMAT_S16:
+ case SND_PCM_FORMAT_U16:
+ bits = 16;
+ break;
+ case SND_PCM_FORMAT_S24:
+ case SND_PCM_FORMAT_U24:
+ bits = 24;
+ break;
+ case SND_PCM_FORMAT_S32:
+ case SND_PCM_FORMAT_U32:
+ bits = 32;
+ break;
+ default:
+ break;
}
-
+ switch (fmt) {
+ case SND_PCM_FORMAT_U8:
+ case SND_PCM_FORMAT_U16:
+ case SND_PCM_FORMAT_U24:
+ case SND_PCM_FORMAT_U32:
+ sgn = 0;
+ break;
+ default:
+ sgn = 1;
+ break;
+ }
+ update_format_bits(options, bits);
+ update_format_signed(options, sgn);
+ update_format_channels(options, channels);
options->rate = rate;
- options->format = format;
return 0;
}
@@ -84,6 +124,10 @@ static void play(void *b, int i)
{
int frames = snd_pcm_bytes_to_frames(pcm_handle, i);
+ if (bits == 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..172619fb 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
@@ -8,9 +8,7 @@
#include
#include
-
-#define B_AUDIO_CHAR 1
-#define B_AUDIO_SHORT 2
+#include
extern "C" {
#include
@@ -121,7 +119,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;
@@ -135,6 +133,7 @@ static void render_proc(void *theCookie, void *buffer, size_t req,
static int init(struct options *options)
{
char **parm = options->driver_parm;
+ int bits;
be_app = new BApplication("application/x-vnd.cm-xmp");
@@ -146,13 +145,33 @@ static int init(struct options *options)
parm_end();
fmt.frame_rate = options->rate;
- fmt.channel_count = options->format & XMP_FORMAT_MONO ? 1 : 2;
- fmt.format = options->format & XMP_FORMAT_8BIT ?
- B_AUDIO_CHAR : B_AUDIO_SHORT;
+ fmt.channel_count = get_channels_from_format(options);
fmt.byte_order = B_HOST_IS_LENDIAN ?
B_MEDIA_LITTLE_ENDIAN : B_MEDIA_BIG_ENDIAN;
fmt.buffer_size = chunk_size;
+ switch (get_bits_from_format(options)) {
+ case 8:
+ fmt.format = get_signed_from_format(options) ?
+ media_raw_audio_format::B_AUDIO_CHAR :
+ media_raw_audio_format::B_AUDIO_UCHAR;
+ bits = 8;
+ break;
+ case 16:
+ default:
+ fmt.format = media_raw_audio_format::B_AUDIO_SHORT;
+ bits = 16;
+ break;
+ case 24:
+ case 32:
+ fmt.format = media_raw_audio_format::B_AUDIO_INT;
+ bits = 32;
+ break;
+ }
+ update_format_bits(options, bits);
+ update_format_signed(options,
+ fmt.format != media_raw_audio_format::B_AUDIO_UCHAR);
+
buffer_len = chunk_size * chunk_num;
buffer = (uint8 *)calloc(1, buffer_len);
buf_read_pos = 0;
@@ -182,7 +201,7 @@ static void play(void *b, int i)
}
if (paused) {
- player->Start();
+ player->Start();
player->SetHasData(true);
paused = 0;
}
@@ -190,7 +209,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..b8258902 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
@@ -42,7 +42,7 @@ static int init(struct options *options)
AUDIO_INITINFO(&ainfo);
ainfo.play.sample_rate = options->rate;
- ainfo.play.channels = options->format & XMP_FORMAT_MONO ? 1 : 2;
+ ainfo.play.channels = get_channels_from_format(options);
ainfo.play.gain = gain;
ainfo.play.buffer_size = bsize;
@@ -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..12b78dbe 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
@@ -35,6 +35,7 @@ static int buf_read_pos;
static int num_chunks;
static int chunk_size;
static int packet_size;
+static int bits;
/* return minimum number of free bytes in buffer, value may change between
@@ -147,23 +148,19 @@ static int init(struct options *options)
ad.mFormatFlags = kAudioFormatFlagIsPacked |
kAudioFormatFlagsNativeEndian;
- if (~options->format & XMP_FORMAT_UNSIGNED) {
+ ad.mChannelsPerFrame = get_channels_from_format(options);
+ ad.mBitsPerChannel = get_bits_from_format(options);
+
+ /* CoreAudio refuses the stream format unless >8-bit is signed */
+ if (ad.mBitsPerChannel > 8 || get_signed_from_format(options)) {
ad.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
}
- ad.mChannelsPerFrame = options->format & XMP_FORMAT_MONO ? 1 : 2;
- ad.mBitsPerChannel = options->format & XMP_FORMAT_8BIT ? 8 : 16;
-
- if (options->format & XMP_FORMAT_8BIT) {
- ad.mBytesPerFrame = ad.mChannelsPerFrame;
- } else {
- ad.mBytesPerFrame = 2 * ad.mChannelsPerFrame;
- }
+ ad.mBytesPerFrame = ad.mChannelsPerFrame * ((ad.mBitsPerChannel + 7) / 8);
ad.mBytesPerPacket = ad.mBytesPerFrame;
ad.mFramesPerPacket = 1;
- packet_size = ad.mFramesPerPacket * ad.mChannelsPerFrame *
- (ad.mBitsPerChannel / 8);
+ packet_size = ad.mBytesPerPacket;
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_DefaultOutput;
@@ -185,12 +182,23 @@ static int init(struct options *options)
goto err1;
size = sizeof(UInt32);
- if ((status = AudioUnitGetProperty(au, kAudioDevicePropertyBufferSize,
+ if ((status = AudioUnitGetProperty(au, kAudioDevicePropertyBufferSize,
kAudioUnitScope_Input, 0, &max_frames, &size)))
goto err1;
+ size = sizeof(ad);
+ if ((status = AudioUnitGetProperty(au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0, &ad, &size)))
+ goto err1;
+
+ if (ad.mChannelsPerFrame > 2 ||
+ ad.mSampleRate < XMP_MIN_SRATE || ad.mSampleRate > XMP_MAX_SRATE ||
+ (ad.mBitsPerChannel != 8 && ad.mBitsPerChannel != 16 &&
+ ad.mBitsPerChannel != 24 && ad.mBitsPerChannel != 32))
+ goto err1;
+
chunk_size = max_frames;
- num_chunks = (options->rate * ad.mBytesPerFrame * latency / 1000
+ num_chunks = (ad.mSampleRate * ad.mBytesPerFrame * latency / 1000
+ chunk_size - 1) / chunk_size;
buffer_len = (num_chunks + 1) * chunk_size;
if ((buffer = calloc(num_chunks + 1, chunk_size)) == NULL)
@@ -208,6 +216,13 @@ static int init(struct options *options)
kAudioUnitScope_Input, 0, &rc, sizeof(rc))))
goto err2;
+ update_format_bits(options, ad.mBitsPerChannel);
+ update_format_signed(options,
+ !!(ad.mFormatFlags & kAudioFormatFlagIsSignedInteger));
+ update_format_channels(options, ad.mChannelsPerFrame);
+ options->rate = ad.mSampleRate;
+ bits = ad.mBitsPerChannel;
+
return 0;
err2:
@@ -219,13 +234,16 @@ static int init(struct options *options)
}
-/* Build and write one tick (one PAL frame or 1/50 s in standard vblank
- * timed mods) of audio data to the output device.
+/* Write audio data to the output device.
*/
static void play(void *b, int i)
{
int j = 0;
+ if (bits == 24) {
+ i = downmix_32_to_24_packed((unsigned char *)b, i);
+ }
+
/* block until we have enough free space in the buffer */
while (buf_free() < i)
delay_ms(100);
diff --git a/src/sound_dart.c b/src/sound_dart.c
index 57d92a49..dbc78691 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
@@ -43,9 +43,10 @@ static short ready = 1;
static HMTX mutex;
/* Buffer update thread (created and called by DART) */
-static LONG APIENTRY OS2_Dart_UpdateBuffers
- (ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags) {
-
+static LONG APIENTRY OS2_Dart_UpdateBuffers(ULONG ulStatus,
+ PMCI_MIX_BUFFER pBuffer,
+ ULONG ulFlags)
+{
if ((ulFlags == MIX_WRITE_COMPLETE) ||
((ulFlags == (MIX_WRITE_COMPLETE | MIX_STREAM_ERROR)) &&
(ulStatus == ERROR_DEVICE_UNDERRUN))) {
@@ -64,6 +65,7 @@ static int init(struct options *options)
char sharing = 0;
int device = 0;
int flags;
+ int bits;
int i;
MCI_AMP_OPEN_PARMS AmpOpenParms;
@@ -107,11 +109,14 @@ static int init(struct options *options)
/* setup playback parameters */
memset(&MixSetupParms, 0, sizeof(MCI_MIXSETUP_PARMS));
- MixSetupParms.ulBitsPerSample =
- options->format & XMP_FORMAT_8BIT ? 8 : 16;
+ bits = get_bits_from_format(options);
+ if (bits > 16) /* TODO: higher? */
+ bits = 16;
+
+ MixSetupParms.ulBitsPerSample = bits;
MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM;
MixSetupParms.ulSamplesPerSec = options->rate;
- MixSetupParms.ulChannels = options->format & XMP_FORMAT_MONO ? 1 : 2;
+ MixSetupParms.ulChannels = get_channels_from_format(options);
MixSetupParms.ulFormatMode = MCI_PLAY;
MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
MixSetupParms.pmixEvent = OS2_Dart_UpdateBuffers;
@@ -152,6 +157,10 @@ static int init(struct options *options)
memset(MixBuffers[1].pBuffer, /*32767 */ 0, bsize);
MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, MixBuffers, 2);
+ update_format_bits(options, MixSetupParms.ulBitsPerSample);
+ update_format_signed(options, 1); /* TODO: verify */
+ update_format_channels(options, MixSetupParms.ulChannels);
+ options->rate = MixSetupParms.ulSamplesPerSec;
return 0;
}
diff --git a/src/sound_file.c b/src/sound_file.c
index a794a07c..2a6224bf 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,12 @@ static int init(struct options *options)
{
char **parm = options->driver_parm;
+ bits = get_bits_from_format(options);
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 +44,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..1fa98744 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 int bits;
+#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 = enc.precision;
+ match_encoding = enc.encoding;
+ }
+ 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)
@@ -70,18 +134,9 @@ 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.channels = get_channels_from_format(options);
+ ainfo.play.precision = precision;
+ ainfo.play.encoding = encoding;
ainfo.play.gain = gain;
ainfo.play.buffer_size = bsize;
ainfo.blocksize = 0;
@@ -91,6 +146,10 @@ static int init(struct options *options)
return -1;
}
+ update_format_bits(options, precision);
+ update_format_signed(options, is_signed(encoding));
+ bits = precision;
+
return 0;
}
@@ -98,6 +157,10 @@ static void play(void *b, int i)
{
int j;
+ if (bits == 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..735b8e51 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,34 @@ static int audio_fd;
static int fragnum, fragsize;
static int do_sync = 1;
+static int bits;
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)) {
+ case 8:
fmt = AFMT_U8 | AFMT_S8;
- else {
+ break;
+ case 16:
+ default:
fmt = AFMT_S16_NE | AFMT_U16_NE;
+ break;
+#if defined(AFMT_S24_NE)
+ case 24:
+ return AFMT_S24_NE;
+#endif
+#if defined(AFMT_S32_NE)
+ case 32:
+ return AFMT_S32_NE;
+#endif
}
- 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,42 +75,51 @@ static int to_fmt(int format)
return fmt;
}
-static int from_fmt(int fmt)
+static void from_fmt(struct options *options, int fmt, int channels)
{
- int format = 0;
+ int sgn = 1;
+ bits = 16;
+#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_bits(options, bits);
+ update_format_signed(options, sgn);
+ update_format_channels(options, channels);
}
-static void setaudio(int *rate, int *format)
+static void setaudio(struct options *options)
{
static int fragset = 0;
int frag = 0;
+ int stereo;
int fmt;
frag = (fragnum << 16) + fragsize;
- fmt = to_fmt(*format);
+ fmt = to_fmt(options);
ioctl(audio_fd, SNDCTL_DSP_SETFMT, &fmt);
- *format = from_fmt(fmt);
-
- fmt = !(*format & XMP_FORMAT_MONO);
- ioctl(audio_fd, SNDCTL_DSP_STEREO, &fmt);
- if (fmt) {
- *format &= ~XMP_FORMAT_MONO;
- } else {
- *format |= XMP_FORMAT_MONO;
- }
- ioctl(audio_fd, SNDCTL_DSP_SPEED, rate);
+ stereo = get_channels_from_format(options) > 1 ? 1 : 0;
+ ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo);
+
+ ioctl(audio_fd, SNDCTL_DSP_SPEED, &options->rate);
+
+ from_fmt(options, fmt, stereo ? 2 : 1);
/* Set the fragments only once */
if (!fragset) {
@@ -140,7 +162,7 @@ static int init(struct options *options)
if (audio_fd < 0)
return -1;
- setaudio(&options->rate, &options->format);
+ setaudio(options);
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == 0) {
snprintf(descbuf, sizeof(descbuf), "%s [%d fragments of %d bytes]",
@@ -158,6 +180,10 @@ static void play(void *b, int i)
{
int j;
+ if (bits == 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..f13e2e63 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,17 +12,39 @@
#include "sound.h"
static pa_simple *s;
+static int bits;
static int init(struct options *options)
{
pa_sample_spec ss;
+ int format = -1;
int error;
- options->format &= ~(XMP_FORMAT_UNSIGNED | XMP_FORMAT_8BIT);
+ bits = get_bits_from_format(options);
+ switch (bits) {
+ case 8:
+ format = PA_SAMPLE_U8;
+ break;
+ case 16:
+ default:
+ format = PA_SAMPLE_S16NE;
+ break;
+#ifdef PA_SAMPLE_S24_32NE
+ case 24:
+ format = PA_SAMPLE_S24_32NE;
+ break;
+#endif
+#ifdef PA_SAMPLE_S32NE
+ case 32:
+ format = PA_SAMPLE_S32NE;
+ bits = 32;
+ break;
+#endif
+ }
- ss.format = PA_SAMPLE_S16NE;
- ss.channels = options->format & XMP_FORMAT_MONO ? 1 : 2;
+ ss.format = format;
+ ss.channels = get_channels_from_format(options);
ss.rate = options->rate;
s = pa_simple_new(NULL, /* Use the default server */
@@ -40,6 +62,8 @@ static int init(struct options *options)
return -1;
}
+ update_format_bits(options, bits);
+ update_format_signed(options, bits != 8);
return 0;
}
@@ -47,6 +71,10 @@ static void play(void *b, int i)
{
int j, error;
+ if (bits == 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..d18b65a1 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
@@ -65,6 +65,7 @@ static int init(struct options *options)
const char *card = NULL;
const char *mode = NULL;
int bits = 0;
+ int chn = 1;
if (!sb_open()) {
fprintf(stderr, "Sound Blaster initialization failed.\n");
@@ -76,9 +77,6 @@ static int init(struct options *options)
card = "16";
mode = "stereo";
bits = 16;
- options->format &= ~XMP_FORMAT_8BIT;
- } else {
- options->format |= XMP_FORMAT_8BIT|XMP_FORMAT_UNSIGNED;
}
if (sb.caps & SBMODE_STEREO) {
if (!card) {
@@ -86,16 +84,15 @@ static int init(struct options *options)
mode = "stereo";
bits = 8;
}
+ chn = 2;
if (options->rate > sb.maxfreq_stereo)
options->rate = sb.maxfreq_stereo;
- options->format &= ~XMP_FORMAT_MONO;
} else {
mode = "mono";
- card = (sb.dspver < SBVER_20)? "1" : "2";
+ card = (sb.dspver < SBVER_20) ? "1" : "2";
bits = 8;
if (options->rate > sb.maxfreq_mono)
options->rate = sb.maxfreq_mono;
- options->format |= XMP_FORMAT_MONO;
}
/* Enable speaker output */
@@ -113,8 +110,12 @@ static int init(struct options *options)
fprintf(stderr, "Sound Blaster: DMA start failed.\n");
return -1;
}
+ update_format_bits(options, bits);
+ update_format_signed(options, bits != 8);
+ update_format_channels(options, chn);
- printf("Sound Blaster %s or compatible (%d bit, %s, %u Hz)\n", card, bits, mode, options->rate);
+ printf("Sound Blaster %s or compatible (%d bit, %s, %u Hz)\n",
+ card, bits, mode, options->rate);
return 0;
}
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..99cd5edb 100644
--- a/src/sound_sndio.c
+++ b/src/sound_sndio.c
@@ -21,6 +21,7 @@
#include "sound.h"
static struct sio_hdl *hdl;
+static int bits;
static int init(struct options *options)
{
@@ -33,20 +34,13 @@ static int init(struct options *options)
}
sio_initpar(&par);
- par.pchan = options->format & XMP_FORMAT_MONO ? 1 : 2;
+ par.pchan = get_channels_from_format(options);
par.rate = options->rate;
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,11 @@ static int init(struct options *options)
fprintf(stderr, "%s: failed to start audio device\n", __func__);
goto error;
}
+
+ update_format_bits(options, par.bits);
+ update_format_signed(options, par.sig);
+ bits = par.bits;
+
return 0;
error:
@@ -83,6 +81,10 @@ static void deinit(void)
static void play(void *buf, int len)
{
+ if (bits == 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..183545b1 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();
@@ -67,30 +75,39 @@ static int init(struct options *options)
write_32l(fd, 0); /* will be written when finished */
fwrite("WAVE", 1, 4, fd);
- chan = options->format & XMP_FORMAT_MONO ? 1 : 2;
+ chan = get_channels_from_format(options);
sampling_rate = options->rate;
- bits_per_sample = options->format & XMP_FORMAT_8BIT ? 8 : 16;
- if (bits_per_sample == 8) {
- options->format |= XMP_FORMAT_UNSIGNED;
- format_16bit = 0;
- } else {
- options->format &= ~XMP_FORMAT_UNSIGNED;
- format_16bit = 1;
- }
+ bits_per_sample = get_bits_from_format(options);
+ update_format_signed(options, bits_per_sample != 8);
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 +118,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 +130,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..904e765f 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
@@ -11,6 +11,7 @@
*/
#include
+#include
#include
#include "sound.h"
@@ -25,12 +26,35 @@ typedef DWORD DWORD_PTR;
/* frame size = (sampling rate * channels * size) / frame rate */
#define OUT_MAXLEN 0x8000
+#ifndef WAVE_FORMAT_EXTENSIBLE
+#define WAVE_FORMAT_EXTENSIBLE 0xfffe /* mmreg.h, 98SE and later */
+#endif
+
+#pragma pack(push,1)
+typedef struct _XMP_WAVEFORMATEXTENSIBLE { /* mmreg.h */
+ WAVEFORMATEX Format;
+ union {
+ WORD wValidBitsPerSample;
+ WORD wSamplesPerBlock;
+ WORD wReserved;
+ } Samples;
+ DWORD dwChannelMask;
+ GUID SubFormat;
+} XMP_WAVEFORMATEXTENSIBLE;
+#pragma pack(pop)
+
+static const GUID XMP_KSDATAFORMAT_SUBTYPE_PCM = { /* mmreg.h, ksuser.dll */
+ WAVE_FORMAT_PCM, 0x0000, 0x0010,
+ { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+};
+
static HWAVEOUT hwaveout;
static WAVEHDR header[MAXBUFFERS];
static LPSTR buffer[MAXBUFFERS]; /* pointers to buffers */
static WORD freebuffer; /* */
static WORD nextbuffer; /* next buffer to be mixed */
static int num_buffers;
+static int bits_per_sample;
static void show_error(int res)
{
@@ -59,11 +83,11 @@ static void show_error(int res)
msg = "Unknown media error";
}
- fprintf(stderr, "Error: %s", msg);
+ fprintf(stderr, "Error: %s\n", msg);
}
static void CALLBACK wave_callback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
- DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+ DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
if (uMsg == WOM_DONE) {
freebuffer++;
@@ -71,12 +95,20 @@ static void CALLBACK wave_callback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance
}
}
+static void set_waveformatex(WAVEFORMATEX *wfe, int rate, int bits, int chn)
+{
+ wfe->wBitsPerSample = bits;
+ wfe->nChannels = chn;
+ wfe->nSamplesPerSec = rate;
+ wfe->nAvgBytesPerSec = wfe->nSamplesPerSec * chn * bits / 8;
+ wfe->nBlockAlign = chn * bits / 8;
+}
+
static int init(struct options *options)
{
char **parm = options->driver_parm;
MMRESULT res;
- WAVEFORMATEX wfe;
- int i;
+ int chn, i;
num_buffers = 10;
@@ -90,16 +122,36 @@ static int init(struct options *options)
if (!waveOutGetNumDevs())
return -1;
- wfe.wFormatTag = WAVE_FORMAT_PCM;
- wfe.wBitsPerSample = options->format & XMP_FORMAT_8BIT ? 8 : 16;
- wfe.nChannels = options->format & XMP_FORMAT_MONO ? 1 : 2;
- wfe.nSamplesPerSec = options->rate;
- wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nChannels *
- wfe.wBitsPerSample / 8;
- wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8;
+ bits_per_sample = get_bits_from_format(options);
+ chn = get_channels_from_format(options);
- res = waveOutOpen(&hwaveout, WAVE_MAPPER, &wfe, (DWORD_PTR) wave_callback,
- 0, CALLBACK_FUNCTION);
+ if (bits_per_sample > 16) {
+ XMP_WAVEFORMATEXTENSIBLE wfx;
+ set_waveformatex(&wfx.Format, options->rate, bits_per_sample, chn);
+ wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
+ wfx.Samples.wValidBitsPerSample = bits_per_sample;
+ wfx.dwChannelMask = 0;
+ wfx.SubFormat = XMP_KSDATAFORMAT_SUBTYPE_PCM;
+
+ res = waveOutOpen(&hwaveout, WAVE_MAPPER, (LPCWAVEFORMATEX)&wfx,
+ (DWORD_PTR)wave_callback, 0, CALLBACK_FUNCTION);
+
+ if (res != MMSYSERR_NOERROR) {
+ /* May be Windows 95; try 16-bit audio instead */
+ bits_per_sample = 16;
+ }
+ }
+
+ if (bits_per_sample <= 16) {
+ WAVEFORMATEX wfe;
+ set_waveformatex(&wfe, options->rate, bits_per_sample, chn);
+ wfe.wFormatTag = WAVE_FORMAT_PCM;
+ wfe.cbSize = 0;
+
+ res = waveOutOpen(&hwaveout, WAVE_MAPPER, &wfe,
+ (DWORD_PTR)wave_callback, 0, CALLBACK_FUNCTION);
+ }
if (res != MMSYSERR_NOERROR) {
show_error(res);
@@ -112,12 +164,14 @@ static int init(struct options *options)
buffer[i] = (LPSTR) malloc(OUT_MAXLEN);
header[i].lpData = buffer[i];
- if (!buffer[i] || res != MMSYSERR_NOERROR) {
- show_error(res);
+ if (!buffer[i]) {
+ show_error(MMSYSERR_NOMEM);
return -1;
}
}
+ update_format_bits(options, bits_per_sample);
+ update_format_signed(options, bits_per_sample != 8);
freebuffer = nextbuffer = 0;
return 0;
@@ -125,6 +179,9 @@ static int init(struct options *options)
static void play(void *b, int len)
{
+ if (bits_per_sample == 24) {
+ len = downmix_32_to_24_packed((unsigned char *)b, len);
+ }
memcpy(buffer[nextbuffer], b, len);
while ((nextbuffer + 1) % num_buffers == freebuffer)
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"