This commit is contained in:
tildearrow 2024-02-04 03:02:12 -05:00
parent 9a2b19630e
commit 4330e27436
10 changed files with 169 additions and 16 deletions

View file

@ -75,6 +75,7 @@ the following instrument types are available:
- [C140](c140.md) - for use with C140 sample chip. - [C140](c140.md) - for use with C140 sample chip.
- [C219](c219.md) - for use with C219 sample chip. - [C219](c219.md) - for use with C219 sample chip.
- [PowerNoise](powernoise.md) - for use with PowerNoise chip. - [PowerNoise](powernoise.md) - for use with PowerNoise chip.
- [Dave](dave.md) - for use with Dave chip.
## macros ## macros

28
doc/4-instrument/dave.md Normal file
View file

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

View file

@ -68,6 +68,7 @@ this is the full list of chips that Furnace supports.
- [AY-3-8910/8914/YM2149(F)/Sunsoft 5B](ay8910.md) - [AY-3-8910/8914/YM2149(F)/Sunsoft 5B](ay8910.md)
- [Microship AY8930](ay8930.md) - [Microship AY8930](ay8930.md)
- [MOS 6581/8580 (SID)](c64.md) - [MOS 6581/8580 (SID)](c64.md)
- [Dave](dave.md)
- [Ensoniq ES5506](es5506.md) - [Ensoniq ES5506](es5506.md)
- [Konami SCC](scc.md) - [Konami SCC](scc.md)
- [FDS](fds.md) - [FDS](fds.md)

47
doc/7-systems/dave.md Normal file
View file

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

View file

@ -252,6 +252,12 @@ enum DivDispatchCmds {
DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val) DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val)
DIV_CMD_POWERNOISE_IO_WRITE, // (port, value) 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 DIV_CMD_MAX
}; };

View file

@ -19,7 +19,6 @@
#include "dave.h" #include "dave.h"
#include "../engine.h" #include "../engine.h"
#include "furIcons.h"
#include <math.h> #include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v; //#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 nextL=next&0xffff;
unsigned short nextR=next>>16; 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[0][h]=(short)nextL;
buf[1][h]=(short)nextR; buf[1][h]=(short)nextR;
} }
@ -180,17 +209,17 @@ void DivPlatformDave::tick(bool sysTick) {
if (chan[i].writeVol) { if (chan[i].writeVol) {
if (i<4) { if (i<4) {
if (chan[i].active && !isMuted[i]) { 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); 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); rWrite(12+i,(63+chan[i].outVol*chan[i].panR)>>6);
} }
} else { } else {
if (i!=0 || chan[4].dacSample<0) { if (i!=0 || chan[4].dacSample<0 || isMuted[4]) {
rWrite(8+i,0); rWrite(8+i,0);
} }
if (i!=0 || chan[5].dacSample<0) { if (i!=0 || chan[5].dacSample<0 || isMuted[5]) {
rWrite(12+i,0); rWrite(12+i,0);
} }
} }
@ -253,8 +282,8 @@ void DivPlatformDave::tick(bool sysTick) {
} }
if (writeControl) { 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[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)?8:0)|((chan[5].dacSample>=0)?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[0].resetPhase=false;
chan[1].resetPhase=false; chan[1].resetPhase=false;
chan[2].resetPhase=false; chan[2].resetPhase=false;
@ -355,8 +384,34 @@ int DivPlatformDave::dispatch(DivCommand c) {
break; break;
case DIV_CMD_WAVE: case DIV_CMD_WAVE:
chan[c.chan].wave=c.value; 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; chan[c.chan].freqChanged=true;
break; 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: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta); int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false; bool return2=false;
@ -418,6 +473,10 @@ int DivPlatformDave::dispatch(DivCommand c) {
void DivPlatformDave::muteChannel(int ch, bool mute) { void DivPlatformDave::muteChannel(int ch, bool mute) {
isMuted[ch]=mute; isMuted[ch]=mute;
chan[ch].writeVol=true; chan[ch].writeVol=true;
if (ch>=4) {
chan[0].writeVol=true;
writeControl=true;
}
} }
void DivPlatformDave::forceIns() { void DivPlatformDave::forceIns() {
@ -427,6 +486,7 @@ void DivPlatformDave::forceIns() {
chan[i].writeVol=true; chan[i].writeVol=true;
} }
writeControl=true; writeControl=true;
rWrite(31,clockDiv?2:0);
} }
void* DivPlatformDave::getChanState(int ch) { void* DivPlatformDave::getChanState(int ch) {
@ -441,7 +501,11 @@ unsigned short DivPlatformDave::getPan(int ch) {
return (chan[ch].panL<<2)|chan[ch].panR; return (chan[ch].panL<<2)|chan[ch].panR;
} }
// TODO: the rest
DivChannelPair DivPlatformDave::getPaired(int ch) { DivChannelPair DivPlatformDave::getPaired(int ch) {
if (chan[ch].highPass) {
DivChannelPair("high",(ch+1)&3);
}
return DivChannelPair(); return DivChannelPair();
} }
@ -463,10 +527,6 @@ DivDispatchOscBuffer* DivPlatformDave::getOscBuffer(int ch) {
return oscBuf[ch]; return oscBuf[ch];
} }
int DivPlatformDave::mapVelocity(int ch, float vel) {
return round(31.0*pow(vel,0.22));
}
unsigned char* DivPlatformDave::getRegisterPool() { unsigned char* DivPlatformDave::getRegisterPool() {
return regPool; return regPool;
} }

View file

@ -63,6 +63,7 @@ class DivPlatformDave: public DivDispatch {
}; };
FixedQueue<QueuedWrite,512> writes; FixedQueue<QueuedWrite,512> writes;
bool writeControl; bool writeControl;
bool clockDiv;
Ep128::Dave* dave; Ep128::Dave* dave;
unsigned char regPool[32]; unsigned char regPool[32];
@ -78,7 +79,6 @@ class DivPlatformDave: public DivDispatch {
DivChannelModeHints getModeHints(int chan); DivChannelModeHints getModeHints(int chan);
DivSamplePos getSamplePos(int ch); DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan); DivDispatchOscBuffer* getOscBuffer(int chan);
int mapVelocity(int ch, float vel);
unsigned char* getRegisterPool(); unsigned char* getRegisterPool();
int getRegisterPoolSize(); int getRegisterPoolSize();
void reset(); void reset();

View file

@ -41,7 +41,7 @@ namespace Ep128 {
}; };
class Dave { class Dave {
private: public:
DaveTables t; DaveTables t;
int clockDiv; // 2 if bit 1 of port 0xBF is 0, 3 otherwise int clockDiv; // 2 if bit 1 of port 0xBF is 0, 3 otherwise
int clockCnt; // counts from 'clockDiv' towards zero int clockCnt; // counts from 'clockDiv' towards zero

View file

@ -250,6 +250,12 @@ const char* cmdName[]={
"POWERNOISE_COUNTER_LOAD", "POWERNOISE_COUNTER_LOAD",
"POWERNOISE_IO_WRITE", "POWERNOISE_IO_WRITE",
"DAVE_HIGH_PASS",
"DAVE_RING_MOD",
"DAVE_SWAP_COUNTERS",
"DAVE_LOW_PASS",
"DAVE_CLOCK_DIV",
"MACRO_RESTART", "MACRO_RESTART",
}; };

View file

@ -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}, {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)"}}, {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 4; 0 to 3 on noise)"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set AUDCTL"}}, {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set noise frequency source (0: fixed; 1-3: channels 1 to 3)"}},
{0x12, {DIV_CMD_STD_NOISE_FREQ, "12xx: Toggle two-tone mode"}}, {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)"}},
} }
); );