From f6360f856ad7fb30f374640fb7a59e66858e37f4 Mon Sep 17 00:00:00 2001 From: Benedikt Freisen Date: Thu, 19 Jun 2025 21:08:39 +0200 Subject: [PATCH] Add Mindscape Music Board emulation based on Ayumi. (#295) --- CHANGELOG.md | 3 + NOTICE | 26 ++- includes/private/sound/ayumi/ayumi.h | 71 ++++++ includes/private/sound/sound_mmb.h | 24 ++ src/sound/ayumi/ayumi.c | 338 +++++++++++++++++++++++++++ src/sound/sound.c | 3 + src/sound/sound.cmake | 10 +- src/sound/sound_mmb.c | 237 +++++++++++++++++++ 8 files changed, 710 insertions(+), 2 deletions(-) create mode 100644 includes/private/sound/ayumi/ayumi.h create mode 100644 includes/private/sound/sound_mmb.h create mode 100644 src/sound/ayumi/ayumi.c create mode 100644 src/sound/sound_mmb.c diff --git a/CHANGELOG.md b/CHANGELOG.md index c574dc76..8e120b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ - Matrox Millennium - Quadram Quadcolor I / I+II +## Added the following Sound Cards to v18 +- Mindscape Music Board + ## Developer Changes to v18 - First release to switch from autotools/make to CMake/Ninja - Legacy autotools and mingw makefiles are removed diff --git a/NOTICE b/NOTICE index 922f31f6..ab5e6d9a 100644 --- a/NOTICE +++ b/NOTICE @@ -18,6 +18,30 @@ nick_without_<> @ users.sourceforge.net Licensed under the GNU General Public License 2.0 +Ayumi +===== +Copyright (c) Peter Sovietov, http://sovietov.com + +Licensed under the MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + reSID ===== Authors of reSID. @@ -106,4 +130,4 @@ The Australian National University. RSA Data Security, Inc. Please read the top of each source file for the details on the various -copyrights. \ No newline at end of file +copyrights. diff --git a/includes/private/sound/ayumi/ayumi.h b/includes/private/sound/ayumi/ayumi.h new file mode 100644 index 00000000..f1593951 --- /dev/null +++ b/includes/private/sound/ayumi/ayumi.h @@ -0,0 +1,71 @@ +/* Author: Peter Sovietov */ + +#ifndef AYUMI_H +#define AYUMI_H + +enum { + TONE_CHANNELS = 3, + DECIMATE_FACTOR = 8, + FIR_SIZE = 192, + DC_FILTER_SIZE = 1024 +}; + +struct tone_channel { + int tone_period; + int tone_counter; + int tone; + int t_off; + int n_off; + int e_on; + int volume; + double pan_left; + double pan_right; +}; + +struct interpolator { + double c[4]; + double y[4]; +}; + +struct dc_filter { + double sum; + double delay[DC_FILTER_SIZE]; +}; + +struct ayumi { + struct tone_channel channels[TONE_CHANNELS]; + int noise_period; + int noise_counter; + int noise; + int envelope_counter; + int envelope_period; + int envelope_shape; + int envelope_segment; + int envelope; + const double* dac_table; + double step; + double x; + struct interpolator interpolator_left; + struct interpolator interpolator_right; + double fir_left[FIR_SIZE * 2]; + double fir_right[FIR_SIZE * 2]; + int fir_index; + struct dc_filter dc_left; + struct dc_filter dc_right; + int dc_index; + double left; + double right; +}; + +int ayumi_configure(struct ayumi* ay, int is_ym, double clock_rate, int sr); +void ayumi_set_pan(struct ayumi* ay, int index, double pan, int is_eqp); +void ayumi_set_tone(struct ayumi* ay, int index, int period); +void ayumi_set_noise(struct ayumi* ay, int period); +void ayumi_set_mixer(struct ayumi* ay, int index, int t_off, int n_off, int e_on); +void ayumi_set_volume(struct ayumi* ay, int index, int volume); +void ayumi_set_envelope(struct ayumi* ay, int period); +void ayumi_set_envelope_shape(struct ayumi* ay, int shape); +void ayumi_process(struct ayumi* ay); +void ayumi_remove_dc(struct ayumi* ay); + +#endif diff --git a/includes/private/sound/sound_mmb.h b/includes/private/sound/sound_mmb.h new file mode 100644 index 00000000..f107cff1 --- /dev/null +++ b/includes/private/sound/sound_mmb.h @@ -0,0 +1,24 @@ +#ifndef _SOUND_MMB_H_ +#define _SOUND_MMB_H_ +#include "sound.h" +#include "ayumi/ayumi.h" + +extern device_t mmb_device; + +typedef struct ay_3_891x_t { + uint8_t index; + uint8_t regs[16]; + struct ayumi chip; +} ay_3_891x_t; + +typedef struct mmb_t { + ay_3_891x_t first; + ay_3_891x_t second; + + int16_t buffer[MAXSOUNDBUFLEN * 2]; + int pos; +} mmb_t; + +void mmb_init(mmb_t *mmb, uint16_t base, uint16_t size, int freq); + +#endif /* _SOUND_MMB_H_ */ diff --git a/src/sound/ayumi/ayumi.c b/src/sound/ayumi/ayumi.c new file mode 100644 index 00000000..ba2f822a --- /dev/null +++ b/src/sound/ayumi/ayumi.c @@ -0,0 +1,338 @@ +/* Author: Peter Sovietov */ + +#include +#include +#include "ayumi/ayumi.h" + +static const double AY_dac_table[] = { + 0.0, 0.0, + 0.00999465934234, 0.00999465934234, + 0.0144502937362, 0.0144502937362, + 0.0210574502174, 0.0210574502174, + 0.0307011520562, 0.0307011520562, + 0.0455481803616, 0.0455481803616, + 0.0644998855573, 0.0644998855573, + 0.107362478065, 0.107362478065, + 0.126588845655, 0.126588845655, + 0.20498970016, 0.20498970016, + 0.292210269322, 0.292210269322, + 0.372838941024, 0.372838941024, + 0.492530708782, 0.492530708782, + 0.635324635691, 0.635324635691, + 0.805584802014, 0.805584802014, + 1.0, 1.0 +}; + +static const double YM_dac_table[] = { + 0.0, 0.0, + 0.00465400167849, 0.00772106507973, + 0.0109559777218, 0.0139620050355, + 0.0169985503929, 0.0200198367285, + 0.024368657969, 0.029694056611, + 0.0350652323186, 0.0403906309606, + 0.0485389486534, 0.0583352407111, + 0.0680552376593, 0.0777752346075, + 0.0925154497597, 0.111085679408, + 0.129747463188, 0.148485542077, + 0.17666895552, 0.211551079576, + 0.246387426566, 0.281101701381, + 0.333730067903, 0.400427252613, + 0.467383840696, 0.53443198291, + 0.635172045472, 0.75800717174, + 0.879926756695, 1.0 +}; + +static void reset_segment(struct ayumi* ay); + +static int update_tone(struct ayumi* ay, int index) { + struct tone_channel* ch = &ay->channels[index]; + ch->tone_counter += 1; + if (ch->tone_counter >= ch->tone_period) { + ch->tone_counter = 0; + ch->tone ^= 1; + } + return ch->tone; +} + +static int update_noise(struct ayumi* ay) { + int bit0x3; + ay->noise_counter += 1; + if (ay->noise_counter >= (ay->noise_period << 1)) { + ay->noise_counter = 0; + bit0x3 = ((ay->noise ^ (ay->noise >> 3)) & 1); + ay->noise = (ay->noise >> 1) | (bit0x3 << 16); + } + return ay->noise & 1; +} + +static void slide_up(struct ayumi* ay) { + ay->envelope += 1; + if (ay->envelope > 31) { + ay->envelope_segment ^= 1; + reset_segment(ay); + } +} + +static void slide_down(struct ayumi* ay) { + ay->envelope -= 1; + if (ay->envelope < 0) { + ay->envelope_segment ^= 1; + reset_segment(ay); + } +} + +static void hold_top(struct ayumi* ay) { + (void) ay; +} + +static void hold_bottom(struct ayumi* ay) { + (void) ay; +} + +static void (* const Envelopes[][2])(struct ayumi*) = { + {slide_down, hold_bottom}, + {slide_down, hold_bottom}, + {slide_down, hold_bottom}, + {slide_down, hold_bottom}, + {slide_up, hold_bottom}, + {slide_up, hold_bottom}, + {slide_up, hold_bottom}, + {slide_up, hold_bottom}, + {slide_down, slide_down}, + {slide_down, hold_bottom}, + {slide_down, slide_up}, + {slide_down, hold_top}, + {slide_up, slide_up}, + {slide_up, hold_top}, + {slide_up, slide_down}, + {slide_up, hold_bottom} +}; + +static void reset_segment(struct ayumi* ay) { + if (Envelopes[ay->envelope_shape][ay->envelope_segment] == slide_down + || Envelopes[ay->envelope_shape][ay->envelope_segment] == hold_top) { + ay->envelope = 31; + return; + } + ay->envelope = 0; +} + +int update_envelope(struct ayumi* ay) { + ay->envelope_counter += 1; + if (ay->envelope_counter >= ay->envelope_period) { + ay->envelope_counter = 0; + Envelopes[ay->envelope_shape][ay->envelope_segment](ay); + } + return ay->envelope; +} + +static void update_mixer(struct ayumi* ay) { + int i; + int out; + int noise = update_noise(ay); + int envelope = update_envelope(ay); + ay->left = 0; + ay->right = 0; + for (i = 0; i < TONE_CHANNELS; i += 1) { + out = (update_tone(ay, i) | ay->channels[i].t_off) & (noise | ay->channels[i].n_off); + out *= ay->channels[i].e_on ? envelope : ay->channels[i].volume * 2 + 1; + ay->left += ay->dac_table[out] * ay->channels[i].pan_left; + ay->right += ay->dac_table[out] * ay->channels[i].pan_right; + } +} + +int ayumi_configure(struct ayumi* ay, int is_ym, double clock_rate, int sr) { + int i; + memset(ay, 0, sizeof(struct ayumi)); + ay->step = clock_rate / (sr * 8 * DECIMATE_FACTOR); + ay->dac_table = is_ym ? YM_dac_table : AY_dac_table; + ay->noise = 1; + ayumi_set_envelope(ay, 1); + for (i = 0; i < TONE_CHANNELS; i += 1) { + ayumi_set_tone(ay, i, 1); + } + return ay->step < 1; +} + +void ayumi_set_pan(struct ayumi* ay, int index, double pan, int is_eqp) { + if (is_eqp) { + ay->channels[index].pan_left = sqrt(1 - pan); + ay->channels[index].pan_right = sqrt(pan); + } else { + ay->channels[index].pan_left = 1 - pan; + ay->channels[index].pan_right = pan; + } +} + +void ayumi_set_tone(struct ayumi* ay, int index, int period) { + period &= 0xfff; + ay->channels[index].tone_period = (period == 0) | period; +} + +void ayumi_set_noise(struct ayumi* ay, int period) { + period &= 0x1f; + ay->noise_period = (period == 0) | period; +} + +void ayumi_set_mixer(struct ayumi* ay, int index, int t_off, int n_off, int e_on) { + ay->channels[index].t_off = t_off & 1; + ay->channels[index].n_off = n_off & 1; + ay->channels[index].e_on = e_on; +} + +void ayumi_set_volume(struct ayumi* ay, int index, int volume) { + ay->channels[index].volume = volume & 0xf; +} + +void ayumi_set_envelope(struct ayumi* ay, int period) { + period &= 0xffff; + ay->envelope_period = (period == 0) | period; +} + +void ayumi_set_envelope_shape(struct ayumi* ay, int shape) { + ay->envelope_shape = shape & 0xf; + ay->envelope_counter = 0; + ay->envelope_segment = 0; + reset_segment(ay); +} + +static double decimate(double* x) { + double y = -0.0000046183113992051936 * (x[1] + x[191]) + + -0.00001117761640887225 * (x[2] + x[190]) + + -0.000018610264502005432 * (x[3] + x[189]) + + -0.000025134586135631012 * (x[4] + x[188]) + + -0.000028494281690666197 * (x[5] + x[187]) + + -0.000026396828793275159 * (x[6] + x[186]) + + -0.000017094212558802156 * (x[7] + x[185]) + + 0.000023798193576966866 * (x[9] + x[183]) + + 0.000051281160242202183 * (x[10] + x[182]) + + 0.00007762197826243427 * (x[11] + x[181]) + + 0.000096759426664120416 * (x[12] + x[180]) + + 0.00010240229300393402 * (x[13] + x[179]) + + 0.000089344614218077106 * (x[14] + x[178]) + + 0.000054875700118949183 * (x[15] + x[177]) + + -0.000069839082210680165 * (x[17] + x[175]) + + -0.0001447966132360757 * (x[18] + x[174]) + + -0.00021158452917708308 * (x[19] + x[173]) + + -0.00025535069106550544 * (x[20] + x[172]) + + -0.00026228714374322104 * (x[21] + x[171]) + + -0.00022258805927027799 * (x[22] + x[170]) + + -0.00013323230495695704 * (x[23] + x[169]) + + 0.00016182578767055206 * (x[25] + x[167]) + + 0.00032846175385096581 * (x[26] + x[166]) + + 0.00047045611576184863 * (x[27] + x[165]) + + 0.00055713851457530944 * (x[28] + x[164]) + + 0.00056212565121518726 * (x[29] + x[163]) + + 0.00046901918553962478 * (x[30] + x[162]) + + 0.00027624866838952986 * (x[31] + x[161]) + + -0.00032564179486838622 * (x[33] + x[159]) + + -0.00065182310286710388 * (x[34] + x[158]) + + -0.00092127787309319298 * (x[35] + x[157]) + + -0.0010772534348943575 * (x[36] + x[156]) + + -0.0010737727700273478 * (x[37] + x[155]) + + -0.00088556645390392634 * (x[38] + x[154]) + + -0.00051581896090765534 * (x[39] + x[153]) + + 0.00059548767193795277 * (x[41] + x[151]) + + 0.0011803558710661009 * (x[42] + x[150]) + + 0.0016527320270369871 * (x[43] + x[149]) + + 0.0019152679330965555 * (x[44] + x[148]) + + 0.0018927324805381538 * (x[45] + x[147]) + + 0.0015481870327877937 * (x[46] + x[146]) + + 0.00089470695834941306 * (x[47] + x[145]) + + -0.0010178225878206125 * (x[49] + x[143]) + + -0.0020037400552054292 * (x[50] + x[142]) + + -0.0027874356824117317 * (x[51] + x[141]) + + -0.003210329988021943 * (x[52] + x[140]) + + -0.0031540624117984395 * (x[53] + x[139]) + + -0.0025657163651900345 * (x[54] + x[138]) + + -0.0014750752642111449 * (x[55] + x[137]) + + 0.0016624165446378462 * (x[57] + x[135]) + + 0.0032591192839069179 * (x[58] + x[134]) + + 0.0045165685815867747 * (x[59] + x[133]) + + 0.0051838984346123896 * (x[60] + x[132]) + + 0.0050774264697459933 * (x[61] + x[131]) + + 0.0041192521414141585 * (x[62] + x[130]) + + 0.0023628575417966491 * (x[63] + x[129]) + + -0.0026543507866759182 * (x[65] + x[127]) + + -0.0051990251084333425 * (x[66] + x[126]) + + -0.0072020238234656924 * (x[67] + x[125]) + + -0.0082672928192007358 * (x[68] + x[124]) + + -0.0081033739572956287 * (x[69] + x[123]) + + -0.006583111539570221 * (x[70] + x[122]) + + -0.0037839040415292386 * (x[71] + x[121]) + + 0.0042781252851152507 * (x[73] + x[119]) + + 0.0084176358598320178 * (x[74] + x[118]) + + 0.01172566057463055 * (x[75] + x[117]) + + 0.013550476647788672 * (x[76] + x[116]) + + 0.013388189369997496 * (x[77] + x[115]) + + 0.010979501242341259 * (x[78] + x[114]) + + 0.006381274941685413 * (x[79] + x[113]) + + -0.007421229604153888 * (x[81] + x[111]) + + -0.01486456304340213 * (x[82] + x[110]) + + -0.021143584622178104 * (x[83] + x[109]) + + -0.02504275058758609 * (x[84] + x[108]) + + -0.025473530942547201 * (x[85] + x[107]) + + -0.021627310017882196 * (x[86] + x[106]) + + -0.013104323383225543 * (x[87] + x[105]) + + 0.017065133989980476 * (x[89] + x[103]) + + 0.036978919264451952 * (x[90] + x[102]) + + 0.05823318062093958 * (x[91] + x[101]) + + 0.079072012081405949 * (x[92] + x[100]) + + 0.097675998716952317 * (x[93] + x[99]) + + 0.11236045936950932 * (x[94] + x[98]) + + 0.12176343577287731 * (x[95] + x[97]) + + 0.125 * x[96]; + memcpy(&x[FIR_SIZE - DECIMATE_FACTOR], x, DECIMATE_FACTOR * sizeof(double)); + return y; +} + +void ayumi_process(struct ayumi* ay) { + int i; + double y1; + double* c_left = ay->interpolator_left.c; + double* y_left = ay->interpolator_left.y; + double* c_right = ay->interpolator_right.c; + double* y_right = ay->interpolator_right.y; + double* fir_left = &ay->fir_left[FIR_SIZE - ay->fir_index * DECIMATE_FACTOR]; + double* fir_right = &ay->fir_right[FIR_SIZE - ay->fir_index * DECIMATE_FACTOR]; + ay->fir_index = (ay->fir_index + 1) % (FIR_SIZE / DECIMATE_FACTOR - 1); + for (i = DECIMATE_FACTOR - 1; i >= 0; i -= 1) { + ay->x += ay->step; + if (ay->x >= 1) { + ay->x -= 1; + y_left[0] = y_left[1]; + y_left[1] = y_left[2]; + y_left[2] = y_left[3]; + y_right[0] = y_right[1]; + y_right[1] = y_right[2]; + y_right[2] = y_right[3]; + update_mixer(ay); + y_left[3] = ay->left; + y_right[3] = ay->right; + y1 = y_left[2] - y_left[0]; + c_left[0] = 0.5 * y_left[1] + 0.25 * (y_left[0] + y_left[2]); + c_left[1] = 0.5 * y1; + c_left[2] = 0.25 * (y_left[3] - y_left[1] - y1); + y1 = y_right[2] - y_right[0]; + c_right[0] = 0.5 * y_right[1] + 0.25 * (y_right[0] + y_right[2]); + c_right[1] = 0.5 * y1; + c_right[2] = 0.25 * (y_right[3] - y_right[1] - y1); + } + fir_left[i] = (c_left[2] * ay->x + c_left[1]) * ay->x + c_left[0]; + fir_right[i] = (c_right[2] * ay->x + c_right[1]) * ay->x + c_right[0]; + } + ay->left = decimate(fir_left); + ay->right = decimate(fir_right); +} + +static double dc_filter(struct dc_filter* dc, int index, double x) { + dc->sum += -dc->delay[index] + x; + dc->delay[index] = x; + return x - dc->sum / DC_FILTER_SIZE; +} + +void ayumi_remove_dc(struct ayumi* ay) { + ay->left = dc_filter(&ay->dc_left, ay->dc_index, ay->left); + ay->right = dc_filter(&ay->dc_right, ay->dc_index, ay->right); + ay->dc_index = (ay->dc_index + 1) & (DC_FILTER_SIZE - 1); +} diff --git a/src/sound/sound.c b/src/sound/sound.c index ab3b5cd3..288f7cc7 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -20,6 +20,7 @@ #include "sound_sb.h" #include "sound_sb_dsp.h" #include "sound_wss.h" +#include "sound_mmb.h" #include "timer.h" #include "thread.h" @@ -50,6 +51,7 @@ SOUND_CARD sc_azt1605 = {"Aztech Sound Galaxy Nova 16 Extra (Clinton)", "azt1605 SOUND_CARD sc_pas16 = {"Pro Audio Spectrum 16", "pas16", &pas16_device}; SOUND_CARD sc_es1371 = {"Ensoniq AudioPCI (ES1371)", "es1371", &es1371_device}; SOUND_CARD sc_sbpci128 = {"Sound Blaster PCI 128", "sbpci128", &es1371_device}; +SOUND_CARD sc_mmb = {"Mindscape Music Board", "mmb", &mmb_device}; int sound_card_available(int card) { if (sound_cards[card] != NULL && sound_cards[card]->device != NULL) @@ -284,4 +286,5 @@ void sound_init_builtin() { pcem_add_sound(&sc_pas16); pcem_add_sound(&sc_es1371); pcem_add_sound(&sc_sbpci128); + pcem_add_sound(&sc_mmb); } diff --git a/src/sound/sound.cmake b/src/sound/sound.cmake index 5594656a..32ee833f 100644 --- a/src/sound/sound.cmake +++ b/src/sound/sound.cmake @@ -1,4 +1,5 @@ set(PCEM_PRIVATE_API ${PCEM_PRIVATE_API} + ${CMAKE_SOURCE_DIR}/includes/private/sound/ayumi/ayumi.h ${CMAKE_SOURCE_DIR}/includes/private/sound/resid-fp/envelope.h ${CMAKE_SOURCE_DIR}/includes/private/sound/resid-fp/extfilt.h ${CMAKE_SOURCE_DIR}/includes/private/sound/resid-fp/filter.h @@ -17,6 +18,7 @@ set(PCEM_PRIVATE_API ${PCEM_PRIVATE_API} ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_emu8k.h ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_gus.h ${CMAKE_SOURCE_DIR}/includes/private/sound/sound.h + ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_mmb.h ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_mpu401_uart.h ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_opl.h ${CMAKE_SOURCE_DIR}/includes/private/sound/sound_pas16.h @@ -43,6 +45,7 @@ set(PCEM_SRC ${PCEM_SRC} sound/sound_dbopl.cc sound/sound_emu8k.c sound/sound_gus.c + sound/sound_mmb.c sound/sound_mpu401_uart.c sound/sound_opl.c sound/sound_pas16.c @@ -59,6 +62,11 @@ set(PCEM_SRC ${PCEM_SRC} sound/soundopenal.c ) +# AYUMI +set(PCEM_SRC ${PCEM_SRC} + sound/ayumi/ayumi.c + ) + # RESID-FP set(PCEM_SRC ${PCEM_SRC} sound/resid-fp/convolve.cc @@ -89,4 +97,4 @@ else() set(PCEM_SRC ${PCEM_SRC} sound/sdl2-midi.c ) -endif() \ No newline at end of file +endif() diff --git a/src/sound/sound_mmb.c b/src/sound/sound_mmb.c new file mode 100644 index 00000000..bf8157f6 --- /dev/null +++ b/src/sound/sound_mmb.c @@ -0,0 +1,237 @@ +#include +#include "ibm.h" +#include "device.h" +#include "io.h" +#include "sound.h" +#include "sound_mmb.h" +#include "cpu.h" + +void mmb_update(mmb_t *mmb) { + for (; mmb->pos < sound_pos_global; mmb->pos++) { + ayumi_process(&mmb->first.chip); + ayumi_process(&mmb->second.chip); + + ayumi_remove_dc(&mmb->first.chip); + ayumi_remove_dc(&mmb->second.chip); + + mmb->buffer[mmb->pos << 1] = (mmb->first.chip.left + mmb->second.chip.left) * 16000; + mmb->buffer[(mmb->pos << 1) + 1] = (mmb->first.chip.right + mmb->second.chip.right) * 16000; + } +} + +void mmb_get_buffer(int32_t *buffer, int len, void *p) { + mmb_t *mmb = (mmb_t *)p; + + int c; + + mmb_update(mmb); + + for (c = 0; c < len * 2 ; c++) + buffer[c] += mmb->buffer[c]; + + mmb->pos = 0; +} + +void mmb_write(uint16_t addr, uint8_t data, void *p) { + mmb_t *mmb = (mmb_t *)p; + int freq; + + mmb_update(mmb); + + switch (addr & 3) { + case 0: + mmb->first.index = data; + break; + case 2: + mmb->second.index = data; + break; + case 1: + case 3: { + ay_3_891x_t *ay = ((addr & 2) == 0) ? &mmb->first : &mmb->second; + + switch (ay->index) { + case 0: + ay->regs[0] = data; + ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]); + break; + case 1: + ay->regs[1] = data & 0xf; + ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]); + break; + case 2: + ay->regs[2] = data; + ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]); + break; + case 3: + ay->regs[3] = data & 0xf; + ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]); + break; + case 4: + ay->regs[4] = data; + ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]); + break; + case 5: + ay->regs[5] = data & 0xf; + ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]); + break; + case 6: + ay->regs[6] = data & 0x1f; + ayumi_set_noise(&ay->chip, ay->regs[6]); + break; + case 7: + ay->regs[7] = data; + ayumi_set_mixer(&ay->chip, 0, data & 1, (data >> 3) & 1, (ay->regs[8] >> 4) & 1); + ayumi_set_mixer(&ay->chip, 1, (data >> 1) & 1, (data >> 4) & 1, (ay->regs[9] >> 4) & 1); + ayumi_set_mixer(&ay->chip, 2, (data >> 2) & 1, (data >> 5) & 1, (ay->regs[10] >> 4) & 1); + break; + case 8: + ay->regs[8] = data; + ayumi_set_volume(&ay->chip, 0, data & 0xf); + ayumi_set_mixer(&ay->chip, 0, ay->regs[7] & 1, (ay->regs[7] >> 3) & 1, (data >> 4) & 1); + break; + case 9: + ay->regs[9] = data; + ayumi_set_volume(&ay->chip, 1, data & 0xf); + ayumi_set_mixer(&ay->chip, 1, (ay->regs[7] >> 1) & 1, (ay->regs[7] >> 4) & 1, (data >> 4) & 1); + break; + case 10: + ay->regs[10] = data; + ayumi_set_volume(&ay->chip, 2, data & 0xf); + ayumi_set_mixer(&ay->chip, 2, (ay->regs[7] >> 2) & 1, (ay->regs[7] >> 5) & 1, (data >> 4) & 1); + break; + case 11: + ay->regs[11] = data; + ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]); + break; + case 12: + ay->regs[12] = data; + ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]); + break; + case 13: + ay->regs[13] = data; + ayumi_set_envelope_shape(&ay->chip, data & 0xf); + break; + case 14: + ay->regs[14] = data; + break; + case 15: + ay->regs[15] = data; + break; + } + break; + } + } +} + +uint8_t mmb_read(uint16_t addr, void *p) { + mmb_t *mmb = (mmb_t *)p; + ay_3_891x_t *ay = ((addr & 2) == 0) ? &mmb->first : &mmb->second; + + switch (ay->index) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + return ay->regs[ay->index]; + case 14: + if (ay->regs[7] & 0x40) + return ay->regs[14]; + else + return 0; + case 15: + if (ay->regs[7] & 0x80) + return ay->regs[15]; + else + return 0; + } +} + +void mmb_init(mmb_t *mmb, uint16_t base, uint16_t size, int freq) { + sound_add_handler(mmb_get_buffer, mmb); + + ayumi_configure(&mmb->first.chip, 0, freq, 48000); + ayumi_configure(&mmb->second.chip, 0, freq, 48000); + + for (int i = 0; i < 3; i++) { + ayumi_set_pan(&mmb->first.chip, i, 0.5, 1); + ayumi_set_pan(&mmb->second.chip, i, 0.5, 1); + } + + io_sethandler(base, size, mmb_read, NULL, NULL, mmb_write, NULL, NULL, mmb); +} + +void *mmb_device_init() { + mmb_t *mmb = malloc(sizeof(mmb_t)); + uint16_t base_addr = (device_get_config_int("addr96") << 6) | (device_get_config_int("addr52") << 2); + memset(mmb, 0, sizeof(mmb_t)); + + /* NOTE: + * The constant clock rate is a deviation from the real hardware which has + * the design flaw that the clock rate is always half the ISA bus clock. */ + mmb_init(mmb, base_addr, 0x0004, 2386364); + + return mmb; +} + +void mmb_device_close(void *p) { + mmb_t *mmb = (mmb_t *)p; + + free(mmb); +} + +static device_config_t mmb_config[] = { + {.name = "addr96", + .description = "Base address A9...A6", + .type = CONFIG_SELECTION, + .selection = {{.description = "0000", .value = 0}, + {.description = "0001", .value = 1}, + {.description = "0010", .value = 2}, + {.description = "0011", .value = 3}, + {.description = "0100", .value = 4}, + {.description = "0101", .value = 5}, + {.description = "0110", .value = 6}, + {.description = "0111", .value = 7}, + {.description = "1000", .value = 8}, + {.description = "1001", .value = 9}, + {.description = "1010", .value = 10}, + {.description = "1011", .value = 11}, + {.description = "1100", .value = 12}, + {.description = "1101", .value = 13}, + {.description = "1110", .value = 14}, + {.description = "1111", .value = 15}, + {.description = ""}}, + .default_int = 12}, + {.name = "addr52", + .description = "Base address A5...A2", + .type = CONFIG_SELECTION, + .selection = {{.description = "0000", .value = 0}, + {.description = "0001", .value = 1}, + {.description = "0010", .value = 2}, + {.description = "0011", .value = 3}, + {.description = "0100", .value = 4}, + {.description = "0101", .value = 5}, + {.description = "0110", .value = 6}, + {.description = "0111", .value = 7}, + {.description = "1000", .value = 8}, + {.description = "1001", .value = 9}, + {.description = "1010", .value = 10}, + {.description = "1011", .value = 11}, + {.description = "1100", .value = 12}, + {.description = "1101", .value = 13}, + {.description = "1110", .value = 14}, + {.description = "1111", .value = 15}, + {.description = ""}}, + .default_int = 0}, + {.type = -1}}; + +device_t mmb_device = {"Mindscape Music Board", 0, mmb_device_init, mmb_device_close, NULL, NULL, NULL, NULL, mmb_config};