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"