From cce84daaf89988d67386436c4600fd5f49d720da Mon Sep 17 00:00:00 2001 From: Dimitris Panokostas Date: Fri, 14 Aug 2020 22:07:43 +0200 Subject: [PATCH] Rewrote sound and audio handling, implemented driveclick WIP, performance is still slower --- .../Amiberry-Release-dispmanx.vgdbsettings | 2 +- .../Amiberry/Amiberry-Release.vgdbsettings | 2 +- VisualGDB/Amiberry/Amiberry.vcxproj | 2 + VisualGDB/Amiberry/Amiberry.vcxproj.filters | 6 + data/floppy_sounds/drive_click.wav | Bin 0 -> 17998 bytes data/floppy_sounds/drive_snatch.wav | Bin 0 -> 39828 bytes data/floppy_sounds/drive_spin.wav | Bin 0 -> 20180 bytes data/floppy_sounds/drive_spinnd.wav | Bin 0 -> 20180 bytes data/floppy_sounds/drive_startup.wav | Bin 0 -> 43492 bytes src/audio.cpp | 2200 +++++++++++++---- src/blkdev_cdimage.cpp | 94 +- src/cd32_fmv.cpp | 82 +- src/devices.cpp | 4 + src/disk.cpp | 3 + src/driveclick.cpp | 600 +++++ src/events.cpp | 14 +- src/include/driveclick.h | 57 + src/include/events.h | 11 +- src/include/newcpu.h | 2 +- src/include/uae/uae.h | 3 + src/newcpu.cpp | 4 +- src/osdep/amiberry.cpp | 26 + src/osdep/amiberry_gfx.cpp | 1 + src/osdep/amiberry_gui.cpp | 2 +- src/osdep/cda_play.cpp | 140 +- src/osdep/cda_play.h | 34 +- src/osdep/sysconfig.h | 2 +- src/osdep/target.h | 11 +- src/sounddep/sound.cpp | 888 +++++-- src/sounddep/sound.h | 149 +- 30 files changed, 3416 insertions(+), 923 deletions(-) create mode 100644 data/floppy_sounds/drive_click.wav create mode 100644 data/floppy_sounds/drive_snatch.wav create mode 100644 data/floppy_sounds/drive_spin.wav create mode 100644 data/floppy_sounds/drive_spinnd.wav create mode 100644 data/floppy_sounds/drive_startup.wav create mode 100644 src/driveclick.cpp create mode 100644 src/include/driveclick.h diff --git a/VisualGDB/Amiberry/Amiberry-Release-dispmanx.vgdbsettings b/VisualGDB/Amiberry/Amiberry-Release-dispmanx.vgdbsettings index 64234cba..1dbf550c 100644 --- a/VisualGDB/Amiberry/Amiberry-Release-dispmanx.vgdbsettings +++ b/VisualGDB/Amiberry/Amiberry-Release-dispmanx.vgdbsettings @@ -114,7 +114,7 @@ false false /home/pi/projects/amiberry/$(TargetFileName) - Local + Remote false false Auto diff --git a/VisualGDB/Amiberry/Amiberry-Release.vgdbsettings b/VisualGDB/Amiberry/Amiberry-Release.vgdbsettings index 5e39e56f..69725fee 100644 --- a/VisualGDB/Amiberry/Amiberry-Release.vgdbsettings +++ b/VisualGDB/Amiberry/Amiberry-Release.vgdbsettings @@ -114,7 +114,7 @@ false false /home/pi/projects/amiberry/$(TargetFileName) - Local + Remote false false Auto diff --git a/VisualGDB/Amiberry/Amiberry.vcxproj b/VisualGDB/Amiberry/Amiberry.vcxproj index 64209e96..8f2f5a35 100644 --- a/VisualGDB/Amiberry/Amiberry.vcxproj +++ b/VisualGDB/Amiberry/Amiberry.vcxproj @@ -222,6 +222,7 @@ + @@ -403,6 +404,7 @@ + diff --git a/VisualGDB/Amiberry/Amiberry.vcxproj.filters b/VisualGDB/Amiberry/Amiberry.vcxproj.filters index a0afba28..d5e46290 100644 --- a/VisualGDB/Amiberry/Amiberry.vcxproj.filters +++ b/VisualGDB/Amiberry/Amiberry.vcxproj.filters @@ -649,6 +649,9 @@ Source files\osdep\gui + + Source files + @@ -1065,5 +1068,8 @@ Source files\osdep + + Source files\include + \ No newline at end of file diff --git a/data/floppy_sounds/drive_click.wav b/data/floppy_sounds/drive_click.wav new file mode 100644 index 0000000000000000000000000000000000000000..c21a5f868ce579dafb4fc6be2e0696df6d0c5ddd GIT binary patch literal 17998 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�`8k-j3bE7GS?nQ}j4u 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 Jgaa@X{vX$~yWs!; literal 0 HcmV?d00001 diff --git a/data/floppy_sounds/drive_spin.wav b/data/floppy_sounds/drive_spin.wav new file mode 100644 index 0000000000000000000000000000000000000000..184524f8d7c22b92fac08edf0aff5353314054a7 GIT binary patch literal 20180 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 literal 0 HcmV?d00001 diff --git a/data/floppy_sounds/drive_spinnd.wav b/data/floppy_sounds/drive_spinnd.wav new file mode 100644 index 0000000000000000000000000000000000000000..184524f8d7c22b92fac08edf0aff5353314054a7 GIT binary patch literal 20180 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 literal 0 HcmV?d00001 diff --git a/data/floppy_sounds/drive_startup.wav b/data/floppy_sounds/drive_startup.wav new file mode 100644 index 0000000000000000000000000000000000000000..6456d8a2d8e9515b595b311b137a35cd341ed129 GIT binary patch literal 43492 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

}pN5&$GdN0k6xiu{G=fi)EAecOEHri{>&!9a1qW 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 D0t`SR literal 0 HcmV?d00001 diff --git a/src/audio.cpp b/src/audio.cpp index bdb2ffd6..2d8169cf 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -20,15 +20,38 @@ #include "custom.h" #include "newcpu.h" #include "autoconf.h" +#include "gensound.h" #include "audio.h" #include "sounddep/sound.h" +#include "events.h" #include "savestate.h" +#ifdef DRIVESOUND +#include "driveclick.h" +#endif +#include "zfile.h" +#include "uae.h" #include "gui.h" #include "xwin.h" +//#include "debug.h" +//#include "sndboard.h" +#ifdef AVIOUTPUT +#include "avioutput.h" +#endif +#ifdef AHI +#include "traps.h" +#include "ahidsound.h" +#include "ahidsound_new.h" +#endif #include "threaddep/thread.h" #include +#define DEBUG_AUDIO 0 +#define DEBUG_AUDIO2 0 +#define DEBUG_AUDIO_HACK 0 +#define DEBUG_CHANNEL_MASK 15 +#define TEST_AUDIO 0 + #define PERIOD_MIN 4 #define PERIOD_MIN_NONCE 60 @@ -40,6 +63,13 @@ STATIC_INLINE bool isaudio (void) return currprefs.produce_sound != 0; } +#if DEBUG_AUDIO > 0 || DEBUG_AUDIO_HACK > 0 || DEBUG_AUDIO2 > 0 +static bool debugchannel (int ch) +{ + return ((1 << ch) & DEBUG_CHANNEL_MASK) != 0; +} +#endif + STATIC_INLINE bool usehacks(void) { return !(currprefs.cs_hacks & 8) && (currprefs.cpu_model >= 68020 || currprefs.m68k_speed != 0 || (currprefs.cs_hacks & 4)); @@ -59,6 +89,7 @@ typedef struct { struct audio_channel_data2 { int current_sample, last_sample; + uae_u8 new_sample; int sample_accum, sample_accum_time; int sinc_output_state; sinc_queue_t sinc_queue[SINC_QUEUE_LENGTH]; @@ -68,6 +99,19 @@ struct audio_channel_data2 int mixvol; unsigned int adk_mask; }; +struct audio_stream_data +{ + bool active; + unsigned int evtime; + struct audio_channel_data2 data[AUDIO_CHANNEL_MAX_STREAM_CH]; + SOUND_STREAM_CALLBACK cb; + void *cb_data; +}; + +#define FIR_WIDTH 512 +#define VOLCNT_BUFFER_SIZE 4096 +union sIntFlt { uae_u32 U32; float F32; }; +static float firmem[2 * FIR_WIDTH + 1]; struct audio_channel_data { @@ -77,26 +121,229 @@ struct audio_channel_data bool dr; bool dsr; bool pbufldl; + int drhpos; bool dat_written; uaecptr lc, pt; int state; int per; int len, wlen; + int volcnt; uae_u16 dat, dat2; struct audio_channel_data2 data; +#if TEST_AUDIO > 0 + bool hisample, losample; + bool have_dat; + int per_original; +#endif /* too fast cpu fixes */ uaecptr ptx; bool ptx_written; bool ptx_tofetch; int dmaofftime_active; + int dmaofftime_cpu_cnt; + uaecptr dmaofftime_pc; + int volcntbufcnt; + float volcntbuf[VOLCNT_BUFFER_SIZE]; }; +static int audio_extra_streams[AUDIO_CHANNEL_STREAMS]; +static int audio_total_extra_streams; + +static int samplecnt; +#if SOUNDSTUFF > 0 +static int extrasamples, outputsample, doublesample; +#endif + +int sampleripper_enabled; +struct ripped_sample +{ + struct ripped_sample *next; + uae_u8 *sample; + int len, per, changed; +}; + +static struct ripped_sample *ripped_samples; + +void write_wavheader (struct zfile *wavfile, uae_u32 size, uae_u32 freq) +{ + uae_u16 tw; + uae_u32 tl; + int bits = 8, channels = 1; + + zfile_fseek (wavfile, 0, SEEK_SET); + zfile_fwrite ("RIFF", 1, 4, wavfile); + tl = 0; + if (size) + tl = size - 8; + zfile_fwrite (&tl, 1, 4, wavfile); + zfile_fwrite ("WAVEfmt ", 1, 8, wavfile); + tl = 16; + zfile_fwrite (&tl, 1, 4, wavfile); + tw = 1; + zfile_fwrite (&tw, 1, 2, wavfile); + tw = channels; + zfile_fwrite (&tw, 1, 2, wavfile); + tl = freq; + zfile_fwrite (&tl, 1, 4, wavfile); + tl = freq * channels * bits / 8; + zfile_fwrite (&tl, 1, 4, wavfile); + tw = channels * bits / 8; + zfile_fwrite (&tw, 1, 2, wavfile); + tw = bits; + zfile_fwrite (&tw, 1, 2, wavfile); + zfile_fwrite ("data", 1, 4, wavfile); + tl = 0; + if (size) + tl = size - 44; + zfile_fwrite (&tl, 1, 4, wavfile); +} + +static void convertsample (uae_u8 *sample, int len) +{ + int i; + for (i = 0; i < len; i++) + sample[i] += 0x80; +} + +static void namesplit (TCHAR *s) +{ + int l; + + l = _tcslen (s) - 1; + while (l >= 0) { + if (s[l] == '.') + s[l] = 0; + if (s[l] == '\\' || s[l] == '/' || s[l] == ':' || s[l] == '?') { + l++; + break; + } + l--; + } + if (l > 0) + memmove (s, s + l, (_tcslen (s + l) + 1) * sizeof (TCHAR)); +} + +//void audio_sampleripper (int mode) +//{ +// struct ripped_sample *rs = ripped_samples; +// int cnt = 1; +// TCHAR path[MAX_DPATH], name[MAX_DPATH], filename[MAX_DPATH]; +// TCHAR underline[] = _T("_"); +// TCHAR extension[4]; +// struct zfile *wavfile; +// +// if (mode < 0) { +// while (rs) { +// struct ripped_sample *next = rs->next; +// xfree(rs); +// rs = next; +// } +// ripped_samples = NULL; +// return; +// } +// +// while (rs) { +// if (rs->changed) { +// int type = -1; +// rs->changed = 0; +// fetch_ripperpath (path, sizeof (path) / sizeof (TCHAR)); +// name[0] = 0; +// if (currprefs.floppyslots[0].dfxtype >= 0) { +// _tcscpy(name, currprefs.floppyslots[0].df); +// type = PATH_FLOPPY; +// } else if (currprefs.cdslots[0].inuse) { +// _tcscpy(name, currprefs.cdslots[0].name); +// type = PATH_CD; +// } +// if (!name[0]) +// underline[0] = 0; +// if (type >= 0) +// cfgfile_resolve_path_load(name, sizeof(name) / sizeof(TCHAR), type); +// namesplit (name); +// _tcscpy (extension, _T("wav")); +// _stprintf (filename, _T("%s%s%s%03d.%s"), path, name, underline, cnt, extension); +// wavfile = zfile_fopen (filename, _T("wb"), 0); +// if (wavfile) { +// int freq = rs->per > 0 ? (currprefs.ntscmode ? 3579545 : 3546895 / rs->per) : 8000; +// write_wavheader (wavfile, 0, 0); +// convertsample (rs->sample, rs->len); +// zfile_fwrite (rs->sample, rs->len, 1, wavfile); +// convertsample (rs->sample, rs->len); +// write_wavheader (wavfile, zfile_ftell(wavfile), freq); +// zfile_fclose (wavfile); +// write_log (_T("SAMPLERIPPER: %d: %dHz %d bytes\n"), cnt, freq, rs->len); +// } else { +// write_log (_T("SAMPLERIPPER: failed to open '%s'\n"), filename); +// } +// } +// cnt++; +// rs = rs->next; +// } +//} + +//static void do_samplerip (struct audio_channel_data *adp) +//{ +// struct ripped_sample *rs = ripped_samples, *prev; +// int len = adp->wlen * 2; +// uae_u8 *smp = chipmem_xlate_indirect (adp->pt); +// int cnt = 0, i; +// +// if (!smp || !chipmem_check_indirect (adp->pt, len)) +// return; +// for (i = 0; i < len; i++) { +// if (smp[i] != 0) +// break; +// } +// if (i == len || len <= 2) +// return; +// prev = NULL; +// while(rs) { +// if (rs->sample) { +// if (len == rs->len && !memcmp (rs->sample, smp, len)) +// break; +// /* replace old identical but shorter sample */ +// if (len > rs->len && !memcmp (rs->sample, smp, rs->len)) { +// xfree (rs->sample); +// rs->sample = xmalloc (uae_u8, len); +// memcpy (rs->sample, smp, len); +// write_log (_T("SAMPLERIPPER: replaced sample %d (%d -> %d)\n"), cnt, rs->len, len); +// rs->len = len; +// rs->per = adp->per / CYCLE_UNIT; +// rs->changed = 1; +// audio_sampleripper (0); +// return; +// } +// } +// prev = rs; +// rs = rs->next; +// cnt++; +// } +// if (rs || cnt > 100) +// return; +// rs = xmalloc (struct ripped_sample ,1); +// if (prev) +// prev->next = rs; +// else +// ripped_samples = rs; +// rs->len = len; +// rs->per = adp->per / CYCLE_UNIT; +// rs->sample = xmalloc (uae_u8, len); +// memcpy (rs->sample, smp, len); +// rs->next = NULL; +// rs->changed = 1; +// write_log (_T("SAMPLERIPPER: sample added (%06X, %d bytes), total %d samples\n"), adp->pt, len, ++cnt); +// audio_sampleripper (0); +//} + static struct audio_channel_data audio_channel[AUDIO_CHANNELS_PAULA]; -static struct audio_channel_data2 *audio_data[AUDIO_CHANNELS_PAULA]; +static struct audio_stream_data audio_stream[AUDIO_CHANNEL_STREAMS]; +static struct audio_channel_data2 *audio_data[AUDIO_CHANNELS_PAULA + AUDIO_CHANNEL_STREAMS * AUDIO_CHANNEL_MAX_STREAM_CH]; int sound_available = 0; void (*sample_handler) (void); -static void (*sample_prehandler) (unsigned long best_evtime); +static void(*sample_prehandler) (unsigned long best_evtime); +static void(*extra_sample_prehandler) (unsigned long best_evtime); +float sample_evtime; float scaled_sample_evtime; int sound_cd_volume[2]; @@ -104,12 +351,13 @@ int sound_paula_volume[2]; static unsigned long last_cycles; static float next_sample_evtime; +static int previous_volcnt_update; typedef uae_s8 sample8_t; #define DO_CHANNEL_1(v, c) do { (v) *= audio_channel[c].data.mixvol; } while (0) #define SBASEVAL16(logn) ((logn) == 1 ? SOUND16_BASE_VAL >> 1 : SOUND16_BASE_VAL) -STATIC_INLINE int FINISH_DATA(int data, int bits, int ch) +STATIC_INLINE int FINISH_DATA (int data, int bits, int ch) { if (bits < 16) { int shift = 16 - bits; @@ -131,6 +379,31 @@ static int saved_ptr, saved_ptr2; static int mixed_on, mixed_stereo_size, mixed_mul1, mixed_mul2; static int led_filter_forced, sound_use_filter, sound_use_filter_sinc, led_filter_on; +#define PAULARATE 3740000 +static float Sinc(float x) +{ + return x ? sinf(x) / x : 1; +} +static float Hamming(float x) +{ + float pi = 4 * atanf(1); + float v; + if (x > -1 && x < 1) + v = cosf(x * pi / 2); + else + v = 0; + return v * v; +} +static void makefir(void) +{ + float pi = 4 * atanf(1); + float *FIRTable = firmem + FIR_WIDTH; + float yscale = float(currprefs.sound_freq) / float(PAULARATE); + float xscale = pi * yscale; + for (int i = -FIR_WIDTH; i <= FIR_WIDTH; i++) + FIRTable[i] = yscale * Sinc(float(i) * xscale) * Hamming(float(i) / float(FIR_WIDTH - 1)); +} + /* denormals are very small floating point numbers that force FPUs into slow mode. All lowpass filters using floats are suspectible to denormals unless a small offset is added to avoid very small floating point numbers. */ @@ -166,12 +439,12 @@ enum { * and to 1 dB with the filter off. */ -static int filter(int input, struct filter_state *fs) +static int filter (int input, struct filter_state *fs) { int o; float normal_output, led_output; - input = uae_s16(input); + input = (uae_s16)input; switch (sound_use_filter) { case FILTER_MODEL_A500: @@ -203,9 +476,9 @@ static int filter(int input, struct filter_state *fs) } if (led_filter_on) - o = int(led_output); + o = led_output; else - o = int(normal_output); + o = normal_output; if (o > 32767) o = 32767; @@ -217,96 +490,100 @@ static int filter(int input, struct filter_state *fs) /* Always put the right word before the left word. */ -static void (*put_sound_word_mono_func)(uae_u32 w); -static void (*put_sound_word_stereo_func)(uae_u32 left, uae_u32 right); - -static void put_sound_word_stereo_func_filter_mixed(uae_u32 lnew, uae_u32 rnew) +static void put_sound_word_right (uae_u32 w) { - uae_u32 rold, lold, tmp; - - lnew = filter(lnew, &sound_filter_state[0]); - rnew = filter(rnew, &sound_filter_state[1]); - - left_word_saved[saved_ptr] = lnew; - right_word_saved[saved_ptr] = rnew; - - saved_ptr = (saved_ptr + 1) & mixed_stereo_size; - - lold = left_word_saved[saved_ptr]; - tmp = (rnew * mixed_mul2 + lold * mixed_mul1) / MIXED_STEREO_SCALE; - - rold = right_word_saved[saved_ptr]; - lnew = (lnew * mixed_mul2 + rold * mixed_mul1) / MIXED_STEREO_SCALE; - - PUT_SOUND_WORD_STEREO(lnew, tmp); + if (mixed_on) { + right_word_saved[saved_ptr] = w; + return; + } + PUT_SOUND_WORD(w); } -static void put_sound_word_stereo_func_filter_notmixed(uae_u32 left, uae_u32 right) +static void put_sound_word_left (uae_u32 w) { - left = filter(left, &sound_filter_state[0]); - right = filter(right, &sound_filter_state[1]); - PUT_SOUND_WORD_STEREO(left, right); + if (mixed_on) { + uae_u32 rold, lold, rnew, lnew, tmp; + + left_word_saved[saved_ptr] = w; + lnew = w - SOUND16_BASE_VAL; + rnew = right_word_saved[saved_ptr] - SOUND16_BASE_VAL; + + saved_ptr = (saved_ptr + 1) & mixed_stereo_size; + + lold = left_word_saved[saved_ptr] - SOUND16_BASE_VAL; + tmp = (rnew * mixed_mul2 + lold * mixed_mul1) / MIXED_STEREO_SCALE; + tmp += SOUND16_BASE_VAL; + + rold = right_word_saved[saved_ptr] - SOUND16_BASE_VAL; + w = (lnew * mixed_mul2 + rold * mixed_mul1) / MIXED_STEREO_SCALE; + + PUT_SOUND_WORD(tmp); + PUT_SOUND_WORD(w); + } else { + PUT_SOUND_WORD(w); + } } -static void put_sound_word_stereo_func_nofilter_mixed(uae_u32 lnew, uae_u32 rnew) +static void put_sound_word_right2 (uae_u32 w) { - uae_u32 rold, lold, tmp; - - left_word_saved[saved_ptr] = lnew; - right_word_saved[saved_ptr] = rnew; - - saved_ptr = (saved_ptr + 1) & mixed_stereo_size; - - lold = left_word_saved[saved_ptr]; - tmp = (rnew * mixed_mul2 + lold * mixed_mul1) / MIXED_STEREO_SCALE; - - rold = right_word_saved[saved_ptr]; - lnew = (lnew * mixed_mul2 + rold * mixed_mul1) / MIXED_STEREO_SCALE; - - PUT_SOUND_WORD_STEREO(lnew, tmp); + if (mixed_on) { + right2_word_saved[saved_ptr2] = w; + return; + } + PUT_SOUND_WORD(w); } -static void put_sound_word_stereo_func_nofilter_notmixed(uae_u32 left, uae_u32 right) +static void put_sound_word_left2 (uae_u32 w) { - PUT_SOUND_WORD_STEREO(left, right); + if (mixed_on) { + uae_u32 rold, lold, rnew, lnew, tmp; + + left2_word_saved[saved_ptr2] = w; + lnew = w - SOUND16_BASE_VAL; + rnew = right2_word_saved[saved_ptr2] - SOUND16_BASE_VAL; + + saved_ptr2 = (saved_ptr2 + 1) & mixed_stereo_size; + + lold = left2_word_saved[saved_ptr2] - SOUND16_BASE_VAL; + tmp = (rnew * mixed_mul2 + lold * mixed_mul1) / MIXED_STEREO_SCALE; + tmp += SOUND16_BASE_VAL; + + rold = right2_word_saved[saved_ptr2] - SOUND16_BASE_VAL; + w = (lnew * mixed_mul2 + rold * mixed_mul1) / MIXED_STEREO_SCALE; + + PUT_SOUND_WORD(tmp); + PUT_SOUND_WORD(w); + } else { + PUT_SOUND_WORD(w); + } } -static void put_sound_word_mono_func_filter(uae_u32 data) -{ - data = filter(data, &sound_filter_state[0]); - PUT_SOUND_WORD(data); -} - -static void put_sound_word_mono_func_nofilter(uae_u32 data) -{ - PUT_SOUND_WORD(data); -} - -static void anti_prehandler(unsigned long best_evtime) +static void anti_prehandler (unsigned long best_evtime) { int i, output; struct audio_channel_data2 *acd; /* Handle accumulator antialiasiation */ - for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { + for (i = 0; audio_data[i]; i++) { acd = audio_data[i]; - output = (acd->current_sample * acd->mixvol) & acd->adk_mask; + output = (acd->current_sample * acd->mixvol) & acd->adk_mask; acd->sample_accum += output * best_evtime; acd->sample_accum_time += best_evtime; } } -static void samplexx_anti_handler (int *datasp) +static void samplexx_anti_handler (int *datasp, int ch_start, int ch_num) { - for (auto i = 0; i < AUDIO_CHANNELS_PAULA; i++) { - auto acd = audio_data[i]; - datasp[i] = acd->sample_accum_time ? (acd->sample_accum / acd->sample_accum_time) : 0; + int i, j; + for (i = ch_start, j = 0; j < ch_num; i++, j++) { + struct audio_channel_data2 *acd = audio_data[i]; + datasp[j] = acd->sample_accum_time ? (acd->sample_accum / acd->sample_accum_time) : 0; acd->sample_accum = 0; acd->sample_accum_time = 0; } } -static void sinc_prehandler_paula(unsigned long best_evtime) +static void sinc_prehandler_paula (unsigned long best_evtime) { int i, output; struct audio_channel_data2 *acd; @@ -330,89 +607,200 @@ static void sinc_prehandler_paula(unsigned long best_evtime) } /* this interpolator performs BLEP mixing (bleps are shaped like integrated sinc - * functions) with a type of BLEP that matches the filtering configuration. */ -static void samplexx_sinc_handler (int *datasp) +* functions) with a type of BLEP that matches the filtering configuration. */ +static void samplexx_sinc_handler (int *datasp, int ch_start, int ch_num) { - int n; + int n, i, k; + int const *winsinc; - if (sound_use_filter_sinc) { + if (sound_use_filter_sinc && ch_start == 0) { n = (sound_use_filter_sinc == FILTER_MODEL_A500) ? 0 : 2; if (led_filter_on) n += 1; } else { n = 4; } - auto winsinc = winsinc_integral[n]; + winsinc = winsinc_integral[n]; - for (auto i = 0; i < AUDIO_CHANNELS_PAULA; i++) { - auto acd = audio_data[i]; + + for (i = ch_start, k = 0; k < ch_num; i++, k++) { + int j, v; + struct audio_channel_data2 *acd = audio_data[i]; /* The sum rings with harmonic components up to infinity... */ - auto sum = acd->sinc_output_state << 17; + int sum = acd->sinc_output_state << 17; /* ...but we cancel them through mixing in BLEPs instead */ - auto offsetpos = acd->sinc_queue_head & (SINC_QUEUE_LENGTH - 1); - for (int j = 0; j < SINC_QUEUE_LENGTH; j += 1) { - auto age = acd->sinc_queue_time - acd->sinc_queue[offsetpos].time; + int offsetpos = acd->sinc_queue_head & (SINC_QUEUE_LENGTH - 1); + for (j = 0; j < SINC_QUEUE_LENGTH; j += 1) { + int age = acd->sinc_queue_time - acd->sinc_queue[offsetpos].time; if (age >= SINC_QUEUE_MAX_AGE || age < 0) break; sum -= winsinc[age] * acd->sinc_queue[offsetpos].output; offsetpos = (offsetpos + 1) & (SINC_QUEUE_LENGTH - 1); } - auto v = sum >> 15; + v = sum >> 15; if (v > 32767) v = 32767; else if (v < -32768) v = -32768; - datasp[i] = v; + datasp[k] = v; + } +} + +static void do_filter(int *data, int num) +{ + if (currprefs.sound_filter) + *data = filter(*data, &sound_filter_state[num]); +} + +static void get_extra_channels(int *data1, int *data2, int sample1, int sample2) +{ + int d1 = *data1 + sample1; + int d2 = (data2 ? *data2 : 0) + sample2; + if (d1 < -32768) + d1 = -32768; + if (d1 > 32767) + d1 = 32767; + if (d2 < -32768) + d2 = -32768; + if (d2 > 32767) + d2 = 32767; + int needswap = currprefs.sound_stereo_swap_paula ^ currprefs.sound_stereo_swap_ahi; + if (needswap) { + *data1 = d2; + if (data2) + *data2 = d1; + } else { + *data1 = d1; + if (data2) + *data2 = d2; } } -static void sample16i_sinc_handler(void) +static void do_extra_channels(int idx, int ch, int *data1, int *data2, int *data3, int *data4, int *data5, int *data6) +{ + idx += AUDIO_CHANNELS_PAULA; + if (ch == 2) { + int datas[2]; + samplexx_anti_handler(datas, idx, 2); + get_extra_channels(data1, data2, datas[0], datas[1]); + } else if (ch == 1) { + int datas[1]; + samplexx_anti_handler(datas, idx, 1); + int d1 = *data1 + datas[0]; + if (d1 < -32768) + d1 = -32768; + if (d1 > 32767) + d1 = 32767; + *data1 = d1; + if (data2) + *data2 = d1; + } else if (ch > 2) { + int datas[AUDIO_CHANNEL_MAX_STREAM_CH]; + samplexx_anti_handler(datas, idx, 6); + get_extra_channels(data1, data2, datas[0], datas[1]); + if (data3 && data4) + get_extra_channels(data3, data4, datas[2], datas[3]); + if (data5 && data6) + get_extra_channels(data5, data6, datas[4], datas[5]); + } +} + +static void get_extra_channels_sample2(int *data1, int *data2, int mode) +{ + if (!audio_total_extra_streams) + return; + int idx = 0; + for (int i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + int ch = audio_extra_streams[i]; + if (ch) { + do_extra_channels(idx, ch, data1, data2, NULL, NULL, NULL, NULL); + idx += ch; + } + } +} + +static void get_extra_channels_sample6(int *data1, int *data2, int *data3, int *data4, int *data5, int *data6, int mode) +{ + if (!audio_total_extra_streams) + return; + int idx = 0; + for (int i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + int ch = audio_extra_streams[i]; + if (ch) { + do_extra_channels(idx, ch, data1, data2, data3, data4, data5, data6); + idx += ch; + } + } +} + +static void sample16i_sinc_handler (void) { int datas[AUDIO_CHANNELS_PAULA], data1; - samplexx_sinc_handler(datas); + samplexx_sinc_handler (datas, 0, AUDIO_CHANNELS_PAULA); data1 = datas[0] + datas[3] + datas[1] + datas[2]; - data1 = FINISH_DATA(data1, 18, 0); + data1 = FINISH_DATA (data1, 18, 0); + + do_filter(&data1, 0); - put_sound_word_mono_func(data1); - check_sound_buffers(); + get_extra_channels_sample2(&data1, NULL, 2); + + set_sound_buffers (); + PUT_SOUND_WORD_MONO (data1); + check_sound_buffers (); } -void sample16_handler(void) +void sample16_handler (void) { + int data0 = audio_channel[0].data.current_sample; + int data1 = audio_channel[1].data.current_sample; + int data2 = audio_channel[2].data.current_sample; + int data3 = audio_channel[3].data.current_sample; int data; - if (audio_channel[0].data.adk_mask) - data = audio_channel[0].data.current_sample * audio_channel[0].data.mixvol; - else - data = 0; - if (audio_channel[1].data.adk_mask) - data += audio_channel[1].data.current_sample * audio_channel[1].data.mixvol; - if (audio_channel[2].data.adk_mask) - data += audio_channel[2].data.current_sample * audio_channel[2].data.mixvol; - if (audio_channel[3].data.adk_mask) - data += audio_channel[3].data.current_sample * audio_channel[3].data.mixvol; - data = FINISH_DATA(data, 16, 0); + DO_CHANNEL_1 (data0, 0); + DO_CHANNEL_1 (data1, 1); + DO_CHANNEL_1 (data2, 2); + DO_CHANNEL_1 (data3, 3); + data0 &= audio_channel[0].data.adk_mask; + data1 &= audio_channel[1].data.adk_mask; + data2 &= audio_channel[2].data.adk_mask; + data3 &= audio_channel[3].data.adk_mask; + data0 += data1; + data0 += data2; + data0 += data3; + data = SBASEVAL16(2) + data0; + data = FINISH_DATA (data, 16, 0); - put_sound_word_mono_func(data); - check_sound_buffers(); + do_filter(&data, 0); + + get_extra_channels_sample2(&data, NULL, 0); + + set_sound_buffers (); + PUT_SOUND_WORD_MONO (data); + check_sound_buffers (); } - + /* This interpolator examines sample points when Paula switches the output - * voltage and computes the average of Paula's output */ -static void sample16i_anti_handler(void) +* voltage and computes the average of Paula's output */ +static void sample16i_anti_handler (void) { - int datas[AUDIO_CHANNELS_PAULA]; + int datas[AUDIO_CHANNELS_PAULA], data1; - samplexx_anti_handler(datas); - auto data1 = datas[0] + datas[3] + datas[1] + datas[2]; - data1 = FINISH_DATA(data1, 16, 0); + samplexx_anti_handler (datas, 0, AUDIO_CHANNELS_PAULA); + data1 = datas[0] + datas[3] + datas[1] + datas[2]; + data1 = FINISH_DATA (data1, 16, 0); - put_sound_word_mono_func(data1); - check_sound_buffers(); + do_filter(&data1, 0); + + get_extra_channels_sample2(&data1, NULL, 1); + + set_sound_buffers (); + PUT_SOUND_WORD_MONO (data1); + check_sound_buffers (); } -static void sample16i_rh_handler(void) +static void sample16i_rh_handler (void) { unsigned long delta, ratio; @@ -457,11 +845,16 @@ static void sample16i_rh_handler(void) delta = audio_channel[3].per; ratio = ((audio_channel[3].evtime % delta) << 8) / delta; data0 += (data3 * (256 - ratio) + data3p * ratio) >> 8; - data = data0; - data = FINISH_DATA(data, 16, 0); + data = SBASEVAL16(2) + data0; + data = FINISH_DATA (data, 16, 0); - put_sound_word_mono_func(data); - check_sound_buffers(); + do_filter(&data, 0); + + get_extra_channels_sample2(&data, NULL, 0); + + set_sound_buffers (); + PUT_SOUND_WORD_MONO (data); + check_sound_buffers (); } static void sample16i_crux_handler (void) @@ -496,7 +889,7 @@ static void sample16i_crux_handler (void) { struct audio_channel_data *cdp; - uae_u32 ratio, ratio1; + unsigned long ratio, ratio1; #define INTERVAL (scaled_sample_evtime * 3) cdp = audio_channel + 0; ratio1 = cdp->per - cdp->evtime; @@ -529,78 +922,236 @@ static void sample16i_crux_handler (void) data1 += data2; data0 += data3; data0 += data1; - data = data0; - data = FINISH_DATA(data, 16, 0); + data = SBASEVAL16(2) + data0; + data = FINISH_DATA (data, 16, 0); - put_sound_word_mono_func(data); - check_sound_buffers(); + do_filter(&data, 0); + + get_extra_channels_sample2(&data, NULL, 0); + + set_sound_buffers (); + PUT_SOUND_WORD_MONO (data); + check_sound_buffers (); +} + +#ifdef HAVE_STEREO_SUPPORT + +STATIC_INLINE void make6ch (uae_s32 d0, uae_s32 d1, uae_s32 d2, uae_s32 d3, uae_s32 *d4, uae_s32 *d5) +{ + uae_s32 sum = d0 + d1 + d2 + d3; + sum /= 8; + *d4 = sum; + *d5 = sum; +} + +void sample16ss_handler (void) +{ + int data0 = audio_channel[0].data.current_sample; + int data1 = audio_channel[1].data.current_sample; + int data2 = audio_channel[2].data.current_sample; + int data3 = audio_channel[3].data.current_sample; + int data4, data5; + DO_CHANNEL_1 (data0, 0); + DO_CHANNEL_1 (data1, 1); + DO_CHANNEL_1 (data2, 2); + DO_CHANNEL_1 (data3, 3); + + data0 &= audio_channel[0].data.adk_mask; + data1 &= audio_channel[1].data.adk_mask; + data2 &= audio_channel[2].data.adk_mask; + data3 &= audio_channel[3].data.adk_mask; + + data0 = FINISH_DATA (data0, 14, 0); + data1 = FINISH_DATA (data1, 14, 0); + data2 = FINISH_DATA (data2, 14, 1); + data3 = FINISH_DATA (data3, 14, 1); + + do_filter(&data0, 0); + do_filter(&data1, 1); + do_filter(&data2, 3); + do_filter(&data3, 2); + + if (currprefs.sound_stereo == SND_6CH) + make6ch(data0, data1, data2, data3, &data4, &data5); + + get_extra_channels_sample6(&data0, &data1, &data3, &data2, &data4, &data5, 0); + + set_sound_buffers (); + put_sound_word_right(data0); + put_sound_word_left (data1); + if (currprefs.sound_stereo == SND_6CH) { + PUT_SOUND_WORD(data4); + PUT_SOUND_WORD(data5); + } + put_sound_word_right2(data3); + put_sound_word_left2 (data2); + check_sound_buffers (); } /* This interpolator examines sample points when Paula switches the output - * voltage and computes the average of Paula's output */ +* voltage and computes the average of Paula's output */ -static void sample16si_anti_handler(void) +static void sample16ss_anti_handler (void) { + int data0, data1, data2, data3, data4, data5; int datas[AUDIO_CHANNELS_PAULA]; - samplexx_anti_handler(datas); - auto data1 = datas[0] + datas[3]; - auto data2 = datas[1] + datas[2]; - data1 = FINISH_DATA(data1, 15, 0); - data2 = FINISH_DATA(data2, 15, 1); + samplexx_anti_handler (datas, 0, AUDIO_CHANNELS_PAULA); + data0 = FINISH_DATA (datas[0], 14, 0); + data1 = FINISH_DATA (datas[1], 14, 0); + data2 = FINISH_DATA (datas[2], 14, 1); + data3 = FINISH_DATA (datas[3], 14, 1); - put_sound_word_stereo_func(data1, data2); - check_sound_buffers(); + do_filter(&data0, 0); + do_filter(&data1, 1); + do_filter(&data2, 3); + do_filter(&data3, 2); + + if (currprefs.sound_stereo == SND_6CH) + make6ch(data0, data1, data2, data3, &data4, &data5); + + get_extra_channels_sample6(&data0, &data1, &data3, &data2, &data4, &data5, 0); + + set_sound_buffers (); + put_sound_word_right(data0); + put_sound_word_left (data1); + if (currprefs.sound_stereo == SND_6CH) { + PUT_SOUND_WORD(data4); + PUT_SOUND_WORD(data5); + } + put_sound_word_right2(data3); + put_sound_word_left2 (data2); + check_sound_buffers (); } -static void sample16si_sinc_handler(void) +static void sample16si_anti_handler (void) { + int datas[AUDIO_CHANNELS_PAULA], data1, data2; + + samplexx_anti_handler (datas, 0, AUDIO_CHANNELS_PAULA); + data1 = datas[0] + datas[3]; + data2 = datas[1] + datas[2]; + data1 = FINISH_DATA (data1, 15, 0); + data2 = FINISH_DATA (data2, 15, 1); + + do_filter(&data1, 0); + do_filter(&data2, 1); + + get_extra_channels_sample2(&data1, &data2, 1); + + set_sound_buffers (); + put_sound_word_right(data1); + put_sound_word_left (data2); + check_sound_buffers (); +} + +static void sample16ss_sinc_handler (void) +{ + int data0, data1, data2, data3, data4, data5; int datas[AUDIO_CHANNELS_PAULA]; - samplexx_sinc_handler(datas); - auto data1 = datas[0] + datas[3]; - auto data2 = datas[1] + datas[2]; - data1 = FINISH_DATA(data1, 17, 0); - data2 = FINISH_DATA(data2, 17, 1); + samplexx_sinc_handler (datas, 0, AUDIO_CHANNELS_PAULA); + data0 = FINISH_DATA (datas[0], 16, 0); + data1 = FINISH_DATA (datas[1], 16, 0); + data2 = FINISH_DATA (datas[2], 16, 1); + data3 = FINISH_DATA (datas[3], 16, 1); - put_sound_word_stereo_func(data1, data2); - check_sound_buffers(); + do_filter(&data0, 0); + do_filter(&data1, 1); + do_filter(&data2, 3); + do_filter(&data3, 2); + + if (currprefs.sound_stereo == SND_6CH) + make6ch(data0, data1, data2, data3, &data4, &data5); + + get_extra_channels_sample6(&data0, &data1, &data3, &data2, &data4, &data5, 0); + + set_sound_buffers (); + put_sound_word_right(data0); + put_sound_word_left (data1); + if (currprefs.sound_stereo == SND_6CH) { + PUT_SOUND_WORD(data4); + PUT_SOUND_WORD(data5); + } + put_sound_word_right2(data3); + put_sound_word_left2 (data2); + check_sound_buffers (); } -void sample16s_handler(void) +static void sample16si_sinc_handler (void) { - int data_l = audio_channel[0].data.adk_mask ? audio_channel[0].data.current_sample * audio_channel[0].data.mixvol : 0; - int data_r = audio_channel[1].data.adk_mask ? audio_channel[1].data.current_sample * audio_channel[1].data.mixvol : 0; - if (audio_channel[2].data.adk_mask) - data_r += audio_channel[2].data.current_sample * audio_channel[2].data.mixvol; - if (audio_channel[3].data.adk_mask) - data_l += audio_channel[3].data.current_sample * audio_channel[3].data.mixvol; - data_l = FINISH_DATA(data_l, 15, 0); - data_r = FINISH_DATA(data_r, 15, 1); + int datas[AUDIO_CHANNELS_PAULA], data1, data2; - put_sound_word_stereo_func(data_l, data_r); - check_sound_buffers(); + samplexx_sinc_handler (datas, 0, AUDIO_CHANNELS_PAULA); + data1 = datas[0] + datas[3]; + data2 = datas[1] + datas[2]; + data1 = FINISH_DATA (data1, 17, 0); + data2 = FINISH_DATA (data2, 17, 1); + + do_filter(&data1, 0); + do_filter(&data2, 1); + + get_extra_channels_sample2(&data1, &data2, 2); + + set_sound_buffers (); + put_sound_word_right(data1); + put_sound_word_left(data2); + check_sound_buffers (); } -static void sample16si_crux_handler(void) +void sample16s_handler (void) { - auto data0 = audio_channel[0].data.current_sample; - auto data1 = audio_channel[1].data.current_sample; - auto data2 = audio_channel[2].data.current_sample; - auto data3 = audio_channel[3].data.current_sample; - auto data0p = audio_channel[0].data.last_sample; - auto data1p = audio_channel[1].data.last_sample; - auto data2p = audio_channel[2].data.last_sample; - auto data3p = audio_channel[3].data.last_sample; + int data0 = audio_channel[0].data.current_sample; + int data1 = audio_channel[1].data.current_sample; + int data2 = audio_channel[2].data.current_sample; + int data3 = audio_channel[3].data.current_sample; + DO_CHANNEL_1 (data0, 0); + DO_CHANNEL_1 (data1, 1); + DO_CHANNEL_1 (data2, 2); + DO_CHANNEL_1 (data3, 3); - DO_CHANNEL_1(data0, 0); - DO_CHANNEL_1(data1, 1); - DO_CHANNEL_1(data2, 2); - DO_CHANNEL_1(data3, 3); - DO_CHANNEL_1(data0p, 0); - DO_CHANNEL_1(data1p, 1); - DO_CHANNEL_1(data2p, 2); - DO_CHANNEL_1(data3p, 3); + data0 &= audio_channel[0].data.adk_mask; + data1 &= audio_channel[1].data.adk_mask; + data2 &= audio_channel[2].data.adk_mask; + data3 &= audio_channel[3].data.adk_mask; + + data0 += data3; + data1 += data2; + data2 = SBASEVAL16(1) + data0; + data2 = FINISH_DATA (data2, 15, 0); + data3 = SBASEVAL16(1) + data1; + data3 = FINISH_DATA (data3, 15, 1); + + do_filter(&data2, 0); + do_filter(&data3, 1); + + get_extra_channels_sample2(&data2, &data3, 0); + + set_sound_buffers (); + put_sound_word_right(data2); + put_sound_word_left(data3); + check_sound_buffers (); +} + +static void sample16si_crux_handler (void) +{ + int data0 = audio_channel[0].data.current_sample; + int data1 = audio_channel[1].data.current_sample; + int data2 = audio_channel[2].data.current_sample; + int data3 = audio_channel[3].data.current_sample; + int data0p = audio_channel[0].data.last_sample; + int data1p = audio_channel[1].data.last_sample; + int data2p = audio_channel[2].data.last_sample; + int data3p = audio_channel[3].data.last_sample; + + DO_CHANNEL_1 (data0, 0); + DO_CHANNEL_1 (data1, 1); + DO_CHANNEL_1 (data2, 2); + DO_CHANNEL_1 (data3, 3); + DO_CHANNEL_1 (data0p, 0); + DO_CHANNEL_1 (data1p, 1); + DO_CHANNEL_1 (data2p, 2); + DO_CHANNEL_1 (data3p, 3); data0 &= audio_channel[0].data.adk_mask; data0p &= audio_channel[0].data.adk_mask; @@ -612,8 +1163,8 @@ static void sample16si_crux_handler(void) data3p &= audio_channel[3].data.adk_mask; { - struct audio_channel_data* cdp; - uae_u32 ratio, ratio1; + struct audio_channel_data *cdp; + unsigned long ratio, ratio1; #define INTERVAL (scaled_sample_evtime * 3) cdp = audio_channel + 0; ratio1 = cdp->per - cdp->evtime; @@ -645,23 +1196,34 @@ static void sample16si_crux_handler(void) } data1 += data2; data0 += data3; - data0 = FINISH_DATA(data0, 15, 0); - data1 = FINISH_DATA(data1, 15, 1); + data2 = SBASEVAL16(1) + data0; + data2 = FINISH_DATA (data2, 15, 0); + data3 = SBASEVAL16(1) + data1; + data3 = FINISH_DATA (data3, 15, 1); - put_sound_word_stereo_func(data0, data1); - check_sound_buffers(); + do_filter(&data2, 0); + do_filter(&data3, 1); + + get_extra_channels_sample2(&data2, &data3, 0); + + set_sound_buffers (); + put_sound_word_right(data2); + put_sound_word_left (data3); + check_sound_buffers (); } static void sample16si_rh_handler (void) { - auto data0 = audio_channel[0].data.current_sample; - auto data1 = audio_channel[1].data.current_sample; - auto data2 = audio_channel[2].data.current_sample; - auto data3 = audio_channel[3].data.current_sample; - auto data0p = audio_channel[0].data.last_sample; - auto data1p = audio_channel[1].data.last_sample; - auto data2p = audio_channel[2].data.last_sample; - auto data3p = audio_channel[3].data.last_sample; + unsigned long delta, ratio; + + int data0 = audio_channel[0].data.current_sample; + int data1 = audio_channel[1].data.current_sample; + int data2 = audio_channel[2].data.current_sample; + int data3 = audio_channel[3].data.current_sample; + int data0p = audio_channel[0].data.last_sample; + int data1p = audio_channel[1].data.last_sample; + int data2p = audio_channel[2].data.last_sample; + int data3p = audio_channel[3].data.last_sample; DO_CHANNEL_1 (data0, 0); DO_CHANNEL_1 (data1, 1); @@ -682,8 +1244,8 @@ static void sample16si_rh_handler (void) data3p &= audio_channel[3].data.adk_mask; /* linear interpolation and summing up... */ - unsigned long delta = audio_channel[0].per; - unsigned long ratio = ((audio_channel[0].evtime % delta) << 8) / delta; + delta = audio_channel[0].per; + ratio = ((audio_channel[0].evtime % delta) << 8) / delta; data0 = (data0 * (256 - ratio) + data0p * ratio) >> 8; delta = audio_channel[1].per; ratio = ((audio_channel[1].evtime % delta) << 8) / delta; @@ -694,32 +1256,69 @@ static void sample16si_rh_handler (void) delta = audio_channel[3].per; ratio = ((audio_channel[3].evtime % delta) << 8) / delta; data0 += (data3 * (256 - ratio) + data3p * ratio) >> 8; - data0 = FINISH_DATA(data0, 15, 0); - data1 = FINISH_DATA(data1, 15, 1); + data2 = SBASEVAL16(1) + data0; + data2 = FINISH_DATA (data2, 15, 0); + data3 = SBASEVAL16(1) + data1; + data3 = FINISH_DATA (data3, 15, 1); - put_sound_word_stereo_func(data0, data1); - check_sound_buffers(); + do_filter(&data2, 0); + do_filter(&data3, 1); + + get_extra_channels_sample2(&data2, &data3, 0); + + set_sound_buffers (); + put_sound_word_right(data2); + put_sound_word_left (data3); + check_sound_buffers (); } +#else +void sample16s_handler (void) +{ + sample16_handler (); +} +static void sample16si_crux_handler (void) +{ + sample16i_crux_handler (); +} +static void sample16si_rh_handler (void) +{ + sample16i_rh_handler (); +} +#endif + static int audio_work_to_do; static void zerostate (int nr) { - auto cdp = audio_channel + nr; + struct audio_channel_data *cdp = audio_channel + nr; +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("%d: ZEROSTATE\n"), nr); +#endif cdp->state = 0; cdp->evtime = MAX_EV; - cdp->intreq2 = false; + cdp->intreq2 = 0; cdp->dmaenstore = false; cdp->dmaofftime_active = 0; + cdp->volcnt = 0; + cdp->volcntbufcnt = 0; + memset(cdp->volcntbuf, 0, sizeof(cdp->volcntbuf)); +#if TEST_AUDIO > 0 + cdp->have_dat = false; +#endif + } static void schedule_audio (void) { unsigned long best = MAX_EV; + int i; eventtab[ev_audio].active = 0; - for (auto i = 0; i < AUDIO_CHANNELS_PAULA; i++) { - auto cdp = audio_channel + i; + eventtab[ev_audio].oldcycles = get_cycles (); + for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { + struct audio_channel_data *cdp = audio_channel + i; if (cdp->evtime != MAX_EV) { if (best > cdp->evtime) { best = cdp->evtime; @@ -727,65 +1326,83 @@ static void schedule_audio (void) } } } - eventtab[ev_audio].evtime = get_cycles() + best; -} - -static void audio_event_reset(void) -{ - last_cycles = get_cycles(); - next_sample_evtime = scaled_sample_evtime; - if (!isrestore()) { - for (auto i = 0; i < AUDIO_CHANNELS_PAULA; i++) - zerostate(i); + for (i = 0; i < audio_total_extra_streams; i++) { + struct audio_stream_data *cdp = audio_stream + i; + if (cdp->evtime != MAX_EV) { + if (best > cdp->evtime) { + best = cdp->evtime; + eventtab[ev_audio].active = 1; + } + } } - schedule_audio(); - events_schedule(); + eventtab[ev_audio].evtime = get_cycles () + best; } -void audio_deactivate(void) +static void audio_event_reset (void) +{ + int i; + + last_cycles = get_cycles (); + next_sample_evtime = scaled_sample_evtime; + if (!isrestore ()) { + for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) + zerostate (i); + for (i = 0; i < audio_total_extra_streams; i++) + audio_stream[i].evtime = MAX_EV; + } + schedule_audio (); + events_schedule (); + samplecnt = 0; + extrasamples = 0; + outputsample = 1; + doublesample = 0; +} + +void audio_deactivate (void) { gui_data.sndbuf_status = 3; gui_data.sndbuf = 0; audio_work_to_do = 0; - pause_sound_buffer(); - clear_sound_buffers(); - audio_event_reset(); + pause_sound_buffer (); + clear_sound_buffers (); + audio_event_reset (); } -int audio_activate(void) +int audio_activate (void) { - auto ret = 0; + int ret = 0; if (!audio_work_to_do) { - restart_sound_buffer(); + restart_sound_buffer (); ret = 1; - audio_event_reset(); + audio_event_reset (); } audio_work_to_do = 4 * maxvpos_nom * 50; return ret; } -STATIC_INLINE int is_audio_active(void) +STATIC_INLINE int is_audio_active (void) { return audio_work_to_do; } static void update_volume(int nr, uae_u16 v) { - auto cdp = audio_channel + nr; + struct audio_channel_data *cdp = audio_channel + nr; // 7 bit register in Paula. v &= 127; if (v > 64) v = 64; cdp->data.audvol = v; - cdp->data.mixvol = v; + if (!currprefs.sound_volcnt) + cdp->data.mixvol = v; } -uae_u16 audio_dmal(void) +uae_u16 audio_dmal (void) { uae_u16 dmal = 0; - for (auto nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { - auto cdp = audio_channel + nr; + for (int nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { + struct audio_channel_data *cdp = audio_channel + nr; if (cdp->dr) dmal |= 1 << (nr * 2); if (cdp->dsr) @@ -795,110 +1412,200 @@ uae_u16 audio_dmal(void) return dmal; } -static int isirq(int nr) +static int isirq (int nr) { - return INTREQR() & (0x80 << nr); + return INTREQR () & (0x80 << nr); +} + +static void audio_setirq_event(uae_u32 nr) +{ + INTREQ_0 (0x8000 | (0x80 << nr)); } static void setirq (int nr, int which) { - INTREQ_0(0x8000 | (0x80 << nr)); -} - -static void newsample(int nr, sample8_t sample) -{ - auto cdp = audio_channel + nr; - cdp->data.last_sample = cdp->data.current_sample; - cdp->data.current_sample = sample; -} - -STATIC_INLINE void setdr(int nr) -{ - auto cdp = audio_channel + nr; - cdp->dr = true; - if (cdp->wlen == 1) { - cdp->dsr = true; +#if DEBUG_AUDIO > 0 + struct audio_channel_data *cdp = audio_channel + nr; + if (debugchannel (nr) && cdp->wlen > 1) + write_log (_T("SETIRQ%d (%d,%d) PC=%08X\n"), nr, which, isirq (nr) ? 1 : 0, M68K_GETPC); +#endif + // audio interrupts are delayed by 2 cycles + if (currprefs.cpu_compatible) { + event2_newevent_xx (-1, 2 * CYCLE_UNIT + CYCLE_UNIT / 2, nr, audio_setirq_event); + } else { + audio_setirq_event(nr); } } -static void loaddat(int nr, bool modper) +static void newsample (int nr, sample8_t sample) { - auto cdp = audio_channel + nr; - auto audav = adkcon & (0x01 << nr); - auto audap = adkcon & (0x10 << nr); + struct audio_channel_data *cdp = audio_channel + nr; +#if DEBUG_AUDIO > 0 + if (!debugchannel (nr)) + sample = 0; +#endif +#if DEBUG_AUDIO > 2 + if (debugchannel (nr)) + write_log (_T("SAMPLE%d: %02x\n"), nr, sample & 0xff); +#endif + if (!(audio_channel_mask & (1 << nr))) + sample = 0; + if (currprefs.sound_volcnt) { + cdp->data.new_sample = sample; + } else { + cdp->data.last_sample = cdp->data.current_sample; + cdp->data.current_sample = sample; + } +} + +static void setdr(int nr, bool startup) +{ + struct audio_channel_data *cdp = audio_channel + nr; +#if TEST_AUDIO > 0 + if (debugchannel (nr) && cdp->dr) + write_log (_T("%d: DR already active (STATE=%d)\n"), nr, cdp->state); +#endif + if (dmaen(DMA_MASTER)) { + +#if DEBUG_AUDIO > 0 + if (debugchannel(nr) && cdp->wlen <= 2) + write_log(_T("DR%d=%d LEN=%d/%d PT=%08X PC=%08X\n"), nr, cdp->dr, cdp->wlen, cdp->len, cdp->pt, M68K_GETPC); +#endif + cdp->dr = true; + cdp->drhpos = current_hpos(); + + if (!startup && cdp->wlen == 1) { + cdp->dsr = true; +#if DEBUG_AUDIO > 0 + if (debugchannel(nr)) + write_log(_T("DSR%d=1 PT=%08X PC=%08X\n"), nr, cdp->pt, M68K_GETPC); +#endif + } + } else { +#if DEBUG_AUDIO > 0 + if (debugchannel(nr)) + write_log(_T("setdr%d ignored, DMA disabled PT=%08X PC=%08X\n"), nr, cdp->pt, M68K_GETPC); +#endif + } +} + +static void loaddat (int nr, bool modper) +{ + struct audio_channel_data *cdp = audio_channel + nr; + int audav = adkcon & (0x01 << nr); + int audap = adkcon & (0x10 << nr); if (audav || (modper && audap)) { if (nr >= 3) return; if (modper && audap) { if (cdp->dat == 0) - cdp[1].per = 65536 * CYCLE_UNIT; + cdp[1].per = 65536 * CYCLE_UNIT; else if (cdp->dat > PERIOD_MIN) cdp[1].per = cdp->dat * CYCLE_UNIT; else cdp[1].per = PERIOD_MIN * CYCLE_UNIT; - } - else if (audav) { + } else if (audav) { update_volume(nr + 1, cdp->dat); } - } - else { + } else { +#if TEST_AUDIO > 0 + if (debugchannel (nr)) { + if (cdp->hisample || cdp->losample) + write_log (_T("%d: high or low sample not used\n"), nr); + if (!cdp->have_dat) + write_log (_T("%d: dat not updated. STATE=%d 1=%04x 2=%04x\n"), nr, cdp->state, cdp->dat, cdp->dat2); + } + cdp->hisample = cdp->losample = true; + cdp->have_dat = false; +#endif +#if DEBUG_AUDIO > 2 || DEBUG_AUDIO2 + if (debugchannel (nr)) + write_log (_T("LOAD%dDAT: New:%04x, Old:%04x\n"), nr, cdp->dat, cdp->dat2); +#endif cdp->dat2 = cdp->dat; } } -static void loaddat(int nr) +static void loaddat (int nr) { - loaddat(nr, false); + loaddat (nr, false); } static void loadper (int nr) { - auto cdp = audio_channel + nr; + struct audio_channel_data *cdp = audio_channel + nr; cdp->evtime = cdp->per; if (cdp->evtime < CYCLE_UNIT) - write_log(_T("LOADPER%d bug %d\n"), nr, cdp->evtime); + write_log (_T("LOADPER%d bug %d\n"), nr, cdp->evtime); } -static void audio_state_channel2(int nr, bool perfin) +static void audio_state_channel2 (int nr, bool perfin) { - auto cdp = audio_channel + nr; - auto chan_ena = (dmacon & DMA_MASTER) && (dmacon & (1 << nr)); - auto old_dma = cdp->dmaenstore; - auto audav = adkcon & (0x01 << nr); - auto audap = adkcon & (0x10 << nr); + struct audio_channel_data *cdp = audio_channel + nr; + bool chan_ena = (dmacon & DMA_MASTER) && (dmacon & (1 << nr)); + bool old_dma = cdp->dmaenstore; + int audav = adkcon & (0x01 << nr); + int audap = adkcon & (0x10 << nr); int napnav = (!audav && !audap) || audav; - auto hpos = current_hpos(); + int hpos = current_hpos (); cdp->dmaenstore = chan_ena; if (currprefs.produce_sound == 0) { - zerostate(nr); + zerostate (nr); return; } - audio_activate(); + audio_activate (); - if ((cdp->state == 2 || cdp->state == 3) && usehacks()) { + if (cdp->state == 2 || cdp->state == 3) { if (!chan_ena && old_dma) { - // DMA switched off, state=2/3 and "too fast CPU": set flag + // DMA switched off, state=2/3 and "too fast CPU": set flag cdp->dmaofftime_active = true; + //cdp->dmaofftime_cpu_cnt = regs.instruction_cnt; + cdp->dmaofftime_pc = M68K_GETPC; } + // check if CPU executed at least 60 instructions (if JIT is off), there are stupid code that + // disable audio DMA, then set new sample, then re-enable without actually wanting to start + // new sample immediately. if (cdp->dmaofftime_active && !old_dma && chan_ena) { + static int warned = 100; // We are still in state=2/3 and program is going to re-enable // DMA. Force state to zero to prevent CPU timed DMA wait // routines in common tracker players to lose notes. - newsample(nr, (cdp->dat2 >> 0) & 0xff); - zerostate(nr); + if (usehacks() && currprefs.cachesize) { + if (warned >= 0) { + warned--; + write_log(_T("Audio %d DMA wait hack: ENABLED. OFF=%08x, ON=%08x\n"), nr, cdp->dmaofftime_pc, M68K_GETPC); + } +#if DEBUG_AUDIO_HACK > 0 + if (debugchannel(nr)) + write_log(_T("%d: INSTADMAOFF\n"), nr, M68K_GETPC); +#endif + newsample(nr, (cdp->dat2 >> 0) & 0xff); + zerostate(nr); + } else { + if (warned >= 0) { + warned--; + write_log(_T("Audio %d DMA wait hack: DISABLED. OFF=%08x, ON=%08x\n"), nr, cdp->dmaofftime_pc, M68K_GETPC); + } + } + cdp->dmaofftime_active = false; } } +#if DEBUG_AUDIO > 0 + if (debugchannel (nr) && old_dma != chan_ena) { + write_log (_T("%d:DMA=%d IRQ=%d PC=%08x\n"), nr, chan_ena, isirq (nr) ? 1 : 0, M68K_GETPC); + } +#endif switch (cdp->state) { case 0: if (chan_ena) { cdp->evtime = MAX_EV; cdp->state = 1; - cdp->dr = true; + setdr(nr, true); cdp->wlen = cdp->len; cdp->ptx_written = false; /* Some programs first start short empty sample and then later switch to @@ -907,163 +1614,249 @@ static void audio_state_channel2(int nr, bool perfin) if (cdp->wlen > 2) cdp->ptx_tofetch = true; cdp->dsr = true; - } - else if (cdp->dat_written && !isirq(nr)) { +#if TEST_AUDIO > 0 + cdp->have_dat = false; +#endif +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) { + write_log (_T("%d:0>1: LEN=%d PC=%08x\n"), nr, cdp->wlen, M68K_GETPC); + } +#endif + } else if (cdp->dat_written && !isirq (nr)) { cdp->state = 2; - setirq(nr, 0); - loaddat(nr); + setirq (nr, 0); + loaddat (nr); if (usehacks() && cdp->per < 10 * CYCLE_UNIT) { + static int warned = 100; // make sure audio.device AUDxDAT startup returns to idle state before DMA is enabled - newsample(nr, (cdp->dat2 >> 0) & 0xff); - zerostate(nr); - } - else { + newsample (nr, (cdp->dat2 >> 0) & 0xff); + zerostate (nr); + if (warned > 0) { + write_log(_T("AUD%d: forced idle state PER=%d PC=%08x\n"), nr, cdp->per, M68K_GETPC); + warned--; + } + } else { cdp->pbufldl = true; - audio_state_channel2(nr, false); + audio_state_channel2 (nr, false); } - } - else { - zerostate(nr); + } else { + zerostate (nr); } break; case 1: cdp->evtime = MAX_EV; if (!chan_ena) { - zerostate(nr); + zerostate (nr); return; } if (!cdp->dat_written) return; - setirq(nr, 10); - setdr(nr); +#if TEST_AUDIO > 0 + if (debugchannel (nr) && !cdp->have_dat) + write_log (_T("%d: state 1 but no have_dat\n"), nr); + cdp->have_dat = false; + cdp->losample = cdp->hisample = false; +#endif + setirq (nr, 10); + setdr(nr, false); if (cdp->wlen != 1) cdp->wlen = (cdp->wlen - 1) & 0xffff; cdp->state = 5; + //if (sampleripper_enabled) + // do_samplerip (cdp); break; case 5: cdp->evtime = MAX_EV; if (!chan_ena) { - zerostate(nr); + zerostate (nr); return; } if (!cdp->dat_written) return; +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("%d:>5: LEN=%d PT=%08X PC=%08X\n"), nr, cdp->wlen, cdp->pt, M68K_GETPC); +#endif if (cdp->ptx_written) { cdp->ptx_written = 0; cdp->lc = cdp->ptx; } - loaddat(nr); + loaddat (nr); if (napnav) - setdr(nr); + setdr(nr, false); cdp->state = 2; - loadper(nr); + loadper (nr); cdp->pbufldl = true; cdp->intreq2 = 0; - audio_state_channel2(nr, false); + cdp->volcnt = 0; + audio_state_channel2 (nr, false); break; case 2: if (cdp->pbufldl) { - newsample(nr, (cdp->dat2 >> 8) & 0xff); - loadper(nr); +#if TEST_AUDIO > 0 + if (debugchannel (nr) && cdp->hisample == false) + write_log (_T("%d: high sample used twice\n"), nr); + cdp->hisample = false; +#endif + newsample (nr, (cdp->dat2 >> 8) & 0xff); + loadper (nr); cdp->pbufldl = false; } if (!perfin) return; + + +#if DEBUG_AUDIO2 > 0 + if (debugchannel(nr)) { + write_log(_T("%d_2->3: LEN=%d/%d DSR=%d\n"), nr, cdp->wlen, cdp->len, cdp->dsr); + } +#endif + +#if DEBUG_AUDIO > 0 + if (debugchannel(nr) && (cdp->wlen <= 2 || cdp->wlen >= cdp->len - 1)) + write_log(_T("%d_2->3: LEN=%d/%d DSR=%d\n"), nr, cdp->wlen, cdp->len, cdp->dsr); +#endif + if (audap) - loaddat(nr, true); + loaddat (nr, true); if (chan_ena) { if (audap) - setdr(nr); + setdr(nr, false); if (cdp->intreq2 && audap) - setirq(nr, 21); - } - else { + setirq (nr, 21); + } else { if (audap) - setirq(nr, 22); + setirq (nr, 22); } cdp->pbufldl = true; cdp->state = 3; - audio_state_channel2(nr, false); + audio_state_channel2 (nr, false); break; case 3: if (cdp->pbufldl) { - newsample(nr, (cdp->dat2 >> 0) & 0xff); - loadper(nr); +#if TEST_AUDIO > 0 + if (debugchannel (nr) && cdp->losample == false) + write_log (_T("%d: low sample used twice\n"), nr); + cdp->losample = false; +#endif + newsample (nr, (cdp->dat2 >> 0) & 0xff); + loadper (nr); cdp->pbufldl = false; } if (!perfin) return; - if (chan_ena) { - loaddat(nr); - if (cdp->intreq2 && napnav) - setirq(nr, 31); - if (napnav) - setdr(nr); + +#if DEBUG_AUDIO2 > 0 + if (debugchannel(nr)) { + write_log(_T("%d_3->2: LEN=%d/%d DSR=%d\n"), nr, cdp->wlen, cdp->len, cdp->dsr); } - else { - if (isirq(nr)) { - zerostate(nr); +#endif + + +#if DEBUG_AUDIO > 0 + if (debugchannel(nr) && (cdp->wlen <= 2 || cdp->wlen >= cdp->len - 1)) + write_log(_T("%d_3->2: LEN=%d/%d DSR=%d\n"), nr, cdp->wlen, cdp->len, cdp->dsr); +#endif + + if (chan_ena) { + loaddat (nr); + if (cdp->intreq2 && napnav) + setirq (nr, 31); + if (napnav) + setdr(nr, false); + } else { + if (isirq (nr)) { +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("%d: IDLE\n"), nr); +#endif + zerostate (nr); return; } - loaddat(nr); + loaddat (nr); if (napnav) - setirq(nr, 32); + setirq (nr, 32); } cdp->intreq2 = 0; cdp->pbufldl = true; cdp->state = 2; - audio_state_channel2(nr, false); + audio_state_channel2 (nr, false); break; } } -static void audio_state_channel(int nr, bool perfin) +static void audio_state_channel (int nr, bool perfin) { - auto cdp = audio_channel + nr; - audio_state_channel2(nr, perfin); - cdp->dat_written = false; + struct audio_channel_data *cdp = audio_channel + nr; + if (nr < AUDIO_CHANNELS_PAULA) { + audio_state_channel2 (nr, perfin); + cdp->dat_written = false; + } else { + bool ok = false; + int streamid = nr - AUDIO_CHANNELS_PAULA + 1; + struct audio_stream_data *asd = &audio_stream[nr - AUDIO_CHANNELS_PAULA]; + if (asd->cb) { + ok = asd->cb(streamid, asd->cb_data); + } + if (!ok) { + audio_state_stream_state(streamid, NULL, 0, MAX_EV); + } + } } -void audio_state_machine(void) +void audio_state_machine (void) { - update_audio(); - for (auto nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { - auto cdp = audio_channel + nr; - audio_state_channel2(nr, false); + update_audio (); + for (int nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { + struct audio_channel_data *cdp = audio_channel + nr; + audio_state_channel2 (nr, false); cdp->dat_written = false; } - schedule_audio(); - events_schedule(); + schedule_audio (); + events_schedule (); } -void audio_reset(void) +void audio_reset (void) { - reset_sound(); - memset(sound_filter_state, 0, sizeof sound_filter_state); - if (!isrestore()) { - for (auto& i : audio_channel) - { - auto cdp = &i; - memset(cdp, 0, sizeof *audio_channel); - cdp->per = int(PERIOD_MAX - 1); + int i; + struct audio_channel_data *cdp; + +#ifdef AHI + ahi_close_sound (); + free_ahi_v2 (); +#endif + reset_sound (); + memset (sound_filter_state, 0, sizeof sound_filter_state); + if (!isrestore ()) { + for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { + cdp = &audio_channel[i]; + memset (cdp, 0, sizeof *audio_channel); + cdp->per = PERIOD_MAX - 1; cdp->data.mixvol = 0; cdp->evtime = MAX_EV; } + for (i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + audio_stream[i].evtime = MAX_EV; + } } - last_cycles = get_cycles(); + last_cycles = get_cycles (); next_sample_evtime = scaled_sample_evtime; - schedule_audio(); - events_schedule(); + schedule_audio (); + events_schedule (); } -static int sound_prefs_changed(void) +static int sound_prefs_changed (void) { if (!config_changed) return 0; if (changed_prefs.produce_sound != currprefs.produce_sound + //|| changed_prefs.win32_soundcard != currprefs.win32_soundcard || changed_prefs.sound_stereo != currprefs.sound_stereo - || changed_prefs.sound_freq != currprefs.sound_freq) + || changed_prefs.sound_maxbsiz != currprefs.sound_maxbsiz + || changed_prefs.sound_freq != currprefs.sound_freq + || changed_prefs.sound_volcnt != currprefs.sound_volcnt + || changed_prefs.sound_auto != currprefs.sound_auto) return 1; if (changed_prefs.sound_stereo_separation != currprefs.sound_stereo_separation @@ -1071,57 +1864,88 @@ static int sound_prefs_changed(void) || changed_prefs.sound_interpol != currprefs.sound_interpol || changed_prefs.sound_volume_paula != currprefs.sound_volume_paula || changed_prefs.sound_volume_cd != currprefs.sound_volume_cd + || changed_prefs.sound_volume_master != currprefs.sound_volume_master + || changed_prefs.sound_volume_board != currprefs.sound_volume_board + || changed_prefs.sound_stereo_swap_paula != currprefs.sound_stereo_swap_paula + || changed_prefs.sound_stereo_swap_ahi != currprefs.sound_stereo_swap_ahi + || changed_prefs.sound_cdaudio != currprefs.sound_cdaudio || changed_prefs.sound_filter != currprefs.sound_filter || changed_prefs.sound_filter_type != currprefs.sound_filter_type) return -1; return 0; } +double softfloat_tan(double v); + /* This computes the 1st order low-pass filter term b0. - * The a1 term is 1.0 - b0. The center frequency marks the -3 dB point. */ +* The a1 term is 1.0 - b0. The center frequency marks the -3 dB point. */ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif -static float rc_calculate_a0(int sample_rate, int cutoff_freq) +static float rc_calculate_a0 (int sample_rate, int cutoff_freq) { + float omega; /* The BLT correction formula below blows up if the cutoff is above nyquist. */ if (cutoff_freq >= sample_rate / 2) return 1.0; - float omega = 2 * M_PI * cutoff_freq / sample_rate; + omega = 2 * M_PI * cutoff_freq / sample_rate; /* Compensate for the bilinear transformation. This allows us to specify the - * stop frequency more exactly, but the filter becomes less steep further - * from stopband. */ - omega = tan(omega / 2.0) * 2.0; - const float out = 1.0 / (1.0 + 1.0 / omega); + * stop frequency more exactly, but the filter becomes less steep further + * from stopband. */ + omega = softfloat_tan (omega / 2.0) * 2.0; + float out = 1.0 / (1.0 + 1.0 / omega); return out; } void check_prefs_changed_audio (void) { + int ch; + if (sound_available) { - auto ch = sound_prefs_changed(); + ch = sound_prefs_changed (); if (ch > 0) { - clear_sound_buffers(); +#ifdef AVIOUTPUT + AVIOutput_Restart (); +#endif + clear_sound_buffers (); } if (ch) { - set_audio(); - audio_activate(); + set_audio (); + audio_activate (); } } +#ifdef DRIVESOUND + driveclick_check_prefs (); +#endif +} + +static void set_extra_prehandler(void) +{ + if (audio_total_extra_streams && sample_prehandler != anti_prehandler) { + extra_sample_prehandler = anti_prehandler; + } else { + extra_sample_prehandler = NULL; + } } void set_audio (void) { - const auto old_mixed_size = mixed_stereo_size; + int old_mixed_size = mixed_stereo_size; + int sep, delay; + int ch; - const auto ch = sound_prefs_changed(); + ch = sound_prefs_changed (); if (ch >= 0) - close_sound(); + close_sound (); currprefs.produce_sound = changed_prefs.produce_sound; + //currprefs.win32_soundcard = changed_prefs.win32_soundcard; currprefs.sound_stereo = changed_prefs.sound_stereo; + currprefs.sound_auto = changed_prefs.sound_auto; currprefs.sound_freq = changed_prefs.sound_freq; + currprefs.sound_maxbsiz = changed_prefs.sound_maxbsiz; + currprefs.sound_volcnt = changed_prefs.sound_volcnt; currprefs.sound_stereo_separation = changed_prefs.sound_stereo_separation; currprefs.sound_mixed_stereo_delay = changed_prefs.sound_mixed_stereo_delay; @@ -1129,19 +1953,24 @@ void set_audio (void) currprefs.sound_filter = changed_prefs.sound_filter; currprefs.sound_filter_type = changed_prefs.sound_filter_type; currprefs.sound_volume_paula = changed_prefs.sound_volume_paula; + currprefs.sound_volume_master = changed_prefs.sound_volume_master; + currprefs.sound_volume_board = changed_prefs.sound_volume_board; currprefs.sound_volume_cd = changed_prefs.sound_volume_cd; + currprefs.sound_cdaudio = changed_prefs.sound_cdaudio; + currprefs.sound_stereo_swap_paula = changed_prefs.sound_stereo_swap_paula; + currprefs.sound_stereo_swap_ahi = changed_prefs.sound_stereo_swap_ahi; sound_cd_volume[0] = sound_cd_volume[1] = (100 - (currprefs.sound_volume_cd < 0 ? 0 : currprefs.sound_volume_cd)) * 32768 / 100; sound_paula_volume[0] = sound_paula_volume[1] = (100 - currprefs.sound_volume_paula) * 32768 / 100; + //sndboard_ext_volume(); if (ch >= 0) { if (currprefs.produce_sound >= 2) { - if (!init_audio()) { - if (!sound_available) { - write_log(_T("Sound is not supported.\n")); - } - else { - write_log(_T("Sorry, can't initialize sound.\n")); + if (!init_audio ()) { + if (! sound_available) { + write_log (_T("Sound is not supported.\n")); + } else { + write_log (_T("Sorry, can't initialize sound.\n")); currprefs.produce_sound = 1; /* So we don't do this every frame */ changed_prefs.produce_sound = 1; @@ -1149,24 +1978,23 @@ void set_audio (void) } } next_sample_evtime = scaled_sample_evtime; - last_cycles = get_cycles(); - compute_vsynctime(); - } - else { - sound_volume(0); + last_cycles = get_cycles (); + compute_vsynctime (); + } else { + sound_volume (0); } - auto sep = (currprefs.sound_stereo_separation = changed_prefs.sound_stereo_separation) * 3 / 2; + sep = (currprefs.sound_stereo_separation = changed_prefs.sound_stereo_separation) * 3 / 2; if (sep >= 15) sep = 16; - const auto delay = currprefs.sound_mixed_stereo_delay = changed_prefs.sound_mixed_stereo_delay; + delay = currprefs.sound_mixed_stereo_delay = changed_prefs.sound_mixed_stereo_delay; mixed_mul1 = MIXED_STEREO_SCALE / 2 - sep; mixed_mul2 = MIXED_STEREO_SCALE / 2 + sep; mixed_stereo_size = delay > 0 ? (1 << delay) - 1 : 0; mixed_on = sep < MIXED_STEREO_MAX || mixed_stereo_size > 0; if (mixed_on && old_mixed_size != mixed_stereo_size) { saved_ptr = 0; - memset(right_word_saved, 0, sizeof right_word_saved); + memset (right_word_saved, 0, sizeof right_word_saved); } led_filter_forced = -1; // always off @@ -1181,11 +2009,13 @@ void set_audio (void) else if (currprefs.sound_filter_type == FILTER_SOUND_TYPE_A1200) sound_use_filter = FILTER_MODEL_A1200; } - a500e_filter1_a0 = rc_calculate_a0(currprefs.sound_freq, 6200); - a500e_filter2_a0 = rc_calculate_a0(currprefs.sound_freq, 20000); - filter_a0 = rc_calculate_a0(currprefs.sound_freq, 7000); - memset(sound_filter_state, 0, sizeof sound_filter_state); - led_filter_audio(); + a500e_filter1_a0 = rc_calculate_a0 (currprefs.sound_freq, 6200); + a500e_filter2_a0 = rc_calculate_a0 (currprefs.sound_freq, 20000); + filter_a0 = rc_calculate_a0 (currprefs.sound_freq, 7000); + memset (sound_filter_state, 0, sizeof sound_filter_state); + led_filter_audio (); + + makefir(); /* Select the right interpolation method. */ if (sample_handler == sample16_handler @@ -1199,8 +2029,7 @@ void set_audio (void) : currprefs.sound_interpol == 4 ? sample16i_crux_handler : currprefs.sound_interpol == 2 ? sample16i_sinc_handler : sample16i_anti_handler); - } - else if (sample_handler == sample16s_handler + } else if (sample_handler == sample16s_handler || sample_handler == sample16si_crux_handler || sample_handler == sample16si_rh_handler || sample_handler == sample16si_sinc_handler @@ -1211,83 +2040,132 @@ void set_audio (void) : currprefs.sound_interpol == 4 ? sample16si_crux_handler : currprefs.sound_interpol == 2 ? sample16si_sinc_handler : sample16si_anti_handler); + } else if (sample_handler == sample16ss_handler + || sample_handler == sample16ss_sinc_handler + || sample_handler == sample16ss_anti_handler) + { + sample_handler = (currprefs.sound_interpol == 0 ? sample16ss_handler + : currprefs.sound_interpol == 3 ? sample16ss_handler + : currprefs.sound_interpol == 4 ? sample16ss_handler + : currprefs.sound_interpol == 2 ? sample16ss_sinc_handler + : sample16ss_anti_handler); } sample_prehandler = NULL; - if (sample_handler == sample16si_sinc_handler || sample_handler == sample16i_sinc_handler) { + if (sample_handler == sample16si_sinc_handler || sample_handler == sample16i_sinc_handler || sample_handler == sample16ss_sinc_handler) { sample_prehandler = sinc_prehandler_paula; sound_use_filter_sinc = sound_use_filter; sound_use_filter = 0; - } - else if (sample_handler == sample16si_anti_handler || sample_handler == sample16i_anti_handler) { + } else if (sample_handler == sample16si_anti_handler || sample_handler == sample16i_anti_handler || sample_handler == sample16ss_anti_handler) { sample_prehandler = anti_prehandler; } for (int i = 0; i < AUDIO_CHANNELS_PAULA; i++) { struct audio_channel_data *cdp = audio_channel + i; audio_data[i] = &cdp->data; - cdp->data.mixvol = cdp->data.audvol; - } - - if (currprefs.sound_stereo) { - if (currprefs.sound_filter) { - if (mixed_on) - put_sound_word_stereo_func = put_sound_word_stereo_func_filter_mixed; - else - put_sound_word_stereo_func = put_sound_word_stereo_func_filter_notmixed; - } - else { - if (mixed_on) - put_sound_word_stereo_func = put_sound_word_stereo_func_nofilter_mixed; - else - put_sound_word_stereo_func = put_sound_word_stereo_func_nofilter_notmixed; + if (currprefs.sound_volcnt) { + cdp->data.mixvol = 1; + } else { + cdp->data.mixvol = cdp->data.audvol; } } - else { - if (currprefs.sound_filter) { - put_sound_word_mono_func = put_sound_word_mono_func_filter; - } - else { - put_sound_word_mono_func = put_sound_word_mono_func_nofilter; - } + audio_total_extra_streams = 0; + for (int i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + audio_extra_streams[i] = 0; } + set_extra_prehandler(); if (currprefs.produce_sound == 0) { - eventtab[ev_audio].active = false; - events_schedule(); + eventtab[ev_audio].active = 0; + events_schedule (); + } else { + audio_activate (); + schedule_audio (); + events_schedule (); } - else { - audio_activate(); - schedule_audio(); - events_schedule(); - } - set_config_changed(); + set_config_changed (); cd_audio_mode_changed = true; } -void update_audio(void) +static void update_audio_volcnt(int cycles, float evtime, bool nextsmp) +{ + if (cycles) { + for (int i = 0; i < AUDIO_CHANNELS_PAULA; i++) { + struct audio_channel_data *cdp = audio_channel + i; + sIntFlt v; + v.U32 = ((cdp->data.new_sample ^ 0x80) << 15) | 0x40000000; + v.F32 -= 3.0; + int cycs = cycles; + while (cycs > 0) { + if (cdp->volcnt < cdp->data.audvol) { + cdp->volcntbuf[cdp->volcntbufcnt] = v.F32; + } else { + cdp->volcntbuf[cdp->volcntbufcnt] = 0; + } + cdp->volcntbufcnt++; + cdp->volcntbufcnt &= (VOLCNT_BUFFER_SIZE - 1); + cdp->volcnt++; + cdp->volcnt &= 63; + cycs -= CYCLE_UNIT; + } + } + } + if (!nextsmp) + return; + float frac = evtime - (int)evtime; + for (int i = 0; i < AUDIO_CHANNELS_PAULA; i++) { + struct audio_channel_data *cdp = audio_channel + i; + float out0 = 0, out1 = 0; + int offs = (cdp->volcntbufcnt - FIR_WIDTH - 1) & (VOLCNT_BUFFER_SIZE - 1); + float v = cdp->volcntbuf[offs]; + for (int j = 1; j < 2 * FIR_WIDTH - 1; j++) { + float w = firmem[j]; + out0 += v * w; + offs++; + offs &= (VOLCNT_BUFFER_SIZE - 1); + v = cdp->volcntbuf[offs]; + out1 += v * w; + } + float out = out0 + frac * (out1 - out0); + out *= 8192; + + cdp->data.last_sample = cdp->data.current_sample; + cdp->data.current_sample = out; + } + (*sample_handler) (); +} + +void update_audio (void) { unsigned long int n_cycles = 0; +#if SOUNDSTUFF > 1 + static int samplecounter; +#endif - if (!isaudio()) + if (!isaudio ()) goto end; - if (isrestore()) + if (isrestore ()) goto end; - if (!is_audio_active()) + if (!is_audio_active ()) goto end; - n_cycles = get_cycles() - last_cycles; + n_cycles = get_cycles () - last_cycles; while (n_cycles > 0) { - auto best_evtime = n_cycles + 1; - uae_u32 rounded; + unsigned long int best_evtime = n_cycles + 1; + unsigned long rounded; int i; for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { if (audio_channel[i].evtime != MAX_EV && best_evtime > audio_channel[i].evtime) best_evtime = audio_channel[i].evtime; } + for (i = 0; i < audio_total_extra_streams; i++) { + if (audio_stream[i].evtime != MAX_EV && best_evtime > audio_stream[i].evtime) + best_evtime= audio_stream[i].evtime; + } /* next_sample_evtime >= 0 so floor() behaves as expected */ - rounded = floorf(next_sample_evtime); - if (next_sample_evtime - rounded >= 0.5) + rounded = floorf (next_sample_evtime); + float nevtime = next_sample_evtime; + if ((next_sample_evtime - rounded) >= 0.5) rounded++; if (currprefs.produce_sound > 1 && best_evtime > rounded) @@ -1299,73 +2177,130 @@ void update_audio(void) /* Decrease time-to-wait counters */ next_sample_evtime -= best_evtime; - if (sample_prehandler && (currprefs.produce_sound > 1)) { - sample_prehandler(best_evtime / CYCLE_UNIT); + if (currprefs.produce_sound > 1) { + if (sample_prehandler) + sample_prehandler (best_evtime / CYCLE_UNIT); + if (extra_sample_prehandler) + extra_sample_prehandler(best_evtime / CYCLE_UNIT); } for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { if (audio_channel[i].evtime != MAX_EV) audio_channel[i].evtime -= best_evtime; } + for (i = 0; i < audio_total_extra_streams; i++) { + if (audio_stream[i].evtime != MAX_EV) + audio_stream[i].evtime -= best_evtime; + } n_cycles -= best_evtime; - /* Test if new sample needs to be outputted */ - if ((rounded == best_evtime) && (currprefs.produce_sound > 1)) { - /* Before the following addition, next_sample_evtime is in range [-0.5, 0.5) */ - next_sample_evtime += scaled_sample_evtime; - (*sample_handler) (); + if (currprefs.produce_sound > 1) { + if (currprefs.sound_volcnt) { + bool nextsmp = false; + if (rounded == best_evtime) { + next_sample_evtime += scaled_sample_evtime; + nextsmp = true; + } + update_audio_volcnt(best_evtime, nevtime, nextsmp); + } else { + /* Test if new sample needs to be outputted */ + if (rounded == best_evtime) { + /* Before the following addition, next_sample_evtime is in range [-0.5, 0.5) */ + next_sample_evtime += scaled_sample_evtime; +#if SOUNDSTUFF > 1 + next_sample_evtime -= extrasamples * 15; + doublesample = 0; + if (--samplecounter <= 0) { + samplecounter = currprefs.sound_freq / 1000; + if (extrasamples > 0) { + outputsample = 1; + doublesample = 1; + extrasamples--; + } else if (extrasamples < 0) { + outputsample = 0; + doublesample = 0; + extrasamples++; + } + } +#endif + (*sample_handler) (); +#if SOUNDSTUFF > 1 + if (outputsample == 0) + outputsample = -1; + else if (outputsample < 0) + outputsample = 1; +#endif + + } + } } for (i = 0; i < AUDIO_CHANNELS_PAULA; i++) { if (audio_channel[i].evtime == 0) { - audio_state_channel(i, true); + audio_state_channel (i, true); + } + } + for (i = 0; i < audio_total_extra_streams; i++) { + if (audio_stream[i].evtime == 0) { + audio_state_channel(i + AUDIO_CHANNELS_PAULA, true); } } } end: - last_cycles = get_cycles() - n_cycles; + last_cycles = get_cycles () - n_cycles; } -void audio_evhandler(void) +void audio_evhandler (void) { - update_audio(); - schedule_audio(); + update_audio (); + schedule_audio (); } -void audio_hsync(void) +void audio_hsync (void) { if (!currprefs.produce_sound) return; - if (!isaudio()) + if (!isaudio ()) return; - if (audio_work_to_do > 0) { + if (audio_work_to_do > 0 && currprefs.sound_auto && !audio_total_extra_streams +#ifdef AVIOUTPUT + && !avioutput_enabled +#endif + ) { audio_work_to_do--; if (audio_work_to_do == 0) - audio_deactivate(); + audio_deactivate (); } - update_audio(); + update_audio (); + previous_volcnt_update = 0; } -void AUDxDAT(int nr, uae_u16 v) +static void audxdat_func(uae_u32 v) { - const auto cdp = audio_channel + nr; - const int chan_ena = (dmacon & DMA_MASTER) && (dmacon & (1 << nr)); - - cdp->dat = v; - cdp->dat_written = true; + int nr = v & 3; + int chan_ena = (v & 0x100) != 0; + struct audio_channel_data *cdp = audio_channel + nr; if (cdp->state == 2 || cdp->state == 3) { if (chan_ena) { +#if DEBUG_AUDIO > 0 + if (debugchannel(nr) && (cdp->wlen >= cdp->len - 1 || cdp->wlen <= 2)) + write_log(_T("AUD%d near loop, IRQ=%d, LC=%08X LEN=%d/%d DSR=%d\n"), nr, isirq(nr) ? 1 : 0, cdp->pt, cdp->wlen, cdp->len, cdp->dsr); +#endif if (cdp->wlen == 1) { cdp->wlen = cdp->len; cdp->intreq2 = true; - } - else { + //if (sampleripper_enabled) + // do_samplerip(cdp); +#if DEBUG_AUDIO > 0 + if (debugchannel(nr) && cdp->wlen > 1) + write_log(_T("AUD%d looped, IRQ=%d, LC=%08X LEN=%d DSR=%d\n"), nr, isirq(nr) ? 1 : 0, cdp->pt, cdp->wlen, cdp->dsr); +#endif + } else { cdp->wlen = (cdp->wlen - 1) & 0xffff; } } - } - else { + } else { audio_activate(); update_audio(); audio_state_channel(nr, false); @@ -1375,10 +2310,47 @@ void AUDxDAT(int nr, uae_u16 v) cdp->dat_written = false; } -uaecptr audio_getpt(int nr, bool reset) +void AUDxDAT (int nr, uae_u16 v, uaecptr addr) { - auto cdp = audio_channel + nr; - const auto p = cdp->pt; + struct audio_channel_data *cdp = audio_channel + nr; + int chan_ena = (dmacon & DMA_MASTER) && (dmacon & (1 << nr)); + +#if DEBUG_AUDIO2 + if (debugchannel(nr)) { + write_log(_T("AUD%dDAT: %04X ADDR=%08X LEN=%d/%d %d,%d,%d %06X\n"), nr, + v, addr, cdp->wlen, cdp->len, cdp->state, chan_ena, isirq(nr) ? 1 : 0, M68K_GETPC); + } +#endif + +#if DEBUG_AUDIO > 0 + if (debugchannel (nr) && (DEBUG_AUDIO > 1 || (!chan_ena || addr == 0xffffffff || (cdp->state != 2 && cdp->state != 3)))) { + write_log (_T("AUD%dDAT: %04X ADDR=%08X LEN=%d/%d %d,%d,%d %06X\n"), nr, + v, addr, cdp->wlen, cdp->len, cdp->state, chan_ena, isirq (nr) ? 1 : 0, M68K_GETPC); + } +#endif + cdp->dat = v; + cdp->dat_written = true; +#if TEST_AUDIO > 0 + if (debugchannel (nr) && cdp->have_dat) + write_log (_T("%d: audxdat 1=%04x 2=%04x but old dat not yet used\n"), nr, cdp->dat, cdp->dat2); + cdp->have_dat = true; +#endif + // AUDxLEN is processed after 2 cycle delay + if (cdp->per < 124 * CYCLE_UNIT || currprefs.cpu_compatible) { + event2_newevent_xx(-1, 2 * CYCLE_UNIT, nr | (chan_ena ? 0x100 : 0), audxdat_func); + } else { + audxdat_func(nr | (chan_ena ? 0x100 : 0)); + } +} +void AUDxDAT (int nr, uae_u16 v) +{ + AUDxDAT (nr, v, 0xffffffff); +} + +uaecptr audio_getpt (int nr, bool reset) +{ + struct audio_channel_data *cdp = audio_channel + nr; + uaecptr p = cdp->pt; cdp->pt += 2; if (reset) cdp->pt = cdp->lc; @@ -1386,53 +2358,75 @@ uaecptr audio_getpt(int nr, bool reset) return p; } -void AUDxLCH(int nr, uae_u16 v) +void AUDxLCH (int nr, uae_u16 v) { - const auto cdp = audio_channel + nr; - audio_activate(); - update_audio(); + struct audio_channel_data *cdp = audio_channel + nr; + audio_activate (); + update_audio (); // someone wants to update PT but DSR has not yet been processed. // too fast CPU and some tracker players: enable DMA, CPU delay, update AUDxPT with loop position if (usehacks() && ((cdp->ptx_tofetch && cdp->state == 1) || cdp->ptx_written)) { + static int warned = 100; cdp->ptx = cdp->lc; cdp->ptx_written = true; - } - else { + if (warned > 0) { + write_log(_T("AUD%dLCH HACK: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); + warned--; + } +#if DEBUG_AUDIO_HACK > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dLCH HACK: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); +#endif + } else { cdp->lc = (cdp->lc & 0xffff) | ((uae_u32)v << 16); +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dLCH: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); +#endif } } -void AUDxLCL(int nr, uae_u16 v) +void AUDxLCL (int nr, uae_u16 v) { - const auto cdp = audio_channel + nr; - audio_activate(); - update_audio(); + struct audio_channel_data *cdp = audio_channel + nr; + audio_activate (); + update_audio (); if (usehacks() && ((cdp->ptx_tofetch && cdp->state == 1) || cdp->ptx_written)) { + static int warned = 100; cdp->ptx = cdp->lc; cdp->ptx_written = true; - } - else { + cdp->ptx_written = true; + if (warned > 0) { + write_log(_T("AUD%dLCL HACK: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); + warned--; + } +#if DEBUG_AUDIO_HACK > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dLCL HACK: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); +#endif + } else { cdp->lc = (cdp->lc & ~0xffff) | (v & 0xFFFE); +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dLCL: %04X %08X (%d) (%d %d %08x)\n"), nr, v, M68K_GETPC, cdp->state, cdp->dsr, cdp->ptx_written, cdp->ptx); +#endif } } -void AUDxPER(int nr, uae_u16 v) +void AUDxPER (int nr, uae_u16 v) { - const auto cdp = audio_channel + nr; + struct audio_channel_data *cdp = audio_channel + nr; - audio_activate(); - update_audio(); - - unsigned long per = v * CYCLE_UNIT; - if (per == 0) - per = PERIOD_MAX - 1; + audio_activate (); + update_audio (); + int per = (v ? v : 65536) * CYCLE_UNIT; if (per < PERIOD_MIN * CYCLE_UNIT) { /* smaller values would cause extremely high cpu usage */ per = PERIOD_MIN * CYCLE_UNIT; } - if (per < PERIOD_MIN_NONCE * CYCLE_UNIT && cdp->dmaenstore) { + if (per < PERIOD_MIN_NONCE * CYCLE_UNIT && !currprefs.cpu_memory_cycle_exact && cdp->dmaenstore) { /* DMAL emulation and low period can cause very very high cpu usage on slow performance PCs * Only do this hack if audio DMA is active. */ @@ -1441,112 +2435,308 @@ void AUDxPER(int nr, uae_u16 v) if (cdp->per == PERIOD_MAX - 1 && per != PERIOD_MAX - 1) { cdp->evtime = CYCLE_UNIT; - if (isaudio()) { - schedule_audio(); - events_schedule(); + if (isaudio ()) { + schedule_audio (); + events_schedule (); } } +#if TEST_AUDIO > 0 + cdp->per_original = v; +#endif cdp->per = per; +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dPER: %d %08X\n"), nr, v, M68K_GETPC); +#endif } -void AUDxLEN(int nr, uae_u16 v) +void AUDxLEN (int nr, uae_u16 v) { - auto cdp = audio_channel + nr; - audio_activate(); - update_audio(); + struct audio_channel_data *cdp = audio_channel + nr; + audio_activate (); + update_audio (); cdp->len = v; +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dLEN: %d %08X\n"), nr, v, M68K_GETPC); +#endif } -void AUDxVOL(int nr, uae_u16 v) +void AUDxVOL (int nr, uae_u16 v) { - auto cdp = audio_channel + nr; + struct audio_channel_data *cdp = audio_channel + nr; - audio_activate(); - update_audio(); + audio_activate (); + update_audio (); update_volume(nr, v); +#if DEBUG_AUDIO > 0 + if (debugchannel (nr)) + write_log (_T("AUD%dVOL: %d %08X\n"), nr, v, M68K_GETPC); +#endif } -void audio_update_adkmasks(void) +void audio_update_adkmasks (void) { - static auto prevcon = -1; - const unsigned long t = adkcon | (adkcon >> 4); + static int prevcon = -1; + unsigned long t = adkcon | (adkcon >> 4); audio_channel[0].data.adk_mask = (((t >> 0) & 1) - 1); audio_channel[1].data.adk_mask = (((t >> 1) & 1) - 1); audio_channel[2].data.adk_mask = (((t >> 2) & 1) - 1); audio_channel[3].data.adk_mask = (((t >> 3) & 1) - 1); if ((prevcon & 0xff) != (adkcon & 0xff)) { - audio_activate(); + audio_activate (); +#if DEBUG_AUDIO > 0 + write_log (_T("ADKCON=%02x %08X\n"), adkcon & 0xff, M68K_GETPC); +#endif prevcon = adkcon; } } -int init_audio(void) +int init_audio (void) { - return init_sound(); + return init_sound (); } -void led_filter_audio(void) +void led_filter_audio (void) { led_filter_on = 0; if (led_filter_forced > 0 || (gui_data.powerled && led_filter_forced >= 0)) led_filter_on = 1; } -void restore_audio_finish(void) +void audio_vsync (void) { - last_cycles = get_cycles(); - schedule_audio(); - events_schedule(); +#if 0 +#if SOUNDSTUFF > 0 + int max, min; + int vsync = isvsync (); + static int lastdir; + + if (1 || !vsync) { + extrasamples = 0; + return; + } + + min = -10 * 10; + max = vsync ? 10 * 10 : 20 * 10; + extrasamples = 0; + if (gui_data.sndbuf < min) { // +1 + extrasamples = (min - gui_data.sndbuf) / 10; + lastdir = 1; + } else if (gui_data.sndbuf > max) { // -1 + extrasamples = (max - gui_data.sndbuf) / 10; + } else if (gui_data.sndbuf > 1 * 50 && lastdir < 0) { + extrasamples--; + } else if (gui_data.sndbuf < -1 * 50 && lastdir > 0) { + extrasamples++; + } else { + lastdir = 0; + } + + if (extrasamples > 99) + extrasamples = 99; + if (extrasamples < -99) + extrasamples = -99; +#endif +#endif } -uae_u8 *restore_audio(int nr, uae_u8 *src) +void restore_audio_finish (void) { - const auto acd = audio_channel + nr; + last_cycles = get_cycles (); + schedule_audio (); + events_schedule (); +} - zerostate(nr); - acd->state = restore_u8(); - acd->data.audvol = restore_u8(); - acd->intreq2 = restore_u8() != 0; - const auto flags = restore_u8(); +uae_u8 *restore_audio (int nr, uae_u8 *src) +{ + struct audio_channel_data *acd = audio_channel + nr; + + zerostate (nr); + acd->state = restore_u8 (); + acd->data.audvol = restore_u8 (); + acd->intreq2 = restore_u8 () ? true : false; + uae_u8 flags = restore_u8 (); acd->dr = acd->dsr = false; if (flags & 1) acd->dr = true; if (flags & 2) acd->dsr = true; - acd->len = restore_u16(); - acd->wlen = restore_u16(); - const auto p = restore_u16(); + acd->len = restore_u16 (); + acd->wlen = restore_u16 (); + uae_u16 p = restore_u16 (); acd->per = p ? p * CYCLE_UNIT : PERIOD_MAX; - acd->dat = acd->dat2 = restore_u16(); - acd->lc = restore_u32(); - acd->pt = restore_u32(); - acd->evtime = restore_u32(); + acd->dat = acd->dat2 = restore_u16 (); + acd->lc = restore_u32 (); + acd->pt = restore_u32 (); + acd->evtime = restore_u32 (); + if (flags & 0x80) + acd->drhpos = restore_u8 (); + else + acd->drhpos = 1; acd->dmaenstore = (dmacon & DMA_MASTER) && (dmacon & (1 << nr)); - acd->data.mixvol = acd->data.audvol; + if (currprefs.sound_volcnt) + acd->data.mixvol = 1; + else + acd->data.mixvol = acd->data.audvol; return src; } -uae_u8 *save_audio(int nr, int *len, uae_u8 *dstptr) +uae_u8 *save_audio (int nr, int *len, uae_u8 *dstptr) { - const auto acd = audio_channel + nr; + struct audio_channel_data *acd = audio_channel + nr; uae_u8 *dst, *dstbak; if (dstptr) dstbak = dst = dstptr; else - dstbak = dst = xmalloc(uae_u8, 100); - save_u8(acd->state); + dstbak = dst = xmalloc (uae_u8, 100); + save_u8 (acd->state); save_u8 (acd->data.audvol); - save_u8(acd->intreq2); - save_u8((acd->dr ? 1 : 0) | (acd->dsr ? 2 : 0) | 0x80); - save_u16(acd->len); - save_u16(acd->wlen); - save_u16(acd->per == PERIOD_MAX ? 0 : acd->per / CYCLE_UNIT); - save_u16(acd->dat); - save_u32(acd->lc); - save_u32(acd->pt); - save_u32(acd->evtime); + save_u8 (acd->intreq2); + save_u8 ((acd->dr ? 1 : 0) | (acd->dsr ? 2 : 0) | 0x80); + save_u16 (acd->len); + save_u16 (acd->wlen); + save_u16 (acd->per == PERIOD_MAX ? 0 : acd->per / CYCLE_UNIT); + save_u16 (acd->dat); + save_u32 (acd->lc); + save_u32 (acd->pt); + save_u32 (acd->evtime); + save_u8 (acd->drhpos); *len = dst - dstbak; return dstbak; } + +static void audio_set_extra_channels(void) +{ + int index = AUDIO_CHANNELS_PAULA; + audio_total_extra_streams = 0; + for (int i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + if (audio_extra_streams[i]) + audio_total_extra_streams++; + for (int j = 0; j < audio_extra_streams[i]; j++) { + audio_data[index++] = &audio_stream[i].data[j]; + } + } + set_extra_prehandler(); +} + +int audio_enable_stream(bool enable, int streamid, int ch, SOUND_STREAM_CALLBACK cb, void *cb_data) +{ + if (streamid == 0) + return 0; + if (!enable) { + if (streamid <= 0) + return 0; + streamid--; + struct audio_stream_data *asd = audio_stream + streamid; + audio_extra_streams[streamid] = 0; + asd->evtime = MAX_EV; + } else { + if (streamid < 0) { + for (int i = 0; i < AUDIO_CHANNEL_STREAMS; i++) { + if (!audio_extra_streams[i]) { + streamid = i; + break; + } + } + if (streamid < 0) + return 0; + } + audio_extra_streams[streamid] = ch; + struct audio_stream_data *asd = audio_stream + streamid; + asd->cb = cb; + asd->cb_data = cb_data; + asd->evtime = CYCLE_UNIT; + for (int i = 0; i < ch; i++) { + struct audio_channel_data2 *acd = &asd->data[i]; + acd->adk_mask = 0xffffffff; + acd->mixvol = 1; + } + audio_activate(); + } + audio_set_extra_channels(); + return streamid + 1; +} + +void audio_state_stream_state(int streamid, int *samplep, int highestch, unsigned int evt) +{ + streamid--; + struct audio_stream_data *asd = audio_stream + streamid; + if (highestch > audio_extra_streams[streamid]) { + audio_extra_streams[streamid] = highestch; + audio_set_extra_channels(); + } + for (int i = 0; i < audio_extra_streams[streamid]; i++) { + struct audio_channel_data2 *acd = &asd->data[i]; + acd->last_sample = acd->current_sample; + acd->current_sample = samplep ? samplep[i] : 0; + } + asd->evtime = evt; +} + +static unsigned int cda_evt; +static uae_s16 dummy_buffer[4] = { 0 }; + +void update_cda_sound(double clk) +{ + cda_evt = clk * CYCLE_UNIT / 44100; +} + +void audio_cda_volume(struct cd_audio_state *cas, int left, int right) +{ + for (int j = 0; j < 2; j++) { + cas->cda_volume[j] = j == 0 ? left : right; + cas->cda_volume[j] = sound_cd_volume[j] * cas->cda_volume[j] / 32768; + if (cas->cda_volume[j]) + cas->cda_volume[j]++; + if (cas->cda_volume[j] >= 32768) + cas->cda_volume[j] = 32768; + } +} + +static bool audio_state_cda(int streamid, void *state) +{ + struct cd_audio_state *cas = (struct cd_audio_state*)state; + if (cas->cda_bufptr >= dummy_buffer && cas->cda_bufptr <= dummy_buffer + 4) { + audio_enable_stream(false, cas->cda_streamid, 0, NULL, NULL); + cas->cda_streamid = 0; + return false; + } + if (cas->cda_streamid <= 0) + return false; + int samples[2]; + samples[0] = cas->cda_bufptr[0] * cas->cda_volume[0] / 32768; + samples[1] = cas->cda_bufptr[1] * cas->cda_volume[1] / 32768; + audio_state_stream_state(streamid, samples, 2, cda_evt); + cas->cda_bufptr += 2; + cas->cda_length--; + if (cas->cda_length <= 0 && cas->cda_next_cd_audio_buffer_callback) { + cas->cda_next_cd_audio_buffer_callback(cas->cda_userdata, cas->cb_data); + } + return true; +} + +void audio_cda_new_buffer(struct cd_audio_state *cas, uae_s16 *buffer, int length, int userdata, CDA_CALLBACK next_cd_audio_buffer_callback, void *cb_data) +{ + if (length < 0 && cas->cda_streamid > 0) { + audio_enable_stream(false, cas->cda_streamid, 0, NULL, cas); + cas->cda_streamid = 0; + return; + } + if (!buffer) { + cas->cda_bufptr = dummy_buffer; + cas->cda_length = 0; + } else { + cas->cda_bufptr = buffer; + cas->cda_length = length; + cas->cda_userdata = userdata; + if (cas->cda_streamid <= 0) + cas->cda_streamid = audio_enable_stream(true, -1, 2, audio_state_cda, cas); + } + cas->cda_next_cd_audio_buffer_callback = next_cd_audio_buffer_callback; + cas->cb_data = cb_data; + if (cas->cda_streamid > 0) + audio_activate(); +} diff --git a/src/blkdev_cdimage.cpp b/src/blkdev_cdimage.cpp index e83265ff..be121449 100644 --- a/src/blkdev_cdimage.cpp +++ b/src/blkdev_cdimage.cpp @@ -438,23 +438,23 @@ static void audio_unpack (struct cdunit *cdu, struct cdtoc *t) sleep_millis(10); } -//static void next_cd_audio_buffer_callback(int bufnum, void *params) -//{ -// struct cdunit *cdu = (struct cdunit*)params; -// uae_sem_wait(&play_sem); -// if (bufnum >= 0) { -// cdu->cda_bufon[bufnum] = 0; -// bufnum = 1 - bufnum; -// if (cdu->cda_bufon[bufnum]) -// audio_cda_new_buffer(&cdu->cas, (uae_s16*)cdu->cda->buffers[bufnum], CDDA_BUFFERS * 2352 / 4, bufnum, next_cd_audio_buffer_callback, cdu); -// else -// bufnum = -1; -// } -// if (bufnum < 0) { -// audio_cda_new_buffer(&cdu->cas, NULL, -1, 0, NULL, cdu); -// } -// uae_sem_post(&play_sem); -//} +static void next_cd_audio_buffer_callback(int bufnum, void *params) +{ + struct cdunit *cdu = (struct cdunit*)params; + uae_sem_wait(&play_sem); + if (bufnum >= 0) { + cdu->cda_bufon[bufnum] = 0; + bufnum = 1 - bufnum; + if (cdu->cda_bufon[bufnum]) + audio_cda_new_buffer(&cdu->cas, (uae_s16*)cdu->cda->buffers[bufnum], CDDA_BUFFERS * 2352 / 4, bufnum, next_cd_audio_buffer_callback, cdu); + else + bufnum = -1; + } + if (bufnum < 0) { + audio_cda_new_buffer(&cdu->cas, NULL, -1, 0, NULL, cdu); + } + uae_sem_post(&play_sem); +} static bool cdda_play_func2 (struct cdunit *cdu, int *outpos) { @@ -479,7 +479,7 @@ static bool cdda_play_func2 (struct cdunit *cdu, int *outpos) cdu->cda_bufon[0] = cdu->cda_bufon[1] = 0; bufnum = 0; - cdu->cda = new cda_audio (CDDA_BUFFERS, 2352, 44100); + cdu->cda = new cda_audio (CDDA_BUFFERS, 2352, 44100, mode != 0); while (cdu->cdda_play > 0) { @@ -583,7 +583,17 @@ static bool cdda_play_func2 (struct cdunit *cdu, int *outpos) } } - cdu->cda->wait(bufnum); + if (mode) { + while (cdu->cda_bufon[bufnum] && cdu->cdda_play > 0) { + if (cd_audio_mode_changed) { + restart = true; + goto end; + } + sleep_millis(10); + } + } else { + cdu->cda->wait(bufnum); + } cdu->cda_bufon[bufnum] = 0; if (cdu->cdda_play <= 0) @@ -682,13 +692,22 @@ static bool cdda_play_func2 (struct cdunit *cdu, int *outpos) if (idleframes <= 0) cdu->cd_last_pos = cdda_pos; - cdu->cda_bufon[bufnum] = 1; - cdu->cda->setvolume (cdu->cdda_volume[0], cdu->cdda_volume[1]); - if (!cdu->cda->play (bufnum)) { - if (cdu->cdda_play > 0) - setstate (cdu, AUDIO_STATUS_PLAY_ERROR, -1); - goto end; - } + if (mode) { + if (cdu->cda_bufon[0] == 0 && cdu->cda_bufon[1] == 0) { + cdu->cda_bufon[bufnum] = 1; + next_cd_audio_buffer_callback(1 - bufnum, cdu); + } + audio_cda_volume(&cdu->cas, cdu->cdda_volume[0], cdu->cdda_volume[1]); + cdu->cda_bufon[bufnum] = 1; + } else { + cdu->cda_bufon[bufnum] = 1; + cdu->cda->setvolume (cdu->cdda_volume[0], cdu->cdda_volume[1]); + if (!cdu->cda->play (bufnum)) { + if (cdu->cdda_play > 0) + setstate (cdu, AUDIO_STATUS_PLAY_ERROR, -1); + goto end; + } + } if (first) { first = false; @@ -719,8 +738,14 @@ static bool cdda_play_func2 (struct cdunit *cdu, int *outpos) end: *outpos = cdda_pos; - cdu->cda->wait (0); - cdu->cda->wait (1); + if (mode) { + next_cd_audio_buffer_callback(-1, cdu); + if (restart) + audio_cda_new_buffer(&cdu->cas, NULL, -1, -1, NULL, NULL); + } else { + cdu->cda->wait (0); + cdu->cda->wait (1); + } while (cdimage_unpack_active == 1) sleep_millis(10); @@ -1955,12 +1980,15 @@ static int parsenrg(struct cdunit *cdu, struct zfile *znrg, const TCHAR *img, co } else { tracknum = frombcd(trk); int index = frombcd(buf[2]); - if (index == 0 && tracknum >= 1 && tracknum <= 99) { + uae_u32 address = get_long_host(buf + 4); + if (tracknum >= 1 && tracknum <= 99) { struct cdtoc *t = &cdu->toc[tracknum - 1]; - t->address = get_long_host(buf + 4); - t->ctrl = buf[0] >> 4; - t->adr = buf[0] & 15; - t->track = tracknum; + if (index == 0) { + t->address = address; + t->ctrl = buf[0] >> 4; + t->adr = buf[0] & 15; + t->track = tracknum; + } } } size -= 8; diff --git a/src/cd32_fmv.cpp b/src/cd32_fmv.cpp index 21bb07c1..3d421d00 100644 --- a/src/cd32_fmv.cpp +++ b/src/cd32_fmv.cpp @@ -379,11 +379,11 @@ static void l64111_setvolume(void) return; write_log(_T("L64111 mute %d\n"), volume ? 0 : 1); if (cda) { - //if (audio_mode) { - // audio_cda_volume(&cas, volume, volume); - //} else { + if (audio_mode) { + audio_cda_volume(&cas, volume, volume); + } else { cda->setvolume(volume, volume); - //} + } } } @@ -1302,22 +1302,22 @@ void cd32_fmv_set_sync(double svpos, double adjust) fmv_syncadjust = adjust; } -//static void fmv_next_cd_audio_buffer_callback(int bufnum, void *param) -//{ -// uae_sem_wait(&play_sem); -// if (bufnum >= 0) { -// fmv_bufon[bufnum] = 0; -// bufnum = 1 - bufnum; -// if (fmv_bufon[bufnum]) -// audio_cda_new_buffer(&cas, (uae_s16*)cda->buffers[bufnum], PCM_SECTORS * KJMP2_SAMPLES_PER_FRAME, bufnum, fmv_next_cd_audio_buffer_callback, param); -// else -// bufnum = -1; -// } -// if (bufnum < 0) { -// audio_cda_new_buffer(&cas, NULL, 0, -1, NULL, NULL); -// } -// uae_sem_post(&play_sem); -//} +static void fmv_next_cd_audio_buffer_callback(int bufnum, void *param) +{ + uae_sem_wait(&play_sem); + if (bufnum >= 0) { + fmv_bufon[bufnum] = 0; + bufnum = 1 - bufnum; + if (fmv_bufon[bufnum]) + audio_cda_new_buffer(&cas, (uae_s16*)cda->buffers[bufnum], PCM_SECTORS * KJMP2_SAMPLES_PER_FRAME, bufnum, fmv_next_cd_audio_buffer_callback, param); + else + bufnum = -1; + } + if (bufnum < 0) { + audio_cda_new_buffer(&cas, NULL, 0, -1, NULL, NULL); + } + uae_sem_post(&play_sem); +} void cd32_fmv_vsync_handler(void) { @@ -1335,13 +1335,13 @@ static void cd32_fmv_audio_handler(void) if (cd_audio_mode_changed || (cl450_play && !cda)) { cd_audio_mode_changed = false; if (cl450_play) { - //if (audio_mode) { - // audio_cda_new_buffer(&cas, NULL, -1, -1, NULL, NULL); - //} + if (audio_mode) { + audio_cda_new_buffer(&cas, NULL, -1, -1, NULL, NULL); + } audio_mode = currprefs.sound_cdaudio; fmv_bufon[0] = fmv_bufon[1] = 0; delete cda; - cda = new cda_audio(PCM_SECTORS, KJMP2_SAMPLES_PER_FRAME * 4, 44100); + cda = new cda_audio(PCM_SECTORS, KJMP2_SAMPLES_PER_FRAME * 4, 44100, audio_mode != 0); l64111_setvolume(); } } @@ -1357,13 +1357,13 @@ static void cd32_fmv_audio_handler(void) if (!cda || !(l64111_regs[A_CONTROL1] & 1)) return; - //if (audio_mode) { - // play0 = fmv_bufon[0]; - // play1 = fmv_bufon[1]; - //} else { + if (audio_mode) { + play0 = fmv_bufon[0]; + play1 = fmv_bufon[1]; + } else { play0 = cda->isplaying(0); play1 = cda->isplaying(1); - //} + } needsectors = PCM_SECTORS; if (!play0 && !play1) { needsectors *= 2; @@ -1387,15 +1387,15 @@ static void cd32_fmv_audio_handler(void) memcpy(cda->buffers[bufnum] + i * KJMP2_SAMPLES_PER_FRAME * 4, pcmaudio[offset2].pcm, KJMP2_SAMPLES_PER_FRAME * 4); pcmaudio[offset2].ready = false; } - //if (audio_mode) { - // if (!play0 && !play1) { - // fmv_bufon[bufnum] = 1; - // fmv_next_cd_audio_buffer_callback(1 - bufnum, NULL); - // } - // fmv_bufon[bufnum] = 1; - //} else { + if (audio_mode) { + if (!play0 && !play1) { + fmv_bufon[bufnum] = 1; + fmv_next_cd_audio_buffer_callback(1 - bufnum, NULL); + } + fmv_bufon[bufnum] = 1; + } else { cda->play(bufnum); - //} + } offset += PCM_SECTORS; offset &= l64111_cb_mask; l64111_regs[A_CB_READ] = offset; @@ -1469,12 +1469,12 @@ static void cd32_fmv_free(void) xfree(videoram); videoram = NULL; if (cda) { - //if (audio_mode) { - // fmv_next_cd_audio_buffer_callback(-1, NULL); - //} else { + if (audio_mode) { + fmv_next_cd_audio_buffer_callback(-1, NULL); + } else { cda->wait(0); cda->wait(1); - //} + } delete cda; } cda = NULL; diff --git a/src/devices.cpp b/src/devices.cpp index 0bc34082..7e57a222 100644 --- a/src/devices.cpp +++ b/src/devices.cpp @@ -30,6 +30,9 @@ #include "gensound.h" #include "gui.h" #include "drawing.h" +#ifdef DRIVESOUND +#include "driveclick.h" +#endif #include "statusline.h" #include "uaeexe.h" #ifdef JIT @@ -257,6 +260,7 @@ void devices_rethink(void) void devices_update_sound(double clk, double syncadjust) { update_sound(clk); + update_cda_sound(clk / syncadjust); } void devices_update_sync(double svpos, double syncadjust) diff --git a/src/disk.cpp b/src/disk.cpp index 8c8a75f8..2e8c716e 100644 --- a/src/disk.cpp +++ b/src/disk.cpp @@ -34,6 +34,9 @@ int disk_debug_track = -1; #ifdef FDI2RAW #include "fdi2raw.h" #endif +#ifdef DRIVESOUND +#include "driveclick.h" +#endif #ifdef CAPS #include "uae/caps.h" #endif diff --git a/src/driveclick.cpp b/src/driveclick.cpp new file mode 100644 index 00000000..5c7fea6d --- /dev/null +++ b/src/driveclick.cpp @@ -0,0 +1,600 @@ +/* +* UAE - The Un*x Amiga Emulator +* +* Drive Click Emulation Support Functions +* +* Copyright 2004 James Bagg, Toni Wilen +*/ + +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef DRIVESOUND + +#include "uae.h" +#include "options.h" +#include "audio.h" +#include "sounddep/sound.h" +#include "zfile.h" +#include "fsdb.h" +#include "events.h" +#include "driveclick.h" + +#ifdef AMIBERRY +SDL_AudioSpec wav_spec; +Uint32 wav_length; +Uint8* wav_buffer; +#endif + +static struct drvsample drvs[4][DS_END]; +static int freq = 44100; + +static int drv_starting[4], drv_spinning[4], drv_has_spun[4], drv_has_disk[4]; + +static int click_initialized, wave_initialized; +#define DS_SHIFT 10 +static int sample_step; +static uae_s16* clickbuffer; +static int clickcnt; + +uae_s16* decodewav(uae_u8* s, int* lenp) +{ + uae_s16* dst; + uae_u8* src = s; + int len; + + if (memcmp(s, "RIFF", 4)) + return 0; + if (memcmp(s + 8, "WAVE", 4)) + return 0; + s += 12; + len = *lenp; + while (s < src + len) { + if (!memcmp(s, "fmt ", 4)) + freq = s[8 + 4] | (s[8 + 5] << 8); + if (!memcmp(s, "data", 4)) { + s += 4; + len = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24); + dst = xmalloc(uae_s16, len / 2); +#ifdef WORDS_BIGENDIAN + int8_t* dst8 = (int8_t*)dst; + for (int i = 0; i < len; i += 2) { + dst8[i] = s[i + 1]; + dst8[i + 1] = s[i]; + } +#else + memcpy(dst, s + 4, len); +#endif + * lenp = len / 2; + return dst; + } + s += 8 + (s[4] | (s[5] << 8) | (s[6] << 16) | (s[7] << 24)); + } + return 0; +} + +static int loadsample(TCHAR* path, struct drvsample* ds) +{ + struct zfile* f; + uae_u8* buf; + int size; + TCHAR name[MAX_DPATH]; + + f = zfile_fopen(path, _T("rb"), ZFD_NORMAL); + if (!f) { + _tcscpy(name, path); + _tcscat(name, _T(".wav")); + f = zfile_fopen(name, _T("rb"), ZFD_NORMAL); + if (!f) { + write_log(_T("driveclick: can't open '%s' (or '%s')\n"), path, name); + return 0; + } + } + zfile_fseek(f, 0, SEEK_END); + size = zfile_ftell(f); + buf = xmalloc(uae_u8, size); + zfile_fseek(f, 0, SEEK_SET); + zfile_fread(buf, size, 1, f); + zfile_fclose(f); + ds->len = size; + ds->p = decodewav(buf, &ds->len); + xfree(buf); + return 1; +} + +static void freesample(struct drvsample* s) +{ + xfree(s->p); + s->p = nullptr; +} + +static void processclicks(struct drvsample* ds) +{ + unsigned int nClick = 0; + + for (int n = 0; n < CLICK_TRACKS; n++) { + ds->indexes[n] = 0; + ds->lengths[n] = 0; + } + for (int n = 0; n < ds->len; n++) { + uae_s16 smp = ds->p[n]; + if (smp > 0x6ff0 && nClick < CLICK_TRACKS) { + ds->indexes[nClick] = n - 128; + ds->lengths[nClick] = 2800; + nClick++; + n += 3000; + } + } + if (nClick == 0) { + for (int n = 0; n < CLICK_TRACKS; n++) { + ds->indexes[n] = 0; + ds->lengths[n] = ds->len; + } + } + else { + if (nClick == 1) { + ds->lengths[0] = ds->len - ds->indexes[0]; + for (int n = 1; n < CLICK_TRACKS; n++) { + ds->indexes[n] = ds->indexes[0]; + ds->lengths[n] = ds->lengths[0]; + } + } + else { + for (int n = nClick; n < CLICK_TRACKS; n++) { + ds->indexes[n] = ds->indexes[nClick - 1]; + ds->lengths[n] = ds->lengths[nClick - 1]; + } + } + } +} + +static void driveclick_close(void) +{ + driveclick_fdrawcmd_close(0); + driveclick_fdrawcmd_close(1); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < DS_END; j++) + freesample(&drvs[i][j]); + } + memset(drvs, 0, sizeof(drvs)); + click_initialized = 0; + wave_initialized = 0; + driveclick_reset(); +} + +void driveclick_free(void) +{ + driveclick_close(); +} + +void driveclick_init(void) +{ + int v, vv; + TCHAR tmp[MAX_DPATH]; + + driveclick_fdrawcmd_detect(); + driveclick_close(); + vv = 0; + for (int i = 0; i < 4; i++) { + struct floppyslot* fs = &currprefs.floppyslots[i]; + for (int j = 0; j < CLICK_TRACKS; j++) { + drvs[i][DS_CLICK].indexes[j] = 0; + drvs[i][DS_CLICK].lengths[j] = 0; + } + if (fs->dfxclick) { + v = 0; + if (fs->dfxclick > 0) { + switch (fs->dfxclick) + { + case 1: + if (driveclick_loadresource(drvs[i], fs->dfxclick)) + v = 3; + for (int j = 0; j < CLICK_TRACKS; j++) + drvs[i][DS_CLICK].lengths[j] = drvs[i][DS_CLICK].len; + wave_initialized = 1; + break; + default: + if (driveclick_fdrawcmd_open(fs->dfxclick - 2)) + v = 1; + break; + } + } else if (fs->dfxclick == -1) { + TCHAR path2[MAX_DPATH]; + wave_initialized = 1; + for (int j = 0; j < CLICK_TRACKS; j++) + drvs[i][DS_CLICK].lengths[j] = drvs[i][DS_CLICK].len; + get_plugin_path (path2, sizeof path2 / sizeof (TCHAR), _T("floppysounds")); + _stprintf (tmp, _T("%sdrive_click_%s"), + path2, fs->dfxclickexternal); + v = loadsample (tmp, &drvs[i][DS_CLICK]); + if (v) + processclicks (&drvs[i][DS_CLICK]); + _stprintf (tmp, _T("%sdrive_spin_%s"), + path2, fs->dfxclickexternal); + v += loadsample (tmp, &drvs[i][DS_SPIN]); + _stprintf (tmp, _T("%sdrive_spinnd_%s"), + path2, fs->dfxclickexternal); + v += loadsample (tmp, &drvs[i][DS_SPINND]); + _stprintf (tmp, _T("%sdrive_startup_%s"), + path2, fs->dfxclickexternal); + v += loadsample (tmp, &drvs[i][DS_START]); + _stprintf (tmp, _T("%sdrive_snatch_%s"), + path2, fs->dfxclickexternal); + v += loadsample (tmp, &drvs[i][DS_SNATCH]); + } + if (v == 0) { + for (int j = 0; j < DS_END; j++) + freesample(&drvs[i][j]); + fs->dfxclick = changed_prefs.floppyslots[i].dfxclick = 0; + } else { + vv++; + } + for (int j = 0; j < DS_END; j++) + drvs[i][j].len <<= DS_SHIFT; + drvs[i][DS_CLICK].pos = drvs[i][DS_CLICK].len; + drvs[i][DS_SNATCH].pos = drvs[i][DS_SNATCH].len; + } + } + if (vv > 0) { + click_initialized = 1; + } + driveclick_reset(); +} + +void driveclick_reset (void) +{ + xfree (clickbuffer); + clickbuffer = NULL; + clickcnt = 0; + for (int i = 0; i < 4; i++) { + drv_starting[i] = 0; + drv_spinning[i] = 0; + drv_has_spun[i] = 0; + drv_has_disk[i] = 0; + if (currprefs.floppyslots[i].df[0]) + driveclick_insert(i, 0); + } + if (!wave_initialized) + return; + clickbuffer = xcalloc (uae_s16, paula_sndbufsize / 2); + sample_step = (freq << DS_SHIFT) / currprefs.sound_freq; +} + +static int driveclick_active (void) +{ + for (int i = 0; i < 4; i++) { + if (currprefs.floppyslots[i].dfxclick) { + if (drv_spinning[i] || drv_starting[i]) + return 1; + } + } + return 0; +} + +static uae_s16 getsample (void) +{ + uae_s32 total_sample = 0; + int total_div = 0; + + for (int i = 0; i < 4; i++) { + int div = 0; + if (currprefs.floppyslots[i].dfxclick) { + uae_s32 smp = 0; + struct drvsample* ds_start = &drvs[i][DS_START]; + struct drvsample* ds_spin = drv_has_disk[i] ? &drvs[i][DS_SPIN] : &drvs[i][DS_SPINND]; + struct drvsample* ds_click = &drvs[i][DS_CLICK]; + struct drvsample* ds_snatch = &drvs[i][DS_SNATCH]; + if (drv_spinning[i] || drv_starting[i]) { + div++; + if (drv_starting[i] && drv_has_spun[i]) { + if (ds_start->p && ds_start->pos < ds_start->len) { + smp = ds_start->p[ds_start->pos >> DS_SHIFT]; + ds_start->pos += sample_step; + } else { + drv_starting[i] = 0; + } + } else if (drv_starting[i] && drv_has_spun[i] == 0) { + if (ds_snatch->p && ds_snatch->pos < ds_snatch->len) { + smp = ds_snatch->p[ds_snatch->pos >> DS_SHIFT]; + ds_snatch->pos += sample_step; + } else { + drv_starting[i] = 0; + ds_start->pos = ds_start->len; + drv_has_spun[i] = 1; + } + } + if (ds_spin->p && drv_starting[i] == 0) { + if (ds_spin->pos >= ds_spin->len) + ds_spin->pos -= ds_spin->len; + smp = ds_spin->p[ds_spin->pos >> DS_SHIFT]; + ds_spin->pos += sample_step; + } + } + if (ds_click->p && ds_click->pos < ds_click->len) { + smp += ds_click->p[ds_click->pos >> DS_SHIFT]; + div++; + ds_click->pos += sample_step; + } + if (div) { + int vol; + if (drv_has_disk[i]) + vol = currprefs.dfxclickvolume_disk[i]; + else + vol = currprefs.dfxclickvolume_empty[i]; + total_sample += (smp * (100 - vol) / 100) / div; + total_div++; + } + } + } + if (!total_div) + return 0; + return total_sample / total_div; +} + +static void mix(void) +{ + int total = ((uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer) / (get_audio_nativechannels(currprefs.sound_stereo) * 2); + while (clickcnt < total) { + clickbuffer[clickcnt++] = getsample(); + } +} + +STATIC_INLINE uae_s16 limit(uae_s32 v) +{ + if (v < -32768) + v = -32768; + if (v > 32767) + v = 32767; + return v; +} + +void driveclick_mix(uae_s16* sndbuffer, int size, int channelmask) +{ + if (!wave_initialized) + return; + mix(); + clickcnt = 0; + switch (get_audio_nativechannels(currprefs.sound_stereo)) + { + case 6: + for (int i = 0; i < size / 6; i++) { + uae_s16 s = clickbuffer[i]; + if (channelmask & 1) + sndbuffer[0] = limit(((sndbuffer[0] + s) * 2) / 3); + else + sndbuffer[0] = sndbuffer[0] * 2 / 3; + if (channelmask & 2) + sndbuffer[1] = limit(((sndbuffer[1] + s) * 2) / 3); + else + sndbuffer[1] = sndbuffer[1] * 2 / 3; + if (channelmask & 4) + sndbuffer[2] = limit(((sndbuffer[2] + s) * 2) / 3); + else + sndbuffer[2] = sndbuffer[2] * 2 / 3; + if (channelmask & 8) + sndbuffer[3] = limit(((sndbuffer[3] + s) * 2) / 3); + else + sndbuffer[3] = sndbuffer[3] * 2 / 3; + if (channelmask & 16) + sndbuffer[4] = limit(((sndbuffer[4] + s) * 2) / 3); + else + sndbuffer[4] = sndbuffer[4] * 2 / 3; + if (channelmask & 32) + sndbuffer[5] = limit(((sndbuffer[5] + s) * 2) / 3); + else + sndbuffer[5] = sndbuffer[5] * 2 / 3; + sndbuffer += 6; + } + break; + case 4: + for (int i = 0; i < size / 4; i++) { + uae_s16 s = clickbuffer[i]; + if (channelmask & 1) + sndbuffer[0] = limit(((sndbuffer[0] + s) * 2) / 3); + else + sndbuffer[0] = sndbuffer[0] * 2 / 3; + if (channelmask & 2) + sndbuffer[1] = limit(((sndbuffer[1] + s) * 2) / 3); + else + sndbuffer[1] = sndbuffer[1] * 2 / 3; + if (channelmask & 4) + sndbuffer[2] = limit(((sndbuffer[2] + s) * 2) / 3); + else + sndbuffer[2] = sndbuffer[2] * 2 / 3; + if (channelmask & 8) + sndbuffer[3] = limit(((sndbuffer[3] + s) * 2) / 3); + else + sndbuffer[3] = sndbuffer[3] * 2 / 3; + sndbuffer += 4; + } + break; + case 2: + for (int i = 0; i < size / 2; i++) { + uae_s16 s = clickbuffer[i]; + if (channelmask & 1) + sndbuffer[0] = limit(((sndbuffer[0] + s) * 2) / 3); + else + sndbuffer[0] = sndbuffer[0] * 2 / 3; + if (channelmask & 2) + sndbuffer[1] = limit(((sndbuffer[1] + s) * 2) / 3); + else + sndbuffer[1] = sndbuffer[1] * 2 / 3; + sndbuffer += 2; + } + break; + case 1: + for (int i = 0; i < size; i++) { + uae_s16 s = clickbuffer[i]; + if (channelmask & 1) + sndbuffer[0] = limit(((sndbuffer[0] + s) * 2) / 3); + else + sndbuffer[0] = sndbuffer[0] * 2 / 3; + sndbuffer++; + } + break; + } +} + +static void dr_audio_activate(void) +{ + if (audio_activate()) + clickcnt = 0; +} + +void driveclick_click(int drive, int cyl) +{ + static int prevcyl[4]; + + if (!click_initialized) + return; + if (!currprefs.floppyslots[drive].dfxclick) + return; + if (prevcyl[drive] == 0 && cyl == 0) // "noclick" check + return; + dr_audio_activate(); + prevcyl[drive] = cyl; + if (!wave_initialized) { + driveclick_fdrawcmd_seek(currprefs.floppyslots[drive].dfxclick - 2, cyl); + return; + } + mix(); + drvs[drive][DS_CLICK].pos = drvs[drive][DS_CLICK].indexes[cyl] << DS_SHIFT; + drvs[drive][DS_CLICK].len = (drvs[drive][DS_CLICK].indexes[cyl] + (drvs[drive][DS_CLICK].lengths[cyl] / 2)) << DS_SHIFT; +} + +void driveclick_motor(int drive, int running) +{ + if (!click_initialized) + return; + if (!currprefs.floppyslots[drive].dfxclick) + return; + if (!wave_initialized) { + driveclick_fdrawcmd_motor(currprefs.floppyslots[drive].dfxclick - 2, running); + return; + } + mix(); + if (running == 0) { + drv_starting[drive] = 0; + drv_spinning[drive] = 0; + } else { + if (drv_spinning[drive] == 0) { + dr_audio_activate(); + drv_starting[drive] = 1; + drv_spinning[drive] = 1; + if (drv_has_disk[drive] && drv_has_spun[drive] == 0 && drvs[drive][DS_SNATCH].pos >= drvs[drive][DS_SNATCH].len) + drvs[drive][DS_SNATCH].pos = 0; + if (running == 2) + drvs[drive][DS_START].pos = 0; + drvs[drive][DS_SPIN].pos = 0; + } + } +} + +void driveclick_insert(int drive, int eject) +{ + if (!click_initialized) + return; + if (!wave_initialized) + return; + if (!currprefs.floppyslots[drive].dfxclick) + return; + if (eject) + drv_has_spun[drive] = 0; + if (drv_has_disk[drive] == 0 && !eject) + dr_audio_activate (); + drv_has_disk[drive] = !eject; +} + +void driveclick_check_prefs(void) +{ + if (!config_changed) + return; + driveclick_fdrawcmd_vsync(); + if (driveclick_active()) + dr_audio_activate(); + if ( + currprefs.dfxclickvolume_disk[0] != changed_prefs.dfxclickvolume_disk[0] || + currprefs.dfxclickvolume_disk[1] != changed_prefs.dfxclickvolume_disk[1] || + currprefs.dfxclickvolume_disk[2] != changed_prefs.dfxclickvolume_disk[2] || + currprefs.dfxclickvolume_disk[3] != changed_prefs.dfxclickvolume_disk[3] || + currprefs.dfxclickvolume_empty[0] != changed_prefs.dfxclickvolume_empty[0] || + currprefs.dfxclickvolume_empty[1] != changed_prefs.dfxclickvolume_empty[1] || + currprefs.dfxclickvolume_empty[2] != changed_prefs.dfxclickvolume_empty[2] || + currprefs.dfxclickvolume_empty[3] != changed_prefs.dfxclickvolume_empty[3] || + currprefs.floppyslots[0].dfxclick != changed_prefs.floppyslots[0].dfxclick || + currprefs.floppyslots[1].dfxclick != changed_prefs.floppyslots[1].dfxclick || + currprefs.floppyslots[2].dfxclick != changed_prefs.floppyslots[2].dfxclick || + currprefs.floppyslots[3].dfxclick != changed_prefs.floppyslots[3].dfxclick || + _tcscmp(currprefs.floppyslots[0].dfxclickexternal, changed_prefs.floppyslots[0].dfxclickexternal) || + _tcscmp(currprefs.floppyslots[1].dfxclickexternal, changed_prefs.floppyslots[1].dfxclickexternal) || + _tcscmp(currprefs.floppyslots[2].dfxclickexternal, changed_prefs.floppyslots[2].dfxclickexternal) || + _tcscmp(currprefs.floppyslots[3].dfxclickexternal, changed_prefs.floppyslots[3].dfxclickexternal)) + { + for (int i = 0; i < 4; i++) { + currprefs.floppyslots[i].dfxclick = changed_prefs.floppyslots[i].dfxclick; + _tcscpy(currprefs.floppyslots[i].dfxclickexternal, changed_prefs.floppyslots[i].dfxclickexternal); + currprefs.dfxclickvolume_empty[i] = changed_prefs.dfxclickvolume_empty[i]; + currprefs.dfxclickvolume_disk[i] = changed_prefs.dfxclickvolume_disk[i]; + } + driveclick_init(); + } +} + +int driveclick_loadresource(struct drvsample* sp, int drivetype) +{ + auto ok = 1; + for (auto type = 0; type < DS_END; type++) { + auto* s = sp + type; + switch (type) { + case 0: + ok = loadsample("data/floppy_sounds/drive_click.wav", s); + break; + case 1: + ok = loadsample("data/floppy_sounds/drive_spin.wav", s); + break; + case 2: + ok = loadsample("data/floppy_sounds/drive_spinnd.wav", s); + break; + case 3: + ok = loadsample("data/floppy_sounds/drive_startup.wav", s); + break; + case 4: + ok = loadsample("data/floppy_sounds/drive_snatch.wav", s); + break; + default: + continue; + } + } + return ok; +} + +void driveclick_fdrawcmd_close(int drive) +{ + +} + +int driveclick_fdrawcmd_open(int drive) +{ + return 0; +} + +void driveclick_fdrawcmd_detect(void) +{ + +} + +void driveclick_fdrawcmd_seek(int drive, int cyl) +{ + +} + +void driveclick_fdrawcmd_motor(int drive, int running) +{ + +} + +void driveclick_fdrawcmd_vsync(void) +{ + +} +#endif diff --git a/src/events.cpp b/src/events.cpp index b1fa2827..0d15a4f7 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -75,9 +75,9 @@ static bool event_check_vsync(void) if (v < 0) { if (currprefs.cachesize) - regs.pissoff = pissoff_value; + pissoff = pissoff_value; else - regs.pissoff = pissoff_nojit_value; + pissoff = pissoff_nojit_value; return true; } //} @@ -95,9 +95,9 @@ static bool event_check_vsync(void) } if (v < 0 && v2 < 0) { if (currprefs.cachesize) - regs.pissoff = pissoff_value; + pissoff = pissoff_value; else - regs.pissoff = pissoff_nojit_value; + pissoff = pissoff_nojit_value; return true; } //} @@ -108,11 +108,11 @@ static bool event_check_vsync(void) void do_cycles_cpu_fastest(uae_u32 cycles_to_add) { - if ((regs.pissoff -= cycles_to_add) > 0) + if ((pissoff -= cycles_to_add) > 0) return; - cycles_to_add = -regs.pissoff; - regs.pissoff = 0; + cycles_to_add = -pissoff; + pissoff = 0; if (is_syncline) { diff --git a/src/include/driveclick.h b/src/include/driveclick.h new file mode 100644 index 00000000..32c8cb8e --- /dev/null +++ b/src/include/driveclick.h @@ -0,0 +1,57 @@ +/* + * UAE - The Un*x Amiga Emulator + * + * Drive Click emulation stuff + * + * Copyright 2004 James Bagg, Toni Wilen + */ + +#ifndef UAE_DRIVECLICK_H +#define UAE_DRIVECLICK_H + +#include "uae/types.h" + +#define CLICK_TRACKS 84 + +struct drvsample { + int len; + int pos; + uae_s16* p; + int indexes[CLICK_TRACKS]; + int lengths[CLICK_TRACKS]; +}; + +#define DS_CLICK 0 +#define DS_SPIN 1 +#define DS_SPINND 2 +#define DS_START 3 +#define DS_SNATCH 4 +#define DS_END 5 + +extern void driveclick_click(int drive, int startOffset); +extern void driveclick_motor(int drive, int running); +extern void driveclick_insert(int drive, int eject); +extern void driveclick_init(void); +extern void driveclick_free(void); +extern void driveclick_reset(void); +extern void driveclick_mix(uae_s16*, int, int); +extern int driveclick_loadresource(struct drvsample*, int); +extern void driveclick_check_prefs(void); +extern uae_s16* decodewav(uae_u8* s, int* len); + +#define DS_BUILD_IN_SOUNDS 1 +#define DS_NAME_CLICK _T("drive_click_") +#define DS_NAME_SPIN _T("drive_spin_") +#define DS_NAME_SPIN_ND _T("drive_spinnd_") +#define DS_NAME_START _T("drive_start_") +#define DS_NAME_SNATCH _T("drive_snatch_") + +extern int driveclick_fdrawcmd_open(int); +extern void driveclick_fdrawcmd_close(int); +extern void driveclick_fdrawcmd_detect(void); +extern void driveclick_fdrawcmd_seek(int, int); +extern void driveclick_fdrawcmd_motor(int, int); +extern void driveclick_fdrawcmd_vsync(void); +extern int driveclick_pcdrivemask, driveclick_pcdrivenum; + +#endif /* UAE_DRIVECLICK_H */ diff --git a/src/include/events.h b/src/include/events.h index f3322d0a..56cf0c30 100644 --- a/src/include/events.h +++ b/src/include/events.h @@ -69,8 +69,9 @@ enum { }; extern int pissoff_value; +extern uae_s32 pissoff; -#define countdown (regs.pissoff) +#define countdown (pissoff) extern struct ev eventtab[ev_max]; extern struct ev2 eventtab2[ev2_max]; @@ -82,19 +83,19 @@ STATIC_INLINE void cycles_do_special(void) { #ifdef JIT if (currprefs.cachesize) { - if (regs.pissoff >= 0) - regs.pissoff = -1; + if (pissoff >= 0) + pissoff = -1; } else #endif { - regs.pissoff = 0; + pissoff = 0; } } STATIC_INLINE void do_extra_cycles (unsigned long cycles_to_add) { - regs.pissoff -= cycles_to_add; + pissoff -= cycles_to_add; } STATIC_INLINE unsigned long int get_cycles (void) diff --git a/src/include/newcpu.h b/src/include/newcpu.h index 793fc558..1c2eaa84 100644 --- a/src/include/newcpu.h +++ b/src/include/newcpu.h @@ -12,6 +12,7 @@ #include "uae/types.h" #include "readcpu.h" #include "machdep/m68k.h" +#include "events.h" extern const int areg_byteinc[]; extern const int imm8_table[]; @@ -172,7 +173,6 @@ struct regstruct uae_u32 pcr; uae_u32 address_space_mask; - uae_s32 pissoff; uae_u8* natmem_offset; #ifdef JIT diff --git a/src/include/uae/uae.h b/src/include/uae/uae.h index 58d23ac0..cbc4cc07 100644 --- a/src/include/uae/uae.h +++ b/src/include/uae/uae.h @@ -10,4 +10,7 @@ void uae_clipboard_put_text(const char* text); typedef const char * (*amiga_plugin_lookup_function)(const char *name); void amiga_set_plugin_lookup_function(amiga_plugin_lookup_function function); +typedef int (*audio_callback)(int type, int16_t* buffer, int size); +int amiga_set_cd_audio_callback(audio_callback func); + #endif // LIBAMIGA_LIBAMIGA_H_ \ No newline at end of file diff --git a/src/newcpu.cpp b/src/newcpu.cpp index 54fc339c..9b54d8ec 100644 --- a/src/newcpu.cpp +++ b/src/newcpu.cpp @@ -31,6 +31,8 @@ /* Need to have these somewhere */ bool check_prefs_changed_comp (bool checkonly) { return false; } #endif +/* For faster JIT cycles handling */ +uae_s32 pissoff = 0; /* Opcode of faulting instruction */ static uae_u32 last_op_for_exception_3; @@ -1158,7 +1160,7 @@ static void m68k_reset(bool hardreset) { uae_u32 v; - regs.pissoff = 0; + pissoff = 0; cpu_cycles = 0; regs.halted = 0; diff --git a/src/osdep/amiberry.cpp b/src/osdep/amiberry.cpp index 7552518b..4061ec58 100644 --- a/src/osdep/amiberry.cpp +++ b/src/osdep/amiberry.cpp @@ -134,6 +134,7 @@ static char rp9_path[MAX_DPATH]; static char controllers_path[MAX_DPATH]; static char retroarch_file[MAX_DPATH]; static char logfile_path[MAX_DPATH]; +static char floppy_sounds_dir[MAX_DPATH]; char last_loaded_config[MAX_DPATH] = {'\0'}; @@ -529,6 +530,11 @@ void target_default_options(struct uae_prefs* p, int type) p->cr[0].locked = true; p->cr[0].rtg = true; _tcscpy(p->cr[0].label, _T("RTG")); + + for (auto& floppyslot : p->floppyslots) + { + floppyslot.dfxclick = 1; + } } void target_save_options(struct zfile* f, struct uae_prefs* p) @@ -1209,6 +1215,7 @@ void load_amiberry_settings(void) #endif snprintf(rp9_path, MAX_DPATH, "%s/rp9/", start_path_data); snprintf(path, MAX_DPATH, "%s/conf/amiberry.conf", start_path_data); + snprintf(floppy_sounds_dir, MAX_DPATH, "%s/data/floppy_sounds/", start_path_data); auto* const fh = zfile_fopen(path, _T("r"), ZFD_NORMAL); if (fh) @@ -2067,3 +2074,22 @@ bool handle_events() return pause_emulation != 0; } +bool get_plugin_path(TCHAR* out, int len, const TCHAR* path) +{ + if (strcmp(path, "floppysounds") == 0) { + if (floppy_sounds_dir) { + strncpy(out, floppy_sounds_dir, len); + } + else { + strncpy(out, "floppy_sounds", len); + } + // make sure out is null-terminated in any case + out[len - 1] = '\0'; + } + else { + write_log("\n-----------------> STUB: get_plugin_path, " + "size: %d, path: %s\n", len, path); + out[0] = '\0'; + } + return TRUE; +} \ No newline at end of file diff --git a/src/osdep/amiberry_gfx.cpp b/src/osdep/amiberry_gfx.cpp index 88f0e435..7f6b1661 100644 --- a/src/osdep/amiberry_gfx.cpp +++ b/src/osdep/amiberry_gfx.cpp @@ -31,6 +31,7 @@ #include "statusline.h" #include "sounddep/sound.h" #include "threaddep/thread.h" + static uae_thread_id display_tid = nullptr; static smp_comm_pipe *volatile display_pipe = nullptr; static uae_sem_t display_sem = nullptr; diff --git a/src/osdep/amiberry_gui.cpp b/src/osdep/amiberry_gui.cpp index 33857b5c..e1c20db8 100644 --- a/src/osdep/amiberry_gui.cpp +++ b/src/osdep/amiberry_gui.cpp @@ -424,7 +424,7 @@ int gui_init() void gui_exit() { sync(); - stop_sound(); + close_sound(); save_amiberry_settings(); ClearConfigFileList(); ClearAvailableROMList(); diff --git a/src/osdep/cda_play.cpp b/src/osdep/cda_play.cpp index 7da13eee..6a0fd1bd 100644 --- a/src/osdep/cda_play.cpp +++ b/src/osdep/cda_play.cpp @@ -1,78 +1,120 @@ +#include "sysconfig.h" #include "sysdeps.h" -#include "audio.h" - #include "cda_play.h" +#include "audio.h" +#include "options.h" + #include "sounddep/sound.h" +#include "uae/uae.h" -cda_audio::~cda_audio() -{ - cdaudio_active = false; - if (active) { - wait(0); - wait(1); - } - for (int i = 0; i < 2; i++) { - xfree (buffers[i]); - buffers[i] = NULL; - } +static int (*g_audio_callback)(int type, int16_t* buffer, int size) = NULL; + +int amiga_set_cd_audio_callback(audio_callback func) { + g_audio_callback = func; + return 1; } -cda_audio::cda_audio(int num_sectors, int sectorsize, int samplerate) +cda_audio::~cda_audio() { - active = false; - playing = false; - volume[0] = volume[1] = 0; - currBuf = 1; + wait(0); + wait(1); + + for (auto& buffer : buffers) + { + xfree(buffer); + buffer = nullptr; + } +} - bufsize = num_sectors * sectorsize; +cda_audio::cda_audio(int num_sectors, int sectorsize, int samplerate, bool internalmode) +{ + active = false; + playing = false; + volume[0] = volume[1] = 0; + + bufsize = num_sectors * sectorsize; this->sectorsize = sectorsize; - for (int i = 0; i < 2; i++) { - buffers[i] = xcalloc (uae_u8, num_sectors * ((bufsize + 4095) & ~4095)); - } - this->num_sectors = num_sectors; - active = true; - playing = true; - cdaudio_active = true; + for (int i = 0; i < 2; i++) + { + buffer_ids[i] = 0; + buffers[i] = xcalloc(uae_u8, num_sectors * ((bufsize + 4095) & ~4095)); + } + this->num_sectors = num_sectors; + + if (internalmode) + return; + + active = true; + playing = true; } -void cda_audio::setvolume(int left, int right) +void cda_audio::setvolume(int left, int right) { - for (int j = 0; j < 2; j++) { - volume[j] = j == 0 ? left : right; + for (int j = 0; j < 2; j++) + { + volume[j] = j == 0 ? left : right; volume[j] = sound_cd_volume[j] * volume[j] / 32768; - if (volume[j]) - volume[j]++; - if (volume[j] >= 32768) - volume[j] = 32768; - } + if (volume[j]) + volume[j]++; + volume[j] = volume[j] * (100 - currprefs.sound_volume_master) / 100; + if (volume[j] >= 32768) + volume[j] = 32768; + } } -bool cda_audio::play(int bufnum) +bool cda_audio::play(int bufnum) { - if (!active) { - return false; - } + if (!active) + return false; - currBuf = bufnum; - uae_s16 *p = (uae_s16*)(buffers[bufnum]); - for (int i = 0; i < num_sectors * sectorsize / 4; i++) { - PUT_CDAUDIO_WORD_STEREO(p[i * 2 + 0] * volume[0] / 32768, p[i * 2 + 1] * volume[1] / 32768); - check_cdaudio_buffers(); - } + uae_s16* p = (uae_s16*)(buffers[bufnum]); + for (int i = 0; i < num_sectors * sectorsize / 4; i++) { + p[i * 2 + 0] = p[i * 2 + 0] * volume[0] / 32768; + p[i * 2 + 1] = p[i * 2 + 1] * volume[1] / 32768; + } - return cdaudio_catchup(); + if (g_audio_callback) { + int len = num_sectors * sectorsize; +#ifdef WORDS_BIGENDIAN + int8_t* d = (int8_t*)p; + int8_t temp = 0; + for (int i = 0; i < len; i += 2) { + temp = d[i + 1]; + d[i + 1] = d[i]; + d[i] = temp; + } +#endif + buffer_ids[bufnum] = g_audio_callback(3, p, len); + } + else { + buffer_ids[bufnum] = 0; + } + + return true; } -void cda_audio::wait(int bufnum) +void cda_audio::wait(int bufnum) { - if (!active || !playing) - return; + if (!active || !playing) + return; + + if (buffer_ids[bufnum] == 0) { + return; + } + + // calling g_audio_callback with NULL parameter to check status + while (!g_audio_callback(3, NULL, buffer_ids[bufnum])) { + Sleep(10); + } } bool cda_audio::isplaying(int bufnum) { if (!active || !playing) return false; - return bufnum != currBuf; + if (buffer_ids[bufnum] == 0) { + return false; + } + return g_audio_callback(3, NULL, buffer_ids[bufnum]); } diff --git a/src/osdep/cda_play.h b/src/osdep/cda_play.h index 20c5cb78..bb0c83fd 100644 --- a/src/osdep/cda_play.h +++ b/src/osdep/cda_play.h @@ -1,26 +1,28 @@ #pragma once +#include "audio.h" + extern volatile bool cd_audio_mode_changed; -class cda_audio +class cda_audio { private: - int bufsize; - int sectorsize; - int volume[2]; - bool playing; - bool active; - int currBuf; - int num_sectors; + int bufsize; + int volume[2]; + bool playing; + bool active; + int buffer_ids[2]; public: - uae_u8 *buffers[2]; - - cda_audio(int num_sectors, int sectorsize, int samplerate); - ~cda_audio(); - void setvolume(int left, int right); - bool play(int bufnum); - void wait(void); - void wait(int bufnum); + uae_u8 *buffers[2]; + int num_sectors; + int sectorsize; + + cda_audio(int num_sectors, int sectorsize, int samplerate, bool internalmode); + ~cda_audio(); + void setvolume(int left, int right); + bool play(int bufnum); + void wait(void); + void wait(int bufnum); bool isplaying(int bufnum); }; diff --git a/src/osdep/sysconfig.h b/src/osdep/sysconfig.h index d766e778..e941973d 100644 --- a/src/osdep/sysconfig.h +++ b/src/osdep/sysconfig.h @@ -9,7 +9,7 @@ #define MAX_DPATH 4096 #endif -/* #define DRIVESOUND */ +#define DRIVESOUND /* #define GFXFILTER */ //#define USE_SOFT_LONG_DOUBLE #define PACKAGE_STRING "AMIBERRY" diff --git a/src/osdep/target.h b/src/osdep/target.h index f03a6229..0ebe0acb 100644 --- a/src/osdep/target.h +++ b/src/osdep/target.h @@ -57,8 +57,6 @@ void update_display(struct uae_prefs*); void black_screen_now(void); void graphics_subshutdown(void); -void stop_sound(); - void keyboard_settrans(); void set_mouse_grab(bool grab); @@ -221,3 +219,12 @@ void restore_host_fp_regs(void* buf); } #endif #endif + +#define MAX_SOUND_DEVICES 100 +#define SOUND_DEVICE_DS 1 +#define SOUND_DEVICE_AL 2 +#define SOUND_DEVICE_PA 3 +#define SOUND_DEVICE_WASAPI 4 +#define SOUND_DEVICE_WASAPI_EXCLUSIVE 5 +#define SOUND_DEVICE_XAUDIO2 6 +#define SOUND_DEVICE_SDL2 7 diff --git a/src/sounddep/sound.cpp b/src/sounddep/sound.cpp index a9004dac..a56afad8 100644 --- a/src/sounddep/sound.cpp +++ b/src/sounddep/sound.cpp @@ -2,245 +2,385 @@ * Sdl sound.c implementation * (c) 2015 */ -#include #include - #include +#include "sysconfig.h" #include "sysdeps.h" -#include "uae.h" + #include "options.h" #include "audio.h" -#include "gensound.h" -#include "sounddep/sound.h" +#include "events.h" +#include "custom.h" +#include "threaddep/thread.h" #include "gui.h" +#include "savestate.h" +#ifdef DRIVESOUND +#include "driveclick.h" +#endif +#include "gensound.h" +#include "xwin.h" +#include "sounddep/sound.h" #ifdef ANDROID #include #endif -// "consumer" means the actual SDL sound output, as opposed to -#define SOUND_CONSUMER_BUFFER_LENGTH (SNDBUFFER_LEN * SOUND_BUFFERS_COUNT / 4) +struct sound_dp +{ + int sndbufsize; + int framesperbuffer; + int sndbuf; + int pullmode; + uae_u8* pullbuffer; + int pullbufferlen; + int pullbuffermaxlen; + double avg_correct; + double cnt_correct; +}; -uae_u16 sndbuffer[SOUND_BUFFERS_COUNT][(SNDBUFFER_LEN + 32) * DEFAULT_SOUND_CHANNELS]; -uae_u16* sndbufpt = sndbuffer[0]; -uae_u16* render_sndbuff = sndbuffer[0]; -uae_u16* finish_sndbuff = sndbuffer[0] + SNDBUFFER_LEN * DEFAULT_SOUND_CHANNELS; +#define SND_STATUSCNT 10 -uae_u16 cdaudio_buffer[CDAUDIO_BUFFERS][(CDAUDIO_BUFFER_LEN + 32) * DEFAULT_SOUND_CHANNELS]; -uae_u16* cdbufpt = cdaudio_buffer[0]; -uae_u16* render_cdbuff = cdaudio_buffer[0]; -uae_u16* finish_cdbuff = cdaudio_buffer[0] + CDAUDIO_BUFFER_LEN * DEFAULT_SOUND_CHANNELS; -bool cdaudio_active = false; -static int cdwrcnt = 0; -static int cdrdcnt = 0; +#define ADJUST_SIZE 20 +#define EXP 1.9 +#define ADJUST_VSSIZE 12 +#define EXPVS 1.6 static int have_sound = 0; +static int statuscnt; -void update_sound(double clk) +#define SND_MAX_BUFFER2 524288 +#define SND_MAX_BUFFER 65536 + +//uae_u16 paula_sndbuffer[SND_MAX_BUFFER]; +uae_u16 paula_sndbuffer[SND_MAX_BUFFER]; +uae_u16* paula_sndbufpt; +int paula_sndbufsize; + +SDL_AudioSpec want, have; +SDL_AudioDeviceID dev; + +void sdl2_audio_callback(void* userdata, Uint8* stream, int len); + +struct sound_device* sound_devices[MAX_SOUND_DEVICES]; +struct sound_device* record_devices[MAX_SOUND_DEVICES]; + +static struct sound_data sdpaula; +static struct sound_data* sdp = &sdpaula; + +static uae_u8* extrasndbuf; +static int extrasndbufsize; +static int extrasndbuffered; + + +int setup_sound(void) { - const auto evtime = clk * CYCLE_UNIT / double(currprefs.sound_freq); - scaled_sample_evtime = evtime; -} - - -static int s_oldrate = 0, s_oldbits = 0, s_oldstereo = 0; -static int sound_thread_active = 0, sound_thread_exit = 0; -static int rdcnt = 0; -static int wrcnt = 0; - - -static void sound_copy_produced_block(void* ud, Uint8* stream, int len) -{ - if (currprefs.sound_stereo) - { - if (cdaudio_active && currprefs.sound_freq == 44100 && cdrdcnt < cdwrcnt) - { - for (auto i = 0; i < SNDBUFFER_LEN * 2; ++i) - sndbuffer[rdcnt & SOUND_BUFFERS_COUNT - 1][i] += cdaudio_buffer[cdrdcnt & CDAUDIO_BUFFERS - 1][i]; - cdrdcnt++; - } - - memcpy(stream, sndbuffer[rdcnt & SOUND_BUFFERS_COUNT - 1], len); - } - else - memcpy(stream, sndbuffer[rdcnt & SOUND_BUFFERS_COUNT - 1], len); - - if (wrcnt - rdcnt >= SOUND_BUFFERS_COUNT / 2) - { - rdcnt++; - } -} - - -static void sound_thread_mixer(void* ud, Uint8* stream, int len) -{ - if (sound_thread_exit) - return; - sound_thread_active = 1; - - const auto sample_size = currprefs.sound_stereo ? 4 : 2; - - while (len > 0) - { - int l = len < SNDBUFFER_LEN * sample_size ? len : SNDBUFFER_LEN * sample_size; - sound_copy_produced_block(ud, stream, l); - stream += l; - len -= l; - } -} - - -static void init_soundbuffer_usage() -{ - sndbufpt = sndbuffer[0]; - render_sndbuff = sndbuffer[0]; - finish_sndbuff = sndbuffer[0] + SNDBUFFER_LEN * 2; - rdcnt = 0; - wrcnt = 0; - - cdbufpt = cdaudio_buffer[0]; - render_cdbuff = cdaudio_buffer[0]; - finish_cdbuff = cdaudio_buffer[0] + CDAUDIO_BUFFER_LEN * 2; - cdrdcnt = 0; - cdwrcnt = 0; -} - - -static int start_sound(int rate, int bits, int stereo) -{ - int frag = 0, buffers, ret; - unsigned int bsize; - - if (SDL_GetAudioStatus() == SDL_AUDIO_STOPPED) - { - init_soundbuffer_usage(); - - s_oldrate = 0; - s_oldbits = 0; - s_oldstereo = 0; - - sound_thread_exit = 0; - } - - // if no settings change, we don't need to do anything - if (rate == s_oldrate && s_oldbits == bits && s_oldstereo == stereo) - return 0; - - - SDL_AudioSpec as; - memset(&as, 0, sizeof(as)); - - as.freq = rate; - as.format = (bits == 8 ? AUDIO_S8 : AUDIO_S16); - as.channels = (stereo ? 2 : 1); - as.samples = SOUND_CONSUMER_BUFFER_LENGTH; - as.callback = sound_thread_mixer; - - if (SDL_OpenAudio(&as, nullptr)) - write_log("Error when opening SDL audio !\n"); - - s_oldrate = rate; - s_oldbits = bits; - s_oldstereo = stereo; - - clear_sound_buffers(); - clear_cdaudio_buffers(); - - SDL_PauseAudio(0); - - return 0; -} - - -void stop_sound() -{ - if (sound_thread_exit == 0) - { - SDL_PauseAudio(1); - sound_thread_exit = 1; - SDL_CloseAudio(); - } -} - -void finish_sound_buffer() -{ - wrcnt++; - sndbufpt = render_sndbuff = sndbuffer[wrcnt & SOUND_BUFFERS_COUNT - 1]; - - if (currprefs.sound_stereo) - finish_sndbuff = sndbufpt + SNDBUFFER_LEN * 2; - else - finish_sndbuff = sndbufpt + SNDBUFFER_LEN; - - while ((wrcnt & SOUND_BUFFERS_COUNT - 1) == (rdcnt & SOUND_BUFFERS_COUNT - 1)) - { - usleep(500); - } -} - -void pause_sound_buffer() -{ - reset_sound(); -} - -void restart_sound_buffer() -{ - sndbufpt = render_sndbuff = sndbuffer[wrcnt & (SOUND_BUFFERS_COUNT - 1)]; - if (currprefs.sound_stereo) - finish_sndbuff = sndbufpt + SNDBUFFER_LEN * 2; - else - finish_sndbuff = sndbufpt + SNDBUFFER_LEN; - - cdbufpt = render_cdbuff = cdaudio_buffer[cdwrcnt & (CDAUDIO_BUFFERS - 1)]; - finish_cdbuff = cdbufpt + CDAUDIO_BUFFER_LEN * 2; -} - -void finish_cdaudio_buffer() -{ - cdwrcnt++; - cdbufpt = render_cdbuff = cdaudio_buffer[cdwrcnt & (CDAUDIO_BUFFERS - 1)]; - finish_cdbuff = cdbufpt + CDAUDIO_BUFFER_LEN * 2; - audio_activate(); -} - - -bool cdaudio_catchup() -{ - while (cdwrcnt > cdrdcnt + CDAUDIO_BUFFERS - 10 && sound_thread_active != 0 && quit_program == 0) - { - sleep_millis(10); - } - return sound_thread_active != 0; -} - -/* Try to determine whether sound is available. This is only for GUI purposes. */ -int setup_sound() -{ - if (start_sound(currprefs.sound_freq, 16, currprefs.sound_stereo) != 0) - return 0; - sound_available = 1; return 1; } +float sound_sync_multiplier = 1.0; +float scaled_sample_evtime_orig; +// Originally from sampler.cpp +float sampler_evtime; + +void update_sound(double clk) +{ + if (!have_sound) + return; + scaled_sample_evtime_orig = clk * CYCLE_UNIT * sound_sync_multiplier / sdp->obtainedfreq; + scaled_sample_evtime = scaled_sample_evtime_orig; + sampler_evtime = clk * CYCLE_UNIT * sound_sync_multiplier; +} + +extern int vsynctimebase_orig; + +#define ADJUST_LIMIT 6 +#define ADJUST_LIMIT2 1 + +void sound_setadjust(double v) +{ + float mult; + + if (v < -ADJUST_LIMIT) + v = -ADJUST_LIMIT; + if (v > ADJUST_LIMIT) + v = ADJUST_LIMIT; + + mult = 1000.0 + v; + if (isvsync_chipset()) { + vsynctimebase = vsynctimebase_orig; + scaled_sample_evtime = scaled_sample_evtime_orig * mult / 1000.0; + } + else if (currprefs.cachesize || currprefs.m68k_speed != 0) { + vsynctimebase = (int)(((double)vsynctimebase_orig) * mult / 1000.0); + scaled_sample_evtime = scaled_sample_evtime_orig; + } + else { + vsynctimebase = (int)(((double)vsynctimebase_orig) * mult / 1000.0); + scaled_sample_evtime = scaled_sample_evtime_orig; + } +} + +static void docorrection(struct sound_dp* s, int sndbuf, double sync, int granulaty) +{ + static int tfprev; + + s->avg_correct += sync; + s->cnt_correct++; + + if (granulaty < 10) + granulaty = 10; + + if (tfprev != timeframes) { + double skipmode, avgskipmode; + double avg = s->avg_correct / s->cnt_correct; + + skipmode = sync / 100.0; + avgskipmode = avg / (10000.0 / granulaty); + + gui_data.sndbuf = sndbuf; + + if (skipmode > ADJUST_LIMIT2) + skipmode = ADJUST_LIMIT2; + if (skipmode < -ADJUST_LIMIT2) + skipmode = -ADJUST_LIMIT2; + + sound_setadjust(skipmode + avgskipmode); + tfprev = timeframes; + } +} + +static double sync_sound(double m) +{ + double skipmode; + if (isvsync()) { + + skipmode = pow(m < 0 ? -m : m, EXPVS) / 2; + if (m < 0) + skipmode = -skipmode; + if (skipmode < -ADJUST_VSSIZE) + skipmode = -ADJUST_VSSIZE; + if (skipmode > ADJUST_VSSIZE) + skipmode = ADJUST_VSSIZE; + + } + else if (1) { + + skipmode = pow(m < 0 ? -m : m, EXP) / 2; + if (m < 0) + skipmode = -skipmode; + if (skipmode < -ADJUST_SIZE) + skipmode = -ADJUST_SIZE; + if (skipmode > ADJUST_SIZE) + skipmode = ADJUST_SIZE; + } + + return skipmode; +} + +static void clearbuffer_sdl2(struct sound_data *sd) +{ + SDL_LockAudioDevice(dev); + SDL_memset(paula_sndbuffer, 0, sizeof paula_sndbuffer); + SDL_UnlockAudioDevice(dev); +} + +static void clearbuffer(struct sound_data* sd) +{ + struct sound_dp* s = sd->data; + clearbuffer_sdl2(sd); + if (s->pullbuffer) { + memset(s->pullbuffer, 0, s->pullbuffermaxlen); + } +} + +static void set_reset(struct sound_data* sd) +{ + sd->reset = true; + sd->resetcnt = 10; + sd->resetframecnt = 0; +} + +static void pause_audio_sdl2(struct sound_data* sd) +{ + sd->waiting_for_buffer = 0; + SDL_PauseAudioDevice(dev, 1); + clearbuffer(sd); +} + +static void resume_audio_sdl2(struct sound_data* sd) +{ + struct sound_dp* s = sd->data; + sd->paused = 0; + clearbuffer(sd); + sd->waiting_for_buffer = 1; + s->avg_correct = 0; + s->cnt_correct = 0; + SDL_PauseAudioDevice(dev, 0); +} + +static void close_audio_sdl2(struct sound_data* sd) +{ + struct sound_dp* s = sd->data; + SDL_PauseAudioDevice(dev, 1); + // shut everything down + SDL_CloseAudioDevice(dev); + xfree(s->pullbuffer); + s->pullbuffer = NULL; + s->pullbufferlen = 0; +} + +void set_volume_sound_device(struct sound_data* sd, int volume, int mute) +{ + //todo +} + +void set_volume(int volume, int mute) +{ + set_volume_sound_device(sdp, volume, mute); + //setvolume_ahi(volume); + config_changed = 1; +} + +static void finish_sound_buffer_pull(struct sound_data* sd, uae_u16* sndbuffer) +{ + struct sound_dp* s = sd->data; + + if (s->pullbufferlen + sd->sndbufsize > s->pullbuffermaxlen) { + write_log(_T("pull overflow! %d %d %d\n"), s->pullbufferlen, sd->sndbufsize, s->pullbuffermaxlen); + s->pullbufferlen = 0; + } + memcpy(s->pullbuffer + s->pullbufferlen, sndbuffer, sd->sndbufsize); + s->pullbufferlen += sd->sndbufsize; +} + +static int open_audio_sdl2(struct sound_data* sd, int index) +{ + struct sound_dp* s = sd->data; + int freq = sd->freq; + int ch = sd->channels; + int size; + + sd->devicetype = SOUND_DEVICE_SDL2; + if (sd->sndbufsize < 0x80) + sd->sndbufsize = 0x80; + s->framesperbuffer = sd->sndbufsize; + s->sndbufsize = s->framesperbuffer; + sd->sndbufsize = s->sndbufsize * ch * 2; + if (sd->sndbufsize > SND_MAX_BUFFER) + sd->sndbufsize = SND_MAX_BUFFER; + + SDL_memset(&want, 0, sizeof want); + want.freq = freq; + want.format = AUDIO_S16; + want.channels = ch; + want.samples = s->framesperbuffer; + want.callback = sdl2_audio_callback; + want.userdata = sd; + + sd->samplesize = ch * 16 / 8; + s->pullmode = 1; + + dev = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); + if (dev == 0) + SDL_Log("Failed to open audio: %s", SDL_GetError()); + + s->pullbuffermaxlen = sd->sndbufsize * 2; + s->pullbuffer = xcalloc(uae_u8, s->pullbuffermaxlen); + s->pullbufferlen = 0; + + clear_sound_buffers(); + SDL_PauseAudioDevice(dev, 0); + + return 1; +} + +int open_sound_device(struct sound_data* sd, int index, int bufsize, int freq, int channels) +{ + int ret = 0; + struct sound_dp* sdp = xcalloc(struct sound_dp, 1); + + sd->data = sdp; + sd->sndbufsize = bufsize; + sd->freq = freq; + sd->channels = channels; + sd->paused = 1; + sd->index = index; + ret = open_audio_sdl2(sd, index); + sd->samplesize = sd->channels * 2; + sd->sndbufframes = sd->sndbufsize / sd->samplesize; + return ret; +} + +void close_sound_device(struct sound_data* sd) +{ + pause_sound_device(sd); + close_audio_sdl2(sd); + xfree(sd->data); + sd->data = NULL; + sd->index = -1; +} + +void pause_sound_device(struct sound_data* sd) +{ + struct sound_dp* s = sd->data; + sd->paused = 1; + gui_data.sndbuf_status = 0; + gui_data.sndbuf = 0; + pause_audio_sdl2(sd); +} +void resume_sound_device(struct sound_data* sd) +{ + struct sound_dp* s = sd->data; + resume_audio_sdl2(sd); + sd->paused = 0; +} + static int open_sound() { - config_changed = 1; - if (start_sound(currprefs.sound_freq, 16, currprefs.sound_stereo) != 0) + int ret = 0, ch; + int size = currprefs.sound_maxbsiz; + + if (!currprefs.produce_sound) { return 0; + } + config_changed = 1; + /* Always interpret buffer size as number of samples, not as actual + buffer size. Of course, since 8192 is the default, we'll have to + scale that to a sane value (assuming that otherwise 16 bits and + stereo would have been enabled and we'd have done the shift by + two anyway). */ + size >>= 2; + size &= ~63; + + sdp->softvolume = -1; + ch = get_audio_nativechannels(currprefs.sound_stereo); + ret = open_sound_device(sdp, 0, size, currprefs.sound_freq, ch); + if (!ret) + return 0; + currprefs.sound_freq = changed_prefs.sound_freq = sdp->freq; + if (ch != sdp->channels) + currprefs.sound_stereo = changed_prefs.sound_stereo = get_audio_stereomode(sdp->channels); + + set_volume(currprefs.sound_volume_master, sdp->mute); + if (get_audio_amigachannels(currprefs.sound_stereo) == 4) + sample_handler = sample16ss_handler; + else + sample_handler = get_audio_ismono(currprefs.sound_stereo) ? sample16_handler : sample16s_handler; + + sdp->obtainedfreq = currprefs.sound_freq; have_sound = 1; sound_available = 1; - - gui_data.sndbuf_avail = true; - - if (currprefs.sound_stereo) - sample_handler = sample16s_handler; - else - sample_handler = sample16_handler; - + gui_data.sndbuf_avail = audio_is_pull() == 0; + + paula_sndbufsize = sdp->sndbufsize; + paula_sndbufpt = paula_sndbuffer; +#ifdef DRIVESOUND + driveclick_init(); +#endif return 1; } @@ -253,9 +393,42 @@ void close_sound() if (!have_sound) return; - stop_sound(); - + close_sound_device(sdp); have_sound = 0; + extrasndbufsize = 0; + extrasndbuffered = 0; + xfree(extrasndbuf); + extrasndbuf = NULL; +} + +bool sound_paused(void) +{ + return sdp->paused != 0; +} + +void pause_sound() +{ + if (sdp->paused) + return; + if (!have_sound) + return; + pause_sound_device(sdp); +} + +void resume_sound() +{ + if (!sdp->paused) + return; + if (!have_sound) + return; + resume_sound_device(sdp); +} + +void reset_sound() +{ + if (!have_sound) + return; + clearbuffer(sdp); } int init_sound() @@ -263,33 +436,304 @@ int init_sound() gui_data.sndbuf_status = 3; gui_data.sndbuf = 0; gui_data.sndbuf_avail = false; - have_sound = open_sound(); - return have_sound; + if (!sound_available) + return 0; + if (currprefs.produce_sound <= 1) + return 0; + if (have_sound) + return 1; + if (!open_sound()) + return 0; + sdp->paused = 1; +#ifdef DRIVESOUND + driveclick_reset(); +#endif + reset_sound(); + resume_sound(); + return 1; } -void pause_sound() +static void disable_sound(void) { - SDL_PauseAudio(1); + close_sound(); + currprefs.produce_sound = changed_prefs.produce_sound = 1; } -void resume_sound() +static int reopen_sound(void) { - SDL_PauseAudio(0); + bool paused = sdp->paused != 0; + close_sound(); + int v = open_sound(); + if (v && !paused) + resume_sound_device(sdp); + return v; } -void reset_sound() +void pause_sound_buffer(void) { + sdp->deactive = true; + reset_sound(); +} + +void restart_sound_buffer(void) +{ + sdp->deactive = false; + //restart_sound_buffer2(sdp); +} + +static void channelswap(uae_s16* sndbuffer, int len) +{ + for (int i = 0; i < len; i += 2) { + uae_s16 t; + t = sndbuffer[i]; + sndbuffer[i] = sndbuffer[i + 1]; + sndbuffer[i + 1] = t; + } +} +static void channelswap6(uae_s16* sndbuffer, int len) +{ + for (int i = 0; i < len; i += 6) { + uae_s16 t; + t = sndbuffer[i + 0]; + sndbuffer[i + 0] = sndbuffer[i + 1]; + sndbuffer[i + 1] = t; + t = sndbuffer[i + 4]; + sndbuffer[i + 4] = sndbuffer[i + 5]; + sndbuffer[i + 5] = t; + } +} + +static void send_sound(struct sound_data* sd, uae_u16* sndbuffer) +{ + if (savestate_state) + return; + if (sd->paused) + return; + if (sd->softvolume >= 0) { + uae_s16* p = (uae_s16*)sndbuffer; + for (int i = 0; i < sd->sndbufsize / 2; i++) { + p[i] = p[i] * sd->softvolume / 32768; + } + } + + finish_sound_buffer_pull(sd, sndbuffer); +} + +bool audio_is_event_frame_possible(int) +{ + return false; +} + +int audio_is_pull(void) +{ + if (sdp->reset) + return 0; + struct sound_dp* s = sdp->data; + if (s && s->pullmode) { + return sdp->paused || sdp->deactive ? -1 : 1; + } + return 0; +} + +int audio_pull_buffer(void) +{ + int cnt = 0; + if (sdp->paused || sdp->deactive || sdp->reset) + return 0; + struct sound_dp* s = sdp->data; + if (s->pullbufferlen > 0) { + cnt++; + int size = (uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer; + if (size > sdp->sndbufsize * 2 / 3) + cnt++; + } + return cnt; +} + +bool audio_is_pull_event(void) +{ + return false; +} + +bool audio_finish_pull(void) +{ + return false; +} + +static void handle_reset(void) +{ + if (sdp->resetframe == timeframes) + return; + sdp->resetframe = timeframes; + sdp->resetframecnt--; + if (sdp->resetframecnt > 0) + return; + sdp->resetframecnt = 20; + + sdp->reset = false; + if (!reopen_sound() || sdp->reset) { + if (sdp->resetcnt <= 0) { + write_log(_T("Reopen sound failed. Retrying with default device.\n")); + close_sound(); + //int type = sound_devices[currprefs.win32_soundcard]->type; + //int max = enumerate_sound_devices(); + //for (int i = 0; i < max; i++) { + // if (sound_devices[i]->alname == NULL && sound_devices[i]->type == type) { + // currprefs.win32_soundcard = changed_prefs.win32_soundcard = i; + // if (open_sound()) + // return; + // break; + // } + //} + currprefs.produce_sound = changed_prefs.produce_sound = 1; + } + else { + write_log(_T("Retrying sound.. %d..\n"), sdp->resetcnt); + sdp->resetcnt--; + sdp->reset = true; + } + } + else { + resume_sound_device(sdp); + } +} + +void finish_sound_buffer() +{ + static unsigned long tframe; + int bufsize = (uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer; + + if (sdp->reset) { + handle_reset(); + paula_sndbufpt = paula_sndbuffer; + return; + } + + if (currprefs.turbo_emulation) { + paula_sndbufpt = paula_sndbuffer; + return; + } + if (currprefs.sound_stereo_swap_paula) { + if (get_audio_nativechannels(currprefs.sound_stereo) == 2 || get_audio_nativechannels(currprefs.sound_stereo) == 4) + channelswap((uae_s16*)paula_sndbuffer, bufsize / 2); + else if (get_audio_nativechannels(currprefs.sound_stereo) == 6) + channelswap6((uae_s16*)paula_sndbuffer, bufsize / 2); + } +#ifdef DRIVESOUND + driveclick_mix((uae_s16*)paula_sndbuffer, bufsize / 2, currprefs.dfxclickchannelmask); +#endif + // must be after driveclick_mix + paula_sndbufpt = paula_sndbuffer; + if (!have_sound) return; - init_soundbuffer_usage(); + // we got buffer that was not full (recording active). Need special handling. + if (bufsize < sdp->sndbufsize && !extrasndbuf) { + extrasndbufsize = sdp->sndbufsize; + extrasndbuf = xcalloc(uae_u8, sdp->sndbufsize); + extrasndbuffered = 0; + } - clear_sound_buffers(); - clear_cdaudio_buffers(); + if (statuscnt > 0 && tframe != timeframes) { + tframe = timeframes; + statuscnt--; + if (statuscnt == 0) + gui_data.sndbuf_status = 0; + } + if (gui_data.sndbuf_status == 3) + gui_data.sndbuf_status = 0; + + if (extrasndbuf) { + int size = extrasndbuffered + bufsize; + int copied = 0; + if (size > extrasndbufsize) { + copied = extrasndbufsize - extrasndbuffered; + memcpy(extrasndbuf + extrasndbuffered, paula_sndbuffer, copied); + send_sound(sdp, (uae_u16*)extrasndbuf); + extrasndbuffered = 0; + } + memcpy(extrasndbuf + extrasndbuffered, (uae_u8*)paula_sndbuffer + copied, bufsize - copied); + extrasndbuffered += bufsize - copied; + } + else { + send_sound(sdp, paula_sndbuffer); + } } +static int set_master_volume(int volume, int mute) +{ + //todo set volume using SDL2 +} + +static int get_master_volume(int* volume, int* mute) +{ + //todo get volume using SDL2 +} + +void sound_mute(int newmute) +{ + if (newmute < 0) + sdp->mute = sdp->mute ? 0 : 1; + else + sdp->mute = newmute; + set_volume(currprefs.sound_volume_master, sdp->mute); + config_changed = 1; +} void sound_volume(int dir) { + currprefs.sound_volume_master -= dir * 10; + currprefs.sound_volume_cd -= dir * 10; + if (currprefs.sound_volume_master < 0) + currprefs.sound_volume_master = 0; + if (currprefs.sound_volume_master > 100) + currprefs.sound_volume_master = 100; + changed_prefs.sound_volume_master = currprefs.sound_volume_master; + if (currprefs.sound_volume_cd < 0) + currprefs.sound_volume_cd = 0; + if (currprefs.sound_volume_cd > 100) + currprefs.sound_volume_cd = 100; + changed_prefs.sound_volume_cd = currprefs.sound_volume_cd; + set_volume(currprefs.sound_volume_master, sdp->mute); config_changed = 1; } + +void master_sound_volume(int dir) +{ + int vol, mute, r; + + r = get_master_volume(&vol, &mute); + if (!r) + return; + if (dir == 0) + mute = mute ? 0 : 1; + vol += dir * (65536 / 10); + if (vol < 0) + vol = 0; + if (vol > 65535) + vol = 65535; + set_master_volume(vol, mute); + config_changed = 1; +} + +// Audio callback function +void sdl2_audio_callback(void* userdata, Uint8* stream, int len) +{ + struct sound_data* sd = (struct sound_data*)userdata; + struct sound_dp* s = sd->data; + int bytestocopy; + + if (s->pullbufferlen <= 0) + return; + + bytestocopy = s->framesperbuffer * sd->samplesize; + if (bytestocopy > 0) { + memcpy(stream, s->pullbuffer, bytestocopy); + } + + if (bytestocopy < s->pullbufferlen) { + memmove(s->pullbuffer, s->pullbuffer + bytestocopy, s->pullbufferlen - bytestocopy); + } + s->pullbufferlen -= bytestocopy; +} diff --git a/src/sounddep/sound.h b/src/sounddep/sound.h index 62d1ef78..cc0a4891 100644 --- a/src/sounddep/sound.h +++ b/src/sounddep/sound.h @@ -7,43 +7,136 @@ */ #pragma once -#define DEFAULT_SOUND_CHANNELS 2 +#include "audio.h" + +#define SOUNDSTUFF 1 #define SOUND_BUFFERS_COUNT 4 #define SNDBUFFER_LEN 1024 -extern uae_u16 sndbuffer[SOUND_BUFFERS_COUNT][(SNDBUFFER_LEN + 32) * DEFAULT_SOUND_CHANNELS]; -extern uae_u16* sndbufpt; -extern uae_u16* render_sndbuff; -extern uae_u16* finish_sndbuff; -extern int sndbufsize; +extern uae_u16 paula_sndbuffer[]; +extern uae_u16* paula_sndbufpt; +extern int paula_sndbufsize; extern void finish_sound_buffer(void); extern void restart_sound_buffer(void); extern void pause_sound_buffer(void); -extern void finish_cdaudio_buffer(void); -extern bool cdaudio_catchup(void); extern int init_sound(void); extern void close_sound(void); extern int setup_sound(void); extern void resume_sound(void); extern void pause_sound(void); extern void reset_sound(void); -extern void sound_volume(int); -extern void stop_sound(void); +extern bool sound_paused(void); +extern void sound_setadjust(double); -#define check_sound_buffers() { if (sndbufpt >= finish_sndbuff) finish_sound_buffer (); } +extern int drivesound_init(void); +extern void drivesound_free(void); +extern void sound_mute(int); +extern void sound_volume(int); +extern void set_volume(int, int); +extern void master_sound_volume(int); + +struct sound_dp; + +struct sound_data +{ + int waiting_for_buffer; + int deactive; + int devicetype; + int obtainedfreq; + int paused; + int mute; + int channels; + int freq; + int samplesize; + int sndbufsize; + int sndbufframes; + int softvolume; + struct sound_dp* data; + int index; + bool reset; + int resetcnt; + int resetframe; + int resetframecnt; +}; + +int open_sound_device(struct sound_data* sd, int index, int exclusive, int bufsize, int freq, int channels); +void close_sound_device(struct sound_data* sd); +void pause_sound_device(struct sound_data* sd); +void resume_sound_device(struct sound_data* sd); +void set_volume_sound_device(struct sound_data* sd, int volume, int mute); + +static uae_u16* paula_sndbufpt_prev, * paula_sndbufpt_start; + +STATIC_INLINE void set_sound_buffers(void) +{ +#if SOUNDSTUFF > 1 + paula_sndbufpt_prev = paula_sndbufpt_start; + paula_sndbufpt_start = paula_sndbufpt; +#endif +} + +STATIC_INLINE void check_sound_buffers() +{ +#if SOUNDSTUFF > 1 + int len; +#endif + + if (currprefs.sound_stereo == SND_4CH_CLONEDSTEREO) { + ((uae_u16*)paula_sndbufpt)[0] = ((uae_u16*)paula_sndbufpt)[-2]; + ((uae_u16*)paula_sndbufpt)[1] = ((uae_u16*)paula_sndbufpt)[-1]; + paula_sndbufpt = (uae_u16*)(((uae_u8*)paula_sndbufpt) + 2 * 2); + } + else if (currprefs.sound_stereo == SND_6CH_CLONEDSTEREO) { + uae_s16* p = ((uae_s16*)paula_sndbufpt); + uae_s32 sum; + p[2] = p[-2]; + p[3] = p[-1]; + sum = (uae_s32)(p[-2]) + (uae_s32)(p[-1]) + (uae_s32)(p[2]) + (uae_s32)(p[3]); + p[0] = sum / 8; + p[1] = sum / 8; + paula_sndbufpt = (uae_u16*)(((uae_u8*)paula_sndbufpt) + 4 * 2); + } +#if SOUNDSTUFF > 1 + if (outputsample == 0) + return; + len = paula_sndbufpt - paula_sndbufpt_start; + if (outputsample < 0) { + int i; + uae_s16* p1 = (uae_s16*)paula_sndbufpt_prev; + uae_s16* p2 = (uae_s16*)paula_sndbufpt_start; + for (i = 0; i < len; i++) { + *p1 = (*p1 + *p2) / 2; + } + paula_sndbufpt = paula_sndbufpt_start; + } +#endif + if ((uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer >= paula_sndbufsize) { + finish_sound_buffer(); + paula_sndbufpt = paula_sndbuffer; + } +#if SOUNDSTUFF > 1 + while (doublesample-- > 0) { + memcpy(paula_sndbufpt, paula_sndbufpt_start, len * 2); + paula_sndbufpt += len; + if ((uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer >= paula_sndbufsize) { + finish_sound_buffer(); + paula_sndbufpt = paula_sndbuffer; + } + } +#endif +} STATIC_INLINE void clear_sound_buffers(void) { - memset(sndbuffer, 0, sizeof(sndbuffer)); + memset(paula_sndbuffer, 0, paula_sndbufsize); + paula_sndbufpt = paula_sndbuffer; } -#define PUT_SOUND_WORD(b) do { *sndbufpt = b; sndbufpt = sndbufpt + 1; } while (0) -#define PUT_SOUND_WORD_STEREO(l,r) do { *((uae_u32 *)sndbufpt) = (r << 16) | (l & 0xffff); sndbufpt = sndbufpt + 2; } while (0) - -#define DEFAULT_SOUND_BITS 16 -#define DEFAULT_SOUND_FREQ 44100 -#define HAVE_STEREO_SUPPORT +#define PUT_SOUND_WORD(b) do { *(uae_u16 *)paula_sndbufpt = b; paula_sndbufpt = (uae_u16 *)(((uae_u8 *)paula_sndbufpt) + 2); } while (0) +#define PUT_SOUND_WORD_MONO(b) PUT_SOUND_WORD(b) +#define SOUND16_BASE_VAL 0 +#define SOUND8_BASE_VAL 128 #define DEFAULT_SOUND_MAXB 16384 #define DEFAULT_SOUND_MINB 16384 @@ -56,22 +149,4 @@ STATIC_INLINE void clear_sound_buffers(void) #define FILTER_SOUND_ON 2 #define FILTER_SOUND_TYPE_A500 0 -#define FILTER_SOUND_TYPE_A1200 1 - - -#define CDAUDIO_BUFFERS 16 -#define CDAUDIO_BUFFER_LEN 1024 -extern uae_u16 cdaudio_buffer[CDAUDIO_BUFFERS][(CDAUDIO_BUFFER_LEN + 32) * DEFAULT_SOUND_CHANNELS]; -extern uae_u16* cdbufpt; -extern uae_u16* render_cdbuff; -extern uae_u16* finish_cdbuff; -extern bool cdaudio_active; - -#define check_cdaudio_buffers() { if (cdbufpt >= finish_cdbuff) finish_cdaudio_buffer (); } - -STATIC_INLINE void clear_cdaudio_buffers(void) -{ - memset(cdaudio_buffer, 0, sizeof(cdaudio_buffer)); -} - -#define PUT_CDAUDIO_WORD_STEREO(l,r) do { *((uae_u32 *)cdbufpt) = (r << 16) | (l & 0xffff); cdbufpt = cdbufpt + 2; } while (0) +#define FILTER_SOUND_TYPE_A1200 1 \ No newline at end of file