Skip to content

Commit 11cec8f

Browse files
committed
Add support for 24-bit and 32-bit audio output.
Drivers with 24-bit and 32-bit audio support: - [x] ALSA - [ ] BSD - [ ] CoreAudio - [x] NetBSD - [x] OSS - [x] PulseAudio - [x] sndio - [x] Raw file - [x] WAV file Drivers that do not support 24-bit or 32-bit: - [ ] Sound Blaster Drivers to verify: - [ ] AHI - [ ] AIFF file - [ ] AIX - [ ] ALSA 0.5 - [ ] BeOS - [ ] DART - [ ] HP-UX - [ ] QNX - [ ] SGI - [ ] Solaris
1 parent 29d31ea commit 11cec8f

29 files changed

+479
-105
lines changed

Changelog

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ Stable versions
33

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

1220
4.2.0 (20230615):
1321
Changes by Özkan Sezer:

README

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ directly to cmatsuoka@gmail.com.
5959
LICENSE
6060

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

6464
This program is free software; you can redistribute it and/or modify it under
6565
the terms of the GNU General Public License as published by the Free Software

src/common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222

2323
#include <xmp.h>
2424

25+
/* Allow building with <4.7.0 for now... */
26+
#if XMP_VERCODE < 0x040700
27+
#define XMP_FORMAT_32BIT (1 << 3)
28+
#endif
29+
2530
struct player_mode {
2631
const char *name;
2732
const char *desc;
@@ -33,6 +38,7 @@ struct options {
3338
int amplify; /* amplification factor */
3439
int rate; /* sampling rate */
3540
int format; /* sample format */
41+
int format_downmix; /* XMP_FORMAT_32BIT may require downmix to 24 */
3642
int max_time; /* max. replay time */
3743
int mix; /* channel separation */
3844
int defpan; /* default pan */

src/main.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Extended Module Player
2-
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
2+
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
33
*
44
* This file is part of the Extended Module Player and is distributed
55
* under the terms of the GNU General Public License. See the COPYING
@@ -335,13 +335,22 @@ int main(int argc, char **argv)
335335
}
336336

337337
if (opt.verbose > 0) {
338+
int format_bits;
339+
if (opt.format & XMP_FORMAT_32BIT) {
340+
format_bits = opt.format_downmix == 24 ? 24 : 32;
341+
} else if (~opt.format & XMP_FORMAT_8BIT) {
342+
format_bits = 16;
343+
} else {
344+
format_bits = 8;
345+
}
346+
338347
report("Extended Module Player " VERSION "\n"
339-
"Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr\n");
348+
"Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr\n");
340349

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

343-
report("Mixer set to %d Hz, %dbit, %s%s%s\n", opt.rate,
344-
opt.format & XMP_FORMAT_8BIT ? 8 : 16,
352+
report("Mixer set to %d Hz, %dbit, %s%s%s%s\n", opt.rate, format_bits,
353+
opt.format & XMP_FORMAT_UNSIGNED ? "unsigned " : "signed ",
345354
opt.interp == XMP_INTERP_LINEAR ? "linear interpolated " :
346355
opt.interp == XMP_INTERP_SPLINE ? "cubic spline interpolated " : "",
347356
opt.format & XMP_FORMAT_MONO ? "mono" : "stereo",

src/options.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Extended Module Player
2-
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
2+
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
33
*
44
* This file is part of the Extended Module Player and is distributed
55
* under the terms of the GNU General Public License. See the COPYING
@@ -96,7 +96,7 @@ static void usage(char *s, struct options *options)
9696
"\nMixer options:\n"
9797
" -A --amiga Use Paula simulation mixer in Amiga formats\n"
9898
" -a --amplify {0|1|2|3} Amplification factor: 0=Normal, 1=x2, 2=x4, 3=x8\n"
99-
" -b --bits {8|16} Software mixer resolution (8 or 16 bits)\n"
99+
" -b --bits {8|16|24|32} Software mixer resolution (8, 16, 24, or 32 bits)\n"
100100
" -c --stdout Mix the module to stdout\n"
101101
" -f --frequency rate Sampling rate in hertz (default 44100)\n"
102102
" -i --interpolation {nearest|linear|spline}\n"
@@ -190,8 +190,19 @@ void get_options(int argc, char **argv, struct options *options)
190190
options->amplify = atoi(optarg);
191191
break;
192192
case 'b':
193-
if (atoi(optarg) == 8) {
193+
options->format &= ~(XMP_FORMAT_8BIT | XMP_FORMAT_32BIT);
194+
options->format_downmix = 0;
195+
switch (atoi(optarg)) {
196+
case 8:
194197
options->format |= XMP_FORMAT_8BIT;
198+
break;
199+
case 24:
200+
options->format |= XMP_FORMAT_32BIT;
201+
options->format_downmix = 24;
202+
break;
203+
case 32:
204+
options->format |= XMP_FORMAT_32BIT;
205+
break;
195206
}
196207
break;
197208
case 'C':
@@ -387,7 +398,11 @@ void get_options(int argc, char **argv, struct options *options)
387398

388399
if (xmp_vercode < 0x040700) {
389400
if (options->rate > 48000)
390-
options->rate = 48000; /* Old max. rate 48kHz */
401+
options->rate = 48000; /* Old max. rate 48 kHz */
402+
403+
options->format &= ~XMP_FORMAT_32BIT;
404+
options->format_downmix = 0;
405+
391406
}
392407

393408
/* apply guess if no driver selected */

src/sound.c

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Extended Module Player
2-
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
2+
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
33
*
44
* This file is part of the Extended Module Player and is distributed
55
* 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)
101101
return NULL;
102102
}
103103

104-
/* Convert little-endian 16 bit samples to big-endian */
105-
void convert_endian(unsigned char *p, int l)
104+
/* Downmix 32-bit to 24-bit aligned (in-place) */
105+
void downmix_32_to_24_aligned(unsigned char *buffer, int buffer_bytes)
106+
{
107+
/* Note: most 24-bit support ignores the high byte.
108+
* sndio, however, expects 24-bit to be sign extended. */
109+
int *buf32 = (int *)buffer;
110+
int i;
111+
112+
for (i = 0; i < buffer_bytes; i += 4) {
113+
*buf32 >>= 8;
114+
buf32++;
115+
}
116+
}
117+
118+
/* Downmix 32-bit to 24-bit packed (in-place).
119+
* Returns the new number of useful bytes in the buffer. */
120+
int downmix_32_to_24_packed(unsigned char *buffer, int buffer_bytes)
121+
{
122+
unsigned char *out = buffer;
123+
int buffer_samples = buffer_bytes >> 2;
124+
int i;
125+
126+
/* Big endian 32-bit (22 11 00 XX) -> 24-bit (22 11 00)
127+
* Little endian 32-bit (XX 00 11 22) -> 24-bit (00 11 22)
128+
* Skip the first byte for little endian to allow reusing the same loop.
129+
*/
130+
if (!is_big_endian()) {
131+
buffer++;
132+
}
133+
134+
for (i = 0; i < buffer_samples; i++) {
135+
out[0] = buffer[0];
136+
out[1] = buffer[1];
137+
out[2] = buffer[2];
138+
out += 3;
139+
buffer += 4;
140+
}
141+
142+
return buffer_samples * 3;
143+
}
144+
145+
146+
/* Convert native endian 16-bit samples for file IO */
147+
void convert_endian_16bit(unsigned char *buffer, int buffer_bytes)
106148
{
107149
unsigned char b;
108150
int i;
109151

110-
for (i = 0; i < l; i++) {
111-
b = p[0];
112-
p[0] = p[1];
113-
p[1] = b;
114-
p += 2;
152+
for (i = 0; i < buffer_bytes; i += 2) {
153+
b = buffer[0];
154+
buffer[0] = buffer[1];
155+
buffer[1] = b;
156+
buffer += 2;
157+
}
158+
}
159+
160+
/* Convert native endian 24-bit unaligned samples for file IO */
161+
void convert_endian_24bit(unsigned char *buffer, int buffer_bytes)
162+
{
163+
unsigned char b;
164+
int i;
165+
166+
for (i = 0; i < buffer_bytes; i += 3) {
167+
b = buffer[0];
168+
buffer[0] = buffer[2];
169+
buffer[2] = b;
170+
buffer += 3;
171+
}
172+
}
173+
174+
/* Convert native endian 32-bit samples for file IO */
175+
void convert_endian_32bit(unsigned char *buffer, int buffer_bytes)
176+
{
177+
unsigned char a, b;
178+
int i;
179+
180+
for (i = 0; i < buffer_bytes; i += 4) {
181+
a = buffer[0];
182+
b = buffer[1];
183+
buffer[0] = buffer[3];
184+
buffer[1] = buffer[2];
185+
buffer[2] = b;
186+
buffer[3] = a;
187+
buffer += 4;
188+
}
189+
}
190+
191+
/* Convert native endian 16-bit, 24-bit, or 32-bit samples for file IO */
192+
void convert_endian(unsigned char *buffer, int buffer_bytes, int bits)
193+
{
194+
switch (bits) {
195+
case 16:
196+
convert_endian_16bit(buffer, buffer_bytes);
197+
break;
198+
case 24:
199+
convert_endian_24bit(buffer, buffer_bytes);
200+
break;
201+
case 32:
202+
convert_endian_32bit(buffer, buffer_bytes);
203+
break;
115204
}
116205
}

src/sound.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,59 @@ extern const struct sound_driver *const sound_driver_list[];
5858
#define chkparm2(x,y,z,w) { if (!strcmp(s, x)) { \
5959
if (2 > sscanf(token, y, z, w)) parm_error(); } }
6060

61-
static inline int is_big_endian(void) {
61+
static inline int is_big_endian(void)
62+
{
6263
unsigned short w = 0x00ff;
6364
return (*(char *)&w == 0x00);
6465
}
6566

67+
static inline int get_bits_from_format(const struct options *options)
68+
{
69+
if ((options->format & XMP_FORMAT_32BIT) && options->format_downmix != 24) {
70+
return 32;
71+
} else if (options->format & XMP_FORMAT_32BIT) {
72+
return 24;
73+
} else if (~options->format & XMP_FORMAT_8BIT) {
74+
return 16;
75+
} else {
76+
return 8;
77+
}
78+
}
79+
80+
static inline int get_signed_from_format(const struct options *options)
81+
{
82+
return !(options->format & XMP_FORMAT_UNSIGNED);
83+
}
84+
85+
static inline void update_format(struct options *options, int bits, int is_signed)
86+
{
87+
options->format &= ~(XMP_FORMAT_32BIT | XMP_FORMAT_8BIT | XMP_FORMAT_UNSIGNED);
88+
options->format_downmix = 0;
89+
90+
switch (bits) {
91+
case 8:
92+
options->format |= XMP_FORMAT_8BIT;
93+
break;
94+
case 24:
95+
options->format |= XMP_FORMAT_32BIT;
96+
options->format_downmix = 24;
97+
break;
98+
case 32:
99+
options->format |= XMP_FORMAT_32BIT;
100+
break;
101+
}
102+
if (!is_signed) {
103+
options->format |= XMP_FORMAT_UNSIGNED;
104+
}
105+
}
106+
66107
void init_sound_drivers(void);
67108
const struct sound_driver *select_sound_driver(struct options *);
68-
void convert_endian(unsigned char *, int);
109+
void downmix_32_to_24_aligned(unsigned char *, int);
110+
int downmix_32_to_24_packed(unsigned char *, int);
111+
void convert_endian_16bit(unsigned char *, int);
112+
void convert_endian_24bit(unsigned char *, int);
113+
void convert_endian_32bit(unsigned char *, int);
114+
void convert_endian(unsigned char *, int, int);
69115

70116
#endif

src/sound_ahi.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ static int init(struct options *options) {
9292
if (AHIBuf[1]) {
9393
/* driver is initialized before calling libxmp, so this is OK : */
9494
options->format &= ~XMP_FORMAT_UNSIGNED;/* no unsigned with AHI */
95+
options->format &= ~XMP_FORMAT_32BIT;
9596
return 0;
9697
}
9798
}

src/sound_aiff.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ static int init(struct options *options)
9999
} else {
100100
fd = stdout;
101101
}
102+
options->format &= ~XMP_FORMAT_32BIT;
102103

103104
fwrite(hed, 1, 54, fd);
104105

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

108109
static void play(void *b, int len)
109110
{
110-
if (swap_endian && bits == 16) {
111-
convert_endian((unsigned char *)b, len);
111+
if (swap_endian) {
112+
convert_endian((unsigned char *)b, len, bits);
112113
}
113114
fwrite(b, 1, len, fd);
114115
size += len;

src/sound_aix.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Extended Module Player
2-
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
2+
* Copyright (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr
33
*
44
* This file is part of the Extended Module Player and is distributed
55
* under the terms of the GNU General Public License. See the COPYING
@@ -83,6 +83,8 @@ static int init(struct options *options)
8383
close(audio_fd);
8484
return -1;
8585
}
86+
options->format &= ~XMP_FORMAT_32BIT;
87+
8688
return 0;
8789
}
8890

0 commit comments

Comments
 (0)