diff --git a/doc/4-instrument/README.md b/doc/4-instrument/README.md index 058fc7f7e..8b106db91 100644 --- a/doc/4-instrument/README.md +++ b/doc/4-instrument/README.md @@ -75,6 +75,7 @@ the following instrument types are available: - [C140](c140.md) - for use with C140 sample chip. - [C219](c219.md) - for use with C219 sample chip. - [PowerNoise](powernoise.md) - for use with PowerNoise chip. +- [Dave](dave.md) - for use with Dave chip. ## macros diff --git a/doc/4-instrument/dave.md b/doc/4-instrument/dave.md new file mode 100644 index 000000000..671da7829 --- /dev/null +++ b/doc/4-instrument/dave.md @@ -0,0 +1,28 @@ +# Dave instrument editor + +the Dave instrument editor consists of these macros: + +- **Volume**: volume sequence. +- **Arpeggio**: pitch sequence. +- **Noise Freq**: set noise frequency source. + - 0: fixed frequency (~62.5KHz) + - 1: channel 1 + - 2: channel 2 + - 3: channel 3 +- **Waveform**: select waveform or noise length. + - 0: square + - 1: bass + - 2: buzz + - 3: reed + - 4: noise + - for noise channel the range is 0 to 3. +- **Panning (left)**: output level for left channel. +- **Panning (right)**: output level for right channel. +- **Pitch**: fine pitch. +- **Phase Reset**: trigger restart of waveform. + - does not apply for noise channel. +- **Control**: set channel parameters. + - **low pass (noise)**: enable low-pass filter. only in noise channel. + - **swap counters (noise)**: enable swap counters mode. only in noise channel. + - **ring mod**: enable ring mod with channel+2. + - **high pass**: enable high-pass filter with the next channel. diff --git a/doc/7-systems/README.md b/doc/7-systems/README.md index 8f5616faf..b1110e29f 100644 --- a/doc/7-systems/README.md +++ b/doc/7-systems/README.md @@ -68,6 +68,7 @@ this is the full list of chips that Furnace supports. - [AY-3-8910/8914/YM2149(F)/Sunsoft 5B](ay8910.md) - [Microship AY8930](ay8930.md) - [MOS 6581/8580 (SID)](c64.md) +- [Dave](dave.md) - [Ensoniq ES5506](es5506.md) - [Konami SCC](scc.md) - [FDS](fds.md) diff --git a/doc/7-systems/dave.md b/doc/7-systems/dave.md new file mode 100644 index 000000000..c6722ef70 --- /dev/null +++ b/doc/7-systems/dave.md @@ -0,0 +1,47 @@ +# Dave + +this is the sound chip used in the Enterprise 128 home computer of the '80s, which competed against other home computers in Europe such as the ZX Spectrum and Amstrad CPC. + +Dave is very similar to POKEY in many aspects. it has most of the signature Atari sounds and POKEY-style high-pass filter. +it has 4 channels, of which 3 generate LFSR-based sounds and the last one is a noise channel which has five lengths and either runs at a fixed frequency, or steals the frequency of another channel. +these channels have ring modulation and the aforementioned high-pass filter capabilities. the noise one also has a pseudo-low-pass filter. +the pitch and volume resolutions are much greater than that of POKEY, with 4096 pitches and 64 volume levels. +it also has stereo output. +on top of that, there's a DAC mode which may be enabled for each side of the stereo output. this mode overrides sound generation output. + +## effects + +- `10xx`: **set waveform or noise length.** + - the following waveforms apply in the first three channels: + - 0: square + - 1: bass + - 2: buzz + - 3: reed + - 4: noise + - if placed in the noise channel, `x` is a value from `0` to `3`. +- `11xx`: **set noise frequency source.** + - 0: fixed frequency (~62.5KHz) + - 1: channel 1 + - 2: channel 2 + - 3: channel 3 +- `12xx`: **toggle high-pass with the next channel.** +- `13xx`: **toggle ring modulation with the channel that is located two channels ahead of this one.** + - in the case of channel 1, it modulates with channel 3. channel 2 modulates with channel 4 and so on. +- `14xx`: **toggle "swap counters" mode.** + - only in noise channel. + - when enabled, the noise length is even shorter and has no effect. +- `15xx`: **toggle low-pass with channel 2.** + - only in noise channel. +- `16xx`: **set global clock divider.** + - 0: divide by 2. + - 1: divide by 3. + +## info + +this chip uses the [Dave](../4-instrument/dave.md) instrument editor. + +when two channels are joined due to high-pass filter, the channel bar will show `high` on a bracket tying them together. + +when two channels are joined due to low-pass filter, the channel bar will show `low` on a bracket tying them together. + +when two channels are joined for ring modulation, the channel bar will show `ring` on a bracket tying them together. diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index fd9697375..aca585d05 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -252,6 +252,12 @@ enum DivDispatchCmds { DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val) DIV_CMD_POWERNOISE_IO_WRITE, // (port, value) + DIV_CMD_DAVE_HIGH_PASS, + DIV_CMD_DAVE_RING_MOD, + DIV_CMD_DAVE_SWAP_COUNTERS, + DIV_CMD_DAVE_LOW_PASS, + DIV_CMD_DAVE_CLOCK_DIV, + DIV_CMD_MAX }; diff --git a/src/engine/platform/dave.cpp b/src/engine/platform/dave.cpp index d316ae58f..9ec8d97e7 100644 --- a/src/engine/platform/dave.cpp +++ b/src/engine/platform/dave.cpp @@ -19,7 +19,6 @@ #include "dave.h" #include "../engine.h" -#include "furIcons.h" #include //#define rWrite(a,v) pendingWrites[a]=v; @@ -106,6 +105,36 @@ void DivPlatformDave::acquire(short** buf, size_t len) { unsigned short nextL=next&0xffff; unsigned short nextR=next>>16; + if ((regPool[7]&0x18)==0x18) { + oscBuf[0]->data[oscBuf[0]->needle++]=0; + oscBuf[1]->data[oscBuf[1]->needle++]=0; + oscBuf[2]->data[oscBuf[2]->needle++]=0; + oscBuf[3]->data[oscBuf[3]->needle++]=0; + oscBuf[4]->data[oscBuf[4]->needle++]=dave->chn0_left<<9; + oscBuf[5]->data[oscBuf[5]->needle++]=dave->chn0_right<<9; + } else if (regPool[7]&0x08) { + oscBuf[0]->data[oscBuf[0]->needle++]=dave->chn0_state?(dave->chn0_right<<8):0; + oscBuf[1]->data[oscBuf[1]->needle++]=dave->chn1_state?(dave->chn1_right<<8):0; + oscBuf[2]->data[oscBuf[2]->needle++]=dave->chn2_state?(dave->chn2_right<<8):0; + oscBuf[3]->data[oscBuf[3]->needle++]=dave->chn3_state?(dave->chn3_right<<8):0; + oscBuf[4]->data[oscBuf[4]->needle++]=dave->chn0_left<<9; + oscBuf[5]->data[oscBuf[5]->needle++]=0; + } else if (regPool[7]&0x10) { + oscBuf[0]->data[oscBuf[0]->needle++]=dave->chn0_state?(dave->chn0_left<<8):0; + oscBuf[1]->data[oscBuf[1]->needle++]=dave->chn1_state?(dave->chn1_left<<8):0; + oscBuf[2]->data[oscBuf[2]->needle++]=dave->chn2_state?(dave->chn2_left<<8):0; + oscBuf[3]->data[oscBuf[3]->needle++]=dave->chn3_state?(dave->chn3_left<<8):0; + oscBuf[4]->data[oscBuf[4]->needle++]=0; + oscBuf[5]->data[oscBuf[5]->needle++]=dave->chn0_right<<9; + } else { + oscBuf[0]->data[oscBuf[0]->needle++]=dave->chn0_state?((dave->chn0_left+dave->chn0_right)<<8):0; + oscBuf[1]->data[oscBuf[1]->needle++]=dave->chn1_state?((dave->chn1_left+dave->chn1_right)<<8):0; + oscBuf[2]->data[oscBuf[2]->needle++]=dave->chn2_state?((dave->chn2_left+dave->chn2_right)<<8):0; + oscBuf[3]->data[oscBuf[3]->needle++]=dave->chn3_state?((dave->chn3_left+dave->chn3_right)<<8):0; + oscBuf[4]->data[oscBuf[4]->needle++]=0; + oscBuf[5]->data[oscBuf[5]->needle++]=0; + } + buf[0][h]=(short)nextL; buf[1][h]=(short)nextR; } @@ -180,17 +209,17 @@ void DivPlatformDave::tick(bool sysTick) { if (chan[i].writeVol) { if (i<4) { if (chan[i].active && !isMuted[i]) { - if (i!=0 || chan[4].dacSample<0) { + if (i!=0 || chan[4].dacSample<0 || isMuted[4]) { rWrite(8+i,(63+chan[i].outVol*chan[i].panL)>>6); } - if (i!=0 || chan[5].dacSample<0) { + if (i!=0 || chan[5].dacSample<0 || isMuted[5]) { rWrite(12+i,(63+chan[i].outVol*chan[i].panR)>>6); } } else { - if (i!=0 || chan[4].dacSample<0) { + if (i!=0 || chan[4].dacSample<0 || isMuted[4]) { rWrite(8+i,0); } - if (i!=0 || chan[5].dacSample<0) { + if (i!=0 || chan[5].dacSample<0 || isMuted[5]) { rWrite(12+i,0); } } @@ -253,8 +282,8 @@ void DivPlatformDave::tick(bool sysTick) { } if (writeControl) { - rWrite(7,(chan[0].resetPhase?1:0)|(chan[1].resetPhase?2:0)|(chan[2].resetPhase?4:0)|((chan[4].dacSample>=0)?8:0)|((chan[5].dacSample>=0)?16:0)); - rWrite(7,((chan[4].dacSample>=0)?8:0)|((chan[5].dacSample>=0)?16:0)); + rWrite(7,(chan[0].resetPhase?1:0)|(chan[1].resetPhase?2:0)|(chan[2].resetPhase?4:0)|((chan[4].dacSample>=0 && !isMuted[4])?8:0)|((chan[5].dacSample>=0 && !isMuted[5])?16:0)); + rWrite(7,((chan[4].dacSample>=0 && !isMuted[4])?8:0)|((chan[5].dacSample>=0 && !isMuted[5])?16:0)); chan[0].resetPhase=false; chan[1].resetPhase=false; chan[2].resetPhase=false; @@ -355,8 +384,34 @@ int DivPlatformDave::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; + if (chan[c.chan].wave>4) chan[c.chan].wave=4; + if (c.chan==3 && chan[c.chan].wave>3) chan[c.chan].wave=3; chan[c.chan].freqChanged=true; break; + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noiseFreq=c.value&3; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_DAVE_HIGH_PASS: + chan[c.chan].highPass=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_DAVE_RING_MOD: + chan[c.chan].ringMod=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_DAVE_SWAP_COUNTERS: + chan[c.chan].swapCounters=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_DAVE_LOW_PASS: + chan[c.chan].lowPass=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_DAVE_CLOCK_DIV: + clockDiv=c.value; + rWrite(31,clockDiv?2:0); + break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; @@ -418,6 +473,10 @@ int DivPlatformDave::dispatch(DivCommand c) { void DivPlatformDave::muteChannel(int ch, bool mute) { isMuted[ch]=mute; chan[ch].writeVol=true; + if (ch>=4) { + chan[0].writeVol=true; + writeControl=true; + } } void DivPlatformDave::forceIns() { @@ -427,6 +486,7 @@ void DivPlatformDave::forceIns() { chan[i].writeVol=true; } writeControl=true; + rWrite(31,clockDiv?2:0); } void* DivPlatformDave::getChanState(int ch) { @@ -441,7 +501,11 @@ unsigned short DivPlatformDave::getPan(int ch) { return (chan[ch].panL<<2)|chan[ch].panR; } +// TODO: the rest DivChannelPair DivPlatformDave::getPaired(int ch) { + if (chan[ch].highPass) { + DivChannelPair("high",(ch+1)&3); + } return DivChannelPair(); } @@ -463,10 +527,6 @@ DivDispatchOscBuffer* DivPlatformDave::getOscBuffer(int ch) { return oscBuf[ch]; } -int DivPlatformDave::mapVelocity(int ch, float vel) { - return round(31.0*pow(vel,0.22)); -} - unsigned char* DivPlatformDave::getRegisterPool() { return regPool; } diff --git a/src/engine/platform/dave.h b/src/engine/platform/dave.h index 9fea7a3b1..3b0183d3b 100644 --- a/src/engine/platform/dave.h +++ b/src/engine/platform/dave.h @@ -63,6 +63,7 @@ class DivPlatformDave: public DivDispatch { }; FixedQueue writes; bool writeControl; + bool clockDiv; Ep128::Dave* dave; unsigned char regPool[32]; @@ -78,7 +79,6 @@ class DivPlatformDave: public DivDispatch { DivChannelModeHints getModeHints(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); - int mapVelocity(int ch, float vel); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/engine/platform/sound/dave/dave.hpp b/src/engine/platform/sound/dave/dave.hpp index 8e44ca49a..161fc823d 100644 --- a/src/engine/platform/sound/dave/dave.hpp +++ b/src/engine/platform/sound/dave/dave.hpp @@ -41,7 +41,7 @@ namespace Ep128 { }; class Dave { - private: + public: DaveTables t; int clockDiv; // 2 if bit 1 of port 0xBF is 0, 3 otherwise int clockCnt; // counts from 'clockDiv' towards zero diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index d7b8426e8..aab72623f 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -250,6 +250,12 @@ const char* cmdName[]={ "POWERNOISE_COUNTER_LOAD", "POWERNOISE_IO_WRITE", + "DAVE_HIGH_PASS", + "DAVE_RING_MOD", + "DAVE_SWAP_COUNTERS", + "DAVE_LOW_PASS", + "DAVE_CLOCK_DIV", + "MACRO_RESTART", }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 96b5228e2..e33b17b1c 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2011,9 +2011,13 @@ void DivEngine::registerSystems() { {DIV_INS_DAVE, DIV_INS_DAVE, DIV_INS_DAVE, DIV_INS_DAVE, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, { - {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 7)"}}, - {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set AUDCTL"}}, - {0x12, {DIV_CMD_STD_NOISE_FREQ, "12xx: Toggle two-tone mode"}}, + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 4; 0 to 3 on noise)"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set noise frequency source (0: fixed; 1-3: channels 1 to 3)"}}, + {0x12, {DIV_CMD_DAVE_HIGH_PASS, "12xx: Toggle high-pass with next channel"}}, + {0x13, {DIV_CMD_DAVE_RING_MOD, "13xx: Toggle ring modulation with channel+2"}}, + {0x14, {DIV_CMD_DAVE_SWAP_COUNTERS, "14xx: Toggle swap counters (noise only)"}}, + {0x15, {DIV_CMD_DAVE_LOW_PASS, "15xx: Toggle low pass (noise only)"}}, + {0x16, {DIV_CMD_DAVE_CLOCK_DIV, "16xx: Set clock divider (0: /2; 1: /3)"}}, } );