Add Mindscape Music Board emulation based on Ayumi. (#295)

This commit is contained in:
Benedikt Freisen
2025-06-19 21:08:39 +02:00
committed by GitHub
parent 2d75892db3
commit f6360f856a
8 changed files with 710 additions and 2 deletions

View File

@@ -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

26
NOTICE
View File

@@ -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.
copyrights.

View File

@@ -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

View File

@@ -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_ */

338
src/sound/ayumi/ayumi.c Normal file
View File

@@ -0,0 +1,338 @@
/* Author: Peter Sovietov */
#include <string.h>
#include <math.h>
#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);
}

View File

@@ -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);
}

View File

@@ -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()
endif()

237
src/sound/sound_mmb.c Normal file
View File

@@ -0,0 +1,237 @@
#include <stdlib.h>
#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};