From c15c3e51f33abd53c9fb81c50b1a4bd0d71d65d8 Mon Sep 17 00:00:00 2001 From: "R. Belmont" Date: Fri, 3 May 2013 02:52:41 +0000 Subject: [PATCH] (MESS) esq1: preliminary emulation of analog stages (VCFs and VCAs) [O. Galibert] New systems added ----------------- Ensoniq ESQ-M [Anonymous] --- src/mess/drivers/esq1.c | 334 ++++++++++++++++++++++++++++++++++++---- src/mess/mess.lst | 1 + 2 files changed, 309 insertions(+), 26 deletions(-) diff --git a/src/mess/drivers/esq1.c b/src/mess/drivers/esq1.c index 1ae0905f1f6..74a3dbb6a4c 100644 --- a/src/mess/drivers/esq1.c +++ b/src/mess/drivers/esq1.c @@ -3,8 +3,9 @@ drivers/esq1.c Ensoniq ESQ-1 Digital Wave Synthesizer + Ensoniq ESQ-M (rack-mount ESQ-1) Ensoniq SQ-80 Cross Wave Synthesizer - Driver by R. Belmont + Driver by R. Belmont and O. Galibert Map for ESQ-1 and ESQ-m: 0000-1fff: OS RAM @@ -109,6 +110,68 @@ NOTES: 35 = MODES 36 = SPLIT / LAYER + + Analog filters (CEM3379): + + The analog part is relatively simple. The digital part outputs 8 + voices, which are filtered, amplified, panned then summed + together. + + The filtering stage is a 4-level lowpass filter with a loopback: + + + +-[+]-<-[*-1]--------------------------+ + | | | + ^ [*r] | + | | | + | v ^ + input ---+-[+]--[LPF]---[LPF]---[LPF]---[LPF]---+--- output + + All 4 LPFs are identical, with a transconductance G: + + output = 1/(1+s/G)^4 * ( (1+r)*input - r*output) + + or + + output = input * (1+r)/((1+s/G)^4+r) + + to which the usual z-transform can be applied (see votrax.c) + + G is voltage controlled through the Vfreq input, with the formula (Vfreq in mV): + + G = 6060*exp(Vfreq/28.5) + + That gives a cutoff frequency (f=G/(2pi)) of 5Hz at 5mV, 964Hz at + 28.5mV and 22686Hz at 90mV. The resistor ladder between the DAC + and the input seem to map 0..255 into a range of -150.4mV to + +83.6mV. + + The resonance is controlled through the Vq input pin, and is not + well defined. Reading between the lines the control seems linear + and tops when then circuit is self-oscillation, at r=4. + + The amplification is exponential for a control voltage between 0 + to 0.2V from -100dB to -20dB, and then linear up to 5V at 0dB. Or + in other words: + amp(Vca) = Vca < 0.2 ? 10**(-5+20*Vca) : Vca*0.1875 + 0.0625 + + + Finally the panning is not very described. What is clear is that + the control voltage at 2.5V gives a gain of -6dB, the max + attenuation at 0/5V is -100dB. The doc also says the gain is + linear between 1V and 3.5V, which makes no sense since it's not + symmetrical, and logarithmic afterwards, probably meaning + exponential, otherwise the change between 0 and 1V would be + minimal. So we're going to do some assumptions: + - 0-1V exponential from -100Db to -30dB + - 1V-2.5V linear from -30dB to -6dB + - 2.5V-5V is 1-amp at 2.5V-v + + Note that this may be incorrect, maybe to sum of squares should be + constant, the half-point should be at -3dB and the linearity in dB + space. + + ***************************************************************************/ #include "emu.h" @@ -124,8 +187,195 @@ NOTES: #define WD1772_TAG "wd1772" -// QWERTYU = a few keys -// top row 1-0 = the soft keys above and below the display (patch select) +class esq1_filters : public device_t, + public device_sound_interface +{ +public: + // construction/destruction + esq1_filters(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + void set_vca(int channel, UINT8 value); + void set_vpan(int channel, UINT8 value); + void set_vq(int channel, UINT8 value); + void set_vfc(int channel, UINT8 value); + +protected: + // device-level overrides + virtual void device_start(); + + // device_sound_interface overrides + virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples); + +private: + struct filter { + UINT8 vca, vpan, vq, vfc; + double amp, lamp, ramp; + double a[5], b[5]; + double x[4], y[4]; + }; + + filter filters[8]; + + sound_stream *stream; + + void recalc_filter(filter &f); +}; + +static const device_type ESQ1_FILTERS = &device_creator; + +esq1_filters::esq1_filters(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, ESQ1_FILTERS, "ESQ1 Filters stage", tag, owner, clock, "esq1-filters", __FILE__), + device_sound_interface(mconfig, *this) +{ +} + +void esq1_filters::set_vca(int channel, UINT8 value) +{ + if(filters[channel].vca != value) { + stream->update(); + filters[channel].vca = value; + recalc_filter(filters[channel]); + } +} + +void esq1_filters::set_vpan(int channel, UINT8 value) +{ + if(filters[channel].vpan != value) { + stream->update(); + filters[channel].vpan = value; + recalc_filter(filters[channel]); + } +} + +void esq1_filters::set_vq(int channel, UINT8 value) +{ + if(filters[channel].vq != value) { + stream->update(); + filters[channel].vq = value; + recalc_filter(filters[channel]); + } +} + +void esq1_filters::set_vfc(int channel, UINT8 value) +{ + if(filters[channel].vfc != value) { + stream->update(); + filters[channel].vfc = value; + recalc_filter(filters[channel]); + } +} + +void esq1_filters::recalc_filter(filter &f) +{ + // Filtering stage + // First let's establish the control values + // Some tuning may be required + + double vfc = -150.4 + (83.6+150.4)*f.vfc/255; + double r = 4.0*f.vq/255; + + + double g = 6060*exp(vfc/28.5); + double zc = g/tan(g/2/44100); + +/* if(f.vfc) { + double ff = g/(2*M_PI); + double fzc = 2*M_PI*ff/tan(M_PI*ff/44100); + fprintf(stderr, "%02x f=%f zc=%f zc1=%f\n", f.vfc, g/(2*M_PI), zc, fzc); + }*/ + + double gzc = zc/g; + double gzc2 = gzc*gzc; + double gzc3 = gzc2*gzc; + double gzc4 = gzc3*gzc; + double r1 = 1+r; + + f.a[0] = r1; + f.a[1] = 4*r1; + f.a[2] = 6*r1; + f.a[3] = 4*r1; + f.a[4] = r1; + + f.b[0] = r1 + 4*gzc + 6*gzc2 + 4*gzc3 + gzc4; + f.b[1] = 4*(r1 + 2*gzc - 2*gzc3 - gzc4); + f.b[2] = 6*(r1 - 2*gzc2 + gzc4); + f.b[3] = 4*(r1 - 2*gzc + 2*gzc3 - gzc4); + f.b[4] = r1 - 4*gzc + 6*gzc2 - 4*gzc3 + gzc4; + +/* if(f.vfc != 0) + for(int i=0; i<5; i++) + printf("a%d=%f\nb%d=%f\n", + i, f.a[i], i, f.b[i]);*/ + + // Amplification stage + double vca = f.vca*(5.0/255.0); + f.amp = vca < 0.2 ? pow(10, -5+20*vca) : vca*0.1875 + 0.0625; + + // Panning stage + // Very approximative at best + // Left/right unverified + double vpan = f.vpan*(5.0/255.0); + double vref = vpan > 2.5 ? 2.5 - vpan : vpan; + double pan_amp = vref < 1 ? pow(10, -5+3.5*vref) : vref*0.312 - 0.280; + if(vref < 2.5) { + f.lamp = pan_amp; + f.ramp = 1-pan_amp; + } else { + f.lamp = 1-pan_amp; + f.ramp = pan_amp; + } +} + +void esq1_filters::device_start() +{ + stream = stream_alloc(8, 2, 44100); + memset(filters, 0, sizeof(filters)); + for(int i=0; i<8; i++) + recalc_filter(filters[i]); +} + +void esq1_filters::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +{ +/* if(0) { + for(int i=0; i<8; i++) + fprintf(stderr, " [%02x %02x %02x %02x]", + filters[i].vca, + filters[i].vpan, + filters[i].vq, + filters[i].vfc); + fprintf(stderr, "\n"); + }*/ + + for(int i=0; i maxl) { + maxl = l; +// fprintf(stderr, "%f\n", maxl); + } + +// l *= 6553; +// r *= 6553; + l *= 2; + r *= 2; + outputs[0][i] = l < -32768 ? -32768 : l > 32767 ? 32767 : int(l); + outputs[1][i] = r < -32768 ? -32768 : r > 32767 ? 32767 : int(r); + } +} class esq1_state : public driver_device { @@ -134,6 +384,7 @@ public: : driver_device(mconfig, type, tag), m_maincpu(*this, "maincpu"), m_duart(*this, "duart"), + m_filters(*this, "filters"), m_fdc(*this, WD1772_TAG), m_panel(*this, "panel"), m_mdout(*this, "mdout") @@ -141,6 +392,7 @@ public: required_device m_maincpu; required_device m_duart; + required_device m_filters; optional_device m_fdc; optional_device m_panel; optional_device m_mdout; @@ -150,6 +402,7 @@ public: DECLARE_READ8_MEMBER(seqdosram_r); DECLARE_WRITE8_MEMBER(seqdosram_w); DECLARE_WRITE8_MEMBER(mapper_w); + DECLARE_WRITE8_MEMBER(analog_w); DECLARE_WRITE_LINE_MEMBER(duart_irq_handler); DECLARE_WRITE_LINE_MEMBER(duart_tx_a); @@ -203,6 +456,18 @@ WRITE8_MEMBER(esq1_state::mapper_w) // printf("mapper_state = %d\n", data ^ 1); } +WRITE8_MEMBER(esq1_state::analog_w) +{ + if(!(offset & 8)) + m_filters->set_vfc(offset & 7, data); + if(!(offset & 16)) + m_filters->set_vq(offset & 7, data); + if(!(offset & 32)) + m_filters->set_vpan(offset & 7, data); + if(!(offset & 64)) + m_filters->set_vca(offset & 7, data); +} + READ8_MEMBER(esq1_state::seqdosram_r) { if (m_mapper_state) @@ -232,8 +497,7 @@ static ADDRESS_MAP_START( esq1_map, AS_PROGRAM, 8, esq1_state ) AM_RANGE(0x4000, 0x5fff) AM_RAM // SEQRAM AM_RANGE(0x6000, 0x63ff) AM_DEVREADWRITE("es5503", es5503_device, read, write) AM_RANGE(0x6400, 0x640f) AM_DEVREADWRITE("duart", duartn68681_device, read, write) - AM_RANGE(0x6800, 0x68ff) AM_NOP - + AM_RANGE(0x6800, 0x68ff) AM_WRITE(analog_w) AM_RANGE(0x7000, 0x7fff) AM_ROMBANK("osbank") AM_RANGE(0x8000, 0xffff) AM_ROM AM_REGION("osrom", 0x8000) // OS "high" ROM is always mapped here ADDRESS_MAP_END @@ -244,6 +508,7 @@ static ADDRESS_MAP_START( sq80_map, AS_PROGRAM, 8, esq1_state ) // AM_RANGE(0x4000, 0x5fff) AM_READWRITE(seqdosram_r, seqdosram_w) AM_RANGE(0x6000, 0x63ff) AM_DEVREADWRITE("es5503", es5503_device, read, write) AM_RANGE(0x6400, 0x640f) AM_DEVREADWRITE("duart", duartn68681_device, read, write) + AM_RANGE(0x6800, 0x68ff) AM_WRITE(analog_w) AM_RANGE(0x6c00, 0x6dff) AM_WRITE(mapper_w) AM_RANGE(0x6e00, 0x6fff) AM_READWRITE(wd1772_r, wd1772_w) AM_RANGE(0x7000, 0x7fff) AM_ROMBANK("osbank") @@ -267,7 +532,7 @@ ADDRESS_MAP_END WRITE_LINE_MEMBER(esq1_state::duart_irq_handler) { - m_maincpu->set_input_line(0, state); + m_maincpu->set_input_line(M6809_IRQ_LINE, state); }; READ8_MEMBER(esq1_state::duart_input) @@ -290,31 +555,31 @@ WRITE8_MEMBER(esq1_state::duart_output) // MIDI send WRITE_LINE_MEMBER(esq1_state::duart_tx_a) { - m_mdout->tx(state); + m_mdout->tx(state); } WRITE_LINE_MEMBER(esq1_state::duart_tx_b) { - m_panel->rx_w(state); + m_panel->rx_w(state); } void esq1_state::send_through_panel(UINT8 data) { - m_panel->xmit_char(data); + m_panel->xmit_char(data); } INPUT_CHANGED_MEMBER(esq1_state::key_stroke) { - if (oldval == 0 && newval == 1) - { - send_through_panel((UINT8)(FPTR)param); - send_through_panel((UINT8)(FPTR)0x00); - } - else if (oldval == 1 && newval == 0) - { - send_through_panel((UINT8)(FPTR)param&0x7f); - send_through_panel((UINT8)(FPTR)0x00); - } + if (oldval == 0 && newval == 1) + { + send_through_panel((UINT8)(FPTR)param); + send_through_panel((UINT8)(FPTR)0x00); + } + else if (oldval == 1 && newval == 0) + { + send_through_panel((UINT8)(FPTR)param&0x7f); + send_through_panel((UINT8)(FPTR)0x00); + } } static SLOT_INTERFACE_START(midiin_slot) @@ -362,15 +627,20 @@ static MACHINE_CONFIG_START( esq1, esq1_state ) MCFG_SERIAL_PORT_ADD("mdout", midiout_intf, midiout_slot, "midiout", NULL) MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") - MCFG_ES5503_ADD("es5503", 7000000, 8, esq1_doc_irq, esq1_adc_read) + + MCFG_SOUND_ADD("filters", ESQ1_FILTERS, 0) MCFG_SOUND_ROUTE(0, "lspeaker", 1.0) MCFG_SOUND_ROUTE(1, "rspeaker", 1.0) - MCFG_SOUND_ROUTE(2, "lspeaker", 1.0) - MCFG_SOUND_ROUTE(3, "rspeaker", 1.0) - MCFG_SOUND_ROUTE(4, "lspeaker", 1.0) - MCFG_SOUND_ROUTE(5, "rspeaker", 1.0) - MCFG_SOUND_ROUTE(6, "lspeaker", 1.0) - MCFG_SOUND_ROUTE(7, "rspeaker", 1.0) + + MCFG_ES5503_ADD("es5503", 7000000, 8, esq1_doc_irq, esq1_adc_read) + MCFG_SOUND_ROUTE_EX(0, "filters", 1.0, 0) + MCFG_SOUND_ROUTE_EX(1, "filters", 1.0, 1) + MCFG_SOUND_ROUTE_EX(2, "filters", 1.0, 2) + MCFG_SOUND_ROUTE_EX(3, "filters", 1.0, 3) + MCFG_SOUND_ROUTE_EX(4, "filters", 1.0, 4) + MCFG_SOUND_ROUTE_EX(5, "filters", 1.0, 5) + MCFG_SOUND_ROUTE_EX(6, "filters", 1.0, 6) + MCFG_SOUND_ROUTE_EX(7, "filters", 1.0, 7) MACHINE_CONFIG_END static MACHINE_CONFIG_DERIVED(sq80, esq1) @@ -435,5 +705,17 @@ ROM_START( sq80 ) ROM_LOAD( "sq80_kpc_150.bin", 0x000000, 0x008000, CRC(8170b728) SHA1(3ad68bb03948e51b20d2e54309baa5c02a468f7c) ) ROM_END +ROM_START( esqm ) + ROM_REGION(0x10000, "osrom", 0) + ROM_LOAD( "1355500157_d640_esq-m_oshi.u14", 0x8000, 0x008000, CRC(ea6a7bae) SHA1(2830f8c52dc443b4ca469dc190b33e2ff15b78e1) ) + + ROM_REGION(0x20000, "es5503", 0) + ROM_LOAD( "esq1wavlo.bin", 0x0000, 0x8000, CRC(4d04ac87) SHA1(867b51229b0a82c886bf3b216aa8893748236d8b) ) + ROM_LOAD( "esq1wavhi.bin", 0x8000, 0x8000, CRC(94c554a3) SHA1(ed0318e5253637585559e8cf24c06d6115bd18f6) ) +ROM_END + + CONS( 1986, esq1, 0 , 0, esq1, esq1, driver_device, 0, "Ensoniq", "ESQ-1", GAME_NOT_WORKING ) +CONS( 1986, esqm, esq1, 0, esq1, esq1, driver_device, 0, "Ensoniq", "ESQ-M", GAME_NOT_WORKING ) CONS( 1988, sq80, 0, 0, sq80, esq1, driver_device, 0, "Ensoniq", "SQ-80", GAME_NOT_WORKING ) + diff --git a/src/mess/mess.lst b/src/mess/mess.lst index 7b90e2db00d..dbfa9f4a84c 100644 --- a/src/mess/mess.lst +++ b/src/mess/mess.lst @@ -164,6 +164,7 @@ d6800 // Dream 6800 // Ensoniq enmirage // 1985 Mirage Digital Multi-Sampler esq1 // 1986 ESQ-1 Digital Wave Synthesizer +esqm // 1986 ESQ-M rack-mount ESQ-1 sq80 // 1988 SQ-80 Digital Wave Synthesizer eps // 1988 EPS vfx // 1989 VFX