diff --git a/src/include/86box/snd_mmb.h b/src/include/86box/snd_mmb.h
new file mode 100644
index 000000000..6e5f7d3a8
--- /dev/null
+++ b/src/include/86box/snd_mmb.h
@@ -0,0 +1,42 @@
+/*
+ * 86Box A hypervisor and IBM PC system emulator that specializes in
+ * running old operating systems and software designed for IBM
+ * PC systems and compatibles from 1981 through fairly recent
+ * system designs based on the PCI bus.
+ *
+ * This file is part of the 86Box distribution.
+ *
+ * Mindscape Music Board emulation.
+ *
+ * Authors: Roy Baer,
+ * Jasmine Iwanek,
+ *
+ * Copyright 2025 Roy Baer.
+ * Copyright 2025 Jasmine Iwanek.
+ */
+#ifndef _SOUND_SND_MMB_H_
+#define _SOUND_SND_MMB_H_
+
+#define MMB_FREQ FREQ_48000
+
+/* 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.
+ */
+#define MMB_CLOCK 2386364
+
+typedef struct ay_3_891x_s {
+ uint8_t index;
+ uint8_t regs[16];
+ struct ayumi chip;
+} ay_3_891x_t;
+
+typedef struct mmb_s {
+ ay_3_891x_t first;
+ ay_3_891x_t second;
+
+ int16_t buffer[SOUNDBUFLEN * 2];
+ int pos;
+} mmb_t;
+
+#endif /* _SOUND_SND_MMB_H_ */
diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h
index a30095c66..b2e202ddb 100644
--- a/src/include/86box/sound.h
+++ b/src/include/86box/sound.h
@@ -205,6 +205,9 @@ extern const device_t ps1snd_device;
extern const device_t ssi2001_device;
extern const device_t entertainer_device;
+/* Mindscape Music Board */
+extern const device_t mmb_device;
+
/* Pro Audio Spectrum Plus, 16, and 16D */
extern const device_t pasplus_device;
extern const device_t pas16_device;
diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt
index 0a04b0ff1..3aaf49bb3 100644
--- a/src/sound/CMakeLists.txt
+++ b/src/sound/CMakeLists.txt
@@ -41,6 +41,7 @@ add_library(snd OBJECT
snd_sb.c
snd_sb_dsp.c
snd_emu8k.c
+ snd_mmb.c
snd_mpu401.c
snd_pas16.c
snd_sn76489.c
@@ -177,6 +178,9 @@ if(MUNT)
endif()
endif()
+add_subdirectory(ayumi)
+target_link_libraries(86Box ayumi)
+
add_subdirectory(ymfm)
target_link_libraries(86Box ymfm)
diff --git a/src/sound/ayumi/CMakeLists.txt b/src/sound/ayumi/CMakeLists.txt
new file mode 100644
index 000000000..f3e4b18c7
--- /dev/null
+++ b/src/sound/ayumi/CMakeLists.txt
@@ -0,0 +1,14 @@
+#
+# 86Box A hypervisor and IBM PC system emulator that specializes in
+# running old operating systems and software designed for IBM
+# PC systems and compatibles from 1981 through fairly recent
+# system designs based on the PCI bus.
+#
+# This file is part of the 86Box distribution.
+#
+# CMake build script.
+#
+
+add_library(ayumi STATIC
+ ayumi.c
+)
diff --git a/src/sound/ayumi/LICENSE b/src/sound/ayumi/LICENSE
new file mode 100644
index 000000000..25371edc6
--- /dev/null
+++ b/src/sound/ayumi/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) Peter Sovietov, http://sovietov.com
+
+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.
\ No newline at end of file
diff --git a/src/sound/ayumi/ayumi.c b/src/sound/ayumi/ayumi.c
new file mode 100644
index 000000000..861b0fd45
--- /dev/null
+++ b/src/sound/ayumi/ayumi.c
@@ -0,0 +1,338 @@
+/* Author: Peter Sovietov */
+
+#include
+#include
+#include "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/ayumi/ayumi.h b/src/sound/ayumi/ayumi.h
new file mode 100644
index 000000000..f15939514
--- /dev/null
+++ b/src/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/src/sound/snd_mmb.c b/src/sound/snd_mmb.c
new file mode 100644
index 000000000..35a72efbc
--- /dev/null
+++ b/src/sound/snd_mmb.c
@@ -0,0 +1,339 @@
+/*
+ * 86Box A hypervisor and IBM PC system emulator that specializes in
+ * running old operating systems and software designed for IBM
+ * PC systems and compatibles from 1981 through fairly recent
+ * system designs based on the PCI bus.
+ *
+ * This file is part of the 86Box distribution.
+ *
+ * Mindscape Music Board emulation.
+ *
+ * Authors: Roy Baer,
+ * Jasmine Iwanek,
+ *
+ * Copyright 2025 Roy Baer.
+ * Copyright 2025 Jasmine Iwanek.
+ */
+#include
+#include
+#include
+#include
+#include
+#define HAVE_STDARG_H
+#include <86box/86box.h>
+#include <86box/device.h>
+#include <86box/io.h>
+#include <86box/sound.h>
+//#i nclude "cpu.h"
+#include "ayumi/ayumi.h"
+#include <86box/snd_mmb.h>
+#include <86box/plat_unused.h>
+
+#ifdef ENABLE_MMB_LOG
+int mmb_do_log = ENABLE_MMB_LOG;
+
+static void
+mmb_log(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (mmb_do_log) {
+ va_start(ap, fmt);
+ pclog_ex(fmt, ap);
+ va_end(ap);
+ }
+}
+#else
+# define mmb_log(fmt, ...)
+#endif
+
+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 *priv)
+{
+ mmb_t *mmb = (mmb_t *) priv;
+
+ mmb_update(mmb);
+
+ for (int c = 0; c < len * 2; c++)
+ buffer[c] += mmb->buffer[c];
+
+ mmb->pos = 0;
+}
+
+void
+mmb_write(uint16_t addr, uint8_t val, void *priv)
+{
+ mmb_t *mmb = (mmb_t *) priv;
+
+ mmb_update(mmb);
+
+ mmb_log("mmb_write(%04X): activity now: %02X\n", addr, val);
+
+ switch (addr & 3) {
+ case 0:
+ mmb->first.index = val;
+ break;
+ case 2:
+ mmb->second.index = val;
+ 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] = val;
+ ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]);
+ break;
+ case 1:
+ ay->regs[1] = val & 0xf;
+ ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]);
+ break;
+ case 2:
+ ay->regs[2] = val;
+ ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]);
+ break;
+ case 3:
+ ay->regs[3] = val & 0xf;
+ ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]);
+ break;
+ case 4:
+ ay->regs[4] = val;
+ ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]);
+ break;
+ case 5:
+ ay->regs[5] = val & 0xf;
+ ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]);
+ break;
+ case 6:
+ ay->regs[6] = val & 0x1f;
+ ayumi_set_noise(&ay->chip, ay->regs[6]);
+ break;
+ case 7:
+ ay->regs[7] = val;
+ ayumi_set_mixer(&ay->chip, 0, val & 1, (val >> 3) & 1, (ay->regs[8] >> 4) & 1);
+ ayumi_set_mixer(&ay->chip, 1, (val >> 1) & 1, (val >> 4) & 1, (ay->regs[9] >> 4) & 1);
+ ayumi_set_mixer(&ay->chip, 2, (val >> 2) & 1, (val >> 5) & 1, (ay->regs[10] >> 4) & 1);
+ break;
+ case 8:
+ ay->regs[8] = val;
+ ayumi_set_volume(&ay->chip, 0, val & 0xf);
+ ayumi_set_mixer(&ay->chip, 0, ay->regs[7] & 1, (ay->regs[7] >> 3) & 1, (val >> 4) & 1);
+ break;
+ case 9:
+ ay->regs[9] = val;
+ ayumi_set_volume(&ay->chip, 1, val & 0xf);
+ ayumi_set_mixer(&ay->chip, 1, (ay->regs[7] >> 1) & 1, (ay->regs[7] >> 4) & 1, (val >> 4) & 1);
+ break;
+ case 10:
+ ay->regs[10] = val;
+ ayumi_set_volume(&ay->chip, 2, val & 0xf);
+ ayumi_set_mixer(&ay->chip, 2, (ay->regs[7] >> 2) & 1, (ay->regs[7] >> 5) & 1, (val >> 4) & 1);
+ break;
+ case 11:
+ ay->regs[11] = val;
+ ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]);
+ break;
+ case 12:
+ ay->regs[12] = val;
+ ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]);
+ break;
+ case 13:
+ ay->regs[13] = val;
+ ayumi_set_envelope_shape(&ay->chip, val & 0xf);
+ break;
+ case 14:
+ ay->regs[14] = val;
+ break;
+ case 15:
+ ay->regs[15] = val;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+uint8_t
+mmb_read(uint16_t addr, void *priv)
+{
+ mmb_t *mmb = (mmb_t *) priv;
+ ay_3_891x_t *ay = ((addr & 2) == 0) ? &mmb->first : &mmb->second;
+ uint8_t ret = 0;
+
+ 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:
+ ret = ay->regs[ay->index];
+ break;
+ case 14:
+ if (ay->regs[7] & 0x40)
+ ret = ay->regs[14];
+ break;
+ case 15:
+ if (ay->regs[7] & 0x80)
+ ret = ay->regs[15];
+ break;
+
+ default:
+ break;
+ }
+
+ mmb_log("mmb_read(%04X): activity now: %02X\n", addr, ret);
+
+ return ret;
+}
+
+void *
+mmb_init(UNUSED(const device_t *info))
+{
+ mmb_t *mmb = calloc(1, sizeof(mmb_t));
+# if 0
+ uint16_t addr = (device_get_config_int("addr96") << 6) | (device_get_config_int("addr52") << 2);
+#else
+ uint16_t addr = 0x300;
+
+#endif
+ sound_add_handler(mmb_get_buffer, mmb);
+
+ ayumi_configure(&mmb->first.chip, 0, MMB_CLOCK, MMB_FREQ);
+ ayumi_configure(&mmb->second.chip, 0, MMB_CLOCK, MMB_FREQ);
+
+ for (uint8_t 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(addr, 0x0004,
+ mmb_read, NULL, NULL,
+ mmb_write, NULL, NULL,
+ mmb);
+
+ return mmb;
+}
+
+void
+mmb_close(void *priv)
+{
+ mmb_t *mmb = (mmb_t *) priv;
+
+ free(mmb);
+}
+
+// clang-format off
+#if 0
+static device_config_t mmb_config[] = {
+ {
+ .name = "addr96",
+ .description = "Base address A9...A6",
+ .type = CONFIG_SELECTION,
+ .default_string = NULL,
+ .default_int = 12,
+ .file_filter = NULL,
+ .spinner = { 0 },
+ .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 = "" }
+ },
+ .bios = { { 0 } }
+ },
+ {
+ .name = "addr52",
+ .description = "Base address A5...A2",
+ .type = CONFIG_SELECTION,
+ .default_string = NULL,
+ .default_int = 0,
+ .file_filter = NULL,
+ .spinner = { 0 },
+ .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 = "" }
+ },
+ .bios = { { 0 } }
+ },
+ { .type = CONFIG_END }
+};
+#endif
+// clang-format on
+
+const device_t mmb_device = {
+ .name = "Mindscape Music Board",
+ .internal_name = "mmb",
+ .flags = DEVICE_ISA,
+ .local = 0,
+ .init = mmb_init,
+ .close = mmb_close,
+ .reset = NULL,
+ .available = NULL,
+ .speed_changed = NULL,
+ .force_redraw = NULL,
+#if 0
+ .config = mmb_config
+#else
+ .config = NULL
+#endif
+};
diff --git a/src/sound/sound.c b/src/sound/sound.c
index 0c8dffe12..c3ecc8632 100644
--- a/src/sound/sound.c
+++ b/src/sound/sound.c
@@ -137,6 +137,7 @@ static const SOUND_CARD sound_cards[] = {
{ &sb_vibra16xv_device },
{ &ssi2001_device },
{ &entertainer_device },
+ { &mmb_device },
{ &pasplus_device },
{ &pas16_device },
{ &pas16d_device },