Skip to content

Commit 46469b7

Browse files
committed
MEGA65: implementing MEGA65 mixer #272
First try ...
1 parent 798bf7e commit 46469b7

File tree

7 files changed

+194
-106
lines changed

7 files changed

+194
-106
lines changed

targets/mega65/audio65.c

Lines changed: 155 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
3131
#include "configdb.h"
3232

3333

34-
int stereo_separation = AUDIO_DEFAULT_SEPARATION;
35-
int audio_volume = AUDIO_DEFAULT_VOLUME;
36-
3734
struct SidEmulation sid[NUMBER_OF_SIDS];
3835

3936
static opl3_chip opl3;
4037

4138
static SDL_AudioDeviceID audio = 0; // SDL audio device
42-
static int stereo_separation_orig = 100;
43-
static int stereo_separation_other = 0;
4439
static int system_sound_mix_freq; // playback sample rate (in Hz) of the emulator itself
4540
static int system_sid_cycles_per_sec;
4641
static double dma_audio_mixing_value;
4742

4843
Uint8 mixer_register = 0x00;
49-
static Uint8 mixer_coeffs[0x100];
44+
static Uint8 mixer_bytes[0x100];
45+
static float mixer_floats[0x80];
46+
static int output_selection; // $00 = HDMI/speakers, $C0 = headphones
47+
static int xemu_volume_int;
48+
static float xemu_volume_float;
49+
static bool mono_downmix;
5050

5151
#if NUMBER_OF_SIDS != 4
5252
# error "Currently NUMBER_OF_SIDS macro must be set to 4!"
@@ -222,35 +222,6 @@ static inline void render_dma_audio ( int channel, Sint16 *buffer, int len )
222222
}
223223

224224

225-
void audio_set_stereo_parameters ( int vol, int sep )
226-
{
227-
if (sep == AUDIO_UNCHANGED_SEPARATION) {
228-
sep = stereo_separation;
229-
} else {
230-
if (sep > 100)
231-
sep = 100;
232-
else if (sep < -100)
233-
sep = -100;
234-
stereo_separation = sep;
235-
}
236-
if (vol == AUDIO_UNCHANGED_VOLUME) {
237-
vol = audio_volume;
238-
} else {
239-
if (vol > 100)
240-
vol = 100;
241-
else if (vol < 0)
242-
vol = 0;
243-
audio_volume = vol;
244-
}
245-
//sep = ((sep + 100) * 0x100) / 200;
246-
sep = (sep + 100) / 2;
247-
//sep = (sep + 100) * 0x100 / 200;
248-
stereo_separation_orig = (sep * vol) / 100;
249-
stereo_separation_other = ((100 - sep) * vol) / 100;
250-
DEBUGPRINT("AUDIO: volume is set to %d%%, stereo separation is %d%% [component-A is %d, component-B is %d]" NL, audio_volume, stereo_separation, stereo_separation_orig, stereo_separation_other);
251-
}
252-
253-
254225
// 10 channels, consist of: 4 channel for audio DMA, 4 channel for SIDs (each SIDs are pre-mixed to one channel by sid.c), 2 OPL3 channel (OPL3 is pre-mixed into two channels in opl3.c)
255226
#define MIXED_CHANNELS 10
256227

@@ -264,6 +235,9 @@ void audio_set_stereo_parameters ( int vol, int sep )
264235
#define STREAMS(n) (streams + ((n) * (AUDIO_BUFFER_SAMPLES_MAX)))
265236
#define STREAMS_SAMPLE(n,d) ((int)(STREAMS(n)[d]))
266237

238+
static float scalers_left[MIXED_CHANNELS];
239+
static float scalers_right[MIXED_CHANNELS];
240+
267241

268242
static void audio_callback ( void *userdata, Uint8 *stereo_out_stream, int len )
269243
{
@@ -310,18 +284,16 @@ static void audio_callback ( void *userdata, Uint8 *stereo_out_stream, int len )
310284
}
311285
// Now mix the result ...
312286
for (int i = 0, j = 0; i < len; i++) {
313-
// mixing streams together
314-
// Currently: put the first two SIDS to the left, the second two to the right, same for DMA audio channels, and OPL3 seems to need 2 channel
315-
const register int orig_left = STREAMS_SAMPLE(0, i) + STREAMS_SAMPLE(1, i) + STREAMS_SAMPLE(4, i) + STREAMS_SAMPLE(5, i) + STREAMS_SAMPLE(8, i);
316-
const register int orig_right = STREAMS_SAMPLE(2, i) + STREAMS_SAMPLE(3, i) + STREAMS_SAMPLE(6, i) + STREAMS_SAMPLE(7, i) + STREAMS_SAMPLE(9, i);
317-
#if 1
318-
// channel stereo separation (including inversion) + volume handling
319-
int left = ((orig_left * stereo_separation_orig) / 100) + ((orig_right * stereo_separation_other) / 100);
320-
int right = ((orig_right * stereo_separation_orig) / 100) + ((orig_left * stereo_separation_other) / 100);
321-
#else
322-
int left = orig_left;
323-
int right = orig_right;
324-
#endif
287+
// do the mixing stuff. NOTE: it seems on modern CPUs, using float/doubles can be even faster than integer math where I also need a division ...
288+
float fl_left = 0, fl_right = 0;
289+
for (int c = 0; c < MIXED_CHANNELS; c++) {
290+
const float sample = (float)STREAMS_SAMPLE(c, i);
291+
fl_left += sample * scalers_left[c];
292+
fl_right += sample * scalers_right[c];
293+
}
294+
// convert to integer
295+
int left = (int)fl_left;
296+
int right = (int)fl_right;
325297
// do some ugly clipping ...
326298
if (left > 0x7FFF) left = 0x7FFF;
327299
else if (left < -0x8000) left = -0x8000;
@@ -355,6 +327,34 @@ void audio65_clear_regs ( void )
355327
}
356328

357329

330+
#define GETMIXFL(n) mixer_floats[((n) + output_selection) >> 1]
331+
332+
333+
static void recalulate_scalers ( void )
334+
{
335+
const float master_left = GETMIXFL(0x1E) * xemu_volume_float;
336+
const float master_right = GETMIXFL(0x3E) * xemu_volume_float;
337+
// Digi (audio DMA) channel 1-2 volume
338+
scalers_left [0] = scalers_left [1] = GETMIXFL(0x10) * master_left;
339+
scalers_right[0] = scalers_right[1] = GETMIXFL(0x30) * master_right;
340+
// Digi (audio DMA) channel 3-4 volume
341+
scalers_left [2] = scalers_left [3] = GETMIXFL(0x12) * master_left;
342+
scalers_right[2] = scalers_right[3] = GETMIXFL(0x32) * master_right;
343+
// SID 1-2 volume
344+
scalers_left [4] = scalers_left [5] = GETMIXFL(0x00) * master_left;
345+
scalers_right[4] = scalers_right[5] = GETMIXFL(0x20) * master_right;
346+
// SID 3-4 volume
347+
scalers_left [6] = scalers_left [7] = GETMIXFL(0x02) * master_left;
348+
scalers_right[6] = scalers_right[7] = GETMIXFL(0x22) * master_right;
349+
// OPL FM volume. NOTE: Xemu uses NukedOPL which emulates OPL3 [stereo], but MEGA65's OPL2(-ish?) implementation has only mono as OPL source
350+
scalers_left [8] = scalers_left [9] = GETMIXFL(0x1C) * master_left;
351+
scalers_right[8] = scalers_right[9] = GETMIXFL(0x3C) * master_right;
352+
if (XEMU_UNLIKELY(mono_downmix))
353+
for (int c = 0; c < MIXED_CHANNELS; c++)
354+
scalers_left[c] = scalers_right[c] = (scalers_left[c] + scalers_right[c]) / 2;
355+
}
356+
357+
358358
void audio65_reset ( void )
359359
{
360360
// We always initialize SIDs/OPL, even if no audio emulation is compiled in
@@ -389,34 +389,130 @@ void audio65_start ( void )
389389

390390
Uint8 audio65_read_mixer_register ( void )
391391
{
392-
return mixer_coeffs[mixer_register];
392+
return mixer_bytes[mixer_register];
393+
}
394+
395+
396+
static inline void write_mixer_register ( const int reg, const Uint8 data )
397+
{
398+
mixer_bytes[reg] = data;
399+
mixer_floats[reg >> 1] = (float)((mixer_bytes[reg & 0xFE] << 8) + mixer_bytes[reg | 0x01]) / (float)0xFFFFU;
393400
}
394401

395402

396403
void audio65_write_mixer_register ( const Uint8 data )
397404
{
398-
mixer_coeffs[mixer_register] = data;
399-
if (mixer_register == 0x1E || mixer_register == 0x1F) { // HDMI-LEFT?
400-
int vol = (mixer_coeffs[0x1E] << 8) + mixer_coeffs[0x1F];
401-
vol = vol * 100 / 65536;
402-
audio_set_stereo_parameters(vol, AUDIO_UNCHANGED_SEPARATION);
403-
}
404-
if (mixer_register == 0x3E || mixer_register == 0x3F) { // HDMI-RIGHT?
405-
int vol = (mixer_coeffs[0x3E] << 8) + mixer_coeffs[0x3F];
406-
vol = vol * 100 / 65536;
407-
audio_set_stereo_parameters(vol, AUDIO_UNCHANGED_SEPARATION);
405+
if (mixer_bytes[mixer_register] != data) {
406+
write_mixer_register(mixer_register, data);
407+
recalulate_scalers();
408408
}
409409
}
410410

411411

412-
void audio65_init ( int sid_cycles_per_sec, int sound_mix_freq, int volume, int separation, unsigned int buffer_size )
412+
static void default_mixer_helper ( const int basereg, const Uint16 value_l, const Uint16 value_r )
413+
{
414+
write_mixer_register(basereg + 0x00, value_l >> 8);
415+
write_mixer_register(basereg + 0x01, value_l & 0xFF);
416+
write_mixer_register(basereg + 0x20, value_r >> 8);
417+
write_mixer_register(basereg + 0x21, value_r & 0xFF);
418+
// repeat the same for headphones output:
419+
write_mixer_register(basereg + 0xC0, value_l >> 8);
420+
write_mixer_register(basereg + 0xC1, value_l & 0xFF);
421+
write_mixer_register(basereg + 0xE0, value_r >> 8);
422+
write_mixer_register(basereg + 0xE1, value_r & 0xFF);
423+
}
424+
425+
426+
static void print_audio_info ( void )
427+
{
428+
DEBUGPRINT("AUDIO: emu-volume = %d%%, mono-downmix = %s, output-reg-shift = $%02X" NL,
429+
xemu_volume_int,
430+
mono_downmix ? "ON" : "off",
431+
output_selection
432+
);
433+
}
434+
435+
436+
void audio65_reset_mixer ( void )
437+
{
438+
mixer_register = 0;
439+
for (int i = 0; i < 0x100; i++)
440+
write_mixer_register(i, 0);
441+
default_mixer_helper(0x00, 0xBEBE, 0x4040); // SID-L input
442+
default_mixer_helper(0x02, 0x4040, 0xBEBE); // SID-R input
443+
default_mixer_helper(0x10, 0xBEBE, 0x4040); // DIGI-L input
444+
default_mixer_helper(0x12, 0x4040, 0xBEBE); // DIGI-R input
445+
default_mixer_helper(0x1C, 0xBEBE, 0xBEBE); // OPL FM! On MEGA65 OPL channel is maybe muted by default, btw ...
446+
default_mixer_helper(0x1E, 0xFFFF, 0xFFFF); // master volume
447+
recalulate_scalers();
448+
}
449+
450+
451+
static void set_volume ( int vol )
452+
{
453+
if (vol < 0) vol = 0;
454+
else if (vol > 100) vol = 100;
455+
xemu_volume_int = vol;
456+
xemu_volume_float = (float)vol / (float)100.0;
457+
}
458+
459+
460+
void audio65_set_volume ( int vol )
461+
{
462+
set_volume(vol);
463+
recalulate_scalers();
464+
print_audio_info();
465+
}
466+
467+
468+
int audio65_get_volume ( void )
469+
{
470+
return xemu_volume_int;
471+
}
472+
473+
474+
void audio65_set_mono_downmix ( const bool status )
475+
{
476+
mono_downmix = status;
477+
recalulate_scalers();
478+
print_audio_info();
479+
}
480+
481+
482+
bool audio65_get_mono_downmix ( void )
483+
{
484+
return mono_downmix;
485+
}
486+
487+
488+
void audio65_set_output ( const int val )
489+
{
490+
output_selection = val;
491+
recalulate_scalers();
492+
print_audio_info();
493+
}
494+
495+
496+
int audio65_get_output ( void )
497+
{
498+
return output_selection;
499+
}
500+
501+
502+
void audio65_init ( int sid_cycles_per_sec, int sound_mix_freq, int volume, unsigned int buffer_size )
413503
{
414504
static volatile int initialized = 0;
415505
if (initialized) {
416506
ERROR_WINDOW("Trying to reinitialize audio??\nRefusing to do so!!");
417507
return;
418508
}
419509
initialized = 1;
510+
// Setting these parameters should be followed by calling recalulate_scalers(). However, audio65_reset_mixer() at the end will call that anyway.
511+
output_selection = AUDIO_OUTPUT_SPEAKERS;
512+
mono_downmix = false;
513+
set_volume(volume);
514+
audio65_reset_mixer();
515+
print_audio_info();
420516
for (int i = 0; i < NUMBER_OF_SIDS; i++)
421517
UNLOCK_SID("INIT", i);
422518
UNLOCK_OPL("INIT");
@@ -451,7 +547,6 @@ void audio65_init ( int sid_cycles_per_sec, int sound_mix_freq, int volume, int
451547
//}
452548
} else
453549
ERROR_WINDOW("Cannot open audio device!");
454-
audio_set_stereo_parameters(volume, separation);
455550
#else
456551
DEBUGPRINT("AUDIO: has been disabled at compilation time." NL);
457552
#endif

targets/mega65/audio65.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,12 @@ extern struct SidEmulation sid[NUMBER_OF_SIDS];
3333
// You may want to disable audio emulation since it can disturb non-real-time emulation
3434
#define AUDIO_EMULATION
3535

36-
#define AUDIO_DEFAULT_SEPARATION 60
37-
#define AUDIO_DEFAULT_VOLUME 100
38-
#define AUDIO_UNCHANGED_SEPARATION -1000
39-
#define AUDIO_UNCHANGED_VOLUME -1000
36+
#define AUDIO_OUTPUT_SPEAKERS 0x00
37+
#define AUDIO_OUTPUT_HEADPHONES 0xC0
4038

41-
extern int stereo_separation;
42-
extern int audio_volume;
4339
extern Uint8 mixer_register;
4440

45-
extern void audio65_init ( int sid_cycles_per_sec, int sound_mix_freq, int volume, int separation, unsigned int buffer_size );
41+
extern void audio65_init ( int sid_cycles_per_sec, int sound_mix_freq, int volume, unsigned int buffer_size );
4642
extern void audio65_reset ( void );
4743
extern void audio65_clear_regs ( void );
4844
extern void audio65_start ( void );
@@ -53,5 +49,12 @@ extern void audio_set_stereo_parameters ( int vol, int sep );
5349

5450
extern Uint8 audio65_read_mixer_register ( void );
5551
extern void audio65_write_mixer_register ( const Uint8 data );
52+
extern void audio65_reset_mixer ( void );
53+
extern void audio65_set_volume ( int vol );
54+
extern int audio65_get_volume ( void );
55+
extern void audio65_set_mono_downmix ( const bool status );
56+
extern bool audio65_get_mono_downmix ( void );
57+
extern void audio65_set_output ( const int val );
58+
extern int audio65_get_output ( void );
5659

5760
#endif

targets/mega65/configdb.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@ static const struct xemutools_configdef_num_st num_options[] = {
132132
{ "umon", 0, "TCP-based dual mode (http / text) monitor port number [NOT YET WORKING]", &configdb.umon, 0, 0xFFFF },
133133
#endif
134134
{ "sdlrenderquality", RENDER_SCALE_QUALITY, "Setting SDL hint for scaling method/quality on rendering (0, 1, 2)", &configdb.sdlrenderquality, 0, 2 },
135-
{ "stereoseparation", AUDIO_DEFAULT_SEPARATION, "Audio stereo separation; 100(hard-stereo) ... 0(mono) ... -100(hard-reversed-stereo); default: " STRINGIFY(AUDIO_DEFAULT_SEPARATION), &configdb.stereoseparation, -100, 100 },
136-
{ "mastervolume", AUDIO_DEFAULT_VOLUME, "Audio emulation mixing final volume (100=unchanged ... 0=silence); default: " STRINGIFY(AUDIO_DEFAULT_VOLUME), &configdb.mastervolume, 0, 100 },
135+
{ "mastervolume", 100, "Audio emulation mixing final volume (100=default/full ... 0=silence)", &configdb.mastervolume, 0, 100 },
137136
// FIXME: as a workaround, I set this to "0" PAL, as newer MEGA65's default is this. HOWEVER this should be not handled this way but using a newer Hyppo!
138137
{ "videostd", 0, "Use given video standard at startup/reset (0 = PAL, 1 = NTSC, -1 = Hyppo default)", &configdb.videostd, -1, 1 },
139138
{ "sidmask", 15, "Enabled SIDs of the four, in form of a bitmask", &configdb.sidmask, 0, 15 },

targets/mega65/configdb.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ struct configdb_st {
105105
int umon;
106106
#endif
107107
int sdlrenderquality;
108-
int stereoseparation;
109108
int mastervolume;
110109
double fast_mhz;
111110
int nosound;

targets/mega65/mega65.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,6 @@ int main ( int argc, char **argv )
774774
SID_CYCLES_PER_SEC, // SID cycles per sec
775775
AUDIO_SAMPLE_FREQ, // sound mix freq
776776
configdb.mastervolume,
777-
configdb.stereoseparation,
778777
configdb.audiobuffersize
779778
);
780779
DEBUGPRINT("MEM: UNHANDLED memory policy: %d" NL, configdb.skip_unhandled_mem);

targets/mega65/memory_mapper.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,8 @@ static void i2c_writer ( const Uint32 addr32, const Uint8 data ) {
268268
if (rel >= I2C_NVRAM_OFFSET && rel < (I2C_NVRAM_OFFSET + I2C_NVRAM_SIZE)) {
269269
//DEBUGPRINT("I2C: NVRAM write ($%02X->$%02X) @ NVRAM+$%X" NL, i2c_regs[rel], data, rel - I2C_NVRAM_OFFSET);
270270
i2c_regs[rel] = data;
271-
} else if (configdb.mega65_model == 3 && rel >= 0x1D0 && rel <= 0x1EF) { // Hyppo needs this on PCB R3 for I2C target setup (audio mixer settings)
272-
// TODO: emulate the mixer stuff
273-
i2c_regs[rel] = data;
271+
} else if (configdb.mega65_model == 3 && rel >= 0x1D0 && rel <= 0x1EF) { // Hyppo needs this on PCB R3 for I2C target setup (audio amp chip settings?)
272+
i2c_regs[rel] = data; // ... though the audio amp is not emulated by Xemu
274273
} else {
275274
DEBUGPRINT("I2C: unhandled write ($%02X) @ I2C+$%X" NL, data, rel);
276275
}

0 commit comments

Comments
 (0)