mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-28 18:41:16 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace
This commit is contained in:
commit
3b0df75bf6
112 changed files with 8512 additions and 634 deletions
|
@ -368,6 +368,24 @@ src/engine/platform/sound/c64/wave8580_PST.cc
|
|||
src/engine/platform/sound/c64/wave8580_P_T.cc
|
||||
src/engine/platform/sound/c64/wave8580__ST.cc
|
||||
|
||||
src/engine/platform/sound/c64_fp/Dac.cpp
|
||||
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
|
||||
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
|
||||
src/engine/platform/sound/c64_fp/Filter6581.cpp
|
||||
src/engine/platform/sound/c64_fp/Filter8580.cpp
|
||||
src/engine/platform/sound/c64_fp/Filter.cpp
|
||||
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
|
||||
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
|
||||
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
|
||||
src/engine/platform/sound/c64_fp/Integrator6581.cpp
|
||||
src/engine/platform/sound/c64_fp/Integrator8580.cpp
|
||||
src/engine/platform/sound/c64_fp/OpAmp.cpp
|
||||
src/engine/platform/sound/c64_fp/SID.cpp
|
||||
src/engine/platform/sound/c64_fp/Spline.cpp
|
||||
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
|
||||
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
||||
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
||||
|
||||
src/engine/platform/sound/tia/TIASnd.cpp
|
||||
|
||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||
|
@ -553,6 +571,7 @@ src/gui/subSongs.cpp
|
|||
src/gui/sysConf.cpp
|
||||
src/gui/sysEx.cpp
|
||||
src/gui/sysManager.cpp
|
||||
src/gui/sysPicker.cpp
|
||||
src/gui/util.cpp
|
||||
src/gui/waveEdit.cpp
|
||||
src/gui/volMeter.cpp
|
||||
|
|
|
@ -12,7 +12,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti
|
|||
- 5: periodic noise
|
||||
- 6: XOR sine
|
||||
- 7: XOR triangle
|
||||
- `12xx`: set waveform (0 to 7F)
|
||||
- `12xx`: set pulse width (0 to 7F)
|
||||
- `13xx`: set resonance of filter (0 to FF)
|
||||
- despite what the internal effects list says (0 to F), you can use a resonance value from 0 to FF (255)
|
||||
- `14xx`: set filter mode and ringmod
|
||||
|
|
|
@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 112: Furnace dev112
|
||||
- 111: Furnace dev111
|
||||
- 110: Furnace dev110
|
||||
- 109: Furnace dev109
|
||||
|
@ -454,6 +455,7 @@ size | description
|
|||
| - 29: SNES
|
||||
| - 30: Sound Unit
|
||||
| - 31: Namco WSG
|
||||
| - 32: OPL (drums)
|
||||
1 | reserved
|
||||
STR | instrument name
|
||||
--- | **FM instrument data**
|
||||
|
@ -545,7 +547,18 @@ size | description
|
|||
4 | extra 1 macro loop (>=17)
|
||||
4 | extra 2 macro loop (>=17)
|
||||
4 | extra 3 macro loop (>=17)
|
||||
1 | arp macro mode
|
||||
1 | arp macro mode (<112) or reserved
|
||||
| - treat this value in a special way.
|
||||
| - before version 112, this byte indicates whether the arp macro mode is fixed or not.
|
||||
| - from that version onwards, the fixed mode is part of the macro values.
|
||||
| - to convert a <112 macro mode to a modern one, do the following:
|
||||
| - is the macro mode set to fixed?
|
||||
| - if yes, then:
|
||||
| - set bit 30 of all arp macro values (this is the fixed mode bit)
|
||||
| - does the macro loop?
|
||||
| - if yes, then do nothing else
|
||||
| - if no, then add one to the macro length, and set the last macro value to 0
|
||||
| - if no, then do nothing
|
||||
1 | reserved (>=17) or volume macro height (>=15) or reserved
|
||||
1 | reserved (>=17) or duty macro height (>=15) or reserved
|
||||
1 | reserved (>=17) or wave macro height (>=15) or reserved
|
||||
|
@ -553,6 +566,7 @@ size | description
|
|||
| - before version 87, if this is the C64 relative cutoff macro, its values were stored offset by 18.
|
||||
4?? | arp macro
|
||||
| - before version 31, this macro's values were stored offset by 12.
|
||||
| - from version 112 onward, bit 30 of a value indicates fixed mode.
|
||||
4?? | duty macro
|
||||
| - before version 87, if this is the C64 relative duty macro, its values were stored offset by 12.
|
||||
4?? | wave macro
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "dispatch.h"
|
||||
#include "song.h"
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "engine.h"
|
||||
#include "instrument.h"
|
||||
|
@ -1406,7 +1407,65 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) {
|
|||
|
||||
if (!preserveOrder) {
|
||||
// move channels
|
||||
// TODO: a lot of work!
|
||||
unsigned char unswappedChannels[DIV_MAX_CHANS];
|
||||
unsigned char swappedChannels[DIV_MAX_CHANS];
|
||||
std::vector<std::vector<int>> swapList;
|
||||
std::vector<int> chanList;
|
||||
|
||||
int tchans=0;
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
tchans+=getChannelCount(song.system[i]);
|
||||
}
|
||||
|
||||
memset(unswappedChannels,0,DIV_MAX_CHANS);
|
||||
memset(swappedChannels,0,DIV_MAX_CHANS);
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
unswappedChannels[i]=i;
|
||||
}
|
||||
|
||||
// prepare swap list
|
||||
int index=0;
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
chanList.clear();
|
||||
for (int j=0; j<getChannelCount(song.system[i]); j++) {
|
||||
chanList.push_back(index);
|
||||
index++;
|
||||
}
|
||||
swapList.push_back(chanList);
|
||||
}
|
||||
swapList[src].swap(swapList[dest]);
|
||||
|
||||
// unfold it
|
||||
index=0;
|
||||
for (std::vector<int>& i: swapList) {
|
||||
for (int& j: i) {
|
||||
swappedChannels[index++]=j;
|
||||
}
|
||||
}
|
||||
|
||||
logV("swap list:");
|
||||
for (int i=0; i<tchans; i++) {
|
||||
logV("- %d -> %d",unswappedChannels[i],swappedChannels[i]);
|
||||
}
|
||||
|
||||
// swap channels
|
||||
bool allComplete=false;
|
||||
while (!allComplete) {
|
||||
logD("doing swap...");
|
||||
allComplete=true;
|
||||
for (int i=0; i<tchans; i++) {
|
||||
if (unswappedChannels[i]!=swappedChannels[i]) {
|
||||
swapChannels(i,swappedChannels[i]);
|
||||
allComplete=false;
|
||||
logD("> %d -> %d",unswappedChannels[i],unswappedChannels[swappedChannels[i]]);
|
||||
unswappedChannels[i]^=unswappedChannels[swappedChannels[i]];
|
||||
unswappedChannels[swappedChannels[i]]^=unswappedChannels[i];
|
||||
unswappedChannels[i]^=unswappedChannels[swappedChannels[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DivSystem srcSystem=song.system[src];
|
||||
|
@ -1712,6 +1771,15 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2
|
|||
base+((pitch*octave)>>1)+pitch2;
|
||||
}
|
||||
|
||||
int DivEngine::calcArp(int note, int arp, int offset) {
|
||||
if (arp<0) {
|
||||
if (!(arp&0x40000000)) return (arp|0x40000000)+offset;
|
||||
} else {
|
||||
if (arp&0x40000000) return (arp&(~0x40000000))+offset;
|
||||
}
|
||||
return note+arp;
|
||||
}
|
||||
|
||||
int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) {
|
||||
int panL=val>>bits;
|
||||
int panR=val&((1<<bits)-1);
|
||||
|
@ -3142,7 +3210,7 @@ bool DivEngine::moveSampleDown(int which) {
|
|||
void DivEngine::noteOn(int chan, int ins, int note, int vol) {
|
||||
if (chan<0 || chan>=chans) return;
|
||||
BUSY_BEGIN;
|
||||
pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true));
|
||||
pendingNotes.push_back(DivNoteEvent(chan,ins,note,vol,true));
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
|
@ -3154,7 +3222,7 @@ void DivEngine::noteOn(int chan, int ins, int note, int vol) {
|
|||
void DivEngine::noteOff(int chan) {
|
||||
if (chan<0 || chan>=chans) return;
|
||||
BUSY_BEGIN;
|
||||
pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
|
@ -3206,7 +3274,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
|||
if ((!midiPoly) || (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel))) {
|
||||
chan[finalChan].midiNote=note;
|
||||
chan[finalChan].midiAge=midiAgeCounter++;
|
||||
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
|
||||
pendingNotes.push_back(DivNoteEvent(finalChan,ins,note,vol,true));
|
||||
return;
|
||||
}
|
||||
if (++finalChan>=chans) {
|
||||
|
@ -3227,7 +3295,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
|||
|
||||
chan[candidate].midiNote=note;
|
||||
chan[candidate].midiAge=midiAgeCounter++;
|
||||
pendingNotes.push(DivNoteEvent(candidate,ins,note,vol,true));
|
||||
pendingNotes.push_back(DivNoteEvent(candidate,ins,note,vol,true));
|
||||
}
|
||||
|
||||
void DivEngine::autoNoteOff(int ch, int note, int vol) {
|
||||
|
@ -3239,7 +3307,7 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) {
|
|||
//if (ch<0 || ch>=chans) return;
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].midiNote==note) {
|
||||
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
|
||||
pendingNotes.push_back(DivNoteEvent(i,-1,-1,-1,false));
|
||||
chan[i].midiNote=-1;
|
||||
}
|
||||
}
|
||||
|
@ -3253,7 +3321,7 @@ void DivEngine::autoNoteOffAll() {
|
|||
}
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].midiNote!=-1) {
|
||||
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
|
||||
pendingNotes.push_back(DivNoteEvent(i,-1,-1,-1,false));
|
||||
chan[i].midiNote=-1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#include <mutex>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
|
||||
#define addWarning(x) \
|
||||
if (warnings.empty()) { \
|
||||
|
@ -46,8 +46,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev111"
|
||||
#define DIV_ENGINE_VERSION 111
|
||||
#define DIV_VERSION "dev112"
|
||||
#define DIV_ENGINE_VERSION 112
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -357,7 +357,7 @@ class DivEngine {
|
|||
DivAudioExportModes exportMode;
|
||||
double exportFadeOut;
|
||||
std::map<String,String> conf;
|
||||
std::queue<DivNoteEvent> pendingNotes;
|
||||
std::deque<DivNoteEvent> pendingNotes;
|
||||
bool isMuted[DIV_MAX_CHANS];
|
||||
std::mutex isBusy, saveLock;
|
||||
String configPath;
|
||||
|
@ -554,6 +554,9 @@ class DivEngine {
|
|||
// calculate frequency/period
|
||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0);
|
||||
|
||||
// calculate arpeggio
|
||||
int calcArp(int note, int arp, int offset=0);
|
||||
|
||||
// convert panning formats
|
||||
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
||||
int convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range);
|
||||
|
@ -624,6 +627,9 @@ class DivEngine {
|
|||
// get japanese system name
|
||||
const char* getSystemNameJ(DivSystem sys);
|
||||
|
||||
// get sys definition
|
||||
const DivSysDef* getSystemDef(DivSystem sys);
|
||||
|
||||
// convert sample rate format
|
||||
int fileToDivRate(int frate);
|
||||
int divToFileRate(int drate);
|
||||
|
|
|
@ -145,7 +145,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.loopModality=0;
|
||||
ds.properNoiseLayout=false;
|
||||
ds.waveDutyIsVol=false;
|
||||
ds.resetMacroOnPorta=true;
|
||||
// TODO: WHAT?! geodude.dmf fails when this is true
|
||||
// but isn't that how Defle behaves???
|
||||
ds.resetMacroOnPorta=false;
|
||||
ds.legacyVolumeSlides=true;
|
||||
ds.compatibleArpeggio=true;
|
||||
ds.noteOffResetsSlides=true;
|
||||
|
@ -507,6 +509,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]-=12;
|
||||
}
|
||||
} else {
|
||||
ins->std.arpMacro.mode=0;
|
||||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]^=0x40000000;
|
||||
}
|
||||
if (ins->std.arpMacro.loop==255 && ins->std.arpMacro.len<255) {
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len++]=0;
|
||||
}
|
||||
}
|
||||
|
||||
ins->std.dutyMacro.len=reader.readC();
|
||||
|
@ -2886,9 +2896,6 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
loopMapFreq[j]=ins->std.arpMacro.len;
|
||||
loopMapWave[j]=ins->std.waveMacro.len;
|
||||
if (fm.val[j]==0xe1) {
|
||||
if (ins->std.arpMacro.mode) {
|
||||
ins->std.arpMacro.loop=(signed int)ins->std.arpMacro.len-1;
|
||||
}
|
||||
break;
|
||||
} else if (fm.val[j]==0xe2 || fm.val[j]==0xe4) {
|
||||
if (++j>=64) break;
|
||||
|
@ -2923,8 +2930,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
logV("unhandled pitch!");
|
||||
} else {
|
||||
if (fm.val[j]>0x80) {
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j]-0x80+24;
|
||||
ins->std.arpMacro.mode=1; // TODO: variable fixed/relative mode
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len]=(fm.val[j]-0x80+24)^0x40000000;
|
||||
} else {
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j];
|
||||
}
|
||||
|
@ -3293,6 +3299,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
ins->std.arpMacro.len=reader.readC();
|
||||
ins->std.arpMacro.loop=reader.readI();
|
||||
ins->std.arpMacro.rel=reader.readI();
|
||||
// TODO: get rid
|
||||
ins->std.arpMacro.mode=reader.readI();
|
||||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]=reader.readC();
|
||||
|
@ -4148,7 +4155,25 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
|
||||
// safety check
|
||||
if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_FDS) {
|
||||
i->type=DIV_INS_STD;
|
||||
switch (song.system[0]) {
|
||||
case DIV_SYSTEM_GB:
|
||||
i->type=DIV_INS_GB;
|
||||
break;
|
||||
case DIV_SYSTEM_C64_6581:
|
||||
case DIV_SYSTEM_C64_8580:
|
||||
i->type=DIV_INS_C64;
|
||||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
i->type=DIV_INS_PCE;
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
i->type=DIV_INS_AY;
|
||||
break;
|
||||
default:
|
||||
i->type=DIV_INS_STD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isSTDSystem(sys) && i->type!=DIV_INS_FM) {
|
||||
i->type=DIV_INS_FM;
|
||||
|
@ -4189,55 +4214,79 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
}
|
||||
} else { // STD
|
||||
if (sys!=DIV_SYSTEM_GB) {
|
||||
w->writeC(i->std.volMacro.len);
|
||||
int realVolMacroLen=i->std.volMacro.len;
|
||||
if (realVolMacroLen>127) realVolMacroLen=127;
|
||||
w->writeC(realVolMacroLen);
|
||||
if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) {
|
||||
for (int j=0; j<i->std.volMacro.len; j++) {
|
||||
for (int j=0; j<realVolMacroLen; j++) {
|
||||
w->writeI(i->std.volMacro.val[j]+18);
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<i->std.volMacro.len; j++) {
|
||||
for (int j=0; j<realVolMacroLen; j++) {
|
||||
w->writeI(i->std.volMacro.val[j]);
|
||||
}
|
||||
}
|
||||
if (i->std.volMacro.len>0) {
|
||||
if (realVolMacroLen>0) {
|
||||
w->writeC(i->std.volMacro.loop);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: take care of new arp macro format
|
||||
w->writeC(i->std.arpMacro.len);
|
||||
if (i->std.arpMacro.mode) {
|
||||
for (int j=0; j<i->std.arpMacro.len; j++) {
|
||||
bool arpMacroMode=false;
|
||||
int arpMacroHowManyFixed=0;
|
||||
int realArpMacroLen=i->std.arpMacro.len;
|
||||
for (int j=0; j<i->std.arpMacro.len; j++) {
|
||||
if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) {
|
||||
arpMacroHowManyFixed++;
|
||||
}
|
||||
}
|
||||
if (arpMacroHowManyFixed>=i->std.arpMacro.len-1) {
|
||||
arpMacroMode=true;
|
||||
}
|
||||
if (i->std.arpMacro.len>0) {
|
||||
if (arpMacroMode && i->std.arpMacro.val[i->std.arpMacro.len-1]==0 && i->std.arpMacro.loop>=i->std.arpMacro.len) {
|
||||
realArpMacroLen--;
|
||||
}
|
||||
}
|
||||
|
||||
if (arpMacroMode) {
|
||||
for (int j=0; j<realArpMacroLen; j++) {
|
||||
w->writeI(i->std.arpMacro.val[j]);
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<i->std.arpMacro.len; j++) {
|
||||
for (int j=0; j<realArpMacroLen; j++) {
|
||||
w->writeI(i->std.arpMacro.val[j]+12);
|
||||
}
|
||||
}
|
||||
if (i->std.arpMacro.len>0) {
|
||||
if (realArpMacroLen>0) {
|
||||
w->writeC(i->std.arpMacro.loop);
|
||||
}
|
||||
w->writeC(i->std.arpMacro.mode);
|
||||
w->writeC(arpMacroMode);
|
||||
|
||||
w->writeC(i->std.dutyMacro.len);
|
||||
int realDutyMacroLen=i->std.dutyMacro.len;
|
||||
if (realDutyMacroLen>127) realDutyMacroLen=127;
|
||||
w->writeC(realDutyMacroLen);
|
||||
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
|
||||
for (int j=0; j<i->std.dutyMacro.len; j++) {
|
||||
for (int j=0; j<realDutyMacroLen; j++) {
|
||||
w->writeI(i->std.dutyMacro.val[j]+12);
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<i->std.dutyMacro.len; j++) {
|
||||
for (int j=0; j<realDutyMacroLen; j++) {
|
||||
w->writeI(i->std.dutyMacro.val[j]);
|
||||
}
|
||||
}
|
||||
if (i->std.dutyMacro.len>0) {
|
||||
if (realDutyMacroLen>0) {
|
||||
w->writeC(i->std.dutyMacro.loop);
|
||||
}
|
||||
|
||||
w->writeC(i->std.waveMacro.len);
|
||||
for (int j=0; j<i->std.waveMacro.len; j++) {
|
||||
int realWaveMacroLen=i->std.waveMacro.len;
|
||||
if (realWaveMacroLen>127) realWaveMacroLen=127;
|
||||
w->writeC(realWaveMacroLen);
|
||||
for (int j=0; j<realWaveMacroLen; j++) {
|
||||
w->writeI(i->std.waveMacro.val[j]);
|
||||
}
|
||||
if (i->std.waveMacro.len>0) {
|
||||
if (realWaveMacroLen>0) {
|
||||
w->writeC(i->std.waveMacro.loop);
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeI(std.ex1Macro.loop);
|
||||
w->writeI(std.ex2Macro.loop);
|
||||
w->writeI(std.ex3Macro.loop);
|
||||
w->writeC(std.arpMacro.mode);
|
||||
w->writeC(0); // this was arp macro mode
|
||||
w->writeC(0); // reserved
|
||||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
|
@ -1342,6 +1342,19 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
}
|
||||
}
|
||||
|
||||
// old arp macro format
|
||||
if (version<112) {
|
||||
if (std.arpMacro.mode) {
|
||||
std.arpMacro.mode=0;
|
||||
for (int i=0; i<std.arpMacro.len; i++) {
|
||||
std.arpMacro.val[i]^=0x40000000;
|
||||
}
|
||||
if ((std.arpMacro.loop>=std.arpMacro.len || (std.arpMacro.rel>std.arpMacro.loop && std.arpMacro.rel<std.arpMacro.len)) && std.arpMacro.len<255) {
|
||||
std.arpMacro.val[std.arpMacro.len++]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -163,19 +163,9 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp.val));
|
||||
} else {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp.val));
|
||||
}
|
||||
}
|
||||
// TODO: why the off mult? this may be a bug!
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(parent->calcArp(chan[i].note,chan[i].std.arp.val)));
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note));
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].useWave && chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -161,18 +161,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.duty.had) {
|
||||
|
|
|
@ -159,18 +159,9 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWrite(0x06,31-chan[i].std.duty.val);
|
||||
|
|
|
@ -167,18 +167,9 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWrite(0x06,chan[i].std.duty.val);
|
||||
|
|
|
@ -92,18 +92,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -104,18 +104,9 @@ void DivPlatformC64::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
|
||||
|
|
|
@ -121,18 +121,9 @@ void DivPlatformFDS::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (chan[i].std.duty.had) {
|
||||
|
|
|
@ -165,28 +165,13 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (i==3) { // noise
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=chan[i].std.arp.val+24;
|
||||
} else {
|
||||
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val,24);
|
||||
if (chan[i].baseFreq>255) chan[i].baseFreq=255;
|
||||
if (chan[i].baseFreq<0) chan[i].baseFreq=0;
|
||||
} else {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val+24);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
|
@ -266,34 +266,16 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
if (i>=5 && chan[i].furnaceDac) {
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,chan[i].std.arp.val,false);
|
||||
} else {
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,chan[i].note+(signed char)chan[i].std.arp.val,false);
|
||||
}
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,parent->calcArp(chan[i].note,chan[i].std.arp.val),false);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,chan[i].note,false);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,22 +172,9 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].std.arp.val,false);
|
||||
chan[i].actualNote=chan[i].std.arp.val;
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note+chan[i].std.arp.val,false);
|
||||
chan[i].actualNote=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note,false);
|
||||
chan[i].actualNote=chan[i].note;
|
||||
chan[i].actualNote=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].actualNote);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].actualNote,false);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,20 +101,12 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6));
|
||||
}
|
||||
// TODO: arp macros on NES PCM?
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
|
@ -189,18 +189,9 @@ void DivPlatformN163::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
if (chan[i].wavePos!=chan[i].std.duty.val) {
|
||||
|
|
|
@ -206,18 +206,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -219,28 +219,15 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (i==3) { // noise
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=chan[i].std.arp.val;
|
||||
} else {
|
||||
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
if (chan[i].baseFreq>255) chan[i].baseFreq=255;
|
||||
if (chan[i].baseFreq<0) chan[i].baseFreq=0;
|
||||
} else {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
|
@ -297,18 +297,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (oplType==3 && chan[i].std.panL.had) {
|
||||
|
@ -491,18 +482,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
|
||||
if (chan[adpcmChan].std.arp.had) {
|
||||
if (!chan[adpcmChan].inPorta) {
|
||||
if (chan[adpcmChan].std.arp.mode) {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val);
|
||||
} else {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val);
|
||||
}
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmChan].note,chan[adpcmChan].std.arp.val));
|
||||
}
|
||||
chan[adpcmChan].freqChanged=true;
|
||||
} else {
|
||||
if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note);
|
||||
chan[adpcmChan].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[adpcmChan].freqChanged) {
|
||||
|
|
|
@ -107,18 +107,9 @@ void DivPlatformOPLL::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.wave.had && chan[i].state.opllPreset!=16) {
|
||||
|
|
|
@ -162,28 +162,12 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
// noise
|
||||
int noiseSeek=chan[i].std.arp.val;
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
int noiseSeek=chan[i].note+chan[i].std.arp.val;
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
int noiseSeek=chan[i].note;
|
||||
int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_PERIODIC(noiseSeek);
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.wave.had && !chan[i].pcm) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -83,18 +83,9 @@ void DivPlatformPCMDAC::tick(bool sysTick) {
|
|||
}
|
||||
if (chan.std.arp.had) {
|
||||
if (!chan.inPorta) {
|
||||
if (chan.std.arp.mode) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.std.arp.val);
|
||||
} else {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.note+chan.std.arp.val);
|
||||
}
|
||||
chan.baseFreq=NOTE_FREQUENCY(parent->calcArp(chan.note,chan.std.arp.val));
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
} else {
|
||||
if (chan.std.arp.mode && chan.std.arp.finished) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.note);
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan.useWave && chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) {
|
||||
|
|
|
@ -347,18 +347,9 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
|
@ -104,18 +104,9 @@ void DivPlatformPET::tick(bool sysTick) {
|
|||
}
|
||||
if (chan.std.arp.had) {
|
||||
if (!chan.inPorta) {
|
||||
if (chan.std.arp.mode) {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.std.arp.val);
|
||||
} else {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp.val);
|
||||
}
|
||||
chan.baseFreq=NOTE_PERIODIC(parent->calcArp(chan.note,chan.std.arp.val));
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
} else {
|
||||
if (chan.std.arp.mode && chan.std.arp.finished) {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.note);
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val) {
|
||||
|
|
|
@ -297,18 +297,9 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
@ -347,7 +338,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
|
||||
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
|
||||
logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
//logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
// Write sample address. Enable volume
|
||||
if (!chan[i].std.vol.had) {
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4);
|
||||
|
|
|
@ -84,18 +84,9 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
@ -116,7 +107,7 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
chan[i].panning|=(chan[i].std.panR.val&15)<<4;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].panning);
|
||||
chWrite(i,1,isMuted[i]?0:chan[i].panning);
|
||||
}
|
||||
if (chan[i].setPos) {
|
||||
// force keyon
|
||||
|
|
|
@ -99,18 +99,9 @@ void DivPlatformSAA1099::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
saaNoise[i/3]=chan[i].std.duty.val&3;
|
||||
|
|
|
@ -115,18 +115,9 @@ void DivPlatformSCC::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -88,18 +88,9 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=(chan[i].std.arp.val<<6);
|
||||
} else {
|
||||
chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp.val)<<6);
|
||||
}
|
||||
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<6);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=(chan[i].note<<6);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
|
|
|
@ -132,22 +132,12 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
chan[i].actualNote=chan[i].std.arp.val;
|
||||
} else {
|
||||
// TODO: check whether this weird octave boundary thing applies to other systems as well
|
||||
int areYouSerious=chan[i].note+chan[i].std.arp.val;
|
||||
while (areYouSerious>0x60) areYouSerious-=12;
|
||||
chan[i].baseFreq=NOTE_PERIODIC(areYouSerious);
|
||||
chan[i].actualNote=areYouSerious;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].actualNote=chan[i].note;
|
||||
// TODO: check whether this weird octave boundary thing applies to other systems as well
|
||||
// TODO: add compatibility flag. this is horrible.
|
||||
int areYouSerious=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
while (areYouSerious>0x60) areYouSerious-=12;
|
||||
chan[i].baseFreq=NOTE_PERIODIC(areYouSerious);
|
||||
chan[i].actualNote=areYouSerious;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
@ -229,12 +219,8 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
} else { // 3 fixed values
|
||||
unsigned char value;
|
||||
if (chan[3].std.arp.had) {
|
||||
if (chan[3].std.arp.mode) {
|
||||
value=chan[3].std.arp.val%12;
|
||||
} else {
|
||||
value=(chan[3].note+chan[3].std.arp.val)%12;
|
||||
}
|
||||
} else {
|
||||
value=parent->calcArp(chan[3].note,chan[3].std.arp.val)%12;
|
||||
} else { // pardon?
|
||||
value=chan[3].note%12;
|
||||
}
|
||||
if (value<3) {
|
||||
|
|
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
|
@ -0,0 +1,6 @@
|
|||
Authors of reSIDfp.
|
||||
|
||||
Dag Lem: Designed and programmed complete emulation engine.
|
||||
Antti S. Lankila: Distortion simulation and calculation of combined waveforms
|
||||
Ken Händel: source code conversion to Java
|
||||
Leandro Nini: port to c++, merge with reSID 1.0
|
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "Dac.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Dac::Dac(unsigned int bits) :
|
||||
dac(new double[bits]),
|
||||
dacLength(bits)
|
||||
{}
|
||||
|
||||
Dac::~Dac()
|
||||
{
|
||||
delete [] dac;
|
||||
}
|
||||
|
||||
double Dac::getOutput(unsigned int input) const
|
||||
{
|
||||
double dacValue = 0.;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
if ((input & (1 << i)) != 0)
|
||||
{
|
||||
dacValue += dac[i];
|
||||
}
|
||||
}
|
||||
|
||||
return dacValue;
|
||||
}
|
||||
|
||||
void Dac::kinkedDac(ChipModel chipModel)
|
||||
{
|
||||
const double R_INFINITY = 1e6;
|
||||
|
||||
// Non-linearity parameter, 8580 DACs are perfectly linear
|
||||
const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00;
|
||||
|
||||
// 6581 DACs are not terminated by a 2R resistor
|
||||
const bool term = chipModel == MOS8580;
|
||||
|
||||
// Calculate voltage contribution by each individual bit in the R-2R ladder.
|
||||
for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++)
|
||||
{
|
||||
double Vn = 1.; // Normalized bit voltage.
|
||||
double R = 1.; // Normalized R
|
||||
const double _2R = _2R_div_R * R; // 2R
|
||||
double Rn = term ? // Rn = 2R for correct termination,
|
||||
_2R : R_INFINITY; // INFINITY for missing termination.
|
||||
|
||||
unsigned int bit;
|
||||
|
||||
// Calculate DAC "tail" resistance by repeated parallel substitution.
|
||||
for (bit = 0; bit < set_bit; bit++)
|
||||
{
|
||||
Rn = (Rn == R_INFINITY) ?
|
||||
R + _2R :
|
||||
R + (_2R * Rn) / (_2R + Rn); // R + 2R || Rn
|
||||
}
|
||||
|
||||
// Source transformation for bit voltage.
|
||||
if (Rn == R_INFINITY)
|
||||
{
|
||||
Rn = _2R;
|
||||
}
|
||||
else
|
||||
{
|
||||
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||
Vn = Vn * Rn / _2R;
|
||||
}
|
||||
|
||||
// Calculate DAC output voltage by repeated source transformation from
|
||||
// the "tail".
|
||||
|
||||
for (++bit; bit < dacLength; bit++)
|
||||
{
|
||||
Rn += R;
|
||||
const double I = Vn / Rn;
|
||||
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||
Vn = Rn * I;
|
||||
}
|
||||
|
||||
dac[set_bit] = Vn;
|
||||
}
|
||||
|
||||
// Normalize to integerish behavior
|
||||
double Vsum = 0.;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
Vsum += dac[i];
|
||||
}
|
||||
|
||||
Vsum /= 1 << dacLength;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
dac[i] /= Vsum;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef DAC_H
|
||||
#define DAC_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Estimate DAC nonlinearity.
|
||||
* The SID DACs are built up as R-2R ladder as follows:
|
||||
*
|
||||
* n n-1 2 1 0 VGND
|
||||
* | | | | | | Termination
|
||||
* 2R 2R 2R 2R 2R 2R only for
|
||||
* | | | | | | MOS 8580
|
||||
* Vo -o-R-o-R-...-o-R-o-R-- --+
|
||||
*
|
||||
*
|
||||
* All MOS 6581 DACs are missing a termination resistor at bit 0. This causes
|
||||
* pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is
|
||||
* actually equal to the output for bit 1), resulting in DAC discontinuities
|
||||
* for the lower bits.
|
||||
* In addition to this, the 6581 DACs exhibit further severe discontinuities
|
||||
* for higher bits, which may be explained by a less than perfect match between
|
||||
* the R and 2R resistors, or by output impedance in the NMOS transistors
|
||||
* providing the bit voltages. A good approximation of the actual DAC output is
|
||||
* achieved for 2R/R ~ 2.20.
|
||||
*
|
||||
* The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities.
|
||||
* These DACs include the correct termination resistor, and also seem to have
|
||||
* very accurately matched R and 2R resistors (2R/R = 2.00).
|
||||
*
|
||||
* On the 6581 the output of the waveform and envelope DACs go through
|
||||
* a voltage follower built with two NMOS:
|
||||
*
|
||||
* Vdd
|
||||
*
|
||||
* |
|
||||
* |-+
|
||||
* Vin -------| T1 (enhancement-mode)
|
||||
* |-+
|
||||
* |
|
||||
* o-------- Vout
|
||||
* |
|
||||
* |-+
|
||||
* +---| T2 (depletion-mode)
|
||||
* | |-+
|
||||
* | |
|
||||
*
|
||||
* GND GND
|
||||
*/
|
||||
class Dac
|
||||
{
|
||||
private:
|
||||
/// analog values
|
||||
double * const dac;
|
||||
|
||||
/// the dac array length
|
||||
const unsigned int dacLength;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initialize DAC model.
|
||||
*
|
||||
* @param bits the number of input bits
|
||||
*/
|
||||
Dac(unsigned int bits);
|
||||
~Dac();
|
||||
|
||||
/**
|
||||
* Build DAC model for specific chip.
|
||||
*
|
||||
* @param chipModel 6581 or 8580
|
||||
*/
|
||||
void kinkedDac(ChipModel chipModel);
|
||||
|
||||
/**
|
||||
* Get the Vo output for a given combination of input bits.
|
||||
*
|
||||
* @param input the digital input
|
||||
* @return the analog output value
|
||||
*/
|
||||
double getOutput(unsigned int input) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2018 VICE Project
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define ENVELOPEGENERATOR_CPP
|
||||
|
||||
#include "EnvelopeGenerator.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Lookup table to convert from attack, decay, or release value to rate
|
||||
* counter period.
|
||||
*
|
||||
* The rate counter is a 15 bit register which is left shifted each cycle.
|
||||
* When the counter reaches a specific comparison value,
|
||||
* the envelope counter is incremented (attack) or decremented
|
||||
* (decay/release) and the rate counter is resetted.
|
||||
*
|
||||
* see [kevtris.org](http://blog.kevtris.org/?p=13)
|
||||
*/
|
||||
const unsigned int EnvelopeGenerator::adsrtable[16] =
|
||||
{
|
||||
0x007f,
|
||||
0x3000,
|
||||
0x1e00,
|
||||
0x0660,
|
||||
0x0182,
|
||||
0x5573,
|
||||
0x000e,
|
||||
0x3805,
|
||||
0x2424,
|
||||
0x2220,
|
||||
0x090c,
|
||||
0x0ecd,
|
||||
0x010e,
|
||||
0x23f7,
|
||||
0x5237,
|
||||
0x64a8
|
||||
};
|
||||
|
||||
void EnvelopeGenerator::reset()
|
||||
{
|
||||
// counter is not changed on reset
|
||||
envelope_pipeline = 0;
|
||||
|
||||
state_pipeline = 0;
|
||||
|
||||
attack = 0;
|
||||
decay = 0;
|
||||
sustain = 0;
|
||||
release = 0;
|
||||
|
||||
gate = false;
|
||||
|
||||
resetLfsr = true;
|
||||
|
||||
exponential_counter = 0;
|
||||
exponential_counter_period = 1;
|
||||
new_exponential_counter_period = 0;
|
||||
|
||||
state = RELEASE;
|
||||
counter_enabled = true;
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeCONTROL_REG(unsigned char control)
|
||||
{
|
||||
const bool gate_next = (control & 0x01) != 0;
|
||||
|
||||
if (gate_next != gate)
|
||||
{
|
||||
gate = gate_next;
|
||||
|
||||
// The rate counter is never reset, thus there will be a delay before the
|
||||
// envelope counter starts counting up (attack) or down (release).
|
||||
|
||||
if (gate_next)
|
||||
{
|
||||
// Gate bit on: Start attack, decay, sustain.
|
||||
next_state = ATTACK;
|
||||
state_pipeline = 2;
|
||||
|
||||
if (resetLfsr || (exponential_pipeline == 2))
|
||||
{
|
||||
envelope_pipeline = (exponential_counter_period == 1) || (exponential_pipeline == 2) ? 2 : 4;
|
||||
}
|
||||
else if (exponential_pipeline == 1)
|
||||
{
|
||||
state_pipeline = 3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gate bit off: Start release.
|
||||
next_state = RELEASE;
|
||||
state_pipeline = envelope_pipeline > 0 ? 3 : 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay)
|
||||
{
|
||||
attack = (attack_decay >> 4) & 0x0f;
|
||||
decay = attack_decay & 0x0f;
|
||||
|
||||
if (state == ATTACK)
|
||||
{
|
||||
rate = adsrtable[attack];
|
||||
}
|
||||
else if (state == DECAY_SUSTAIN)
|
||||
{
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release)
|
||||
{
|
||||
// From the sustain levels it follows that both the low and high 4 bits
|
||||
// of the envelope counter are compared to the 4-bit sustain value.
|
||||
// This has been verified by sampling ENV3.
|
||||
//
|
||||
// For a detailed description see:
|
||||
// http://ploguechipsounds.blogspot.it/2010/11/new-research-on-sid-adsr.html
|
||||
sustain = (sustain_release & 0xf0) | ((sustain_release >> 4) & 0x0f);
|
||||
|
||||
release = sustain_release & 0x0f;
|
||||
|
||||
if (state == RELEASE)
|
||||
{
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2018 VICE Project
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef ENVELOPEGENERATOR_H
|
||||
#define ENVELOPEGENERATOR_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* A 15 bit [LFSR] is used to implement the envelope rates, in effect dividing
|
||||
* the clock to the envelope counter by the currently selected rate period.
|
||||
*
|
||||
* In addition, another 5 bit counter is used to implement the exponential envelope decay,
|
||||
* in effect further dividing the clock to the envelope counter.
|
||||
* The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope counter
|
||||
* values 255, 93, 54, 26, 14, 6, respectively.
|
||||
*
|
||||
* [LFSR]: https://en.wikipedia.org/wiki/Linear_feedback_shift_register
|
||||
*/
|
||||
class EnvelopeGenerator
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The envelope state machine's distinct states. In addition to this,
|
||||
* envelope has a hold mode, which freezes envelope counter to zero.
|
||||
*/
|
||||
enum State
|
||||
{
|
||||
ATTACK, DECAY_SUSTAIN, RELEASE
|
||||
};
|
||||
|
||||
private:
|
||||
/// XOR shift register for ADSR prescaling.
|
||||
unsigned int lfsr;
|
||||
|
||||
/// Comparison value (period) of the rate counter before next event.
|
||||
unsigned int rate;
|
||||
|
||||
/**
|
||||
* During release mode, the SID approximates envelope decay via piecewise
|
||||
* linear decay rate.
|
||||
*/
|
||||
unsigned int exponential_counter;
|
||||
|
||||
/**
|
||||
* Comparison value (period) of the exponential decay counter before next
|
||||
* decrement.
|
||||
*/
|
||||
unsigned int exponential_counter_period;
|
||||
unsigned int new_exponential_counter_period;
|
||||
|
||||
unsigned int state_pipeline;
|
||||
|
||||
///
|
||||
unsigned int envelope_pipeline;
|
||||
|
||||
unsigned int exponential_pipeline;
|
||||
|
||||
/// Current envelope state
|
||||
State state;
|
||||
State next_state;
|
||||
|
||||
/// Whether counter is enabled. Only switching to ATTACK can release envelope.
|
||||
bool counter_enabled;
|
||||
|
||||
/// Gate bit
|
||||
bool gate;
|
||||
|
||||
///
|
||||
bool resetLfsr;
|
||||
|
||||
/// The current digital value of envelope output.
|
||||
unsigned char envelope_counter;
|
||||
|
||||
/// Attack register
|
||||
unsigned char attack;
|
||||
|
||||
/// Decay register
|
||||
unsigned char decay;
|
||||
|
||||
/// Sustain register
|
||||
unsigned char sustain;
|
||||
|
||||
/// Release register
|
||||
unsigned char release;
|
||||
|
||||
/// The ENV3 value, sampled at the first phase of the clock
|
||||
unsigned char env3;
|
||||
|
||||
private:
|
||||
static const unsigned int adsrtable[16];
|
||||
|
||||
private:
|
||||
void set_exponential_counter();
|
||||
|
||||
void state_change();
|
||||
|
||||
public:
|
||||
/**
|
||||
* SID clocking.
|
||||
*/
|
||||
void clock();
|
||||
|
||||
/**
|
||||
* Get the Envelope Generator digital output.
|
||||
*/
|
||||
unsigned int output() const { return envelope_counter; }
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
EnvelopeGenerator() :
|
||||
lfsr(0x7fff),
|
||||
rate(0),
|
||||
exponential_counter(0),
|
||||
exponential_counter_period(1),
|
||||
new_exponential_counter_period(0),
|
||||
state_pipeline(0),
|
||||
envelope_pipeline(0),
|
||||
exponential_pipeline(0),
|
||||
state(RELEASE),
|
||||
next_state(RELEASE),
|
||||
counter_enabled(true),
|
||||
gate(false),
|
||||
resetLfsr(false),
|
||||
envelope_counter(0xaa),
|
||||
attack(0),
|
||||
decay(0),
|
||||
sustain(0),
|
||||
release(0),
|
||||
env3(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Write control register.
|
||||
*
|
||||
* @param control
|
||||
* control register value
|
||||
*/
|
||||
void writeCONTROL_REG(unsigned char control);
|
||||
|
||||
/**
|
||||
* Write Attack/Decay register.
|
||||
*
|
||||
* @param attack_decay
|
||||
* attack/decay value
|
||||
*/
|
||||
void writeATTACK_DECAY(unsigned char attack_decay);
|
||||
|
||||
/**
|
||||
* Write Sustain/Release register.
|
||||
*
|
||||
* @param sustain_release
|
||||
* sustain/release value
|
||||
*/
|
||||
void writeSUSTAIN_RELEASE(unsigned char sustain_release);
|
||||
|
||||
/**
|
||||
* Return the envelope current value.
|
||||
*
|
||||
* @return envelope counter value
|
||||
*/
|
||||
unsigned char readENV() const { return env3; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(ENVELOPEGENERATOR_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::clock()
|
||||
{
|
||||
env3 = envelope_counter;
|
||||
|
||||
if (unlikely(new_exponential_counter_period > 0))
|
||||
{
|
||||
exponential_counter_period = new_exponential_counter_period;
|
||||
new_exponential_counter_period = 0;
|
||||
}
|
||||
|
||||
if (unlikely(state_pipeline))
|
||||
{
|
||||
state_change();
|
||||
}
|
||||
|
||||
if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0))
|
||||
{
|
||||
if (likely(counter_enabled))
|
||||
{
|
||||
if (state == ATTACK)
|
||||
{
|
||||
if (++envelope_counter==0xff)
|
||||
{
|
||||
next_state = DECAY_SUSTAIN;
|
||||
state_pipeline = 3;
|
||||
}
|
||||
}
|
||||
else if ((state == DECAY_SUSTAIN) || (state == RELEASE))
|
||||
{
|
||||
if (--envelope_counter==0x00)
|
||||
{
|
||||
counter_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
set_exponential_counter();
|
||||
}
|
||||
}
|
||||
else if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0))
|
||||
{
|
||||
exponential_counter = 0;
|
||||
|
||||
if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain))
|
||||
|| (state == RELEASE))
|
||||
{
|
||||
// The envelope counter can flip from 0x00 to 0xff by changing state to
|
||||
// attack, then to release. The envelope counter will then continue
|
||||
// counting down in the release state.
|
||||
// This has been verified by sampling ENV3.
|
||||
|
||||
envelope_pipeline = 1;
|
||||
}
|
||||
}
|
||||
else if (unlikely(resetLfsr))
|
||||
{
|
||||
lfsr = 0x7fff;
|
||||
resetLfsr = false;
|
||||
|
||||
if (state == ATTACK)
|
||||
{
|
||||
// The first envelope step in the attack state also resets the exponential
|
||||
// counter. This has been verified by sampling ENV3.
|
||||
exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled
|
||||
|
||||
// The envelope counter can flip from 0xff to 0x00 by changing state to
|
||||
// release, then to attack. The envelope counter is then frozen at
|
||||
// zero; to unlock this situation the state must be changed to release,
|
||||
// then to attack. This has been verified by sampling ENV3.
|
||||
|
||||
envelope_pipeline = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (counter_enabled && (++exponential_counter == exponential_counter_period))
|
||||
exponential_pipeline = exponential_counter_period != 1 ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ADSR delay bug.
|
||||
// If the rate counter comparison value is set below the current value of the
|
||||
// rate counter, the counter will continue counting up until it wraps around
|
||||
// to zero at 2^15 = 0x8000, and then count rate_period - 1 before the
|
||||
// envelope can constly be stepped.
|
||||
// This has been verified by sampling ENV3.
|
||||
|
||||
// check to see if LFSR matches table value
|
||||
if (likely(lfsr != rate))
|
||||
{
|
||||
// it wasn't a match, clock the LFSR once
|
||||
// by performing XOR on last 2 bits
|
||||
const unsigned int feedback = ((lfsr << 14) ^ (lfsr << 13)) & 0x4000;
|
||||
lfsr = (lfsr >> 1) | feedback;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetLfsr = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what happens on chip during state switching,
|
||||
* based on die reverse engineering and transistor level
|
||||
* emulation.
|
||||
*
|
||||
* Attack
|
||||
*
|
||||
* 0 - Gate on
|
||||
* 1 - Counting direction changes
|
||||
* During this cycle the decay rate is "accidentally" activated
|
||||
* 2 - Counter is being inverted
|
||||
* Now the attack rate is correctly activated
|
||||
* Counter is enabled
|
||||
* 3 - Counter will be counting upward from now on
|
||||
*
|
||||
* Decay
|
||||
*
|
||||
* 0 - Counter == $ff
|
||||
* 1 - Counting direction changes
|
||||
* The attack state is still active
|
||||
* 2 - Counter is being inverted
|
||||
* During this cycle the decay state is activated
|
||||
* 3 - Counter will be counting downward from now on
|
||||
*
|
||||
* Release
|
||||
*
|
||||
* 0 - Gate off
|
||||
* 1 - During this cycle the release state is activated if coming from sustain/decay
|
||||
* *2 - Counter is being inverted, the release state is activated
|
||||
* *3 - Counter will be counting downward from now on
|
||||
*
|
||||
* (* only if coming directly from Attack state)
|
||||
*
|
||||
* Freeze
|
||||
*
|
||||
* 0 - Counter == $00
|
||||
* 1 - Nothing
|
||||
* 2 - Counter is disabled
|
||||
*/
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::state_change()
|
||||
{
|
||||
state_pipeline--;
|
||||
|
||||
switch (next_state)
|
||||
{
|
||||
case ATTACK:
|
||||
if (state_pipeline == 1)
|
||||
{
|
||||
// The decay rate is "accidentally" enabled during first cycle of attack phase
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
else if (state_pipeline == 0)
|
||||
{
|
||||
state = ATTACK;
|
||||
// The attack rate is correctly enabled during second cycle of attack phase
|
||||
rate = adsrtable[attack];
|
||||
counter_enabled = true;
|
||||
}
|
||||
break;
|
||||
case DECAY_SUSTAIN:
|
||||
if (state_pipeline == 0)
|
||||
{
|
||||
state = DECAY_SUSTAIN;
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
break;
|
||||
case RELEASE:
|
||||
if (((state == ATTACK) && (state_pipeline == 0))
|
||||
|| ((state == DECAY_SUSTAIN) && (state_pipeline == 1)))
|
||||
{
|
||||
state = RELEASE;
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::set_exponential_counter()
|
||||
{
|
||||
// Check for change of exponential counter period.
|
||||
//
|
||||
// For a detailed description see:
|
||||
// http://ploguechipsounds.blogspot.it/2010/03/sid-6581r3-adsr-tables-up-close.html
|
||||
switch (envelope_counter)
|
||||
{
|
||||
case 0xff:
|
||||
case 0x00:
|
||||
new_exponential_counter_period = 1;
|
||||
break;
|
||||
|
||||
case 0x5d:
|
||||
new_exponential_counter_period = 2;
|
||||
break;
|
||||
|
||||
case 0x36:
|
||||
new_exponential_counter_period = 4;
|
||||
break;
|
||||
|
||||
case 0x1a:
|
||||
new_exponential_counter_period = 8;
|
||||
break;
|
||||
|
||||
case 0x0e:
|
||||
new_exponential_counter_period = 16;
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
new_exponential_counter_period = 30;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define EXTERNALFILTER_CPP
|
||||
|
||||
#include "ExternalFilter.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the 3 dB attenuation point.
|
||||
*
|
||||
* @param res the resistance value in Ohms
|
||||
* @param cap the capacitance value in Farads
|
||||
*/
|
||||
inline double getRC(double res, double cap)
|
||||
{
|
||||
return res * cap;
|
||||
}
|
||||
|
||||
ExternalFilter::ExternalFilter() :
|
||||
w0lp_1_s7(0),
|
||||
w0hp_1_s17(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void ExternalFilter::setClockFrequency(double frequency)
|
||||
{
|
||||
const double dt = 1. / frequency;
|
||||
|
||||
// Low-pass: R = 10kOhm, C = 1000pF; w0l = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-9) = 0.091
|
||||
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-9 = 15915.5 Hz
|
||||
w0lp_1_s7 = static_cast<int>((dt / (dt + getRC(10e3, 1000e-12))) * (1 << 7) + 0.5);
|
||||
|
||||
// High-pass: R = 10kOhm, C = 10uF; w0h = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-5) = 0.00000999
|
||||
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-5 = 1.59155 Hz
|
||||
w0hp_1_s17 = static_cast<int>((dt / (dt + getRC(10e3, 10e-6))) * (1 << 17) + 0.5);
|
||||
}
|
||||
|
||||
void ExternalFilter::reset()
|
||||
{
|
||||
// State of filter.
|
||||
Vlp = 0; //1 << (15 + 11);
|
||||
Vhp = 0;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef EXTERNALFILTER_H
|
||||
#define EXTERNALFILTER_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* The audio output stage in a Commodore 64 consists of two STC networks, a
|
||||
* low-pass RC filter with 3 dB frequency 16kHz followed by a DC-blocker which
|
||||
* acts as a high-pass filter with a cutoff dependent on the attached audio
|
||||
* equipment impedance. Here we suppose an impedance of 10kOhm resulting
|
||||
* in a 3 dB attenuation at 1.6Hz.
|
||||
* To operate properly the 6581 audio output needs a pull-down resistor
|
||||
*(1KOhm recommended, not needed on 8580)
|
||||
*
|
||||
* ~~~
|
||||
* 9/12V
|
||||
* -----+
|
||||
* audio| 10k |
|
||||
* +---o----R---o--------o-----(K) +-----
|
||||
* out | | | | | |audio
|
||||
* -----+ R 1k C 1000 | | 10 uF |
|
||||
* | | pF +-C----o-----C-----+ 10k
|
||||
* 470 | |
|
||||
* GND GND pF R 1K | amp
|
||||
* * * | +-----
|
||||
*
|
||||
* GND
|
||||
* ~~~
|
||||
*
|
||||
* The STC networks are connected with a [BJT] based [common collector]
|
||||
* used as a voltage follower (featuring a 2SC1815 NPN transistor).
|
||||
* * The C64c board additionally includes a [bootstrap] condenser to increase
|
||||
* the input impedance of the common collector.
|
||||
*
|
||||
* [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor
|
||||
* [common collector]: https://en.wikipedia.org/wiki/Common_collector
|
||||
* [bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_(electronics)
|
||||
*/
|
||||
class ExternalFilter
|
||||
{
|
||||
private:
|
||||
/// Lowpass filter voltage
|
||||
int Vlp;
|
||||
|
||||
/// Highpass filter voltage
|
||||
int Vhp;
|
||||
|
||||
int w0lp_1_s7;
|
||||
|
||||
int w0hp_1_s17;
|
||||
|
||||
public:
|
||||
/**
|
||||
* SID clocking.
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
int clock(unsigned short input);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ExternalFilter();
|
||||
|
||||
/**
|
||||
* Setup of the external filter sampling parameters.
|
||||
*
|
||||
* @param frequency the main system clock frequency
|
||||
*/
|
||||
void setClockFrequency(double frequency);
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(EXTERNALFILTER_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int ExternalFilter::clock(unsigned short input)
|
||||
{
|
||||
const int Vi = (static_cast<unsigned int>(input)<<11) - (1 << (11+15));
|
||||
const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7);
|
||||
const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17);
|
||||
Vlp += dVlp;
|
||||
Vhp += dVhp;
|
||||
return (Vlp - Vhp) >> 11;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "Filter.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
void Filter::enable(bool enable)
|
||||
{
|
||||
enabled = enable;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
writeRES_FILT(filt);
|
||||
}
|
||||
else
|
||||
{
|
||||
filt1 = filt2 = filt3 = filtE = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Filter::reset()
|
||||
{
|
||||
writeFC_LO(0);
|
||||
writeFC_HI(0);
|
||||
writeMODE_VOL(0);
|
||||
writeRES_FILT(0);
|
||||
}
|
||||
|
||||
void Filter::writeFC_LO(unsigned char fc_lo)
|
||||
{
|
||||
fc = (fc & 0x7f8) | (fc_lo & 0x007);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
void Filter::writeFC_HI(unsigned char fc_hi)
|
||||
{
|
||||
fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
void Filter::writeRES_FILT(unsigned char res_filt)
|
||||
{
|
||||
filt = res_filt;
|
||||
|
||||
updateResonance((res_filt >> 4) & 0x0f);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
filt1 = (filt & 0x01) != 0;
|
||||
filt2 = (filt & 0x02) != 0;
|
||||
filt3 = (filt & 0x04) != 0;
|
||||
filtE = (filt & 0x08) != 0;
|
||||
}
|
||||
|
||||
updatedMixing();
|
||||
}
|
||||
|
||||
void Filter::writeMODE_VOL(unsigned char mode_vol)
|
||||
{
|
||||
vol = mode_vol & 0x0f;
|
||||
lp = (mode_vol & 0x10) != 0;
|
||||
bp = (mode_vol & 0x20) != 0;
|
||||
hp = (mode_vol & 0x40) != 0;
|
||||
voice3off = (mode_vol & 0x80) != 0;
|
||||
|
||||
updatedMixing();
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2017 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTER_H
|
||||
#define FILTER_H
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* SID filter base class
|
||||
*/
|
||||
class Filter
|
||||
{
|
||||
protected:
|
||||
/// Current volume amplifier setting.
|
||||
unsigned short* currentGain;
|
||||
|
||||
/// Current filter/voice mixer setting.
|
||||
unsigned short* currentMixer;
|
||||
|
||||
/// Filter input summer setting.
|
||||
unsigned short* currentSummer;
|
||||
|
||||
/// Filter resonance value.
|
||||
unsigned short* currentResonance;
|
||||
|
||||
/// Filter highpass state.
|
||||
int Vhp;
|
||||
|
||||
/// Filter bandpass state.
|
||||
int Vbp;
|
||||
|
||||
/// Filter lowpass state.
|
||||
int Vlp;
|
||||
|
||||
/// Filter external input.
|
||||
int ve;
|
||||
|
||||
/// Filter cutoff frequency.
|
||||
unsigned int fc;
|
||||
|
||||
/// Routing to filter or outside filter
|
||||
bool filt1, filt2, filt3, filtE;
|
||||
|
||||
/// Switch voice 3 off.
|
||||
bool voice3off;
|
||||
|
||||
/// Highpass, bandpass, and lowpass filter modes.
|
||||
bool hp, bp, lp;
|
||||
|
||||
/// Current volume.
|
||||
unsigned char vol;
|
||||
|
||||
private:
|
||||
/// Filter enabled.
|
||||
bool enabled;
|
||||
|
||||
/// Selects which inputs to route through filter.
|
||||
unsigned char filt;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
virtual void updatedCenterFrequency() = 0;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*/
|
||||
virtual void updateResonance(unsigned char res) = 0;
|
||||
|
||||
/**
|
||||
* Mixing configuration modified (offsets change)
|
||||
*/
|
||||
virtual void updatedMixing() = 0;
|
||||
|
||||
public:
|
||||
Filter() :
|
||||
currentGain(nullptr),
|
||||
currentMixer(nullptr),
|
||||
currentSummer(nullptr),
|
||||
currentResonance(nullptr),
|
||||
Vhp(0),
|
||||
Vbp(0),
|
||||
Vlp(0),
|
||||
ve(0),
|
||||
fc(0),
|
||||
filt1(false),
|
||||
filt2(false),
|
||||
filt3(false),
|
||||
filtE(false),
|
||||
voice3off(false),
|
||||
hp(false),
|
||||
bp(false),
|
||||
lp(false),
|
||||
vol(0),
|
||||
enabled(true),
|
||||
filt(0) {}
|
||||
|
||||
virtual ~Filter() {}
|
||||
|
||||
/**
|
||||
* SID clocking - 1 cycle
|
||||
*
|
||||
* @param v1 voice 1 in
|
||||
* @param v2 voice 2 in
|
||||
* @param v3 voice 3 in
|
||||
* @return filtered output
|
||||
*/
|
||||
virtual unsigned short clock(int v1, int v2, int v3) = 0;
|
||||
|
||||
/**
|
||||
* Enable filter.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
void enable(bool enable);
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Write Frequency Cutoff Low register.
|
||||
*
|
||||
* @param fc_lo Frequency Cutoff Low-Byte
|
||||
*/
|
||||
void writeFC_LO(unsigned char fc_lo);
|
||||
|
||||
/**
|
||||
* Write Frequency Cutoff High register.
|
||||
*
|
||||
* @param fc_hi Frequency Cutoff High-Byte
|
||||
*/
|
||||
void writeFC_HI(unsigned char fc_hi);
|
||||
|
||||
/**
|
||||
* Write Resonance/Filter register.
|
||||
*
|
||||
* @param res_filt Resonance/Filter
|
||||
*/
|
||||
void writeRES_FILT(unsigned char res_filt);
|
||||
|
||||
/**
|
||||
* Write filter Mode/Volume register.
|
||||
*
|
||||
* @param mode_vol Filter Mode/Volume
|
||||
*/
|
||||
void writeMODE_VOL(unsigned char mode_vol);
|
||||
|
||||
virtual void input(int input) = 0;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define FILTER6581_CPP
|
||||
|
||||
#include "Filter6581.h"
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Filter6581::~Filter6581()
|
||||
{
|
||||
delete [] f0_dac;
|
||||
}
|
||||
|
||||
void Filter6581::updatedCenterFrequency()
|
||||
{
|
||||
const unsigned short Vw = f0_dac[fc];
|
||||
hpIntegrator->setVw(Vw);
|
||||
bpIntegrator->setVw(Vw);
|
||||
}
|
||||
|
||||
void Filter6581::updatedMixing()
|
||||
{
|
||||
currentGain = gain_vol[vol];
|
||||
|
||||
unsigned int ni = 0;
|
||||
unsigned int no = 0;
|
||||
|
||||
(filt1 ? ni : no)++;
|
||||
(filt2 ? ni : no)++;
|
||||
|
||||
if (filt3) ni++;
|
||||
else if (!voice3off) no++;
|
||||
|
||||
(filtE ? ni : no)++;
|
||||
|
||||
currentSummer = summer[ni];
|
||||
|
||||
if (lp) no++;
|
||||
if (bp) no++;
|
||||
if (hp) no++;
|
||||
|
||||
currentMixer = mixer[no];
|
||||
}
|
||||
|
||||
void Filter6581::setFilterCurve(double curvePosition)
|
||||
{
|
||||
delete [] f0_dac;
|
||||
f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTER6581_H
|
||||
#define FILTER6581_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "FilterModelConfig6581.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator6581;
|
||||
|
||||
/**
|
||||
* The SID filter is modeled with a two-integrator-loop biquadratic filter,
|
||||
* which has been confirmed by Bob Yannes to be the actual circuit used in
|
||||
* the SID chip.
|
||||
*
|
||||
* Measurements show that excellent emulation of the SID filter is achieved,
|
||||
* except when high resonance is combined with high sustain levels.
|
||||
* In this case the SID op-amps are performing less than ideally and are
|
||||
* causing some peculiar behavior of the SID filter. This however seems to
|
||||
* have more effect on the overall amplitude than on the color of the sound.
|
||||
*
|
||||
* The theory for the filter circuit can be found in "Microelectric Circuits"
|
||||
* by Adel S. Sedra and Kenneth C. Smith.
|
||||
* The circuit is modeled based on the explanation found there except that
|
||||
* an additional inverter is used in the feedback from the bandpass output,
|
||||
* allowing the summer op-amp to operate in single-ended mode. This yields
|
||||
* filter outputs with levels independent of Q, which corresponds with the
|
||||
* results obtained from a real SID.
|
||||
*
|
||||
* We have been able to model the summer and the two integrators of the circuit
|
||||
* to form components of an IIR filter.
|
||||
* Vhp is the output of the summer, Vbp is the output of the first integrator,
|
||||
* and Vlp is the output of the second integrator in the filter circuit.
|
||||
*
|
||||
* According to Bob Yannes, the active stages of the SID filter are not really
|
||||
* op-amps. Rather, simple NMOS inverters are used. By biasing an inverter
|
||||
* into its region of quasi-linear operation using a feedback resistor from
|
||||
* input to output, a MOS inverter can be made to act like an op-amp for
|
||||
* small signals centered around the switching threshold.
|
||||
*
|
||||
* In 2008, Michael Huth facilitated closer investigation of the SID 6581
|
||||
* filter circuit by publishing high quality microscope photographs of the die.
|
||||
* Tommi Lempinen has done an impressive work on re-vectorizing and annotating
|
||||
* the die photographs, substantially simplifying further analysis of the
|
||||
* filter circuit.
|
||||
*
|
||||
* The filter schematics below are reverse engineered from these re-vectorized
|
||||
* and annotated die photographs. While the filter first depicted in reSID 0.9
|
||||
* is a correct model of the basic filter, the schematics are now completed
|
||||
* with the audio mixer and output stage, including details on intended
|
||||
* relative resistor values. Also included are schematics for the NMOS FET
|
||||
* voltage controlled resistors (VCRs) used to control cutoff frequency, the
|
||||
* DAC which controls the VCRs, the NMOS op-amps, and the output buffer.
|
||||
*
|
||||
*
|
||||
* SID filter / mixer / output
|
||||
* ---------------------------
|
||||
* ~~~
|
||||
* +---------------------------------------------------+
|
||||
* | |
|
||||
* | +--1R1-- \--+ D7 |
|
||||
* | +---R1--+ | | |
|
||||
* | | | o--2R1-- \--o D6 |
|
||||
* | +---------o--<A]--o--o | $17 |
|
||||
* | | o--4R1-- \--o D5 1=open | (3.5R1)
|
||||
* | | | | |
|
||||
* | | +--8R1-- \--o D4 | (7.0R1)
|
||||
* | | | |
|
||||
* $17 | | (CAP2B) | (CAP1B) |
|
||||
* 0=to mixer | +--R8--+ +---R8--+ +---C---o +---C---o
|
||||
* 1=to filter | | | | | | | |
|
||||
* ------R8--o--o--[A>--o--Rw--o--[A>--o--Rw--o--[A>--o
|
||||
* ve (EXT IN) | | | |
|
||||
* D3 \ ---------------R8--o | | (CAP2A) | (CAP1A)
|
||||
* | v3 | | vhp | vbp | vlp
|
||||
* D2 | \ -----------R8--o +-----+ | |
|
||||
* | | v2 | | | |
|
||||
* D1 | | \ -------R8--o | +----------------+ |
|
||||
* | | | v1 | | | |
|
||||
* D0 | | | \ ---R8--+ | | +---------------------------+
|
||||
* | | | | | | |
|
||||
* R6 R6 R6 R6 R6 R6 R6
|
||||
* | | | | $18 | | | $18
|
||||
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||
* | | | | | | |
|
||||
* +---o---o---o-------------o---o---+ 12V
|
||||
* |
|
||||
* | D3 +--/ --1R2--+ |
|
||||
* | +---R8--+ | | +---R2--+ |
|
||||
* | | | D2 o--/ --2R2--o | | ||--+
|
||||
* +---o--[A>--o------o o--o--[A>--o--||
|
||||
* D1 o--/ --4R2--o (4.25R2) ||--+
|
||||
* $18 | | |
|
||||
* 0=open D0 +--/ --8R2--+ (8.75R2) |
|
||||
*
|
||||
* vo (AUDIO
|
||||
* OUT)
|
||||
*
|
||||
*
|
||||
* v1 - voice 1
|
||||
* v2 - voice 2
|
||||
* v3 - voice 3
|
||||
* ve - ext in
|
||||
* vhp - highpass output
|
||||
* vbp - bandpass output
|
||||
* vlp - lowpass output
|
||||
* vo - audio out
|
||||
* [A> - single ended inverting op-amp (self-biased NMOS inverter)
|
||||
* Rn - "resistors", implemented with custom NMOS FETs
|
||||
* Rw - cutoff frequency resistor (VCR)
|
||||
* C - capacitor
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* R2 ~ 2.0*R1
|
||||
* R6 ~ 6.0*R1
|
||||
* R8 ~ 8.0*R1
|
||||
* R24 ~ 24.0*R1
|
||||
*
|
||||
* The Rn "resistors" in the circuit are implemented with custom NMOS FETs,
|
||||
* probably because of space constraints on the SID die. The silicon substrate
|
||||
* is laid out in a narrow strip or "snake", with a strip length proportional
|
||||
* to the intended resistance. The polysilicon gate electrode covers the entire
|
||||
* silicon substrate and is fixed at 12V in order for the NMOS FET to operate
|
||||
* in triode mode (a.k.a. linear mode or ohmic mode).
|
||||
*
|
||||
* Even in "linear mode", an NMOS FET is only an approximation of a resistor,
|
||||
* as the apparant resistance increases with increasing drain-to-source
|
||||
* voltage. If the drain-to-source voltage should approach the gate voltage
|
||||
* of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and
|
||||
* the NMOS FET will not operate anywhere like a resistor.
|
||||
*
|
||||
*
|
||||
*
|
||||
* NMOS FET voltage controlled resistor (VCR)
|
||||
* ------------------------------------------
|
||||
* ~~~
|
||||
* Vw
|
||||
*
|
||||
* |
|
||||
* |
|
||||
* R1
|
||||
* |
|
||||
* +--R1--o
|
||||
* | __|__
|
||||
* | -----
|
||||
* | | |
|
||||
* vi -----o----+ +--o----- vo
|
||||
* | |
|
||||
* +----R24----+
|
||||
*
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* Rn - "resistors", implemented with custom NMOS FETs
|
||||
* Vw - voltage from 11-bit DAC (frequency cutoff control)
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* An approximate value for R24 can be found by using the formula for the
|
||||
* filter cutoff frequency:
|
||||
*
|
||||
* FCmin = 1/(2*pi*Rmax*C)
|
||||
*
|
||||
* Assuming that a the setting for minimum cutoff frequency in combination with
|
||||
* a low level input signal ensures that only negligible current will flow
|
||||
* through the transistor in the schematics above, values for FCmin and C can
|
||||
* be substituted in this formula to find Rmax.
|
||||
* Using C = 470pF and FCmin = 220Hz (measured value), we get:
|
||||
*
|
||||
* FCmin = 1/(2*pi*Rmax*C)
|
||||
* Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm
|
||||
*
|
||||
* From this it follows that:
|
||||
* R24 = Rmax ~ 1.5MOhm
|
||||
* R1 ~ R24/24 ~ 64kOhm
|
||||
* R2 ~ 2.0*R1 ~ 128kOhm
|
||||
* R6 ~ 6.0*R1 ~ 384kOhm
|
||||
* R8 ~ 8.0*R1 ~ 512kOhm
|
||||
*
|
||||
* Note that these are only approximate values for one particular SID chip,
|
||||
* due to process variations the values can be substantially different in
|
||||
* other chips.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Filter frequency cutoff DAC
|
||||
* ---------------------------
|
||||
*
|
||||
* ~~~
|
||||
* 12V 10 9 8 7 6 5 4 3 2 1 0 VGND
|
||||
* | | | | | | | | | | | | | Missing
|
||||
* 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination
|
||||
* | | | | | | | | | | | | |
|
||||
* Vw --o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o- -+
|
||||
*
|
||||
*
|
||||
* Bit on: 12V
|
||||
* Bit off: 5V (VGND)
|
||||
* ~~~
|
||||
* As is the case with all MOS 6581 DACs, the termination to (virtual) ground
|
||||
* at bit 0 is missing.
|
||||
*
|
||||
* Furthermore, the control of the two VCRs imposes a load on the DAC output
|
||||
* which varies with the input signals to the VCRs. This can be seen from the
|
||||
* VCR figure above.
|
||||
*
|
||||
*
|
||||
*
|
||||
* "Op-amp" (self-biased NMOS inverter)
|
||||
* ------------------------------------
|
||||
* ~~~
|
||||
*
|
||||
* 12V
|
||||
*
|
||||
* |
|
||||
* +-----------o
|
||||
* | |
|
||||
* | +------o
|
||||
* | | |
|
||||
* | | ||--+
|
||||
* | +--||
|
||||
* | ||--+
|
||||
* ||--+ |
|
||||
* vi -----|| o---o----- vo
|
||||
* ||--+ | |
|
||||
* | ||--+ |
|
||||
* |-------|| |
|
||||
* | ||--+ |
|
||||
* ||--+ | |
|
||||
* +--|| | |
|
||||
* | ||--+ | |
|
||||
* | | | |
|
||||
* | +-----------o |
|
||||
* | | |
|
||||
* | |
|
||||
* | GND |
|
||||
* | |
|
||||
* +----------------------+
|
||||
*
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* The schematics above are laid out to show that the "op-amp" logically
|
||||
* consists of two building blocks; a saturated load NMOS inverter (on the
|
||||
* right hand side of the schematics) with a buffer / bias input stage
|
||||
* consisting of a variable saturated load NMOS inverter (on the left hand
|
||||
* side of the schematics).
|
||||
*
|
||||
* Provided a reasonably high input impedance and a reasonably low output
|
||||
* impedance, the "op-amp" can be modeled as a voltage transfer function
|
||||
* mapping input voltage to output voltage.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Output buffer (NMOS voltage follower)
|
||||
* -------------------------------------
|
||||
* ~~~
|
||||
*
|
||||
* 12V
|
||||
*
|
||||
* |
|
||||
* |
|
||||
* ||--+
|
||||
* vi -----||
|
||||
* ||--+
|
||||
* |
|
||||
* o------ vo
|
||||
* | (AUDIO
|
||||
* Rext OUT)
|
||||
* |
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* Rext - external resistor, 1kOhm
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* The external resistor Rext is needed to complete the NMOS voltage follower,
|
||||
* this resistor has a recommended value of 1kOhm.
|
||||
*
|
||||
* Die photographs show that actually, two NMOS transistors are used in the
|
||||
* voltage follower. However the two transistors are coupled in parallel (all
|
||||
* terminals are pairwise common), which implies that we can model the two
|
||||
* transistors as one.
|
||||
*/
|
||||
class Filter6581 final : public Filter
|
||||
{
|
||||
private:
|
||||
const unsigned short* f0_dac;
|
||||
|
||||
unsigned short** mixer;
|
||||
unsigned short** summer;
|
||||
unsigned short** gain_res;
|
||||
unsigned short** gain_vol;
|
||||
|
||||
const int voiceScaleS11;
|
||||
const int voiceDC;
|
||||
|
||||
/// VCR + associated capacitor connected to highpass output.
|
||||
std::unique_ptr<Integrator6581> const hpIntegrator;
|
||||
|
||||
/// VCR + associated capacitor connected to bandpass output.
|
||||
std::unique_ptr<Integrator6581> const bpIntegrator;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
void updatedCenterFrequency() override;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*
|
||||
* In the MOS 6581, 1/Q is controlled linearly by res.
|
||||
*/
|
||||
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||
|
||||
void updatedMixing() override;
|
||||
|
||||
public:
|
||||
Filter6581() :
|
||||
f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)),
|
||||
mixer(FilterModelConfig6581::getInstance()->getMixer()),
|
||||
summer(FilterModelConfig6581::getInstance()->getSummer()),
|
||||
gain_res(FilterModelConfig6581::getInstance()->getGainRes()),
|
||||
gain_vol(FilterModelConfig6581::getInstance()->getGainVol()),
|
||||
voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()),
|
||||
voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()),
|
||||
hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()),
|
||||
bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator())
|
||||
{
|
||||
input(0);
|
||||
}
|
||||
|
||||
~Filter6581();
|
||||
|
||||
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||
|
||||
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||
|
||||
/**
|
||||
* Set filter curve type based on single parameter.
|
||||
*
|
||||
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||
*/
|
||||
void setFilterCurve(double curvePosition);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(FILTER6581_CPP)
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
unsigned short Filter6581::clock(int voice1, int voice2, int voice3)
|
||||
{
|
||||
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||
|
||||
int Vi = 0;
|
||||
int Vo = 0;
|
||||
|
||||
(filt1 ? Vi : Vo) += voice1;
|
||||
(filt2 ? Vi : Vo) += voice2;
|
||||
(filt3 ? Vi : Vo) += voice3;
|
||||
(filtE ? Vi : Vo) += ve;
|
||||
|
||||
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||
Vbp = hpIntegrator->solve(Vhp);
|
||||
Vlp = bpIntegrator->solve(Vbp);
|
||||
|
||||
if (lp) Vo += Vlp;
|
||||
if (bp) Vo += Vbp;
|
||||
if (hp) Vo += Vhp;
|
||||
|
||||
return currentGain[currentMixer[Vo]];
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2019 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define FILTER8580_CPP
|
||||
|
||||
#include "Filter8580.h"
|
||||
|
||||
#include "Integrator8580.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* W/L ratio of frequency DAC bit 0,
|
||||
* other bit are proportional.
|
||||
* When no bit are selected a resistance with half
|
||||
* W/L ratio is selected.
|
||||
*/
|
||||
const double DAC_WL0 = 0.00615;
|
||||
|
||||
Filter8580::~Filter8580() {}
|
||||
|
||||
void Filter8580::updatedCenterFrequency()
|
||||
{
|
||||
double wl;
|
||||
double dacWL = DAC_WL0;
|
||||
if (fc)
|
||||
{
|
||||
wl = 0.;
|
||||
for (unsigned int i = 0; i < 11; i++)
|
||||
{
|
||||
if (fc & (1 << i))
|
||||
{
|
||||
wl += dacWL;
|
||||
}
|
||||
dacWL *= 2.;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wl = dacWL/2.;
|
||||
}
|
||||
|
||||
hpIntegrator->setFc(wl);
|
||||
bpIntegrator->setFc(wl);
|
||||
}
|
||||
|
||||
void Filter8580::updatedMixing()
|
||||
{
|
||||
currentGain = gain_vol[vol];
|
||||
|
||||
unsigned int ni = 0;
|
||||
unsigned int no = 0;
|
||||
|
||||
(filt1 ? ni : no)++;
|
||||
(filt2 ? ni : no)++;
|
||||
|
||||
if (filt3) ni++;
|
||||
else if (!voice3off) no++;
|
||||
|
||||
(filtE ? ni : no)++;
|
||||
|
||||
currentSummer = summer[ni];
|
||||
|
||||
if (lp) no++;
|
||||
if (bp) no++;
|
||||
if (hp) no++;
|
||||
|
||||
currentMixer = mixer[no];
|
||||
}
|
||||
|
||||
void Filter8580::setFilterCurve(double curvePosition)
|
||||
{
|
||||
// Adjust cp
|
||||
// 1.2 <= cp <= 1.8
|
||||
cp = 1.8 - curvePosition * 3./5.;
|
||||
|
||||
hpIntegrator->setV(cp);
|
||||
bpIntegrator->setV(cp);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTER8580_H
|
||||
#define FILTER8580_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "FilterModelConfig8580.h"
|
||||
#include "Integrator8580.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator8580;
|
||||
|
||||
/**
|
||||
* Filter for 8580 chip
|
||||
* --------------------
|
||||
* The 8580 filter stage had been redesigned to be more linear and robust
|
||||
* against temperature change. It also features real op-amps and a
|
||||
* revisited resonance model.
|
||||
* The filter schematics below are reverse engineered from re-vectorized
|
||||
* and annotated die photographs. Credits to Michael Huth for the microscope
|
||||
* photographs of the die, Tommi Lempinen for re-vectorizating and annotating
|
||||
* the images and ttlworks from forum.6502.org for the circuit analysis.
|
||||
*
|
||||
* ~~~
|
||||
*
|
||||
* +---------------------------------------------------+
|
||||
* | $17 +----Rf-+ |
|
||||
* | | | |
|
||||
* | D4&!D5 o- \-R3-o |
|
||||
* | | | $17 |
|
||||
* | !D4&!D5 o- \-R2-o |
|
||||
* | | | +---R8-- \--+ !D6&D7 |
|
||||
* | D4&!D5 o- \-R1-o | | |
|
||||
* | | | o---RC-- \--o D6&D7 |
|
||||
* | +---------o--<A]--o--o | |
|
||||
* | | o---R4-- \--o D6&!D7 |
|
||||
* | | | | |
|
||||
* | | +---Ri-- \--o !D6&!D7 |
|
||||
* | | | |
|
||||
* $17 | | (CAP2B) | (CAP1B) |
|
||||
* 0=to mixer | +--R7--+ +---R7--+ +---C---o +---C---o
|
||||
* 1=to filter | | | | | | | |
|
||||
* +------R7--o--o--[A>--o--Rfc-o--[A>--o--Rfc-o--[A>--o
|
||||
* ve (EXT IN) | | | |
|
||||
* D3 \ --------------R12--o | | (CAP2A) | (CAP1A)
|
||||
* | v3 | | vhp | vbp | vlp
|
||||
* D2 | \ -----------R7--o +-----+ | |
|
||||
* | | v2 | | | |
|
||||
* D1 | | \ -------R7--o | +----------------+ |
|
||||
* | | | v1 | | | |
|
||||
* D0 | | | \ ---R7--+ | | +---------------------------+
|
||||
* | | | | | | |
|
||||
* R9 R5 R5 R5 R5 R5 R5
|
||||
* | | | | $18 | | | $18
|
||||
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||
* | | | | | | |
|
||||
* +---o---o---o-------------o---o---+
|
||||
* |
|
||||
* | D3 +--/ --1R4--+
|
||||
* | +---R8--+ | | +---R2--+
|
||||
* | | | D2 o--/ --2R4--o | |
|
||||
* +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT)
|
||||
* D1 o--/ --4R4--o
|
||||
* $18 | |
|
||||
* 0=open D0 +--/ --8R4--+
|
||||
*
|
||||
*
|
||||
*
|
||||
* Resonance
|
||||
* ---------
|
||||
* For resonance, we have two tiny DACs that controls both the input
|
||||
* and feedback resistances.
|
||||
*
|
||||
* The "resistors" are switched in as follows by bits in register $17:
|
||||
*
|
||||
* feedback:
|
||||
* R1: bit4&!bit5
|
||||
* R2: !bit4&bit5
|
||||
* R3: bit4&bit5
|
||||
* Rf: always on
|
||||
*
|
||||
* input:
|
||||
* R4: bit6&!bit7
|
||||
* R8: !bit6&bit7
|
||||
* RC: bit6&bit7
|
||||
* Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7
|
||||
*
|
||||
*
|
||||
* The relative "resistor" values are approximately (using channel length):
|
||||
*
|
||||
* R1 = 15.3*Ri
|
||||
* R2 = 7.3*Ri
|
||||
* R3 = 4.7*Ri
|
||||
* Rf = 1.4*Ri
|
||||
* R4 = 1.4*Ri
|
||||
* R8 = 2.0*Ri
|
||||
* RC = 2.8*Ri
|
||||
*
|
||||
*
|
||||
* Approximate values for 1/Q can now be found as follows (assuming an
|
||||
* ideal op-amp):
|
||||
*
|
||||
* res feedback input -gain (1/Q)
|
||||
* --- -------- ----- ----------
|
||||
* 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71
|
||||
* 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78
|
||||
* 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85
|
||||
* 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92
|
||||
* 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00
|
||||
* 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10
|
||||
* 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20
|
||||
* 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30
|
||||
* 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43
|
||||
* 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56
|
||||
* A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70
|
||||
* B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86
|
||||
* C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00
|
||||
* D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18
|
||||
* E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38
|
||||
* F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60
|
||||
*
|
||||
*
|
||||
* These data indicate that the following function for 1/Q has been
|
||||
* modeled in the MOS 8580:
|
||||
*
|
||||
* 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8)
|
||||
*
|
||||
*
|
||||
*
|
||||
* Op-amps
|
||||
* -------
|
||||
* Unlike the 6581, the 8580 has real OpAmps.
|
||||
*
|
||||
* Temperature compensated differential amplifier:
|
||||
*
|
||||
* 9V
|
||||
*
|
||||
* |
|
||||
* +-------o-o-o-------+
|
||||
* | | | |
|
||||
* | R R |
|
||||
* +--|| | | ||--+
|
||||
* ||---o o---||
|
||||
* +--|| | | ||--+
|
||||
* | | | |
|
||||
* o-----+ | | o--- Va
|
||||
* | | | | |
|
||||
* +--|| | | | ||--+
|
||||
* ||-o-+---+---||
|
||||
* +--|| | | ||--+
|
||||
* | | | |
|
||||
* | |
|
||||
* GND | | GND
|
||||
* ||--+ +--||
|
||||
* in- -----|| ||------ in+
|
||||
* ||----o----||
|
||||
* |
|
||||
* 8 Current sink
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* Inverter + non-inverting output amplifier:
|
||||
*
|
||||
* Va ---o---||-------------------o--------------------+
|
||||
* | | 9V |
|
||||
* | +----------+----------+ | |
|
||||
* | 9V | | 9V | ||--+ |
|
||||
* | | | 9V | | +-|| |
|
||||
* | R | | | ||--+ ||--+ |
|
||||
* | | | ||--+ +--|| o---o--- Vout
|
||||
* | o---o---|| ||--+ ||--+
|
||||
* | | ||--+ o-----||
|
||||
* | ||--+ | ||--+ ||--+
|
||||
* +-----|| o-----|| |
|
||||
* ||--+ | ||--+
|
||||
* | R | GND
|
||||
* |
|
||||
* GND GND
|
||||
* GND
|
||||
*
|
||||
*
|
||||
*
|
||||
* Virtual ground
|
||||
* --------------
|
||||
* A PolySi resitive voltage divider provides the voltage
|
||||
* for the positive input of the filter op-amps.
|
||||
*
|
||||
* 5V
|
||||
* +----------+
|
||||
* | | |\ |
|
||||
* R1 +---|-\ |
|
||||
* 5V | |A >---o--- Vref
|
||||
* o-------|+/
|
||||
* | | |/
|
||||
* R10 R4
|
||||
* | |
|
||||
* o---+
|
||||
* |
|
||||
* R10
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* Rn = n*R1
|
||||
*
|
||||
*
|
||||
*
|
||||
* Rfc - freq control DAC resistance ladder
|
||||
* ----------------------------------------
|
||||
* The 8580 has 11 bits for frequency control, but 12 bit DACs.
|
||||
* If those 11 bits would be '0', the impedance of the DACs would be "infinitely high".
|
||||
* To get around this, there is an 11 input NOR gate below the DACs sensing those 11 bits.
|
||||
* If all are 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB.
|
||||
*
|
||||
* ----o---o--...--o---o---o---
|
||||
* | | | | |
|
||||
* Rb10 Rb9 ... Rb1 Rb0 R0
|
||||
* | | | | |
|
||||
* ----o---o--...--o---o---o---
|
||||
*
|
||||
*
|
||||
*
|
||||
* Crystal stabilized precision switched capacitor voltage divider
|
||||
* ---------------------------------------------------------------
|
||||
* There is a FET working as a temperature sensor close to the DACs which changes the gate voltage
|
||||
* of the frequency control DACs according to the temperature of the DACs,
|
||||
* to reduce the effects of temperature on the filter curve.
|
||||
* An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors
|
||||
* whose AC resistance is then used as a voltage divider.
|
||||
* This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such.
|
||||
*
|
||||
* |\ OpAmp has a smaller capacitor than the other OPs
|
||||
* Vref ---|+\
|
||||
* |A >---o--- Vdac
|
||||
* +-------|-/ |
|
||||
* | |/ |
|
||||
* | |
|
||||
* C1 | C2 |
|
||||
* +---||---o---+ +---o-----||-------o
|
||||
* | | | | | |
|
||||
* o----+ | ----- | |
|
||||
* | | | ----- +----+ +-----o
|
||||
* | ----- | | | |
|
||||
* | ----- | ----- |
|
||||
* | | | ----- |
|
||||
* | +-----------+ | |
|
||||
* | /Q Q | +-------+
|
||||
* GND +-----------+ FET close to DAC
|
||||
* | clk/8 | working as temperature sensor
|
||||
* +-----------+
|
||||
*/
|
||||
class Filter8580 final : public Filter
|
||||
{
|
||||
private:
|
||||
unsigned short** mixer;
|
||||
unsigned short** summer;
|
||||
unsigned short** gain_res;
|
||||
unsigned short** gain_vol;
|
||||
|
||||
const int voiceScaleS11;
|
||||
const int voiceDC;
|
||||
|
||||
double cp;
|
||||
|
||||
/// VCR + associated capacitor connected to highpass output.
|
||||
std::unique_ptr<Integrator8580> const hpIntegrator;
|
||||
|
||||
/// VCR + associated capacitor connected to bandpass output.
|
||||
std::unique_ptr<Integrator8580> const bpIntegrator;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
void updatedCenterFrequency() override;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*
|
||||
* @param res the new resonance value
|
||||
*/
|
||||
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||
|
||||
void updatedMixing() override;
|
||||
|
||||
public:
|
||||
Filter8580() :
|
||||
mixer(FilterModelConfig8580::getInstance()->getMixer()),
|
||||
summer(FilterModelConfig8580::getInstance()->getSummer()),
|
||||
gain_res(FilterModelConfig8580::getInstance()->getGainRes()),
|
||||
gain_vol(FilterModelConfig8580::getInstance()->getGainVol()),
|
||||
voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()),
|
||||
voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()),
|
||||
cp(0.5),
|
||||
hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()),
|
||||
bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator())
|
||||
{
|
||||
setFilterCurve(cp);
|
||||
input(0);
|
||||
}
|
||||
|
||||
~Filter8580();
|
||||
|
||||
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||
|
||||
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||
|
||||
/**
|
||||
* Set filter curve type based on single parameter.
|
||||
*
|
||||
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||
*/
|
||||
void setFilterCurve(double curvePosition);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(FILTER8580_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
unsigned short Filter8580::clock(int voice1, int voice2, int voice3)
|
||||
{
|
||||
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||
|
||||
int Vi = 0;
|
||||
int Vo = 0;
|
||||
|
||||
(filt1 ? Vi : Vo) += voice1;
|
||||
(filt2 ? Vi : Vo) += voice2;
|
||||
(filt3 ? Vi : Vo) += voice3;
|
||||
(filtE ? Vi : Vo) += ve;
|
||||
|
||||
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||
Vbp = hpIntegrator->solve(Vhp);
|
||||
Vlp = bpIntegrator->solve(Vbp);
|
||||
|
||||
if (lp) Vo += Vlp;
|
||||
if (bp) Vo += Vbp;
|
||||
if (hp) Vo += Vhp;
|
||||
|
||||
return currentGain[currentMixer[Vo]];
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "FilterModelConfig.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
FilterModelConfig::FilterModelConfig(
|
||||
double vvr,
|
||||
double vdv,
|
||||
double c,
|
||||
double vdd,
|
||||
double vth,
|
||||
double ucox,
|
||||
const Spline::Point *opamp_voltage,
|
||||
int opamp_size
|
||||
) :
|
||||
voice_voltage_range(vvr),
|
||||
voice_DC_voltage(vdv),
|
||||
C(c),
|
||||
Vdd(vdd),
|
||||
Vth(vth),
|
||||
Ut(26.0e-3),
|
||||
uCox(ucox),
|
||||
Vddt(Vdd - Vth),
|
||||
vmin(opamp_voltage[0].x),
|
||||
vmax(std::max(Vddt, opamp_voltage[0].y)),
|
||||
denorm(vmax - vmin),
|
||||
norm(1.0 / denorm),
|
||||
N16(norm * ((1 << 16) - 1)),
|
||||
currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C))
|
||||
{
|
||||
// Convert op-amp voltage transfer to 16 bit values.
|
||||
|
||||
std::vector<Spline::Point> scaled_voltage(opamp_size);
|
||||
|
||||
for (int i = 0; i < opamp_size; i++)
|
||||
{
|
||||
scaled_voltage[i].x = N16 * (opamp_voltage[i].x - opamp_voltage[i].y + denorm) / 2.;
|
||||
scaled_voltage[i].y = N16 * (opamp_voltage[i].x - vmin);
|
||||
}
|
||||
|
||||
// Create lookup table mapping capacitor voltage to op-amp input voltage:
|
||||
|
||||
Spline s(scaled_voltage);
|
||||
|
||||
for (int x = 0; x < (1 << 16); x++)
|
||||
{
|
||||
const Spline::Point out = s.evaluate(x);
|
||||
// If Vmax > max opamp_voltage the first elements may be negative
|
||||
double tmp = out.x > 0. ? out.x : 0.;
|
||||
assert(tmp < 65535.5);
|
||||
opamp_rev[x] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTERMODELCONFIG_H
|
||||
#define FILTERMODELCONFIG_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#include "Spline.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class FilterModelConfig
|
||||
{
|
||||
protected:
|
||||
const double voice_voltage_range;
|
||||
const double voice_DC_voltage;
|
||||
|
||||
/// Capacitor value.
|
||||
const double C;
|
||||
|
||||
/// Transistor parameters.
|
||||
//@{
|
||||
const double Vdd;
|
||||
const double Vth; ///< Threshold voltage
|
||||
const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV
|
||||
const double uCox; ///< Transconductance coefficient: u*Cox
|
||||
const double Vddt; ///< Vdd - Vth
|
||||
//@}
|
||||
|
||||
// Derived stuff
|
||||
const double vmin, vmax;
|
||||
const double denorm, norm;
|
||||
|
||||
/// Fixed point scaling for 16 bit op-amp output.
|
||||
const double N16;
|
||||
|
||||
/// Current factor coefficient for op-amp integrators.
|
||||
const double currFactorCoeff;
|
||||
|
||||
/// Lookup tables for gain and summer op-amps in output stage / filter.
|
||||
//@{
|
||||
unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
//@}
|
||||
|
||||
/// Reverse op-amp transfer function.
|
||||
unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
|
||||
private:
|
||||
FilterModelConfig (const FilterModelConfig&) DELETE;
|
||||
FilterModelConfig& operator= (const FilterModelConfig&) DELETE;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @param vvr voice voltage range
|
||||
* @param vdv voice DC voltage
|
||||
* @param c capacitor value
|
||||
* @param vdd Vdd
|
||||
* @param vth threshold voltage
|
||||
* @param ucox u*Cox
|
||||
* @param ominv opamp min voltage
|
||||
* @param omaxv opamp max voltage
|
||||
*/
|
||||
FilterModelConfig(
|
||||
double vvr,
|
||||
double vdv,
|
||||
double c,
|
||||
double vdd,
|
||||
double vth,
|
||||
double ucox,
|
||||
const Spline::Point *opamp_voltage,
|
||||
int opamp_size
|
||||
);
|
||||
|
||||
~FilterModelConfig()
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
delete [] mixer[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
delete [] summer[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
delete [] gain_vol[i];
|
||||
delete [] gain_res[i];
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
unsigned short** getGainVol() { return gain_vol; }
|
||||
unsigned short** getGainRes() { return gain_res; }
|
||||
unsigned short** getSummer() { return summer; }
|
||||
unsigned short** getMixer() { return mixer; }
|
||||
|
||||
/**
|
||||
* The digital range of one voice is 20 bits; create a scaling term
|
||||
* for multiplication which fits in 11 bits.
|
||||
*/
|
||||
int getVoiceScaleS11() const { return static_cast<int>((norm * ((1 << 11) - 1)) * voice_voltage_range); }
|
||||
|
||||
/**
|
||||
* The "zero" output level of the voices.
|
||||
*/
|
||||
int getNormalizedVoiceDC() const { return static_cast<int>(N16 * (voice_DC_voltage - vmin)); }
|
||||
|
||||
inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; }
|
||||
inline double getVddt() const { return Vddt; }
|
||||
inline double getVth() const { return Vth; }
|
||||
inline double getVoiceDCVoltage() const { return voice_DC_voltage; }
|
||||
|
||||
// helper functions
|
||||
inline unsigned short getNormalizedValue(double value) const
|
||||
{
|
||||
const double tmp = N16 * (value - vmin);
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
inline unsigned short getNormalizedCurrentFactor(double wl) const
|
||||
{
|
||||
const double tmp = (1 << 13) * currFactorCoeff * wl;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
inline unsigned short getNVmin() const {
|
||||
const double tmp = N16 * vmin;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "FilterModelConfig6581.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "Integrator6581.h"
|
||||
#include "OpAmp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
#ifndef HAVE_CXX11
|
||||
/**
|
||||
* Compute log(1+x) without losing precision for small values of x
|
||||
*
|
||||
* @note when compiling with -ffastm-math the compiler will
|
||||
* optimize the expression away leaving a plain log(1. + x)
|
||||
*/
|
||||
inline double log1p(double x)
|
||||
{
|
||||
return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x);
|
||||
}
|
||||
#endif
|
||||
|
||||
const unsigned int OPAMP_SIZE = 33;
|
||||
|
||||
/**
|
||||
* This is the SID 6581 op-amp voltage transfer function, measured on
|
||||
* CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14.
|
||||
* All measured chips have op-amps with output voltages (and thus input
|
||||
* voltages) within the range of 0.81V - 10.31V.
|
||||
*/
|
||||
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||
{
|
||||
{ 0.81, 10.31 }, // Approximate start of actual range
|
||||
{ 2.40, 10.31 },
|
||||
{ 2.60, 10.30 },
|
||||
{ 2.70, 10.29 },
|
||||
{ 2.80, 10.26 },
|
||||
{ 2.90, 10.17 },
|
||||
{ 3.00, 10.04 },
|
||||
{ 3.10, 9.83 },
|
||||
{ 3.20, 9.58 },
|
||||
{ 3.30, 9.32 },
|
||||
{ 3.50, 8.69 },
|
||||
{ 3.70, 8.00 },
|
||||
{ 4.00, 6.89 },
|
||||
{ 4.40, 5.21 },
|
||||
{ 4.54, 4.54 }, // Working point (vi = vo)
|
||||
{ 4.60, 4.19 },
|
||||
{ 4.80, 3.00 },
|
||||
{ 4.90, 2.30 }, // Change of curvature
|
||||
{ 4.95, 2.03 },
|
||||
{ 5.00, 1.88 },
|
||||
{ 5.05, 1.77 },
|
||||
{ 5.10, 1.69 },
|
||||
{ 5.20, 1.58 },
|
||||
{ 5.40, 1.44 },
|
||||
{ 5.60, 1.33 },
|
||||
{ 5.80, 1.26 },
|
||||
{ 6.00, 1.21 },
|
||||
{ 6.40, 1.12 },
|
||||
{ 7.00, 1.02 },
|
||||
{ 7.50, 0.97 },
|
||||
{ 8.50, 0.89 },
|
||||
{ 10.00, 0.81 },
|
||||
{ 10.31, 0.81 }, // Approximate end of actual range
|
||||
};
|
||||
|
||||
std::unique_ptr<FilterModelConfig6581> FilterModelConfig6581::instance(nullptr);
|
||||
|
||||
FilterModelConfig6581* FilterModelConfig6581::getInstance()
|
||||
{
|
||||
if (!instance.get())
|
||||
{
|
||||
instance.reset(new FilterModelConfig6581());
|
||||
}
|
||||
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
FilterModelConfig6581::FilterModelConfig6581() :
|
||||
FilterModelConfig(
|
||||
1.5, // voice voltage range
|
||||
5.075, // voice DC voltage
|
||||
470e-12, // capacitor value
|
||||
12.18, // Vdd
|
||||
1.31, // Vth
|
||||
20e-6, // uCox
|
||||
opamp_voltage,
|
||||
OPAMP_SIZE
|
||||
),
|
||||
WL_vcr(9.0 / 1.0),
|
||||
WL_snake(1.0 / 115.0),
|
||||
dac_zero(6.65),
|
||||
dac_scale(2.63),
|
||||
dac(DAC_BITS)
|
||||
{
|
||||
dac.kinkedDac(MOS6581);
|
||||
|
||||
// Create lookup tables for gains / summers.
|
||||
|
||||
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||
|
||||
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||
// input configurations (2 - 6 input "resistors").
|
||||
//
|
||||
// Note that all "on" transistors are modeled as one. This is not
|
||||
// entirely accurate, since the input for each transistor is different,
|
||||
// and transistors are not linear components. However modeling all
|
||||
// transistors separately would be extremely costly.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||
const int size = idiv << 16;
|
||||
const double n = idiv;
|
||||
opampModel.reset();
|
||||
summer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// The audio mixer operates at n ~ 8/6, and has 8 fundamentally different
|
||||
// input configurations (0 - 7 input "resistors").
|
||||
//
|
||||
// All "on", transistors are modeled as one - see comments above for
|
||||
// the filter summer.
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const int idiv = (i == 0) ? 1 : i;
|
||||
const int size = (i == 0) ? 1 : i << 16;
|
||||
const double n = i * 8.0 / 6.0;
|
||||
opampModel.reset();
|
||||
mixer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the audio
|
||||
// output gain necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that gain ~ vol/12 (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = n8 / 12.0;
|
||||
opampModel.reset();
|
||||
gain_vol[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that 1/Q ~ ~res/8 (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = (~n8 & 0xf) / 8.0;
|
||||
opampModel.reset();
|
||||
gain_res[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
const double nVddt = N16 * (Vddt - vmin);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << 16); i++)
|
||||
{
|
||||
// The table index is right-shifted 16 times in order to fit in
|
||||
// 16 bits; the argument to sqrt is thus multiplied by (1 << 16).
|
||||
const double tmp = nVddt - sqrt(static_cast<double>(i << 16));
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
vcr_nVg[i] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
// EKV model:
|
||||
//
|
||||
// Ids = Is * (if - ir)
|
||||
// Is = (2 * u*Cox * Ut^2)/k * W/L
|
||||
// if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||
// ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||
|
||||
// moderate inversion characteristic current
|
||||
const double Is = (2. * uCox * Ut * Ut) * WL_vcr;
|
||||
|
||||
// Normalized current factor for 1 cycle at 1MHz.
|
||||
const double N15 = norm * ((1 << 15) - 1);
|
||||
const double n_Is = N15 * 1.0e-6 / C * Is;
|
||||
|
||||
// kVgt_Vx = k*(Vg - Vt) - Vx
|
||||
// I.e. if k != 1.0, Vg must be scaled accordingly.
|
||||
for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++)
|
||||
{
|
||||
const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut)));
|
||||
// Scaled by m*2^15
|
||||
const double tmp = n_Is * log_term * log_term;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
vcr_n_Ids_term[kVgt_Vx] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short* FilterModelConfig6581::getDAC(double adjustment) const
|
||||
{
|
||||
const double dac_zero = getDacZero(adjustment);
|
||||
|
||||
unsigned short* f0_dac = new unsigned short[1 << DAC_BITS];
|
||||
|
||||
for (unsigned int i = 0; i < (1 << DAC_BITS); i++)
|
||||
{
|
||||
const double fcd = dac.getOutput(i);
|
||||
f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS));
|
||||
}
|
||||
|
||||
return f0_dac;
|
||||
}
|
||||
|
||||
std::unique_ptr<Integrator6581> FilterModelConfig6581::buildIntegrator()
|
||||
{
|
||||
return MAKE_UNIQUE(Integrator6581, this, WL_snake);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTERMODELCONFIG6581_H
|
||||
#define FILTERMODELCONFIG6581_H
|
||||
|
||||
#include "FilterModelConfig.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Dac.h"
|
||||
|
||||
#include "sidcxx14.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator6581;
|
||||
|
||||
/**
|
||||
* Calculate parameters for 6581 filter emulation.
|
||||
*/
|
||||
class FilterModelConfig6581 final : public FilterModelConfig
|
||||
{
|
||||
private:
|
||||
static const unsigned int DAC_BITS = 11;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<FilterModelConfig6581> instance;
|
||||
// This allows access to the private constructor
|
||||
#ifdef HAVE_CXX11
|
||||
friend std::unique_ptr<FilterModelConfig6581>::deleter_type;
|
||||
#else
|
||||
friend class std::auto_ptr<FilterModelConfig6581>;
|
||||
#endif
|
||||
|
||||
/// Transistor parameters.
|
||||
//@{
|
||||
const double WL_vcr; ///< W/L for VCR
|
||||
const double WL_snake; ///< W/L for "snake"
|
||||
//@}
|
||||
|
||||
/// DAC parameters.
|
||||
//@{
|
||||
const double dac_zero;
|
||||
const double dac_scale;
|
||||
//@}
|
||||
|
||||
/// DAC lookup table
|
||||
Dac dac;
|
||||
|
||||
/// VCR - 6581 only.
|
||||
//@{
|
||||
unsigned short vcr_nVg[1 << 16];
|
||||
unsigned short vcr_n_Ids_term[1 << 16];
|
||||
//@}
|
||||
|
||||
private:
|
||||
double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); }
|
||||
|
||||
FilterModelConfig6581();
|
||||
~FilterModelConfig6581() DEFAULT;
|
||||
|
||||
public:
|
||||
static FilterModelConfig6581* getInstance();
|
||||
|
||||
/**
|
||||
* Construct an 11 bit cutoff frequency DAC output voltage table.
|
||||
* Ownership is transferred to the requester which becomes responsible
|
||||
* of freeing the object when done.
|
||||
*
|
||||
* @param adjustment
|
||||
* @return the DAC table
|
||||
*/
|
||||
unsigned short* getDAC(double adjustment) const;
|
||||
|
||||
/**
|
||||
* Construct an integrator solver.
|
||||
*
|
||||
* @return the integrator
|
||||
*/
|
||||
std::unique_ptr<Integrator6581> buildIntegrator();
|
||||
|
||||
inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; }
|
||||
inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; }
|
||||
// only used if SLOPE_FACTOR is defined
|
||||
inline double getUt() const { return Ut; }
|
||||
inline double getN16() const { return N16; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "FilterModelConfig8580.h"
|
||||
|
||||
#include "Integrator8580.h"
|
||||
#include "OpAmp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/*
|
||||
* R1 = 15.3*Ri
|
||||
* R2 = 7.3*Ri
|
||||
* R3 = 4.7*Ri
|
||||
* Rf = 1.4*Ri
|
||||
* R4 = 1.4*Ri
|
||||
* R8 = 2.0*Ri
|
||||
* RC = 2.8*Ri
|
||||
*
|
||||
* res feedback input
|
||||
* --- -------- -----
|
||||
* 0 Rf Ri
|
||||
* 1 Rf|R1 Ri
|
||||
* 2 Rf|R2 Ri
|
||||
* 3 Rf|R3 Ri
|
||||
* 4 Rf R4
|
||||
* 5 Rf|R1 R4
|
||||
* 6 Rf|R2 R4
|
||||
* 7 Rf|R3 R4
|
||||
* 8 Rf R8
|
||||
* 9 Rf|R1 R8
|
||||
* A Rf|R2 R8
|
||||
* B Rf|R3 R8
|
||||
* C Rf RC
|
||||
* D Rf|R1 RC
|
||||
* E Rf|R2 RC
|
||||
* F Rf|R3 RC
|
||||
*/
|
||||
const double resGain[16] =
|
||||
{
|
||||
1.4/1.0, // Rf/Ri 1.4
|
||||
((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263
|
||||
((1.4*7.3)/(1.4+7.3))/1.0, // (Rf|R2)/Ri 1.17471
|
||||
((1.4*4.7)/(1.4+4.7))/1.0, // (Rf|R3)/Ri 1.07869
|
||||
1.4/1.4, // Rf/R4 1
|
||||
((1.4*15.3)/(1.4+15.3))/1.4, // (Rf|R1)/R4 0.916168
|
||||
((1.4*7.3)/(1.4+7.3))/1.4, // (Rf|R2)/R4 0.83908
|
||||
((1.4*4.7)/(1.4+4.7))/1.4, // (Rf|R3)/R4 0.770492
|
||||
1.4/2.0, // Rf/R8 0.7
|
||||
((1.4*15.3)/(1.4+15.3))/2.0, // (Rf|R1)/R8 0.641317
|
||||
((1.4*7.3)/(1.4+7.3))/2.0, // (Rf|R2)/R8 0.587356
|
||||
((1.4*4.7)/(1.4+4.7))/2.0, // (Rf|R3)/R8 0.539344
|
||||
1.4/2.8, // Rf/RC 0.5
|
||||
((1.4*15.3)/(1.4+15.3))/2.8, // (Rf|R1)/RC 0.458084
|
||||
((1.4*7.3)/(1.4+7.3))/2.8, // (Rf|R2)/RC 0.41954
|
||||
((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246
|
||||
};
|
||||
|
||||
const unsigned int OPAMP_SIZE = 21;
|
||||
|
||||
/**
|
||||
* This is the SID 8580 op-amp voltage transfer function, measured on
|
||||
* CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25.
|
||||
*/
|
||||
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||
{
|
||||
{ 1.30, 8.91 }, // Approximate start of actual range
|
||||
{ 4.76, 8.91 },
|
||||
{ 4.77, 8.90 },
|
||||
{ 4.78, 8.88 },
|
||||
{ 4.785, 8.86 },
|
||||
{ 4.79, 8.80 },
|
||||
{ 4.795, 8.60 },
|
||||
{ 4.80, 8.25 },
|
||||
{ 4.805, 7.50 },
|
||||
{ 4.81, 6.10 },
|
||||
{ 4.815, 4.05 }, // Change of curvature
|
||||
{ 4.82, 2.27 },
|
||||
{ 4.825, 1.65 },
|
||||
{ 4.83, 1.55 },
|
||||
{ 4.84, 1.47 },
|
||||
{ 4.85, 1.43 },
|
||||
{ 4.87, 1.37 },
|
||||
{ 4.90, 1.34 },
|
||||
{ 5.00, 1.30 },
|
||||
{ 5.10, 1.30 },
|
||||
{ 8.91, 1.30 }, // Approximate end of actual range
|
||||
};
|
||||
|
||||
std::unique_ptr<FilterModelConfig8580> FilterModelConfig8580::instance(nullptr);
|
||||
|
||||
FilterModelConfig8580* FilterModelConfig8580::getInstance()
|
||||
{
|
||||
if (!instance.get())
|
||||
{
|
||||
instance.reset(new FilterModelConfig8580());
|
||||
}
|
||||
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
FilterModelConfig8580::FilterModelConfig8580() :
|
||||
FilterModelConfig(
|
||||
0.25, // voice voltage range FIXME measure
|
||||
4.80, // voice DC voltage FIXME was 4.76
|
||||
22e-9, // capacitor value
|
||||
9.09, // Vdd
|
||||
0.80, // Vth
|
||||
100e-6, // uCox
|
||||
opamp_voltage,
|
||||
OPAMP_SIZE
|
||||
)
|
||||
{
|
||||
// Create lookup tables for gains / summers.
|
||||
|
||||
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||
|
||||
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||
// input configurations (2 - 6 input "resistors").
|
||||
//
|
||||
// Note that all "on" transistors are modeled as one. This is not
|
||||
// entirely accurate, since the input for each transistor is different,
|
||||
// and transistors are not linear components. However modeling all
|
||||
// transistors separately would be extremely costly.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||
const int size = idiv << 16;
|
||||
const double n = idiv;
|
||||
opampModel.reset();
|
||||
summer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// The audio mixer operates at n ~ 8/5, and has 8 fundamentally different
|
||||
// input configurations (0 - 7 input "resistors").
|
||||
//
|
||||
// All "on", transistors are modeled as one - see comments above for
|
||||
// the filter summer.
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const int idiv = (i == 0) ? 1 : i;
|
||||
const int size = (i == 0) ? 1 : i << 16;
|
||||
const double n = i * 8.0 / 5.0;
|
||||
opampModel.reset();
|
||||
mixer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the audio output gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the volume "resistor" ladders
|
||||
// it follows that gain ~ vol/16 (assuming ideal op-amps
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = n8 / 16.0;
|
||||
opampModel.reset();
|
||||
gain_vol[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
opampModel.reset();
|
||||
gain_res[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Integrator8580> FilterModelConfig8580::buildIntegrator()
|
||||
{
|
||||
return MAKE_UNIQUE(Integrator8580, this);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef FILTERMODELCONFIG8580_H
|
||||
#define FILTERMODELCONFIG8580_H
|
||||
|
||||
#include "FilterModelConfig.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "sidcxx14.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator8580;
|
||||
|
||||
/**
|
||||
* Calculate parameters for 8580 filter emulation.
|
||||
*/
|
||||
class FilterModelConfig8580 final : public FilterModelConfig
|
||||
{
|
||||
private:
|
||||
static std::unique_ptr<FilterModelConfig8580> instance;
|
||||
// This allows access to the private constructor
|
||||
#ifdef HAVE_CXX11
|
||||
friend std::unique_ptr<FilterModelConfig8580>::deleter_type;
|
||||
#else
|
||||
friend class std::auto_ptr<FilterModelConfig8580>;
|
||||
#endif
|
||||
|
||||
private:
|
||||
FilterModelConfig8580();
|
||||
~FilterModelConfig8580() DEFAULT;
|
||||
|
||||
public:
|
||||
static FilterModelConfig8580* getInstance();
|
||||
|
||||
/**
|
||||
* Construct an integrator solver.
|
||||
*
|
||||
* @return the integrator
|
||||
*/
|
||||
std::unique_ptr<Integrator8580> buildIntegrator();
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define INTEGRATOR_CPP
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
// This is needed when compiling with --disable-inline
|
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef INTEGRATOR6581_H
|
||||
#define INTEGRATOR6581_H
|
||||
|
||||
#include "FilterModelConfig6581.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
|
||||
// uncomment to enable use of the slope factor
|
||||
// in the EKV model
|
||||
// actually produces worse results, needs investigation
|
||||
//#define SLOPE_FACTOR
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
# include <cmath>
|
||||
#endif
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Find output voltage in inverting integrator SID op-amp circuits, using a
|
||||
* single fixpoint iteration step.
|
||||
*
|
||||
* A circuit diagram of a MOS 6581 integrator is shown below.
|
||||
*
|
||||
* +---C---+
|
||||
* | |
|
||||
* vi --o--Rw--o-o--[A>--o-- vo
|
||||
* | | vx
|
||||
* +--Rs--+
|
||||
*
|
||||
* From Kirchoff's current law it follows that
|
||||
*
|
||||
* IRw + IRs + ICr = 0
|
||||
*
|
||||
* Using the formula for current through a capacitor, i = C*dv/dt, we get
|
||||
*
|
||||
* IRw + IRs + C*(vc - vc0)/dt = 0
|
||||
* dt/C*(IRw + IRs) + vc - vc0 = 0
|
||||
* vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx))
|
||||
*
|
||||
* which may be rewritten as the following iterative fixpoint function:
|
||||
*
|
||||
* vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc)))
|
||||
*
|
||||
* To accurately calculate the currents through Rs and Rw, we need to use
|
||||
* transistor models. Rs has a gate voltage of Vdd = 12V, and can be
|
||||
* assumed to always be in triode mode. For Rw, the situation is rather
|
||||
* more complex, as it turns out that this transistor will operate in
|
||||
* both subthreshold, triode, and saturation modes.
|
||||
*
|
||||
* The Shichman-Hodges transistor model routinely used in textbooks may
|
||||
* be written as follows:
|
||||
*
|
||||
* Ids = 0 , Vgst < 0 (subthreshold mode)
|
||||
* Ids = K*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode)
|
||||
* Ids = K*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode)
|
||||
*
|
||||
* where
|
||||
* K = u*Cox/2 (transconductance coefficient)
|
||||
* W/L = ratio between substrate width and length
|
||||
* Vgst = Vg - Vs - Vt (overdrive voltage)
|
||||
*
|
||||
* This transistor model is also called the quadratic model.
|
||||
*
|
||||
* Note that the equation for the triode mode can be reformulated as
|
||||
* independent terms depending on Vgs and Vgd, respectively, by the
|
||||
* following substitution:
|
||||
*
|
||||
* Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt
|
||||
*
|
||||
* Ids = K*W/L*(2*Vgst - Vds)*Vds
|
||||
* = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt)
|
||||
* = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt)
|
||||
* = K*W/L*(Vgst^2 - Vgdt^2)
|
||||
*
|
||||
* This turns out to be a general equation which covers both the triode
|
||||
* and saturation modes (where the second term is 0 in saturation mode).
|
||||
* The equation is also symmetrical, i.e. it can calculate negative
|
||||
* currents without any change of parameters (since the terms for drain
|
||||
* and source are identical except for the sign).
|
||||
*
|
||||
* FIXME: Subthreshold as function of Vgs, Vgd.
|
||||
*
|
||||
* Ids = I0*W/L*e^(Vgst/(Ut/k)) , Vgst < 0 (subthreshold mode)
|
||||
*
|
||||
* where
|
||||
* I0 = (2 * uCox * Ut^2) / k
|
||||
*
|
||||
* The remaining problem with the textbook model is that the transition
|
||||
* from subthreshold the triode/saturation is not continuous.
|
||||
*
|
||||
* Realizing that the subthreshold and triode/saturation modes may both
|
||||
* be defined by independent (and equal) terms of Vgs and Vds,
|
||||
* respectively, the corresponding terms can be blended into (equal)
|
||||
* continuous functions suitable for table lookup.
|
||||
*
|
||||
* The EKV model (Enz, Krummenacher and Vittoz) essentially performs this
|
||||
* blending using an elegant mathematical formulation:
|
||||
*
|
||||
* Ids = Is * (if - ir)
|
||||
* Is = ((2 * u*Cox * Ut^2)/k) * W/L
|
||||
* if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||
* ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||
*
|
||||
* For our purposes, the EKV model preserves two important properties
|
||||
* discussed above:
|
||||
*
|
||||
* - It consists of two independent terms, which can be represented by
|
||||
* the same lookup table.
|
||||
* - It is symmetrical, i.e. it calculates current in both directions,
|
||||
* facilitating a branch-free implementation.
|
||||
*
|
||||
* Rw in the circuit diagram above is a VCR (voltage controlled resistor),
|
||||
* as shown in the circuit diagram below.
|
||||
*
|
||||
*
|
||||
* Vdd
|
||||
* |
|
||||
* Vdd _|_
|
||||
* | +---+ +---- Vw
|
||||
* _|_ |
|
||||
* +--+ +---o Vg
|
||||
* | __|__
|
||||
* | ----- Rw
|
||||
* | | |
|
||||
* vi -----o------+ +-------- vo
|
||||
*
|
||||
*
|
||||
* In order to calculalate the current through the VCR, its gate voltage
|
||||
* must be determined.
|
||||
*
|
||||
* Assuming triode mode and applying Kirchoff's current law, we get the
|
||||
* following equation for Vg:
|
||||
*
|
||||
* u*Cox/2*W/L*((nVddt - Vg)^2 - (nVddt - vi)^2 + (nVddt - Vg)^2 - (nVddt - Vw)^2) = 0
|
||||
* 2*(nVddt - Vg)^2 - (nVddt - vi)^2 - (nVddt - Vw)^2 = 0
|
||||
* (nVddt - Vg) = sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||
*
|
||||
* Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||
*/
|
||||
class Integrator6581
|
||||
{
|
||||
private:
|
||||
unsigned int nVddt_Vw_2;
|
||||
mutable int vx;
|
||||
mutable int vc;
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
// Slope factor n = 1/k
|
||||
// where k is the gate coupling coefficient
|
||||
// k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage)
|
||||
mutable double n;
|
||||
#endif
|
||||
const unsigned short nVddt;
|
||||
const unsigned short nVt;
|
||||
const unsigned short nVmin;
|
||||
const unsigned short nSnake;
|
||||
|
||||
const FilterModelConfig6581* fmc;
|
||||
|
||||
public:
|
||||
Integrator6581(const FilterModelConfig6581* fmc,
|
||||
double WL_snake) :
|
||||
nVddt_Vw_2(0),
|
||||
vx(0),
|
||||
vc(0),
|
||||
#ifdef SLOPE_FACTOR
|
||||
n(1.4),
|
||||
#endif
|
||||
nVddt(fmc->getNormalizedValue(fmc->getVddt())),
|
||||
nVt(fmc->getNormalizedValue(fmc->getVth())),
|
||||
nVmin(fmc->getNVmin()),
|
||||
nSnake(fmc->getNormalizedCurrentFactor(WL_snake)),
|
||||
fmc(fmc) {}
|
||||
|
||||
void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; }
|
||||
|
||||
int solve(int vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(INTEGRATOR_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int Integrator6581::solve(int vi) const
|
||||
{
|
||||
// Make sure Vgst>0 so we're not in subthreshold mode
|
||||
assert(vx < nVddt);
|
||||
|
||||
// Check that transistor is actually in triode mode
|
||||
// Vds < Vgs - Vth
|
||||
assert(vi < nVddt);
|
||||
|
||||
// "Snake" voltages for triode mode calculation.
|
||||
const unsigned int Vgst = nVddt - vx;
|
||||
const unsigned int Vgdt = nVddt - vi;
|
||||
|
||||
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||
|
||||
// "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||
const int n_I_snake = nSnake * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||
|
||||
// VCR gate voltage. // Scaled by m*2^16
|
||||
// Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2)
|
||||
const int nVg = static_cast<int>(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16));
|
||||
#ifdef SLOPE_FACTOR
|
||||
const double nVp = static_cast<double>(nVg - nVt) / n; // Pinch-off voltage
|
||||
const int kVg = static_cast<int>(nVp + 0.5) - nVmin;
|
||||
#else
|
||||
const int kVg = (nVg - nVt) - nVmin;
|
||||
#endif
|
||||
|
||||
// VCR voltages for EKV model table lookup.
|
||||
const int kVgt_Vs = (vx < kVg) ? kVg - vx : 0;
|
||||
assert(kVgt_Vs < (1 << 16));
|
||||
const int kVgt_Vd = (vi < kVg) ? kVg - vi : 0;
|
||||
assert(kVgt_Vd < (1 << 16));
|
||||
|
||||
// VCR current, scaled by m*2^15*2^15 = m*2^30
|
||||
const unsigned int If = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15;
|
||||
const unsigned int Ir = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15;
|
||||
#ifdef SLOPE_FACTOR
|
||||
const double iVcr = static_cast<double>(If - Ir);
|
||||
const int n_I_vcr = static_cast<int>((iVcr * n) + 0.5);
|
||||
#else
|
||||
const int n_I_vcr = If - Ir;
|
||||
#endif
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
// estimate new slope factor based on gate voltage
|
||||
const double gamma = 1.0; // body effect factor
|
||||
const double phi = 0.8; // bulk Fermi potential
|
||||
const double Vp = nVp / fmc->getN16();
|
||||
n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt())));
|
||||
assert((n > 1.2) && (n < 1.8));
|
||||
#endif
|
||||
|
||||
// Change in capacitor charge.
|
||||
vc += n_I_snake + n_I_vcr;
|
||||
|
||||
// vx = g(vc)
|
||||
const int tmp = (vc >> 15) + (1 << 15);
|
||||
assert(tmp < (1 << 16));
|
||||
vx = fmc->getOpampRev(tmp);
|
||||
|
||||
// Return vo.
|
||||
return vx - (vc >> 14);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define INTEGRATOR8580_CPP
|
||||
|
||||
#include "Integrator8580.h"
|
||||
|
||||
// This is needed when compiling with --disable-inline
|
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef INTEGRATOR8580_H
|
||||
#define INTEGRATOR8580_H
|
||||
|
||||
#include "FilterModelConfig8580.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* 8580 integrator
|
||||
*
|
||||
* +---C---+
|
||||
* | |
|
||||
* vi -----Rfc---o--[A>--o-- vo
|
||||
* vx
|
||||
*
|
||||
* IRfc + ICr = 0
|
||||
* IRfc + C*(vc - vc0)/dt = 0
|
||||
* dt/C*(IRfc) + vc - vc0 = 0
|
||||
* vc = vc0 - n*(IRfc(vi,vx))
|
||||
* vc = vc0 - n*(IRfc(vi,g(vc)))
|
||||
*
|
||||
* IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vddt - vx)^2 - (Vddt - vi)^2)
|
||||
*
|
||||
* Rfc gate voltage is generated by an OP Amp and depends on chip temperature.
|
||||
*/
|
||||
class Integrator8580
|
||||
{
|
||||
private:
|
||||
mutable int vx;
|
||||
mutable int vc;
|
||||
|
||||
unsigned short nVgt;
|
||||
unsigned short n_dac;
|
||||
|
||||
const FilterModelConfig8580* fmc;
|
||||
|
||||
public:
|
||||
Integrator8580(const FilterModelConfig8580* fmc) :
|
||||
vx(0),
|
||||
vc(0),
|
||||
fmc(fmc)
|
||||
{
|
||||
setV(1.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Filter Cutoff resistor ratio.
|
||||
*/
|
||||
void setFc(double wl)
|
||||
{
|
||||
// Normalized current factor, 1 cycle at 1MHz.
|
||||
// Fit in 5 bits.
|
||||
n_dac = fmc->getNormalizedCurrentFactor(wl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set FC gate voltage multiplier.
|
||||
*/
|
||||
void setV(double v)
|
||||
{
|
||||
// Gate voltage is controlled by the switched capacitor voltage divider
|
||||
// Ua = Ue * v = 4.76v 1<v<2
|
||||
assert(v > 1.0 && v < 2.0);
|
||||
const double Vg = fmc->getVoiceDCVoltage() * v;
|
||||
const double Vgt = Vg - fmc->getVth();
|
||||
|
||||
// Vg - Vth, normalized so that translated values can be subtracted:
|
||||
// Vgt - x = (Vgt - t) - (x - t)
|
||||
nVgt = fmc->getNormalizedValue(Vgt);
|
||||
}
|
||||
|
||||
int solve(int vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(INTEGRATOR8580_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int Integrator8580::solve(int vi) const
|
||||
{
|
||||
// Make sure we're not in subthreshold mode
|
||||
assert(vx < nVgt);
|
||||
|
||||
// DAC voltages
|
||||
const unsigned int Vgst = nVgt - vx;
|
||||
const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode
|
||||
|
||||
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||
|
||||
// DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||
const int n_I_dac = n_dac * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||
|
||||
// Change in capacitor charge.
|
||||
vc += n_I_dac;
|
||||
|
||||
// vx = g(vc)
|
||||
const int tmp = (vc >> 15) + (1 << 15);
|
||||
assert(tmp < (1 << 16));
|
||||
vx = fmc->getOpampRev(tmp);
|
||||
|
||||
// Return vo.
|
||||
return vx - (vc >> 14);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "OpAmp.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
const double EPSILON = 1e-8;
|
||||
|
||||
double OpAmp::solve(double n, double vi) const
|
||||
{
|
||||
// Start off with an estimate of x and a root bracket [ak, bk].
|
||||
// f is decreasing, so that f(ak) > 0 and f(bk) < 0.
|
||||
double ak = vmin;
|
||||
double bk = vmax;
|
||||
|
||||
const double a = n + 1.;
|
||||
const double b = Vddt;
|
||||
const double b_vi = (b > vi) ? (b - vi) : 0.;
|
||||
const double c = n * (b_vi * b_vi);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const double xk = x;
|
||||
|
||||
// Calculate f and df.
|
||||
|
||||
Spline::Point out = opamp->evaluate(x);
|
||||
const double vo = out.x;
|
||||
const double dvo = out.y;
|
||||
|
||||
const double b_vx = (b > x) ? b - x : 0.;
|
||||
const double b_vo = (b > vo) ? b - vo : 0.;
|
||||
|
||||
// f = a*(b - vx)^2 - c - (b - vo)^2
|
||||
const double f = a * (b_vx * b_vx) - c - (b_vo * b_vo);
|
||||
|
||||
// df = 2*((b - vo)*dvo - a*(b - vx))
|
||||
const double df = 2. * (b_vo * dvo - a * b_vx);
|
||||
|
||||
// Newton-Raphson step: xk1 = xk - f(xk)/f'(xk)
|
||||
x -= f / df;
|
||||
|
||||
if (unlikely(fabs(x - xk) < EPSILON))
|
||||
{
|
||||
out = opamp->evaluate(x);
|
||||
return out.x;
|
||||
}
|
||||
|
||||
// Narrow down root bracket.
|
||||
(f < 0. ? bk : ak) = xk;
|
||||
|
||||
if (unlikely(x <= ak) || unlikely(x >= bk))
|
||||
{
|
||||
// Bisection step (ala Dekker's method).
|
||||
x = (ak + bk) * 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef OPAMP_H
|
||||
#define OPAMP_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Spline.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Find output voltage in inverting gain and inverting summer SID op-amp
|
||||
* circuits, using a combination of Newton-Raphson and bisection.
|
||||
*
|
||||
* +---R2--+
|
||||
* | |
|
||||
* vi ---R1--o--[A>--o-- vo
|
||||
* vx
|
||||
*
|
||||
* From Kirchoff's current law it follows that
|
||||
*
|
||||
* IR1f + IR2r = 0
|
||||
*
|
||||
* Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2)
|
||||
* for the currents, we get:
|
||||
*
|
||||
* n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0
|
||||
*
|
||||
* Our root function f can thus be written as:
|
||||
*
|
||||
* f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0
|
||||
*
|
||||
* Using substitution constants
|
||||
*
|
||||
* a = n + 1
|
||||
* b = Vddt
|
||||
* c = n*(Vddt - vi)^2
|
||||
*
|
||||
* the equations for the root function and its derivative can be written as:
|
||||
*
|
||||
* f = a*(b - vx)^2 - c - (b - vo)^2
|
||||
* df = 2*((b - vo)*dvo - a*(b - vx))
|
||||
*/
|
||||
class OpAmp
|
||||
{
|
||||
private:
|
||||
/// Current root position (cached as guess to speed up next iteration)
|
||||
mutable double x;
|
||||
|
||||
const double Vddt;
|
||||
const double vmin;
|
||||
const double vmax;
|
||||
|
||||
std::unique_ptr<Spline> const opamp;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opamp input -> output voltage conversion
|
||||
*
|
||||
* @param opamp opamp mapping table as pairs of points (in -> out)
|
||||
* @param opamplength length of the opamp array
|
||||
* @param kVddt transistor dt parameter (in volts)
|
||||
*/
|
||||
OpAmp(const std::vector<Spline::Point> &opamp, double Vddt) :
|
||||
x(0.),
|
||||
Vddt(Vddt),
|
||||
vmin(opamp.front().x),
|
||||
vmax(opamp.back().x),
|
||||
opamp(new Spline(opamp)) {}
|
||||
|
||||
void reset() const
|
||||
{
|
||||
x = vmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve the opamp equation for input vi in loading context n
|
||||
*
|
||||
* @param n the ratio of input/output loading
|
||||
* @param vi input
|
||||
* @return vo
|
||||
*/
|
||||
double solve(double n, double vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef POTENTIOMETER_H
|
||||
#define POTENTIOMETER_H
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Potentiometer representation.
|
||||
*
|
||||
* This class will probably never be implemented in any real way.
|
||||
*
|
||||
* @author Ken Händel
|
||||
* @author Dag Lem
|
||||
*/
|
||||
class Potentiometer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Read paddle value. Not modeled.
|
||||
*
|
||||
* @return paddle value (always 0xff)
|
||||
*/
|
||||
unsigned char readPOT() const { return 0xff; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
20
src/engine/platform/sound/c64_fp/README
Normal file
20
src/engine/platform/sound/c64_fp/README
Normal file
|
@ -0,0 +1,20 @@
|
|||
reSIDfp is a fork of Dag Lem's reSID 0.16, a reverse engineered software emulation
|
||||
of the MOS6581/8580 SID (Sound Interface Device).
|
||||
|
||||
The project was started by Antti S. Lankila in order to improve SID emulation
|
||||
with special focus on the 6581 filter.
|
||||
The codebase has been later on ported to java by Ken Händel within the jsidplay2 project
|
||||
and has seen further work by Antti Lankila.
|
||||
It was then ported back to c++ and integrated with improvements from reSID 1.0 by Leandro Nini.
|
||||
|
||||
|
||||
Main differences from reSID:
|
||||
|
||||
* combined waveforms are emulated by a parametrized model based on samplings from Kevtris;
|
||||
* envelope generator is implemented like in the real machine with a shift register;
|
||||
* high quality resampling is done in two steps to allow computational savings using lower order filters;
|
||||
* part of the calculations are done with floats instead of fixed point;
|
||||
* interpolation is accomplished with Fritsch-Carlson method to preserve monotonicity.
|
||||
|
||||
|
||||
reSIDfp is free software. See the file COPYING for copying permission.
|
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define SID_CPP
|
||||
|
||||
#include "SID.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "array.h"
|
||||
#include "Dac.h"
|
||||
#include "Filter6581.h"
|
||||
#include "Filter8580.h"
|
||||
#include "Potentiometer.h"
|
||||
#include "WaveformCalculator.h"
|
||||
#include "resample/TwoPassSincResampler.h"
|
||||
#include "resample/ZeroOrderResampler.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
const unsigned int ENV_DAC_BITS = 8;
|
||||
const unsigned int OSC_DAC_BITS = 12;
|
||||
|
||||
/**
|
||||
* The waveform D/A converter introduces a DC offset in the signal
|
||||
* to the envelope multiplying D/A converter. The "zero" level of
|
||||
* the waveform D/A converter can be found as follows:
|
||||
*
|
||||
* Measure the "zero" voltage of voice 3 on the SID audio output
|
||||
* pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 =
|
||||
* $0f, all other registers zeroed).
|
||||
*
|
||||
* Then set the sustain level for voice 3 to maximum and search for
|
||||
* the waveform output value yielding the same voltage as found
|
||||
* above. This is done by trying out different waveform output
|
||||
* values until the correct value is found, e.g. with the following
|
||||
* program:
|
||||
*
|
||||
* lda #$08
|
||||
* sta $d412
|
||||
* lda #$0b
|
||||
* sta $d417
|
||||
* lda #$0f
|
||||
* sta $d418
|
||||
* lda #$f0
|
||||
* sta $d414
|
||||
* lda #$21
|
||||
* sta $d412
|
||||
* lda #$01
|
||||
* sta $d40e
|
||||
*
|
||||
* ldx #$00
|
||||
* lda #$38 ; Tweak this to find the "zero" level
|
||||
*l cmp $d41b
|
||||
* bne l
|
||||
* stx $d40e ; Stop frequency counter - freeze waveform output
|
||||
* brk
|
||||
*
|
||||
* The waveform output range is 0x000 to 0xfff, so the "zero"
|
||||
* level should ideally have been 0x800. In the measured chip, the
|
||||
* waveform output "zero" level was found to be 0x380 (i.e. $d41b
|
||||
* = 0x38) at an audio output voltage of 5.94V.
|
||||
*
|
||||
* With knowledge of the mixer op-amp characteristics, further estimates
|
||||
* of waveform voltages can be obtained by sampling the EXT IN pin.
|
||||
* From EXT IN samples, the corresponding waveform output can be found by
|
||||
* using the model for the mixer.
|
||||
*
|
||||
* Such measurements have been done on a chip marked MOS 6581R4AR
|
||||
* 0687 14, and the following results have been obtained:
|
||||
* * The full range of one voice is approximately 1.5V.
|
||||
* * The "zero" level rides at approximately 5.0V.
|
||||
*
|
||||
*
|
||||
* zero-x did the measuring on the 8580 (https://sourceforge.net/p/vice-emu/bugs/1036/#c5b3):
|
||||
* When it sits on basic from powerup it's at 4.72
|
||||
* Run 1.prg and check the output pin level.
|
||||
* Then run 2.prg andadjust it until the output level is the same...
|
||||
* 0x94-0xA8 gives me the same 4.72 1.prg shows.
|
||||
* On another 8580 it's 0x90-0x9C
|
||||
* Third chip 0x94-0xA8
|
||||
* Fourth chip 0x90-0xA4
|
||||
* On the 8580 that plays digis the output is 4.66 and 0x93 is the only value to reach that.
|
||||
* To me that seems as regular 8580s have somewhat wide 0-level range,
|
||||
* whereas that digi-compatible 8580 has it very narrow.
|
||||
* On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg
|
||||
*/
|
||||
//@{
|
||||
unsigned int constexpr OFFSET_6581 = 0x380;
|
||||
unsigned int constexpr OFFSET_8580 = 0x9c0;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* Bus value stays alive for some time after each operation.
|
||||
* Values differs between chip models, the timings used here
|
||||
* are taken from VICE [1].
|
||||
* See also the discussion "How do I reliably detect 6581/8580 sid?" on CSDb [2].
|
||||
*
|
||||
* Results from real C64 (testprogs/SID/bitfade/delayfrq0.prg):
|
||||
*
|
||||
* (new SID) (250469/8580R5) (250469/8580R5)
|
||||
* delayfrq0 ~7a000 ~108000
|
||||
*
|
||||
* (old SID) (250407/6581)
|
||||
* delayfrq0 ~01d00
|
||||
*
|
||||
* [1]: http://sourceforge.net/p/vice-emu/patches/99/
|
||||
* [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1
|
||||
*/
|
||||
//@{
|
||||
int constexpr BUS_TTL_6581 = 0x01d00;
|
||||
int constexpr BUS_TTL_8580 = 0xa2000;
|
||||
//@}
|
||||
|
||||
SID::SID() :
|
||||
filter6581(new Filter6581()),
|
||||
filter8580(new Filter8580()),
|
||||
externalFilter(new ExternalFilter()),
|
||||
resampler(nullptr),
|
||||
potX(new Potentiometer()),
|
||||
potY(new Potentiometer())
|
||||
{
|
||||
voice[0].reset(new Voice());
|
||||
voice[1].reset(new Voice());
|
||||
voice[2].reset(new Voice());
|
||||
|
||||
muted[0] = muted[1] = muted[2] = false;
|
||||
|
||||
reset();
|
||||
setChipModel(MOS8580);
|
||||
}
|
||||
|
||||
SID::~SID()
|
||||
{
|
||||
// Needed to delete auto_ptr with complete type
|
||||
}
|
||||
|
||||
void SID::setFilter6581Curve(double filterCurve)
|
||||
{
|
||||
filter6581->setFilterCurve(filterCurve);
|
||||
}
|
||||
|
||||
void SID::setFilter8580Curve(double filterCurve)
|
||||
{
|
||||
filter8580->setFilterCurve(filterCurve);
|
||||
}
|
||||
|
||||
void SID::enableFilter(bool enable)
|
||||
{
|
||||
filter6581->enable(enable);
|
||||
filter8580->enable(enable);
|
||||
}
|
||||
|
||||
void SID::voiceSync(bool sync)
|
||||
{
|
||||
if (sync)
|
||||
{
|
||||
// Synchronize the 3 waveform generators.
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the time to next voice sync
|
||||
nextVoiceSync = std::numeric_limits<int>::max();
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
WaveformGenerator* const wave = voice[i]->wave();
|
||||
const unsigned int freq = wave->readFreq();
|
||||
|
||||
if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsigned int accumulator = wave->readAccumulator();
|
||||
const unsigned int thisVoiceSync = ((0x7fffff - accumulator) & 0xffffff) / freq + 1;
|
||||
|
||||
if (thisVoiceSync < nextVoiceSync)
|
||||
{
|
||||
nextVoiceSync = thisVoiceSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SID::setChipModel(ChipModel model)
|
||||
{
|
||||
switch (model)
|
||||
{
|
||||
case MOS6581:
|
||||
filter = filter6581.get();
|
||||
modelTTL = BUS_TTL_6581;
|
||||
break;
|
||||
|
||||
case MOS8580:
|
||||
filter = filter8580.get();
|
||||
modelTTL = BUS_TTL_8580;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw SIDError("Unknown chip type");
|
||||
}
|
||||
|
||||
this->model = model;
|
||||
|
||||
// calculate waveform-related tables
|
||||
matrix_t* tables = WaveformCalculator::getInstance()->buildTable(model);
|
||||
|
||||
// calculate envelope DAC table
|
||||
{
|
||||
Dac dacBuilder(ENV_DAC_BITS);
|
||||
dacBuilder.kinkedDac(model);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << ENV_DAC_BITS); i++)
|
||||
{
|
||||
envDAC[i] = static_cast<float>(dacBuilder.getOutput(i));
|
||||
}
|
||||
}
|
||||
|
||||
// calculate oscillator DAC table
|
||||
const bool is6581 = model == MOS6581;
|
||||
|
||||
{
|
||||
Dac dacBuilder(OSC_DAC_BITS);
|
||||
dacBuilder.kinkedDac(model);
|
||||
|
||||
const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++)
|
||||
{
|
||||
const double dacValue = dacBuilder.getOutput(i);
|
||||
oscDAC[i] = static_cast<float>(dacValue - offset);
|
||||
}
|
||||
}
|
||||
|
||||
// set voice tables
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->setEnvDAC(envDAC);
|
||||
voice[i]->setWavDAC(oscDAC);
|
||||
voice[i]->wave()->setModel(is6581);
|
||||
voice[i]->wave()->setWaveformModels(tables);
|
||||
}
|
||||
}
|
||||
|
||||
void SID::reset()
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->reset();
|
||||
}
|
||||
|
||||
filter6581->reset();
|
||||
filter8580->reset();
|
||||
externalFilter->reset();
|
||||
|
||||
if (resampler.get())
|
||||
{
|
||||
resampler->reset();
|
||||
}
|
||||
|
||||
busValue = 0;
|
||||
busValueTtl = 0;
|
||||
voiceSync(false);
|
||||
}
|
||||
|
||||
void SID::input(int value)
|
||||
{
|
||||
filter6581->input(value);
|
||||
filter8580->input(value);
|
||||
}
|
||||
|
||||
unsigned char SID::read(int offset)
|
||||
{
|
||||
switch (offset)
|
||||
{
|
||||
case 0x19: // X value of paddle
|
||||
busValue = potX->readPOT();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1a: // Y value of paddle
|
||||
busValue = potY->readPOT();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1b: // Voice #3 waveform output
|
||||
busValue = voice[2]->wave()->readOSC();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1c: // Voice #3 ADSR output
|
||||
busValue = voice[2]->envelope()->readENV();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Reading from a write-only or non-existing register
|
||||
// makes the bus discharge faster.
|
||||
// Emulate this by halving the residual TTL.
|
||||
busValueTtl /= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return busValue;
|
||||
}
|
||||
|
||||
void SID::write(int offset, unsigned char value)
|
||||
{
|
||||
busValue = value;
|
||||
busValueTtl = modelTTL;
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x00: // Voice #1 frequency (Low-byte)
|
||||
voice[0]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x01: // Voice #1 frequency (High-byte)
|
||||
voice[0]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x02: // Voice #1 pulse width (Low-byte)
|
||||
voice[0]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x03: // Voice #1 pulse width (bits #8-#15)
|
||||
voice[0]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x04: // Voice #1 control register
|
||||
voice[0]->writeCONTROL_REG(muted[0] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x05: // Voice #1 Attack and Decay length
|
||||
voice[0]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x06: // Voice #1 Sustain volume and Release length
|
||||
voice[0]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x07: // Voice #2 frequency (Low-byte)
|
||||
voice[1]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x08: // Voice #2 frequency (High-byte)
|
||||
voice[1]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x09: // Voice #2 pulse width (Low-byte)
|
||||
voice[1]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x0a: // Voice #2 pulse width (bits #8-#15)
|
||||
voice[1]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x0b: // Voice #2 control register
|
||||
voice[1]->writeCONTROL_REG(muted[1] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x0c: // Voice #2 Attack and Decay length
|
||||
voice[1]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x0d: // Voice #2 Sustain volume and Release length
|
||||
voice[1]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x0e: // Voice #3 frequency (Low-byte)
|
||||
voice[2]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x0f: // Voice #3 frequency (High-byte)
|
||||
voice[2]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x10: // Voice #3 pulse width (Low-byte)
|
||||
voice[2]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x11: // Voice #3 pulse width (bits #8-#15)
|
||||
voice[2]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x12: // Voice #3 control register
|
||||
voice[2]->writeCONTROL_REG(muted[2] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x13: // Voice #3 Attack and Decay length
|
||||
voice[2]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x14: // Voice #3 Sustain volume and Release length
|
||||
voice[2]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x15: // Filter cut off frequency (bits #0-#2)
|
||||
filter6581->writeFC_LO(value);
|
||||
filter8580->writeFC_LO(value);
|
||||
break;
|
||||
|
||||
case 0x16: // Filter cut off frequency (bits #3-#10)
|
||||
filter6581->writeFC_HI(value);
|
||||
filter8580->writeFC_HI(value);
|
||||
break;
|
||||
|
||||
case 0x17: // Filter control
|
||||
filter6581->writeRES_FILT(value);
|
||||
filter8580->writeRES_FILT(value);
|
||||
break;
|
||||
|
||||
case 0x18: // Volume and filter modes
|
||||
filter6581->writeMODE_VOL(value);
|
||||
filter8580->writeMODE_VOL(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Update voicesync just in case.
|
||||
voiceSync(false);
|
||||
}
|
||||
|
||||
void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency)
|
||||
{
|
||||
externalFilter->setClockFrequency(clockFrequency);
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case DECIMATE:
|
||||
resampler.reset(new ZeroOrderResampler(clockFrequency, samplingFrequency));
|
||||
break;
|
||||
|
||||
case RESAMPLE:
|
||||
resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw SIDError("Unknown sampling method");
|
||||
}
|
||||
}
|
||||
|
||||
void SID::clockSilent(unsigned int cycles)
|
||||
{
|
||||
ageBusValue(cycles);
|
||||
|
||||
while (cycles != 0)
|
||||
{
|
||||
int delta_t = std::min(nextVoiceSync, cycles);
|
||||
|
||||
if (delta_t > 0)
|
||||
{
|
||||
for (int i = 0; i < delta_t; i++)
|
||||
{
|
||||
// clock waveform generators (can affect OSC3)
|
||||
voice[0]->wave()->clock();
|
||||
voice[1]->wave()->clock();
|
||||
voice[2]->wave()->clock();
|
||||
|
||||
voice[0]->wave()->output(voice[2]->wave());
|
||||
voice[1]->wave()->output(voice[0]->wave());
|
||||
voice[2]->wave()->output(voice[1]->wave());
|
||||
|
||||
// clock ENV3 only
|
||||
voice[2]->envelope()->clock();
|
||||
}
|
||||
|
||||
cycles -= delta_t;
|
||||
nextVoiceSync -= delta_t;
|
||||
}
|
||||
|
||||
if (nextVoiceSync == 0)
|
||||
{
|
||||
voiceSync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
372
src/engine/platform/sound/c64_fp/SID.h
Normal file
372
src/engine/platform/sound/c64_fp/SID.h
Normal file
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef SIDFP_H
|
||||
#define SIDFP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Filter;
|
||||
class Filter6581;
|
||||
class Filter8580;
|
||||
class ExternalFilter;
|
||||
class Potentiometer;
|
||||
class Voice;
|
||||
class Resampler;
|
||||
|
||||
/**
|
||||
* SID error exception.
|
||||
*/
|
||||
class SIDError
|
||||
{
|
||||
private:
|
||||
const char* message;
|
||||
|
||||
public:
|
||||
SIDError(const char* msg) :
|
||||
message(msg) {}
|
||||
const char* getMessage() const { return message; }
|
||||
};
|
||||
|
||||
/**
|
||||
* MOS6581/MOS8580 emulation.
|
||||
*/
|
||||
class SID
|
||||
{
|
||||
private:
|
||||
/// Currently active filter
|
||||
Filter* filter;
|
||||
|
||||
/// Filter used, if model is set to 6581
|
||||
std::unique_ptr<Filter6581> const filter6581;
|
||||
|
||||
/// Filter used, if model is set to 8580
|
||||
std::unique_ptr<Filter8580> const filter8580;
|
||||
|
||||
/**
|
||||
* External filter that provides high-pass and low-pass filtering
|
||||
* to adjust sound tone slightly.
|
||||
*/
|
||||
std::unique_ptr<ExternalFilter> const externalFilter;
|
||||
|
||||
/// Resampler used by audio generation code.
|
||||
std::unique_ptr<Resampler> resampler;
|
||||
|
||||
/// Paddle X register support
|
||||
std::unique_ptr<Potentiometer> const potX;
|
||||
|
||||
/// Paddle Y register support
|
||||
std::unique_ptr<Potentiometer> const potY;
|
||||
|
||||
/// SID voices
|
||||
std::unique_ptr<Voice> voice[3];
|
||||
|
||||
/// Time to live for the last written value
|
||||
int busValueTtl;
|
||||
|
||||
/// Current chip model's bus value TTL
|
||||
int modelTTL;
|
||||
|
||||
/// Time until #voiceSync must be run.
|
||||
unsigned int nextVoiceSync;
|
||||
|
||||
/// Currently active chip model.
|
||||
ChipModel model;
|
||||
|
||||
/// Last written value
|
||||
unsigned char busValue;
|
||||
|
||||
/// Flags for muted channels
|
||||
bool muted[3];
|
||||
|
||||
/**
|
||||
* Emulated nonlinearity of the envelope DAC.
|
||||
*
|
||||
* @See Dac
|
||||
*/
|
||||
float envDAC[256];
|
||||
|
||||
/**
|
||||
* Emulated nonlinearity of the oscillator DAC.
|
||||
*
|
||||
* @See Dac
|
||||
*/
|
||||
float oscDAC[4096];
|
||||
|
||||
private:
|
||||
/**
|
||||
* Age the bus value and zero it if it's TTL has expired.
|
||||
*
|
||||
* @param n the number of cycles
|
||||
*/
|
||||
void ageBusValue(unsigned int n);
|
||||
|
||||
/**
|
||||
* Get output sample.
|
||||
*
|
||||
* @return the output sample
|
||||
*/
|
||||
int output() const;
|
||||
|
||||
/**
|
||||
* Calculate the numebr of cycles according to current parameters
|
||||
* that it takes to reach sync.
|
||||
*
|
||||
* @param sync whether to do the actual voice synchronization
|
||||
*/
|
||||
void voiceSync(bool sync);
|
||||
|
||||
public:
|
||||
SID();
|
||||
~SID();
|
||||
|
||||
/**
|
||||
* Set chip model.
|
||||
*
|
||||
* @param model chip model to use
|
||||
* @throw SIDError
|
||||
*/
|
||||
void setChipModel(ChipModel model);
|
||||
|
||||
/**
|
||||
* Get currently emulated chip model.
|
||||
*/
|
||||
ChipModel getChipModel() const { return model; }
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller
|
||||
* is responsible for keeping the value within 16 bits. Note that to mix in
|
||||
* an external audio signal, the signal should be resampled to 1MHz first to
|
||||
* avoid sampling noise.
|
||||
*
|
||||
* @param value input level to set
|
||||
*/
|
||||
void input(int value);
|
||||
|
||||
/**
|
||||
* Read registers.
|
||||
*
|
||||
* Reading a write only register returns the last char written to any SID register.
|
||||
* The individual bits in this value start to fade down towards zero after a few cycles.
|
||||
* All bits reach zero within approximately $2000 - $4000 cycles.
|
||||
* It has been claimed that this fading happens in an orderly fashion,
|
||||
* however sampling of write only registers reveals that this is not the case.
|
||||
* NOTE: This is not correctly modeled.
|
||||
* The actual use of write only registers has largely been made
|
||||
* in the belief that all SID registers are readable.
|
||||
* To support this belief the read would have to be done immediately
|
||||
* after a write to the same register (remember that an intermediate write
|
||||
* to another register would yield that value instead).
|
||||
* With this in mind we return the last value written to any SID register
|
||||
* for $2000 cycles without modeling the bit fading.
|
||||
*
|
||||
* @param offset SID register to read
|
||||
* @return value read from chip
|
||||
*/
|
||||
unsigned char read(int offset);
|
||||
|
||||
/**
|
||||
* Write registers.
|
||||
*
|
||||
* @param offset chip register to write
|
||||
* @param value value to write
|
||||
*/
|
||||
void write(int offset, unsigned char value);
|
||||
|
||||
/**
|
||||
* SID voice muting.
|
||||
*
|
||||
* @param channel channel to modify
|
||||
* @param enable is muted?
|
||||
*/
|
||||
void mute(int channel, bool enable) { muted[channel] = enable; }
|
||||
|
||||
/**
|
||||
* Setting of SID sampling parameters.
|
||||
*
|
||||
* Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
|
||||
* The default end of passband frequency is pass_freq = 0.9*sample_freq/2
|
||||
* for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies.
|
||||
*
|
||||
* For resampling, the ratio between the clock frequency and the sample frequency
|
||||
* is limited as follows: 125*clock_freq/sample_freq < 16384
|
||||
* E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not be set
|
||||
* lower than ~ 8kHz. A lower sample frequency would make the resampling code
|
||||
* overfill its 16k sample ring buffer.
|
||||
*
|
||||
* The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2
|
||||
*
|
||||
* E.g. for a 44.1kHz sampling rate the end of passband frequency
|
||||
* is limited to slightly below 20kHz.
|
||||
* This constraint ensures that the FIR table is not overfilled.
|
||||
*
|
||||
* @param clockFrequency System clock frequency at Hz
|
||||
* @param method sampling method to use
|
||||
* @param samplingFrequency Desired output sampling rate
|
||||
* @param highestAccurateFrequency
|
||||
* @throw SIDError
|
||||
*/
|
||||
void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency);
|
||||
|
||||
/**
|
||||
* Clock SID forward using chosen output sampling algorithm.
|
||||
*
|
||||
* @param cycles c64 clocks to clock
|
||||
* @param buf audio output buffer
|
||||
* @return number of samples produced
|
||||
*/
|
||||
int clock(unsigned int cycles, short* buf);
|
||||
|
||||
/**
|
||||
* Clock SID forward with no audio production.
|
||||
*
|
||||
* _Warning_:
|
||||
* You can't mix this method of clocking with the audio-producing
|
||||
* clock() because components that don't affect OSC3/ENV3 are not
|
||||
* emulated.
|
||||
*
|
||||
* @param cycles c64 clocks to clock.
|
||||
*/
|
||||
void clockSilent(unsigned int cycles);
|
||||
|
||||
/**
|
||||
* Set filter curve parameter for 6581 model.
|
||||
*
|
||||
* @see Filter6581::setFilterCurve(double)
|
||||
*/
|
||||
void setFilter6581Curve(double filterCurve);
|
||||
|
||||
/**
|
||||
* Set filter curve parameter for 8580 model.
|
||||
*
|
||||
* @see Filter8580::setFilterCurve(double)
|
||||
*/
|
||||
void setFilter8580Curve(double filterCurve);
|
||||
|
||||
/**
|
||||
* Enable filter emulation.
|
||||
*
|
||||
* @param enable false to turn off filter emulation
|
||||
*/
|
||||
void enableFilter(bool enable);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(SID_CPP)
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "ExternalFilter.h"
|
||||
#include "Voice.h"
|
||||
#include "resample/Resampler.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
void SID::ageBusValue(unsigned int n)
|
||||
{
|
||||
if (likely(busValueTtl != 0))
|
||||
{
|
||||
busValueTtl -= n;
|
||||
|
||||
if (unlikely(busValueTtl <= 0))
|
||||
{
|
||||
busValue = 0;
|
||||
busValueTtl = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
int SID::output() const
|
||||
{
|
||||
const int v1 = voice[0]->output(voice[2]->wave());
|
||||
const int v2 = voice[1]->output(voice[0]->wave());
|
||||
const int v3 = voice[2]->output(voice[1]->wave());
|
||||
|
||||
return externalFilter->clock(filter->clock(v1, v2, v3));
|
||||
}
|
||||
|
||||
|
||||
RESID_INLINE
|
||||
int SID::clock(unsigned int cycles, short* buf)
|
||||
{
|
||||
ageBusValue(cycles);
|
||||
int s = 0;
|
||||
|
||||
while (cycles != 0)
|
||||
{
|
||||
unsigned int delta_t = std::min(nextVoiceSync, cycles);
|
||||
|
||||
if (likely(delta_t > 0))
|
||||
{
|
||||
for (unsigned int i = 0; i < delta_t; i++)
|
||||
{
|
||||
// clock waveform generators
|
||||
voice[0]->wave()->clock();
|
||||
voice[1]->wave()->clock();
|
||||
voice[2]->wave()->clock();
|
||||
|
||||
// clock envelope generators
|
||||
voice[0]->envelope()->clock();
|
||||
voice[1]->envelope()->clock();
|
||||
voice[2]->envelope()->clock();
|
||||
|
||||
if (unlikely(resampler->input(output())))
|
||||
{
|
||||
buf[s++] = resampler->getOutput();
|
||||
}
|
||||
}
|
||||
|
||||
cycles -= delta_t;
|
||||
nextVoiceSync -= delta_t;
|
||||
}
|
||||
|
||||
if (unlikely(nextVoiceSync == 0))
|
||||
{
|
||||
voiceSync(true);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "Spline.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Spline::Spline(const std::vector<Point> &input) :
|
||||
params(input.size()),
|
||||
c(¶ms[0])
|
||||
{
|
||||
assert(input.size() > 2);
|
||||
|
||||
const size_t coeffLength = input.size() - 1;
|
||||
|
||||
std::vector<double> dxs(coeffLength);
|
||||
std::vector<double> ms(coeffLength);
|
||||
|
||||
// Get consecutive differences and slopes
|
||||
for (size_t i = 0; i < coeffLength; i++)
|
||||
{
|
||||
assert(input[i].x < input[i + 1].x);
|
||||
|
||||
const double dx = input[i + 1].x - input[i].x;
|
||||
const double dy = input[i + 1].y - input[i].y;
|
||||
dxs[i] = dx;
|
||||
ms[i] = dy/dx;
|
||||
}
|
||||
|
||||
// Get degree-1 coefficients
|
||||
params[0].c = ms[0];
|
||||
for (size_t i = 1; i < coeffLength; i++)
|
||||
{
|
||||
const double m = ms[i - 1];
|
||||
const double mNext = ms[i];
|
||||
if (m * mNext <= 0)
|
||||
{
|
||||
params[i].c = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double dx = dxs[i - 1];
|
||||
const double dxNext = dxs[i];
|
||||
const double common = dx + dxNext;
|
||||
params[i].c = 3.0 * common / ((common + dxNext) / m + (common + dx) / mNext);
|
||||
}
|
||||
}
|
||||
params[coeffLength].c = ms[coeffLength - 1];
|
||||
|
||||
// Get degree-2 and degree-3 coefficients
|
||||
for (size_t i = 0; i < coeffLength; i++)
|
||||
{
|
||||
params[i].x1 = input[i].x;
|
||||
params[i].x2 = input[i + 1].x;
|
||||
params[i].d = input[i].y;
|
||||
|
||||
const double c1 = params[i].c;
|
||||
const double m = ms[i];
|
||||
const double invDx = 1.0 / dxs[i];
|
||||
const double common = c1 + params[i + 1].c - m - m;
|
||||
params[i].b = (m - c1 - common) * invDx;
|
||||
params[i].a = common * invDx * invDx;
|
||||
}
|
||||
|
||||
// Fix the upper range, because we interpolate outside original bounds if necessary.
|
||||
params[coeffLength - 1].x2 = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
Spline::Point Spline::evaluate(double x) const
|
||||
{
|
||||
if ((x < c->x1) || (x > c->x2))
|
||||
{
|
||||
for (size_t i = 0; i < params.size(); i++)
|
||||
{
|
||||
if (x <= params[i].x2)
|
||||
{
|
||||
c = ¶ms[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate
|
||||
const double diff = x - c->x1;
|
||||
|
||||
Point out;
|
||||
|
||||
// y = a*x^3 + b*x^2 + c*x + d
|
||||
out.x = ((c->a * diff + c->b) * diff + c->c) * diff + c->d;
|
||||
|
||||
// dy = 3*a*x^2 + 2*b*x + c
|
||||
out.y = (3.0 * c->a * diff + 2.0 * c->b) * diff + c->c;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef SPLINE_H
|
||||
#define SPLINE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Fritsch-Carlson monotone cubic spline interpolation.
|
||||
*
|
||||
* Based on the implementation from the [Monotone cubic interpolation] wikipedia page.
|
||||
*
|
||||
* [Monotone cubic interpolation]: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
|
||||
*/
|
||||
class Spline
|
||||
{
|
||||
public:
|
||||
typedef struct
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
} Point;
|
||||
|
||||
private:
|
||||
typedef struct
|
||||
{
|
||||
double x1;
|
||||
double x2;
|
||||
double a;
|
||||
double b;
|
||||
double c;
|
||||
double d;
|
||||
} Param;
|
||||
|
||||
typedef std::vector<Param> ParamVector;
|
||||
|
||||
private:
|
||||
/// Interpolation parameters
|
||||
ParamVector params;
|
||||
|
||||
/// Last used parameters, cached for speed up
|
||||
mutable ParamVector::const_pointer c;
|
||||
|
||||
public:
|
||||
Spline(const std::vector<Point> &input);
|
||||
|
||||
/**
|
||||
* Evaluate y and its derivative at given point x.
|
||||
*/
|
||||
Point evaluate(double x) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
130
src/engine/platform/sound/c64_fp/Voice.h
Normal file
130
src/engine/platform/sound/c64_fp/Voice.h
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef VOICE_H
|
||||
#define VOICE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
#include "WaveformGenerator.h"
|
||||
#include "EnvelopeGenerator.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Representation of SID voice block.
|
||||
*/
|
||||
class Voice
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<WaveformGenerator> const waveformGenerator;
|
||||
|
||||
std::unique_ptr<EnvelopeGenerator> const envelopeGenerator;
|
||||
|
||||
/// The DAC LUT for analog waveform output
|
||||
float* wavDAC; //-V730_NOINIT this is initialized in the SID constructor
|
||||
|
||||
/// The DAC LUT for analog envelope output
|
||||
float* envDAC; //-V730_NOINIT this is initialized in the SID constructor
|
||||
|
||||
public:
|
||||
/**
|
||||
* Amplitude modulated waveform output.
|
||||
*
|
||||
* The waveform DAC generates a voltage between virtual ground and Vdd
|
||||
* (5-12 V for the 6581 and 4.75-9 V for the 8580)
|
||||
* corresponding to oscillator state 0 .. 4095.
|
||||
*
|
||||
* The envelope DAC generates a voltage between waveform gen output and
|
||||
* the virtual ground level, corresponding to envelope state 0 .. 255.
|
||||
*
|
||||
* Ideal range [-2048*255, 2047*255].
|
||||
*
|
||||
* @param ringModulator Ring-modulator for waveform
|
||||
* @return the voice analog output
|
||||
*/
|
||||
RESID_INLINE
|
||||
int output(const WaveformGenerator* ringModulator) const
|
||||
{
|
||||
unsigned int const wav = waveformGenerator->output(ringModulator);
|
||||
unsigned int const env = envelopeGenerator->output();
|
||||
|
||||
// DAC imperfections are emulated by using the digital output
|
||||
// as an index into a DAC lookup table.
|
||||
return static_cast<int>(wavDAC[wav] * envDAC[env]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
Voice() :
|
||||
waveformGenerator(new WaveformGenerator()),
|
||||
envelopeGenerator(new EnvelopeGenerator()) {}
|
||||
|
||||
/**
|
||||
* Set the analog DAC emulation for waveform generator.
|
||||
* Must be called before any operation.
|
||||
*
|
||||
* @param dac
|
||||
*/
|
||||
void setWavDAC(float* dac) { wavDAC = dac; }
|
||||
|
||||
/**
|
||||
* Set the analog DAC emulation for envelope.
|
||||
* Must be called before any operation.
|
||||
*
|
||||
* @param dac
|
||||
*/
|
||||
void setEnvDAC(float* dac) { envDAC = dac; }
|
||||
|
||||
WaveformGenerator* wave() const { return waveformGenerator.get(); }
|
||||
|
||||
EnvelopeGenerator* envelope() const { return envelopeGenerator.get(); }
|
||||
|
||||
/**
|
||||
* Write control register.
|
||||
*
|
||||
* @param control Control register value.
|
||||
*/
|
||||
void writeCONTROL_REG(unsigned char control)
|
||||
{
|
||||
waveformGenerator->writeCONTROL_REG(control);
|
||||
envelopeGenerator->writeCONTROL_REG(control);
|
||||
}
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
waveformGenerator->reset();
|
||||
envelopeGenerator->reset();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
204
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
Normal file
204
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "WaveformCalculator.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
WaveformCalculator* WaveformCalculator::getInstance()
|
||||
{
|
||||
static WaveformCalculator instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters derived with the Monte Carlo method based on
|
||||
* samplings by kevtris. Code and data available in the project repository [1].
|
||||
*
|
||||
* The score here reported is the acoustic error
|
||||
* calculated XORing the estimated and the sampled values.
|
||||
* In parentheses the number of mispredicted bits
|
||||
* on a total of 32768.
|
||||
*
|
||||
* [1] https://github.com/libsidplayfp/combined-waveforms
|
||||
*/
|
||||
const CombinedWaveformConfig config[2][4] =
|
||||
{
|
||||
{ /* kevtris chip G (6581 R2) */
|
||||
{0.90251f, 0.f, 0.f, 1.9147f, 1.6747f, 0.62376f }, // error 1689 (280)
|
||||
{0.93088f, 2.4843f, 0.f, 1.0353f, 1.1484f, 0.f }, // error 6128 (130)
|
||||
{0.90988f, 2.26303f, 1.13126f, 1.0035f, 1.13801f, 0.f }, // error 14243 (632)
|
||||
{0.91f, 1.192f, 0.f, 1.0169f, 1.2f, 0.637f }, // error 64 (2)
|
||||
},
|
||||
{ /* kevtris chip V (8580 R5) */
|
||||
{0.9632f, 0.f, 0.975f, 1.7467f, 2.36132f, 0.975395f}, // error 1380 (169)
|
||||
{0.92886f, 1.67696f, 0.f, 1.1014f, 1.4352f, 0.f }, // error 8007 (218)
|
||||
{0.94043f, 1.7937f, 0.981f, 1.1213f, 1.4259f, 0.f }, // error 11957 (362)
|
||||
{0.96211f, 0.98695f, 1.00387f, 1.46499f, 1.98375f, 0.77777f }, // error 2369 (89)
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate bitstate based on emulation of combined waves.
|
||||
*
|
||||
* @param config model parameters matrix
|
||||
* @param waveform the waveform to emulate, 1 .. 7
|
||||
* @param accumulator the high bits of the accumulator value
|
||||
*/
|
||||
short calculateCombinedWaveform(const CombinedWaveformConfig& config, int waveform, int accumulator)
|
||||
{
|
||||
float o[12];
|
||||
|
||||
// Saw
|
||||
for (unsigned int i = 0; i < 12; i++)
|
||||
{
|
||||
o[i] = (accumulator & (1 << i)) != 0 ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
// convert to Triangle
|
||||
if ((waveform & 3) == 1)
|
||||
{
|
||||
const bool top = (accumulator & 0x800) != 0;
|
||||
|
||||
for (int i = 11; i > 0; i--)
|
||||
{
|
||||
o[i] = top ? 1.0f - o[i - 1] : o[i - 1];
|
||||
}
|
||||
|
||||
o[0] = 0.f;
|
||||
}
|
||||
|
||||
// or to Saw+Triangle
|
||||
else if ((waveform & 3) == 3)
|
||||
{
|
||||
// bottom bit is grounded via T waveform selector
|
||||
o[0] *= config.stmix;
|
||||
|
||||
for (int i = 1; i < 12; i++)
|
||||
{
|
||||
/*
|
||||
* Enabling the S waveform pulls the XOR circuit selector transistor down
|
||||
* (which would normally make the descending ramp of the triangle waveform),
|
||||
* so ST does not actually have a sawtooth and triangle waveform combined,
|
||||
* but merely combines two sawtooths, one rising double the speed the other.
|
||||
*
|
||||
* http://www.lemon64.com/forum/viewtopic.php?t=25442&postdays=0&postorder=asc&start=165
|
||||
*/
|
||||
o[i] = o[i - 1] * (1.f - config.stmix) + o[i] * config.stmix;
|
||||
}
|
||||
}
|
||||
|
||||
// topbit for Saw
|
||||
if ((waveform & 2) == 2)
|
||||
{
|
||||
o[11] *= config.topbit;
|
||||
}
|
||||
|
||||
// ST, P* waveforms
|
||||
if (waveform == 3 || waveform > 4)
|
||||
{
|
||||
float distancetable[12 * 2 + 1];
|
||||
distancetable[12] = 1.f;
|
||||
for (int i = 12; i > 0; i--)
|
||||
{
|
||||
distancetable[12-i] = 1.0f / pow(config.distance1, i);
|
||||
distancetable[12+i] = 1.0f / pow(config.distance2, i);
|
||||
}
|
||||
|
||||
float tmp[12];
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
float avg = 0.f;
|
||||
float n = 0.f;
|
||||
|
||||
for (int j = 0; j < 12; j++)
|
||||
{
|
||||
const float weight = distancetable[i - j + 12];
|
||||
avg += o[j] * weight;
|
||||
n += weight;
|
||||
}
|
||||
|
||||
// pulse control bit
|
||||
if (waveform > 4)
|
||||
{
|
||||
const float weight = distancetable[i - 12 + 12];
|
||||
avg += config.pulsestrength * weight;
|
||||
n += weight;
|
||||
}
|
||||
|
||||
tmp[i] = (o[i] + avg / n) * 0.5f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
o[i] = tmp[i];
|
||||
}
|
||||
}
|
||||
|
||||
short value = 0;
|
||||
|
||||
for (unsigned int i = 0; i < 12; i++)
|
||||
{
|
||||
if (o[i] > config.bias)
|
||||
{
|
||||
value |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
matrix_t* WaveformCalculator::buildTable(ChipModel model)
|
||||
{
|
||||
const CombinedWaveformConfig* cfgArray = config[model == MOS6581 ? 0 : 1];
|
||||
|
||||
cw_cache_t::iterator lb = CACHE.lower_bound(cfgArray);
|
||||
|
||||
if (lb != CACHE.end() && !(CACHE.key_comp()(cfgArray, lb->first)))
|
||||
{
|
||||
return &(lb->second);
|
||||
}
|
||||
|
||||
matrix_t wftable(8, 4096);
|
||||
|
||||
for (unsigned int idx = 0; idx < 1 << 12; idx++)
|
||||
{
|
||||
wftable[0][idx] = 0xfff;
|
||||
wftable[1][idx] = static_cast<short>((idx & 0x800) == 0 ? idx << 1 : (idx ^ 0xfff) << 1);
|
||||
wftable[2][idx] = static_cast<short>(idx);
|
||||
wftable[3][idx] = calculateCombinedWaveform(cfgArray[0], 3, idx);
|
||||
wftable[4][idx] = 0xfff;
|
||||
wftable[5][idx] = calculateCombinedWaveform(cfgArray[1], 5, idx);
|
||||
wftable[6][idx] = calculateCombinedWaveform(cfgArray[2], 6, idx);
|
||||
wftable[7][idx] = calculateCombinedWaveform(cfgArray[3], 7, idx);
|
||||
}
|
||||
#ifdef HAVE_CXX11
|
||||
return &(CACHE.emplace_hint(lb, cw_cache_t::value_type(cfgArray, wftable))->second);
|
||||
#else
|
||||
return &(CACHE.insert(lb, cw_cache_t::value_type(cfgArray, wftable))->second);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
128
src/engine/platform/sound/c64_fp/WaveformCalculator.h
Normal file
128
src/engine/platform/sound/c64_fp/WaveformCalculator.h
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef WAVEFORMCALCULATOR_h
|
||||
#define WAVEFORMCALCULATOR_h
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "array.h"
|
||||
#include "sidcxx11.h"
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Combined waveform model parameters.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
float bias;
|
||||
float pulsestrength;
|
||||
float topbit;
|
||||
float distance1;
|
||||
float distance2;
|
||||
float stmix;
|
||||
} CombinedWaveformConfig;
|
||||
|
||||
/**
|
||||
* Combined waveform calculator for WaveformGenerator.
|
||||
* By combining waveforms, the bits of each waveform are effectively short
|
||||
* circuited. A zero bit in one waveform will result in a zero output bit
|
||||
* (thus the infamous claim that the waveforms are AND'ed).
|
||||
* However, a zero bit in one waveform may also affect the neighboring bits
|
||||
* in the output.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 1 1
|
||||
* Bit # 1 0 9 8 7 6 5 4 3 2 1 0
|
||||
* -----------------------
|
||||
* Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0
|
||||
*
|
||||
* Triangle 0 0 1 1 1 1 1 1 0 0 0 0
|
||||
*
|
||||
* AND 0 0 0 1 1 1 1 1 0 0 0 0
|
||||
*
|
||||
* Output 0 0 0 0 1 1 1 0 0 0 0 0
|
||||
*
|
||||
*
|
||||
* Re-vectorized die photographs reveal the mechanism behind this behavior.
|
||||
* Each waveform selector bit acts as a switch, which directly connects
|
||||
* internal outputs into the waveform DAC inputs as follows:
|
||||
*
|
||||
* - Noise outputs the shift register bits to DAC inputs as described above.
|
||||
* Each output is also used as input to the next bit when the shift register
|
||||
* is shifted. Lower four bits are grounded.
|
||||
* - Pulse connects a single line to all DAC inputs. The line is connected to
|
||||
* either 5V (pulse on) or 0V (pulse off) at bit 11, and ends at bit 0.
|
||||
* - Triangle connects the upper 11 bits of the (MSB EOR'ed) accumulator to the
|
||||
* DAC inputs, so that DAC bit 0 = 0, DAC bit n = accumulator bit n - 1.
|
||||
* - Sawtooth connects the upper 12 bits of the accumulator to the DAC inputs,
|
||||
* so that DAC bit n = accumulator bit n. Sawtooth blocks out the MSB from
|
||||
* the EOR used to generate the triangle waveform.
|
||||
*
|
||||
* We can thus draw the following conclusions:
|
||||
*
|
||||
* - The shift register may be written to by combined waveforms.
|
||||
* - The pulse waveform interconnects all bits in combined waveforms via the
|
||||
* pulse line.
|
||||
* - The combination of triangle and sawtooth interconnects neighboring bits
|
||||
* of the sawtooth waveform.
|
||||
*
|
||||
* Also in the 6581 the MSB of the oscillator, used as input for the
|
||||
* triangle xor logic and the pulse adder's last bit, is connected directly
|
||||
* to the waveform selector, while in the 8580 it is latched at sid_clk2
|
||||
* before being forwarded to the selector. Thus in the 6581 if the sawtooth MSB
|
||||
* is pulled down it might affect the oscillator's adder
|
||||
* driving the top bit low.
|
||||
*
|
||||
*/
|
||||
class WaveformCalculator
|
||||
{
|
||||
private:
|
||||
typedef std::map<const CombinedWaveformConfig*, matrix_t> cw_cache_t;
|
||||
|
||||
private:
|
||||
cw_cache_t CACHE;
|
||||
|
||||
WaveformCalculator() DEFAULT;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Get the singleton instance.
|
||||
*/
|
||||
static WaveformCalculator* getInstance();
|
||||
|
||||
/**
|
||||
* Build waveform tables for use by WaveformGenerator.
|
||||
*
|
||||
* @param model Chip model to use
|
||||
* @return Waveform table
|
||||
*/
|
||||
matrix_t* buildTable(ChipModel model);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
357
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
Normal file
357
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
Normal file
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2021 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define WAVEFORMGENERATOR_CPP
|
||||
|
||||
#include "WaveformGenerator.h"
|
||||
|
||||
/*
|
||||
* This fixes tests
|
||||
* SID/wb_testsuite/noise_writeback_check_8_to_C_old
|
||||
* SID/wb_testsuite/noise_writeback_check_9_to_C_old
|
||||
* SID/wb_testsuite/noise_writeback_check_A_to_C_old
|
||||
* SID/wb_testsuite/noise_writeback_check_C_to_C_old
|
||||
*
|
||||
* but breaks SID/wf12nsr/wf12nsr
|
||||
*
|
||||
* needs more digging...
|
||||
*/
|
||||
//#define NO_WB_NOI_PUL
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Number of cycles after which the waveform output fades to 0 when setting
|
||||
* the waveform register to 0.
|
||||
* Values measured on warm chips (6581R3/R4 and 8580R5)
|
||||
* checking OSC3.
|
||||
* Times vary wildly with temperature and may differ
|
||||
* from chip to chip so the numbers here represent
|
||||
* only the big difference between the old and new models.
|
||||
*
|
||||
* See [VICE Bug #290](http://sourceforge.net/p/vice-emu/bugs/290/)
|
||||
* and [VICE Bug #1128](http://sourceforge.net/p/vice-emu/bugs/1128/)
|
||||
*/
|
||||
// ~95ms
|
||||
const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000;
|
||||
const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400;
|
||||
// ~1s
|
||||
//const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000;
|
||||
// ~1s
|
||||
const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000;
|
||||
const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000;
|
||||
|
||||
/**
|
||||
* Number of cycles after which the shift register is reset
|
||||
* when the test bit is set.
|
||||
* Values measured on warm chips (6581R3/R4 and 8580R5)
|
||||
* checking OSC3.
|
||||
* Times vary wildly with temperature and may differ
|
||||
* from chip to chip so the numbers here represent
|
||||
* only the big difference between the old and new models.
|
||||
*/
|
||||
// ~210ms
|
||||
const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000;
|
||||
const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000;
|
||||
// ~2.15s
|
||||
//const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000;
|
||||
// ~2.8s
|
||||
const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000;
|
||||
const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300;
|
||||
|
||||
/*
|
||||
* This is what happens when the lfsr is clocked:
|
||||
*
|
||||
* cycle 0: bit 19 of the accumulator goes from low to high, the noise register acts normally,
|
||||
* the output may overwrite a bit;
|
||||
*
|
||||
* cycle 1: first phase of the shift, the bits are interconnected and the output of each bit
|
||||
* is latched into the following. The output may overwrite the latched value.
|
||||
*
|
||||
* cycle 2: second phase of the shift, the latched value becomes active in the first
|
||||
* half of the clock and from the second half the register returns to normal operation.
|
||||
*
|
||||
* When the test or reset lines are active the first phase is executed at every cyle
|
||||
* until the signal is released triggering the second phase.
|
||||
*/
|
||||
void WaveformGenerator::clock_shift_register(unsigned int bit0)
|
||||
{
|
||||
shift_register = (shift_register >> 1) | bit0;
|
||||
|
||||
// New noise waveform output.
|
||||
set_noise_output();
|
||||
}
|
||||
|
||||
unsigned int WaveformGenerator::get_noise_writeback()
|
||||
{
|
||||
return
|
||||
~(
|
||||
(1 << 2) | // Bit 20
|
||||
(1 << 4) | // Bit 18
|
||||
(1 << 8) | // Bit 14
|
||||
(1 << 11) | // Bit 11
|
||||
(1 << 13) | // Bit 9
|
||||
(1 << 17) | // Bit 5
|
||||
(1 << 20) | // Bit 2
|
||||
(1 << 22) // Bit 0
|
||||
) |
|
||||
((waveform_output & (1 << 11)) >> 9) | // Bit 11 -> bit 20
|
||||
((waveform_output & (1 << 10)) >> 6) | // Bit 10 -> bit 18
|
||||
((waveform_output & (1 << 9)) >> 1) | // Bit 9 -> bit 14
|
||||
((waveform_output & (1 << 8)) << 3) | // Bit 8 -> bit 11
|
||||
((waveform_output & (1 << 7)) << 6) | // Bit 7 -> bit 9
|
||||
((waveform_output & (1 << 6)) << 11) | // Bit 6 -> bit 5
|
||||
((waveform_output & (1 << 5)) << 15) | // Bit 5 -> bit 2
|
||||
((waveform_output & (1 << 4)) << 18); // Bit 4 -> bit 0
|
||||
}
|
||||
|
||||
void WaveformGenerator::write_shift_register()
|
||||
{
|
||||
if (unlikely(waveform > 0x8) && likely(!test) && likely(shift_pipeline != 1))
|
||||
{
|
||||
// Write changes to the shift register output caused by combined waveforms
|
||||
// back into the shift register. This happens only when the register is clocked
|
||||
// (see $D1+$81_wave_test [1]) or when the test bit is falling.
|
||||
// A bit once set to zero cannot be changed, hence the and'ing.
|
||||
//
|
||||
// [1] ftp://ftp.untergrund.net/users/nata/sid_test/$D1+$81_wave_test.7z
|
||||
//
|
||||
// FIXME: Write test program to check the effect of 1 bits and whether
|
||||
// neighboring bits are affected.
|
||||
|
||||
#ifdef NO_WB_NOI_PUL
|
||||
if (waveform == 0xc)
|
||||
return;
|
||||
#endif
|
||||
shift_register &= get_noise_writeback();
|
||||
|
||||
noise_output &= waveform_output;
|
||||
set_no_noise_or_noise_output();
|
||||
}
|
||||
}
|
||||
|
||||
void WaveformGenerator::set_noise_output()
|
||||
{
|
||||
noise_output =
|
||||
((shift_register & (1 << 2)) << 9) | // Bit 20 -> bit 11
|
||||
((shift_register & (1 << 4)) << 6) | // Bit 18 -> bit 10
|
||||
((shift_register & (1 << 8)) << 1) | // Bit 14 -> bit 9
|
||||
((shift_register & (1 << 11)) >> 3) | // Bit 11 -> bit 8
|
||||
((shift_register & (1 << 13)) >> 6) | // Bit 9 -> bit 7
|
||||
((shift_register & (1 << 17)) >> 11) | // Bit 5 -> bit 6
|
||||
((shift_register & (1 << 20)) >> 15) | // Bit 2 -> bit 5
|
||||
((shift_register & (1 << 22)) >> 18); // Bit 0 -> bit 4
|
||||
|
||||
set_no_noise_or_noise_output();
|
||||
}
|
||||
|
||||
void WaveformGenerator::setWaveformModels(matrix_t* models)
|
||||
{
|
||||
model_wave = models;
|
||||
}
|
||||
|
||||
void WaveformGenerator::synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const
|
||||
{
|
||||
// A special case occurs when a sync source is synced itself on the same
|
||||
// cycle as when its MSB is set high. In this case the destination will
|
||||
// not be synced. This has been verified by sampling OSC3.
|
||||
if (unlikely(msb_rising) && syncDest->sync && !(sync && syncSource->msb_rising))
|
||||
{
|
||||
syncDest->accumulator = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool do_pre_writeback(unsigned int waveform_prev, unsigned int waveform, bool is6581)
|
||||
{
|
||||
// no writeback without combined waveforms
|
||||
if (likely(waveform_prev <= 0x8))
|
||||
return false;
|
||||
// no writeback when changing to noise
|
||||
if (waveform == 8)
|
||||
return false;
|
||||
// What's happening here?
|
||||
if (is6581 &&
|
||||
((((waveform_prev & 0x3) == 0x1) && ((waveform & 0x3) == 0x2))
|
||||
|| (((waveform_prev & 0x3) == 0x2) && ((waveform & 0x3) == 0x1))))
|
||||
return false;
|
||||
if (waveform_prev == 0xc)
|
||||
{
|
||||
if (is6581)
|
||||
return false;
|
||||
else if ((waveform != 0x9) && (waveform != 0xe))
|
||||
return false;
|
||||
}
|
||||
#ifdef NO_WB_NOI_PUL
|
||||
if (waveform == 0xc)
|
||||
return false;
|
||||
#endif
|
||||
// ok do the writeback
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* When noise and pulse are combined all the bits are
|
||||
* connected and the four lower ones are grounded.
|
||||
* This causes the adjacent bits to be pulled down,
|
||||
* with different strength depending on model.
|
||||
*
|
||||
* This is just a rough attempt at modelling the effect.
|
||||
*/
|
||||
|
||||
static unsigned int noise_pulse6581(unsigned int noise)
|
||||
{
|
||||
return (noise < 0xf00) ? 0x000 : noise & (noise << 1) & (noise << 2);
|
||||
}
|
||||
|
||||
static unsigned int noise_pulse8580(unsigned int noise)
|
||||
{
|
||||
return (noise < 0xfc0) ? noise & (noise << 1) : 0xfc0;
|
||||
}
|
||||
|
||||
void WaveformGenerator::set_no_noise_or_noise_output()
|
||||
{
|
||||
no_noise_or_noise_output = no_noise | noise_output;
|
||||
|
||||
// pulse+noise
|
||||
if (unlikely((waveform & 0xc) == 0xc))
|
||||
no_noise_or_noise_output = is6581
|
||||
? noise_pulse6581(no_noise_or_noise_output)
|
||||
: noise_pulse8580(no_noise_or_noise_output);
|
||||
|
||||
}
|
||||
|
||||
void WaveformGenerator::writeCONTROL_REG(unsigned char control)
|
||||
{
|
||||
const unsigned int waveform_prev = waveform;
|
||||
const bool test_prev = test;
|
||||
|
||||
waveform = (control >> 4) & 0x0f;
|
||||
test = (control & 0x08) != 0;
|
||||
sync = (control & 0x02) != 0;
|
||||
|
||||
// Substitution of accumulator MSB when sawtooth = 0, ring_mod = 1.
|
||||
ring_msb_mask = ((~control >> 5) & (control >> 2) & 0x1) << 23;
|
||||
|
||||
if (waveform != waveform_prev)
|
||||
{
|
||||
// Set up waveform table.
|
||||
wave = (*model_wave)[waveform & 0x7];
|
||||
|
||||
// no_noise and no_pulse are used in set_waveform_output() as bitmasks to
|
||||
// only let the noise or pulse influence the output when the noise or pulse
|
||||
// waveforms are selected.
|
||||
no_noise = (waveform & 0x8) != 0 ? 0x000 : 0xfff;
|
||||
set_no_noise_or_noise_output();
|
||||
no_pulse = (waveform & 0x4) != 0 ? 0x000 : 0xfff;
|
||||
|
||||
if (waveform == 0)
|
||||
{
|
||||
// Change to floating DAC input.
|
||||
// Reset fading time for floating DAC input.
|
||||
floating_output_ttl = is6581 ? FLOATING_OUTPUT_TTL_6581R3 : FLOATING_OUTPUT_TTL_8580R5;
|
||||
}
|
||||
}
|
||||
|
||||
if (test != test_prev)
|
||||
{
|
||||
if (test)
|
||||
{
|
||||
// Reset accumulator.
|
||||
accumulator = 0;
|
||||
|
||||
// Flush shift pipeline.
|
||||
shift_pipeline = 0;
|
||||
|
||||
// Set reset time for shift register.
|
||||
shift_register_reset = is6581 ? SHIFT_REGISTER_RESET_6581R3 : SHIFT_REGISTER_RESET_8580R5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When the test bit is falling, the second phase of the shift is
|
||||
// completed by enabling SRAM write.
|
||||
|
||||
// During first phase of the shift the bits are interconnected
|
||||
// and the output of each bit is latched into the following.
|
||||
// The output may overwrite the latched value.
|
||||
if (do_pre_writeback(waveform_prev, waveform, is6581))
|
||||
{
|
||||
shift_register &= get_noise_writeback();
|
||||
}
|
||||
|
||||
// bit0 = (bit22 | test) ^ bit17 = 1 ^ bit17 = ~bit17
|
||||
clock_shift_register((~shift_register << 17) & (1 << 22));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaveformGenerator::waveBitfade()
|
||||
{
|
||||
waveform_output &= waveform_output >> 1;
|
||||
osc3 = waveform_output;
|
||||
if (waveform_output != 0)
|
||||
floating_output_ttl = is6581 ? FLOATING_OUTPUT_FADE_6581R3 : FLOATING_OUTPUT_FADE_8580R5;
|
||||
}
|
||||
|
||||
void WaveformGenerator::shiftregBitfade()
|
||||
{
|
||||
shift_register |= shift_register >> 1;
|
||||
shift_register |= 0x400000;
|
||||
if (shift_register != 0x7fffff)
|
||||
shift_register_reset = is6581 ? SHIFT_REGISTER_FADE_6581R3 : SHIFT_REGISTER_FADE_8580R5;
|
||||
}
|
||||
|
||||
void WaveformGenerator::reset()
|
||||
{
|
||||
// accumulator is not changed on reset
|
||||
freq = 0;
|
||||
pw = 0;
|
||||
|
||||
msb_rising = false;
|
||||
|
||||
waveform = 0;
|
||||
osc3 = 0;
|
||||
|
||||
test = false;
|
||||
sync = false;
|
||||
|
||||
wave = model_wave ? (*model_wave)[0] : nullptr;
|
||||
|
||||
ring_msb_mask = 0;
|
||||
no_noise = 0xfff;
|
||||
no_pulse = 0xfff;
|
||||
pulse_output = 0xfff;
|
||||
|
||||
shift_register_reset = 0;
|
||||
shift_register = 0x7fffff;
|
||||
// when reset is released the shift register is clocked once
|
||||
// so the lower bit is zeroed out
|
||||
// bit0 = (bit22 | test) ^ bit17 = 1 ^ 1 = 0
|
||||
clock_shift_register(0);
|
||||
|
||||
shift_pipeline = 0;
|
||||
|
||||
waveform_output = 0;
|
||||
floating_output_ttl = 0;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
396
src/engine/platform/sound/c64_fp/WaveformGenerator.h
Normal file
396
src/engine/platform/sound/c64_fp/WaveformGenerator.h
Normal file
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef WAVEFORMGENERATOR_H
|
||||
#define WAVEFORMGENERATOR_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
#include "array.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* A 24 bit accumulator is the basis for waveform generation.
|
||||
* FREQ is added to the lower 16 bits of the accumulator each cycle.
|
||||
* The accumulator is set to zero when TEST is set, and starts counting
|
||||
* when TEST is cleared.
|
||||
*
|
||||
* Waveforms are generated as follows:
|
||||
*
|
||||
* - No waveform:
|
||||
* When no waveform is selected, the DAC input is floating.
|
||||
*
|
||||
*
|
||||
* - Triangle:
|
||||
* The upper 12 bits of the accumulator are used.
|
||||
* The MSB is used to create the falling edge of the triangle by inverting
|
||||
* the lower 11 bits. The MSB is thrown away and the lower 11 bits are
|
||||
* left-shifted (half the resolution, full amplitude).
|
||||
* Ring modulation substitutes the MSB with MSB EOR NOT sync_source MSB.
|
||||
*
|
||||
*
|
||||
* - Sawtooth:
|
||||
* The output is identical to the upper 12 bits of the accumulator.
|
||||
*
|
||||
*
|
||||
* - Pulse:
|
||||
* The upper 12 bits of the accumulator are used.
|
||||
* These bits are compared to the pulse width register by a 12 bit digital
|
||||
* comparator; output is either all one or all zero bits.
|
||||
* The pulse setting is delayed one cycle after the compare.
|
||||
* The test bit, when set to one, holds the pulse waveform output at 0xfff
|
||||
* regardless of the pulse width setting.
|
||||
*
|
||||
*
|
||||
* - Noise:
|
||||
* The noise output is taken from intermediate bits of a 23-bit shift register
|
||||
* which is clocked by bit 19 of the accumulator.
|
||||
* The shift is delayed 2 cycles after bit 19 is set high.
|
||||
*
|
||||
* Operation: Calculate EOR result, shift register, set bit 0 = result.
|
||||
*
|
||||
* reset +--------------------------------------------+
|
||||
* | | |
|
||||
* test--OR-->EOR<--+ |
|
||||
* | | |
|
||||
* 2 2 2 1 1 1 1 1 1 1 1 1 1 |
|
||||
* Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <---+
|
||||
* | | | | | | | |
|
||||
* Waveform bits: 1 1 9 8 7 6 5 4
|
||||
* 1 0
|
||||
*
|
||||
* The low 4 waveform bits are zero (grounded).
|
||||
*/
|
||||
class WaveformGenerator
|
||||
{
|
||||
private:
|
||||
matrix_t* model_wave;
|
||||
|
||||
short* wave;
|
||||
|
||||
// PWout = (PWn/40.95)%
|
||||
unsigned int pw;
|
||||
|
||||
unsigned int shift_register;
|
||||
|
||||
/// Emulation of pipeline causing bit 19 to clock the shift register.
|
||||
int shift_pipeline;
|
||||
|
||||
unsigned int ring_msb_mask;
|
||||
unsigned int no_noise;
|
||||
unsigned int noise_output;
|
||||
unsigned int no_noise_or_noise_output;
|
||||
unsigned int no_pulse;
|
||||
unsigned int pulse_output;
|
||||
|
||||
/// The control register right-shifted 4 bits; used for output function table lookup.
|
||||
unsigned int waveform;
|
||||
|
||||
unsigned int waveform_output;
|
||||
|
||||
/// Current accumulator value.
|
||||
unsigned int accumulator;
|
||||
|
||||
// Fout = (Fn*Fclk/16777216)Hz
|
||||
unsigned int freq;
|
||||
|
||||
/// 8580 tri/saw pipeline
|
||||
unsigned int tri_saw_pipeline;
|
||||
|
||||
/// The OSC3 value
|
||||
unsigned int osc3;
|
||||
|
||||
/// Remaining time to fully reset shift register.
|
||||
unsigned int shift_register_reset;
|
||||
|
||||
// The wave signal TTL when no waveform is selected
|
||||
unsigned int floating_output_ttl;
|
||||
|
||||
/// The control register bits. Gate is handled by EnvelopeGenerator.
|
||||
//@{
|
||||
bool test;
|
||||
bool sync;
|
||||
//@}
|
||||
|
||||
/// Tell whether the accumulator MSB was set high on this cycle.
|
||||
bool msb_rising;
|
||||
|
||||
bool is6581; //-V730_NOINIT this is initialized in the SID constructor
|
||||
|
||||
private:
|
||||
void clock_shift_register(unsigned int bit0);
|
||||
|
||||
unsigned int get_noise_writeback();
|
||||
|
||||
void write_shift_register();
|
||||
|
||||
void set_noise_output();
|
||||
|
||||
void set_no_noise_or_noise_output();
|
||||
|
||||
void waveBitfade();
|
||||
|
||||
void shiftregBitfade();
|
||||
|
||||
public:
|
||||
void setWaveformModels(matrix_t* models);
|
||||
|
||||
/**
|
||||
* Set the chip model.
|
||||
* Must be called before any operation.
|
||||
*
|
||||
* @param is6581 true if MOS6581, false if CSG8580
|
||||
*/
|
||||
void setModel(bool is6581) { this->is6581 = is6581; }
|
||||
|
||||
/**
|
||||
* SID clocking.
|
||||
*/
|
||||
void clock();
|
||||
|
||||
/**
|
||||
* Synchronize oscillators.
|
||||
* This must be done after all the oscillators have been clock()'ed,
|
||||
* so that they are in the same state.
|
||||
*
|
||||
* @param syncDest The oscillator that will be synced
|
||||
* @param syncSource The sync source oscillator
|
||||
*/
|
||||
void synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
WaveformGenerator() :
|
||||
model_wave(nullptr),
|
||||
wave(nullptr),
|
||||
pw(0),
|
||||
shift_register(0),
|
||||
shift_pipeline(0),
|
||||
ring_msb_mask(0),
|
||||
no_noise(0),
|
||||
noise_output(0),
|
||||
no_noise_or_noise_output(0),
|
||||
no_pulse(0),
|
||||
pulse_output(0),
|
||||
waveform(0),
|
||||
waveform_output(0),
|
||||
accumulator(0x555555), // Accumulator's even bits are high on powerup
|
||||
freq(0),
|
||||
tri_saw_pipeline(0x555),
|
||||
osc3(0),
|
||||
shift_register_reset(0),
|
||||
floating_output_ttl(0),
|
||||
test(false),
|
||||
sync(false),
|
||||
msb_rising(false) {}
|
||||
|
||||
/**
|
||||
* Write FREQ LO register.
|
||||
*
|
||||
* @param freq_lo low 8 bits of frequency
|
||||
*/
|
||||
void writeFREQ_LO(unsigned char freq_lo) { freq = (freq & 0xff00) | (freq_lo & 0xff); }
|
||||
|
||||
/**
|
||||
* Write FREQ HI register.
|
||||
*
|
||||
* @param freq_hi high 8 bits of frequency
|
||||
*/
|
||||
void writeFREQ_HI(unsigned char freq_hi) { freq = (freq_hi << 8 & 0xff00) | (freq & 0xff); }
|
||||
|
||||
/**
|
||||
* Write PW LO register.
|
||||
*
|
||||
* @param pw_lo low 8 bits of pulse width
|
||||
*/
|
||||
void writePW_LO(unsigned char pw_lo) { pw = (pw & 0xf00) | (pw_lo & 0x0ff); }
|
||||
|
||||
/**
|
||||
* Write PW HI register.
|
||||
*
|
||||
* @param pw_hi high 8 bits of pulse width
|
||||
*/
|
||||
void writePW_HI(unsigned char pw_hi) { pw = (pw_hi << 8 & 0xf00) | (pw & 0x0ff); }
|
||||
|
||||
/**
|
||||
* Write CONTROL REGISTER register.
|
||||
*
|
||||
* @param control control register value
|
||||
*/
|
||||
void writeCONTROL_REG(unsigned char control);
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* 12-bit waveform output.
|
||||
*
|
||||
* @param ringModulator The oscillator ring-modulating current one.
|
||||
* @return the waveform generator digital output
|
||||
*/
|
||||
unsigned int output(const WaveformGenerator* ringModulator);
|
||||
|
||||
/**
|
||||
* Read OSC3 value.
|
||||
*/
|
||||
unsigned char readOSC() const { return static_cast<unsigned char>(osc3 >> 4); }
|
||||
|
||||
/**
|
||||
* Read accumulator value.
|
||||
*/
|
||||
unsigned int readAccumulator() const { return accumulator; }
|
||||
|
||||
/**
|
||||
* Read freq value.
|
||||
*/
|
||||
unsigned int readFreq() const { return freq; }
|
||||
|
||||
/**
|
||||
* Read test value.
|
||||
*/
|
||||
bool readTest() const { return test; }
|
||||
|
||||
/**
|
||||
* Read sync value.
|
||||
*/
|
||||
bool readSync() const { return sync; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(WAVEFORMGENERATOR_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
void WaveformGenerator::clock()
|
||||
{
|
||||
if (unlikely(test))
|
||||
{
|
||||
if (unlikely(shift_register_reset != 0) && unlikely(--shift_register_reset == 0))
|
||||
{
|
||||
shiftregBitfade();
|
||||
|
||||
// New noise waveform output.
|
||||
set_noise_output();
|
||||
}
|
||||
|
||||
// The test bit sets pulse high.
|
||||
pulse_output = 0xfff;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate new accumulator value;
|
||||
const unsigned int accumulator_old = accumulator;
|
||||
accumulator = (accumulator + freq) & 0xffffff;
|
||||
|
||||
// Check which bit have changed
|
||||
const unsigned int accumulator_bits_set = ~accumulator_old & accumulator;
|
||||
|
||||
// Check whether the MSB is set high. This is used for synchronization.
|
||||
msb_rising = (accumulator_bits_set & 0x800000) != 0;
|
||||
|
||||
// Shift noise register once for each time accumulator bit 19 is set high.
|
||||
// The shift is delayed 2 cycles.
|
||||
if (unlikely((accumulator_bits_set & 0x080000) != 0))
|
||||
{
|
||||
// Pipeline: Detect rising bit, shift phase 1, shift phase 2.
|
||||
shift_pipeline = 2;
|
||||
}
|
||||
else if (unlikely(shift_pipeline != 0) && --shift_pipeline == 0)
|
||||
{
|
||||
// bit0 = (bit22 | test) ^ bit17
|
||||
clock_shift_register(((shift_register << 22) ^ (shift_register << 17)) & (1 << 22));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
unsigned int WaveformGenerator::output(const WaveformGenerator* ringModulator)
|
||||
{
|
||||
// Set output value.
|
||||
if (likely(waveform != 0))
|
||||
{
|
||||
const unsigned int ix = (accumulator ^ (~ringModulator->accumulator & ring_msb_mask)) >> 12;
|
||||
|
||||
// The bit masks no_pulse and no_noise are used to achieve branch-free
|
||||
// calculation of the output value.
|
||||
waveform_output = wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output;
|
||||
|
||||
// Triangle/Sawtooth output is delayed half cycle on 8580.
|
||||
// This will appear as a one cycle delay on OSC3 as it is latched first phase of the clock.
|
||||
if ((waveform & 3) && !is6581)
|
||||
{
|
||||
osc3 = tri_saw_pipeline & (no_pulse | pulse_output) & no_noise_or_noise_output;
|
||||
tri_saw_pipeline = wave[ix];
|
||||
}
|
||||
else
|
||||
{
|
||||
osc3 = waveform_output;
|
||||
}
|
||||
|
||||
// In the 6581 the top bit of the accumulator may be driven low by combined waveforms
|
||||
// when the sawtooth is selected
|
||||
// FIXME doesn't seem to always happen
|
||||
if ((waveform & 2) && unlikely(waveform & 0xd) && is6581)
|
||||
accumulator &= (waveform_output << 12) | 0x7fffff;
|
||||
|
||||
write_shift_register();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Age floating DAC input.
|
||||
if (likely(floating_output_ttl != 0) && unlikely(--floating_output_ttl == 0))
|
||||
{
|
||||
waveBitfade();
|
||||
}
|
||||
}
|
||||
|
||||
// The pulse level is defined as (accumulator >> 12) >= pw ? 0xfff : 0x000.
|
||||
// The expression -((accumulator >> 12) >= pw) & 0xfff yields the same
|
||||
// results without any branching (and thus without any pipeline stalls).
|
||||
// NB! This expression relies on that the result of a boolean expression
|
||||
// is either 0 or 1, and furthermore requires two's complement integer.
|
||||
// A few more cycles may be saved by storing the pulse width left shifted
|
||||
// 12 bits, and dropping the and with 0xfff (this is valid since pulse is
|
||||
// used as a bit mask on 12 bit values), yielding the expression
|
||||
// -(accumulator >= pw24). However this only results in negligible savings.
|
||||
|
||||
// The result of the pulse width compare is delayed one cycle.
|
||||
// Push next pulse level into pulse level pipeline.
|
||||
pulse_output = ((accumulator >> 12) >= pw) ? 0xfff : 0x000;
|
||||
|
||||
return waveform_output;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
73
src/engine/platform/sound/c64_fp/array.h
Normal file
73
src/engine/platform/sound/c64_fp/array.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright (C) 2011-2014 Leandro Nini
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ARRAY_H
|
||||
#define ARRAY_H
|
||||
|
||||
/**
|
||||
* Counter.
|
||||
*/
|
||||
class counter
|
||||
{
|
||||
private:
|
||||
unsigned int c;
|
||||
|
||||
public:
|
||||
counter() : c(1) {}
|
||||
void increase() { ++c; }
|
||||
unsigned int decrease() { return --c; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Reference counted pointer to matrix wrapper, for use with standard containers.
|
||||
*/
|
||||
template<typename T>
|
||||
class matrix
|
||||
{
|
||||
private:
|
||||
T* data;
|
||||
counter* count;
|
||||
const unsigned int x, y;
|
||||
|
||||
public:
|
||||
matrix(unsigned int x, unsigned int y) :
|
||||
data(new T[x * y]),
|
||||
count(new counter()),
|
||||
x(x),
|
||||
y(y) {}
|
||||
|
||||
matrix(const matrix& p) :
|
||||
data(p.data),
|
||||
count(p.count),
|
||||
x(p.x),
|
||||
y(p.y) { count->increase(); }
|
||||
|
||||
~matrix() { if (count->decrease() == 0) { delete count; delete [] data; } }
|
||||
|
||||
unsigned int length() const { return x * y; }
|
||||
|
||||
T* operator[](unsigned int a) { return &data[a * y]; }
|
||||
|
||||
T const* operator[](unsigned int a) const { return &data[a * y]; }
|
||||
};
|
||||
|
||||
typedef matrix<short> matrix_t;
|
||||
|
||||
#endif
|
86
src/engine/platform/sound/c64_fp/resample/Resampler.h
Normal file
86
src/engine/platform/sound/c64_fp/resample/Resampler.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef RESAMPLER_H
|
||||
#define RESAMPLER_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "../sidcxx11.h"
|
||||
|
||||
#include "../siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Abstraction of a resampling process. Given enough input, produces output.
|
||||
* Constructors take additional arguments that configure these objects.
|
||||
*/
|
||||
class Resampler
|
||||
{
|
||||
protected:
|
||||
inline short softClip(int x) const
|
||||
{
|
||||
constexpr int threshold = 28000;
|
||||
if (likely(x < threshold))
|
||||
return x;
|
||||
|
||||
constexpr double t = threshold / 32768.;
|
||||
constexpr double a = 1. - t;
|
||||
constexpr double b = 1. / a;
|
||||
|
||||
double value = static_cast<double>(x - threshold) / 32768.;
|
||||
value = t + a * tanh(b * value);
|
||||
return static_cast<short>(value * 32768.);
|
||||
}
|
||||
|
||||
virtual int output() const = 0;
|
||||
|
||||
Resampler() {}
|
||||
|
||||
public:
|
||||
virtual ~Resampler() {}
|
||||
|
||||
/**
|
||||
* Input a sample into resampler. Output "true" when resampler is ready with new sample.
|
||||
*
|
||||
* @param sample input sample
|
||||
* @return true when a sample is ready
|
||||
*/
|
||||
virtual bool input(int sample) = 0;
|
||||
|
||||
/**
|
||||
* Output a sample from resampler.
|
||||
*
|
||||
* @return resampled sample
|
||||
*/
|
||||
short getOutput() const
|
||||
{
|
||||
return softClip(output());
|
||||
}
|
||||
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
393
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
Executable file
393
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
Executable file
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "SincResampler.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "../siddefs-fp.h"
|
||||
|
||||
#ifdef HAVE_EMMINTRIN_H
|
||||
# include <emmintrin.h>
|
||||
#elif defined HAVE_MMINTRIN_H
|
||||
# include <mmintrin.h>
|
||||
#elif defined(HAVE_ARM_NEON_H)
|
||||
# include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
typedef std::map<std::string, matrix_t> fir_cache_t;
|
||||
|
||||
/// Cache for the expensive FIR table computation results.
|
||||
fir_cache_t FIR_CACHE;
|
||||
|
||||
/// Maximum error acceptable in I0 is 1e-6, or ~96 dB.
|
||||
const double I0E = 1e-6;
|
||||
|
||||
const int BITS = 16;
|
||||
|
||||
/**
|
||||
* Compute the 0th order modified Bessel function of the first kind.
|
||||
* This function is originally from resample-1.5/filterkit.c by J. O. Smith.
|
||||
* It is used to build the Kaiser window for resampling.
|
||||
*
|
||||
* @param x evaluate I0 at x
|
||||
* @return value of I0 at x.
|
||||
*/
|
||||
double I0(double x)
|
||||
{
|
||||
double sum = 1.;
|
||||
double u = 1.;
|
||||
double n = 1.;
|
||||
const double halfx = x / 2.;
|
||||
|
||||
do
|
||||
{
|
||||
const double temp = halfx / n;
|
||||
u *= temp * temp;
|
||||
sum += u;
|
||||
n += 1.;
|
||||
}
|
||||
while (u >= I0E * sum);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate convolution with sample and sinc.
|
||||
*
|
||||
* @param a sample buffer input
|
||||
* @param b sinc buffer
|
||||
* @param bLength length of the sinc buffer
|
||||
* @return convolved result
|
||||
*/
|
||||
int convolve(const short* a, const short* b, int bLength)
|
||||
{
|
||||
#ifdef HAVE_EMMINTRIN_H
|
||||
int out = 0;
|
||||
|
||||
const uintptr_t offset = (uintptr_t)(a) & 0x0f;
|
||||
|
||||
// check for aligned accesses
|
||||
if (offset == ((uintptr_t)(b) & 0x0f))
|
||||
{
|
||||
if (offset)
|
||||
{
|
||||
const int l = (0x10 - offset)/2;
|
||||
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
out += *a++ * *b++;
|
||||
}
|
||||
|
||||
bLength -= offset;
|
||||
}
|
||||
|
||||
__m128i acc = _mm_setzero_si128();
|
||||
|
||||
const int n = bLength / 8;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
const __m128i tmp = _mm_madd_epi16(*(__m128i*)a, *(__m128i*)b);
|
||||
acc = _mm_add_epi16(acc, tmp);
|
||||
a += 8;
|
||||
b += 8;
|
||||
}
|
||||
|
||||
__m128i vsum = _mm_add_epi32(acc, _mm_srli_si128(acc, 8));
|
||||
vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4));
|
||||
out += _mm_cvtsi128_si32(vsum);
|
||||
|
||||
bLength &= 7;
|
||||
}
|
||||
#elif defined HAVE_MMINTRIN_H
|
||||
__m64 acc = _mm_setzero_si64();
|
||||
|
||||
const int n = bLength / 4;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
const __m64 tmp = _mm_madd_pi16(*(__m64*)a, *(__m64*)b);
|
||||
acc = _mm_add_pi16(acc, tmp);
|
||||
a += 4;
|
||||
b += 4;
|
||||
}
|
||||
|
||||
int out = _mm_cvtsi64_si32(acc) + _mm_cvtsi64_si32(_mm_srli_si64(acc, 32));
|
||||
_mm_empty();
|
||||
|
||||
bLength &= 3;
|
||||
#elif defined(HAVE_ARM_NEON_H)
|
||||
#if (defined(__arm64__) && defined(__APPLE__)) || defined(__aarch64__)
|
||||
int32x4_t acc1Low = vdupq_n_s32(0);
|
||||
int32x4_t acc1High = vdupq_n_s32(0);
|
||||
int32x4_t acc2Low = vdupq_n_s32(0);
|
||||
int32x4_t acc2High = vdupq_n_s32(0);
|
||||
|
||||
const int n = bLength / 16;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int16x8_t v11 = vld1q_s16(a);
|
||||
int16x8_t v12 = vld1q_s16(a + 8);
|
||||
int16x8_t v21 = vld1q_s16(b);
|
||||
int16x8_t v22 = vld1q_s16(b + 8);
|
||||
|
||||
acc1Low = vmlal_s16(acc1Low, vget_low_s16(v11), vget_low_s16(v21));
|
||||
acc1High = vmlal_high_s16(acc1High, v11, v21);
|
||||
acc2Low = vmlal_s16(acc2Low, vget_low_s16(v12), vget_low_s16(v22));
|
||||
acc2High = vmlal_high_s16(acc2High, v12, v22);
|
||||
|
||||
a += 16;
|
||||
b += 16;
|
||||
}
|
||||
|
||||
bLength &= 15;
|
||||
|
||||
if (bLength >= 8)
|
||||
{
|
||||
int16x8_t v1 = vld1q_s16(a);
|
||||
int16x8_t v2 = vld1q_s16(b);
|
||||
|
||||
acc1Low = vmlal_s16(acc1Low, vget_low_s16(v1), vget_low_s16(v2));
|
||||
acc1High = vmlal_high_s16(acc1High, v1, v2);
|
||||
|
||||
a += 8;
|
||||
b += 8;
|
||||
}
|
||||
|
||||
bLength &= 7;
|
||||
|
||||
if (bLength >= 4)
|
||||
{
|
||||
int16x4_t v1 = vld1_s16(a);
|
||||
int16x4_t v2 = vld1_s16(b);
|
||||
|
||||
acc1Low = vmlal_s16(acc1Low, v1, v2);
|
||||
|
||||
a += 4;
|
||||
b += 4;
|
||||
}
|
||||
|
||||
int32x4_t accSumsNeon = vaddq_s32(acc1Low, acc1High);
|
||||
accSumsNeon = vaddq_s32(accSumsNeon, acc2Low);
|
||||
accSumsNeon = vaddq_s32(accSumsNeon, acc2High);
|
||||
|
||||
int out = vaddvq_s32(accSumsNeon);
|
||||
|
||||
bLength &= 3;
|
||||
#else
|
||||
int32x4_t acc = vdupq_n_s32(0);
|
||||
|
||||
const int n = bLength / 4;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
const int16x4_t h_vec = vld1_s16(a);
|
||||
const int16x4_t x_vec = vld1_s16(b);
|
||||
acc = vmlal_s16(acc, h_vec, x_vec);
|
||||
a += 4;
|
||||
b += 4;
|
||||
}
|
||||
|
||||
int out = vgetq_lane_s32(acc, 0) +
|
||||
vgetq_lane_s32(acc, 1) +
|
||||
vgetq_lane_s32(acc, 2) +
|
||||
vgetq_lane_s32(acc, 3);
|
||||
|
||||
bLength &= 3;
|
||||
#endif
|
||||
#else
|
||||
int out = 0;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < bLength; i++)
|
||||
{
|
||||
out += *a++ * *b++;
|
||||
}
|
||||
|
||||
return (out + (1 << 14)) >> 15;
|
||||
}
|
||||
|
||||
int SincResampler::fir(int subcycle)
|
||||
{
|
||||
// Find the first of the nearest fir tables close to the phase
|
||||
int firTableFirst = (subcycle * firRES >> 10);
|
||||
const int firTableOffset = (subcycle * firRES) & 0x3ff;
|
||||
|
||||
// Find firN most recent samples, plus one extra in case the FIR wraps.
|
||||
int sampleStart = sampleIndex - firN + RINGSIZE - 1;
|
||||
|
||||
const int v1 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN);
|
||||
|
||||
// Use next FIR table, wrap around to first FIR table using
|
||||
// previous sample.
|
||||
if (unlikely(++firTableFirst == firRES))
|
||||
{
|
||||
firTableFirst = 0;
|
||||
++sampleStart;
|
||||
}
|
||||
|
||||
const int v2 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN);
|
||||
|
||||
// Linear interpolation between the sinc tables yields good
|
||||
// approximation for the exact value.
|
||||
return v1 + (firTableOffset * (v2 - v1) >> 10);
|
||||
}
|
||||
|
||||
SincResampler::SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) :
|
||||
sampleIndex(0),
|
||||
cyclesPerSample(static_cast<int>(clockFrequency / samplingFrequency * 1024.)),
|
||||
sampleOffset(0),
|
||||
outputValue(0)
|
||||
{
|
||||
// 16 bits -> -96dB stopband attenuation.
|
||||
const double A = -20. * log10(1.0 / (1 << BITS));
|
||||
// A fraction of the bandwidth is allocated to the transition band, which we double
|
||||
// because we design the filter to transition halfway at nyquist.
|
||||
const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * M_PI * 2.;
|
||||
|
||||
// For calculation of beta and N see the reference for the kaiserord
|
||||
// function in the MATLAB Signal Processing Toolbox:
|
||||
// http://www.mathworks.com/help/signal/ref/kaiserord.html
|
||||
const double beta = 0.1102 * (A - 8.7);
|
||||
const double I0beta = I0(beta);
|
||||
const double cyclesPerSampleD = clockFrequency / samplingFrequency;
|
||||
|
||||
{
|
||||
// The filter order will maximally be 124 with the current constraints.
|
||||
// N >= (96.33 - 7.95)/(2 * pi * 2.285 * (maxfreq - passbandfreq) >= 123
|
||||
// The filter order is equal to the number of zero crossings, i.e.
|
||||
// it should be an even number (sinc is symmetric with respect to x = 0).
|
||||
int N = static_cast<int>((A - 7.95) / (2.285 * dw) + 0.5);
|
||||
N += N & 1;
|
||||
|
||||
// The filter length is equal to the filter order + 1.
|
||||
// The filter length must be an odd number (sinc is symmetric with respect to
|
||||
// x = 0).
|
||||
firN = static_cast<int>(N * cyclesPerSampleD) + 1;
|
||||
firN |= 1;
|
||||
|
||||
// Check whether the sample ring buffer would overflow.
|
||||
assert(firN < RINGSIZE);
|
||||
|
||||
// Error is bounded by err < 1.234 / L^2, so L = sqrt(1.234 / (2^-16)) = sqrt(1.234 * 2^16).
|
||||
firRES = static_cast<int>(ceil(sqrt(1.234 * (1 << BITS)) / cyclesPerSampleD));
|
||||
|
||||
// firN*firRES represent the total resolution of the sinc sampling. JOS
|
||||
// recommends a length of 2^BITS, but we don't quite use that good a filter.
|
||||
// The filter test program indicates that the filter performs well, though.
|
||||
}
|
||||
|
||||
// Create the map key
|
||||
std::ostringstream o;
|
||||
o << firN << "," << firRES << "," << cyclesPerSampleD;
|
||||
const std::string firKey = o.str();
|
||||
fir_cache_t::iterator lb = FIR_CACHE.lower_bound(firKey);
|
||||
|
||||
// The FIR computation is expensive and we set sampling parameters often, but
|
||||
// from a very small set of choices. Thus, caching is used to speed initialization.
|
||||
if (lb != FIR_CACHE.end() && !(FIR_CACHE.key_comp()(firKey, lb->first)))
|
||||
{
|
||||
firTable = &(lb->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allocate memory for FIR tables.
|
||||
matrix_t tempTable(firRES, firN);
|
||||
#ifdef HAVE_CXX11
|
||||
firTable = &(FIR_CACHE.emplace_hint(lb, fir_cache_t::value_type(firKey, tempTable))->second);
|
||||
#else
|
||||
firTable = &(FIR_CACHE.insert(lb, fir_cache_t::value_type(firKey, tempTable))->second);
|
||||
#endif
|
||||
|
||||
// The cutoff frequency is midway through the transition band, in effect the same as nyquist.
|
||||
const double wc = M_PI;
|
||||
|
||||
// Calculate the sinc tables.
|
||||
const double scale = 32768.0 * wc / cyclesPerSampleD / M_PI;
|
||||
|
||||
// we're not interested in the fractional part
|
||||
// so use int division before converting to double
|
||||
const int tmp = firN / 2;
|
||||
const double firN_2 = static_cast<double>(tmp);
|
||||
|
||||
for (int i = 0; i < firRES; i++)
|
||||
{
|
||||
const double jPhase = (double) i / firRES + firN_2;
|
||||
|
||||
for (int j = 0; j < firN; j++)
|
||||
{
|
||||
const double x = j - jPhase;
|
||||
|
||||
const double xt = x / firN_2;
|
||||
const double kaiserXt = fabs(xt) < 1. ? I0(beta * sqrt(1. - xt * xt)) / I0beta : 0.;
|
||||
|
||||
const double wt = wc * x / cyclesPerSampleD;
|
||||
const double sincWt = fabs(wt) >= 1e-8 ? sin(wt) / wt : 1.;
|
||||
|
||||
(*firTable)[i][j] = static_cast<short>(scale * sincWt * kaiserXt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SincResampler::input(int input)
|
||||
{
|
||||
bool ready = false;
|
||||
|
||||
/*
|
||||
* Clip the input as it may overflow the 16 bit range.
|
||||
*
|
||||
* Approximate measured input ranges:
|
||||
* 6581: [-24262,+25080] (Kawasaki_Synthesizer_Demo)
|
||||
* 8580: [-21514,+35232] (64_Forever, Drum_Fool)
|
||||
*/
|
||||
sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = softClip(input);
|
||||
sampleIndex = (sampleIndex + 1) & (RINGSIZE - 1);
|
||||
|
||||
if (sampleOffset < 1024)
|
||||
{
|
||||
outputValue = fir(sampleOffset);
|
||||
ready = true;
|
||||
sampleOffset += cyclesPerSample;
|
||||
}
|
||||
|
||||
sampleOffset -= 1024;
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
void SincResampler::reset()
|
||||
{
|
||||
memset(sample, 0, sizeof(sample));
|
||||
sampleOffset = 0;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
114
src/engine/platform/sound/c64_fp/resample/SincResampler.h
Normal file
114
src/engine/platform/sound/c64_fp/resample/SincResampler.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef SINCRESAMPLER_H
|
||||
#define SINCRESAMPLER_H
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "../array.h"
|
||||
|
||||
#include "../sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* This is the theoretically correct (and computationally intensive) audio sample generation.
|
||||
* The samples are generated by resampling to the specified sampling frequency.
|
||||
* The work rate is inversely proportional to the percentage of the bandwidth
|
||||
* allocated to the filter transition band.
|
||||
*
|
||||
* This implementation is based on the paper "A Flexible Sampling-Rate Conversion Method",
|
||||
* by J. O. Smith and P. Gosset, or rather on the expanded tutorial on the
|
||||
* [Digital Audio Resampling Home Page](http://www-ccrma.stanford.edu/~jos/resample/).
|
||||
*
|
||||
* By building shifted FIR tables with samples according to the sampling frequency,
|
||||
* this implementation dramatically reduces the computational effort in the
|
||||
* filter convolutions, without any loss of accuracy.
|
||||
* The filter convolutions are also vectorizable on current hardware.
|
||||
*/
|
||||
class SincResampler final : public Resampler
|
||||
{
|
||||
private:
|
||||
/// Size of the ring buffer, must be a power of 2
|
||||
static const int RINGSIZE = 2048;
|
||||
|
||||
private:
|
||||
/// Table of the fir filter coefficients
|
||||
matrix_t* firTable;
|
||||
|
||||
int sampleIndex;
|
||||
|
||||
/// Filter resolution
|
||||
int firRES;
|
||||
|
||||
/// Filter length
|
||||
int firN;
|
||||
|
||||
const int cyclesPerSample;
|
||||
|
||||
int sampleOffset;
|
||||
|
||||
int outputValue;
|
||||
|
||||
short sample[RINGSIZE * 2];
|
||||
|
||||
private:
|
||||
int fir(int subcycle);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
|
||||
* The default end of passband frequency is pass_freq = 0.9*sample_freq/2
|
||||
* for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies.
|
||||
*
|
||||
* For resampling, the ratio between the clock frequency and the sample frequency
|
||||
* is limited as follows: 125*clock_freq/sample_freq < 16384
|
||||
* E.g. provided a clock frequency of ~ 1MHz, the sample frequency
|
||||
* can not be set lower than ~ 8kHz.
|
||||
* A lower sample frequency would make the resampling code overfill its 16k sample ring buffer.
|
||||
*
|
||||
* The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2
|
||||
*
|
||||
* E.g. for a 44.1kHz sampling rate the end of passband frequency is limited
|
||||
* to slightly below 20kHz. This constraint ensures that the FIR table is not overfilled.
|
||||
*
|
||||
* @param clockFrequency System clock frequency at Hz
|
||||
* @param samplingFrequency Desired output sampling rate
|
||||
* @param highestAccurateFrequency
|
||||
*/
|
||||
SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency);
|
||||
|
||||
bool input(int input) override;
|
||||
|
||||
int output() const override { return outputValue; }
|
||||
|
||||
void reset() override;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TWOPASSSINCRESAMPLER_H
|
||||
#define TWOPASSSINCRESAMPLER_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Resampler.h"
|
||||
#include "SincResampler.h"
|
||||
|
||||
#include "../sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Compose a more efficient SINC from chaining two other SINCs.
|
||||
*/
|
||||
class TwoPassSincResampler final : public Resampler
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<SincResampler> const s1;
|
||||
std::unique_ptr<SincResampler> const s2;
|
||||
|
||||
private:
|
||||
TwoPassSincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency, double intermediateFrequency) :
|
||||
s1(new SincResampler(clockFrequency, intermediateFrequency, highestAccurateFrequency)),
|
||||
s2(new SincResampler(intermediateFrequency, samplingFrequency, highestAccurateFrequency))
|
||||
{}
|
||||
|
||||
public:
|
||||
// Named constructor
|
||||
static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency, double highestAccurateFrequency)
|
||||
{
|
||||
// Calculation according to Laurent Ganier. It evaluates to about 120 kHz at typical settings.
|
||||
// Some testing around the chosen value seems to confirm that this does work.
|
||||
double const intermediateFrequency = 2. * highestAccurateFrequency
|
||||
+ sqrt(2. * highestAccurateFrequency * clockFrequency
|
||||
* (samplingFrequency - 2. * highestAccurateFrequency) / samplingFrequency);
|
||||
return new TwoPassSincResampler(clockFrequency, samplingFrequency, highestAccurateFrequency, intermediateFrequency);
|
||||
}
|
||||
|
||||
bool input(int sample) override
|
||||
{
|
||||
return s1->input(sample) && s2->input(s1->output());
|
||||
}
|
||||
|
||||
int output() const override
|
||||
{
|
||||
return s2->output();
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
s1->reset();
|
||||
s2->reset();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef ZEROORDER_RESAMPLER_H
|
||||
#define ZEROORDER_RESAMPLER_H
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
#include "../sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Return sample with linear interpolation.
|
||||
*
|
||||
* @author Antti Lankila
|
||||
*/
|
||||
class ZeroOrderResampler final : public Resampler
|
||||
{
|
||||
|
||||
private:
|
||||
/// Last sample
|
||||
int cachedSample;
|
||||
|
||||
/// Number of cycles per sample
|
||||
const int cyclesPerSample;
|
||||
|
||||
int sampleOffset;
|
||||
|
||||
/// Calculated sample
|
||||
int outputValue;
|
||||
|
||||
public:
|
||||
ZeroOrderResampler(double clockFrequency, double samplingFrequency) :
|
||||
cachedSample(0),
|
||||
cyclesPerSample(static_cast<int>(clockFrequency / samplingFrequency * 1024.)),
|
||||
sampleOffset(0),
|
||||
outputValue(0) {}
|
||||
|
||||
bool input(int sample) override
|
||||
{
|
||||
bool ready = false;
|
||||
|
||||
if (sampleOffset < 1024)
|
||||
{
|
||||
outputValue = cachedSample + (sampleOffset * (sample - cachedSample) >> 10);
|
||||
ready = true;
|
||||
sampleOffset += cyclesPerSample;
|
||||
}
|
||||
|
||||
sampleOffset -= 1024;
|
||||
|
||||
cachedSample = sample;
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
int output() const override { return outputValue; }
|
||||
|
||||
void reset() override
|
||||
{
|
||||
sampleOffset = 0;
|
||||
cachedSample = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
87
src/engine/platform/sound/c64_fp/resample/test.cpp
Normal file
87
src/engine/platform/sound/c64_fp/resample/test.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2012-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include "Resampler.h"
|
||||
#include "TwoPassSincResampler.h"
|
||||
|
||||
/**
|
||||
* Simple sin waveform in, power output measurement function.
|
||||
* It would be far better to use FFT.
|
||||
*/
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
const double RATE = 985248.4;
|
||||
const int RINGSIZE = 2048;
|
||||
|
||||
std::auto_ptr<reSIDfp::TwoPassSincResampler> r(reSIDfp::TwoPassSincResampler::create(RATE, 48000.0, 20000.0));
|
||||
|
||||
std::map<double, double> results;
|
||||
clock_t start = clock();
|
||||
|
||||
for (double freq = 1000.; freq < RATE / 2.; freq *= 1.01)
|
||||
{
|
||||
/* prefill resampler buffer */
|
||||
int k = 0;
|
||||
double omega = 2 * M_PI * freq / RATE;
|
||||
|
||||
for (int j = 0; j < RINGSIZE; j ++)
|
||||
{
|
||||
int signal = static_cast<int>(32768.0 * sin(k++ * omega) * sqrt(2));
|
||||
r->input(signal);
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
float pwr = 0;
|
||||
|
||||
/* Now, during measurement stage, put 100 cycles of waveform through filter. */
|
||||
for (int j = 0; j < 100000; j ++)
|
||||
{
|
||||
int signal = static_cast<int>(32768.0 * sin(k++ * omega) * sqrt(2));
|
||||
|
||||
if (r->input(signal))
|
||||
{
|
||||
float out = r->output();
|
||||
pwr += out * out;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
results.insert(std::make_pair(freq, 10 * log10(pwr / n)));
|
||||
}
|
||||
|
||||
clock_t end = clock();
|
||||
|
||||
for (std::map<double, double>::iterator it = results.begin(); it != results.end(); ++it)
|
||||
{
|
||||
std::cout << std::fixed << std::setprecision(0) << std::setw(6) << (*it).first << " Hz " << (*it).second << " dB" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Filtering time " << (end - start) * 1000. / CLOCKS_PER_SEC << " ms" << std::endl;
|
||||
}
|
29
src/engine/platform/sound/c64_fp/sidcxx11.h
Normal file
29
src/engine/platform/sound/c64_fp/sidcxx11.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014-2015 Leandro Nini
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef SIDCXX11_H
|
||||
#define SIDCXX11_H
|
||||
|
||||
#define DEFAULT = default
|
||||
#define DELETE = delete
|
||||
|
||||
#define HAVE_CXX11
|
||||
|
||||
#endif
|
29
src/engine/platform/sound/c64_fp/sidcxx14.h
Normal file
29
src/engine/platform/sound/c64_fp/sidcxx14.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014-2015 Leandro Nini
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef SIDCXX14_H
|
||||
#define SIDCXX14_H
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
#define MAKE_UNIQUE(type, ...) std::make_unique<type>(__VA_ARGS__)
|
||||
#define HAVE_CXX14
|
||||
|
||||
#endif
|
62
src/engine/platform/sound/c64_fp/siddefs-fp.h
Normal file
62
src/engine/platform/sound/c64_fp/siddefs-fp.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// This file is part of reSID, a MOS6581 SID emulator engine.
|
||||
// Copyright (C) 1999 Dag Lem <resid@nimrod.no>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#ifndef SIDDEFS_FP_H
|
||||
#define SIDDEFS_FP_H
|
||||
|
||||
// Compilation configuration.
|
||||
#define RESID_BRANCH_HINTS 0
|
||||
|
||||
// Compiler specifics.
|
||||
#define HAVE_BUILTIN_EXPECT 0
|
||||
|
||||
#ifndef M_PI
|
||||
# define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
// Branch prediction macros, lifted off the Linux kernel.
|
||||
#if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT
|
||||
# define likely(x) __builtin_expect(!!(x), 1)
|
||||
# define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
# define likely(x) (x)
|
||||
# define unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
namespace reSIDfp {
|
||||
|
||||
typedef enum { MOS6581=1, MOS8580 } ChipModel;
|
||||
|
||||
typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#ifndef __VERSION_CC__
|
||||
extern const char* residfp_version_string;
|
||||
#else
|
||||
const char* residfp_version_string = "furnace";
|
||||
#endif
|
||||
}
|
||||
|
||||
// Inlining on/off.
|
||||
#define RESID_INLINING 1
|
||||
#define RESID_INLINE inline
|
||||
|
||||
#endif // SIDDEFS_FP_H
|
21
src/engine/platform/sound/c64_fp/version.cc
Normal file
21
src/engine/platform/sound/c64_fp/version.cc
Normal file
|
@ -0,0 +1,21 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// This file is part of reSID, a MOS6581 SID emulator engine.
|
||||
// Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define __VERSION_CC__
|
||||
#include "siddefs-fp.h"
|
|
@ -78,18 +78,9 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_SU(i,chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_SU(i,chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_SU(i,parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_SU(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
@ -366,7 +357,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
writeControlUpper(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
chan[c.chan].baseCutoff=c.value;
|
||||
chan[c.chan].baseCutoff=c.value*4;
|
||||
if (!chan[c.chan].std.ex1.has) {
|
||||
chan[c.chan].cutoff=chan[c.chan].baseCutoff;
|
||||
chWrite(c.chan,0x06,chan[c.chan].cutoff&0xff);
|
||||
|
|
|
@ -134,18 +134,9 @@ void DivPlatformSwan::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had && !(i==1 && pcm)) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
|
@ -87,20 +87,18 @@ void DivPlatformTIA::tick(bool sysTick) {
|
|||
rWrite(0x19+i,chan[i].outVol&15);
|
||||
}
|
||||
}
|
||||
// TODO: the way arps work on TIA is really weird
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=0x80000000|chan[i].std.arp.val;
|
||||
if (chan[i].std.arp.val<0 && (!(chan[i].std.arp.val&0x40000000))) {
|
||||
chan[i].baseFreq=0x80000000|(chan[i].std.arp.val|0x40000000);
|
||||
} else if (chan[i].std.arp.val>=0 && chan[i].std.arp.val&0x40000000) {
|
||||
chan[i].baseFreq=0x80000000|(chan[i].std.arp.val&(~0x40000000));
|
||||
} else {
|
||||
chan[i].baseFreq=(chan[i].note+chan[i].std.arp.val)<<8;
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=chan[i].note<<8;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
chan[i].shape=chan[i].std.wave.val&15;
|
||||
|
|
|
@ -123,18 +123,9 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.duty.had) {
|
||||
|
|
|
@ -159,18 +159,9 @@ void DivPlatformVERA::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=calcNoteFreq(0,parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWriteLo(i,3,chan[i].std.duty.val);
|
||||
|
@ -209,18 +200,9 @@ void DivPlatformVERA::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[16].std.arp.had) {
|
||||
if (!chan[16].inPorta) {
|
||||
if (chan[16].std.arp.mode) {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp.val);
|
||||
} else {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp.val);
|
||||
}
|
||||
chan[16].baseFreq=calcNoteFreq(16,parent->calcArp(chan[16].note,chan[16].std.arp.val));
|
||||
}
|
||||
chan[16].freqChanged=true;
|
||||
} else {
|
||||
if (chan[16].std.arp.mode && chan[16].std.arp.finished) {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].note);
|
||||
chan[16].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[16].freqChanged) {
|
||||
double off=65536.0;
|
||||
|
|
|
@ -94,18 +94,9 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||
|
|
|
@ -153,18 +153,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
|
@ -321,18 +321,9 @@ void DivPlatformX1_010::tick(bool sysTick) {
|
|||
if ((!chan[i].pcm) || chan[i].furnacePCM) {
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NoteX1_010(i,parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had && !chan[i].pcm) {
|
||||
|
|
|
@ -231,18 +231,9 @@ void DivPlatformYM2203::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
|
|
|
@ -386,18 +386,9 @@ void DivPlatformYM2608::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
|
@ -538,18 +529,9 @@ void DivPlatformYM2608::tick(bool sysTick) {
|
|||
|
||||
if (chan[15].std.arp.had) {
|
||||
if (!chan[15].inPorta) {
|
||||
if (chan[15].std.arp.mode) {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val);
|
||||
} else {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val);
|
||||
}
|
||||
chan[15].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[15].note,chan[15].std.arp.val));
|
||||
}
|
||||
chan[15].freqChanged=true;
|
||||
} else {
|
||||
if (chan[15].std.arp.mode && chan[15].std.arp.finished) {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].note);
|
||||
chan[15].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[15].freqChanged) {
|
||||
|
|
|
@ -427,18 +427,9 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
|
@ -580,18 +571,9 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
|
||||
if (chan[13].std.arp.had) {
|
||||
if (!chan[13].inPorta) {
|
||||
if (chan[13].std.arp.mode) {
|
||||
chan[13].baseFreq=NOTE_ADPCMB(chan[13].std.arp.val);
|
||||
} else {
|
||||
chan[13].baseFreq=NOTE_ADPCMB(chan[13].note+(signed char)chan[13].std.arp.val);
|
||||
}
|
||||
chan[13].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[13].note,chan[13].std.arp.val));
|
||||
}
|
||||
chan[13].freqChanged=true;
|
||||
} else {
|
||||
if (chan[13].std.arp.mode && chan[13].std.arp.finished) {
|
||||
chan[13].baseFreq=NOTE_ADPCMB(chan[13].note);
|
||||
chan[13].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[13].freqChanged) {
|
||||
|
|
|
@ -410,18 +410,9 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
|
@ -562,18 +553,9 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
|
||||
if (chan[15].std.arp.had) {
|
||||
if (!chan[15].inPorta) {
|
||||
if (chan[15].std.arp.mode) {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val);
|
||||
} else {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val);
|
||||
}
|
||||
chan[15].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[15].note,chan[15].std.arp.val));
|
||||
}
|
||||
chan[15].freqChanged=true;
|
||||
} else {
|
||||
if (chan[15].std.arp.mode && chan[15].std.arp.finished) {
|
||||
chan[15].baseFreq=NOTE_ADPCMB(chan[15].note);
|
||||
chan[15].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[15].freqChanged) {
|
||||
|
|
|
@ -95,18 +95,9 @@ void DivPlatformYMZ280B::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
|
@ -81,18 +81,9 @@ void DivPlatformZXBeeper::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
|
@ -1010,10 +1010,27 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
//output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
|
||||
}
|
||||
|
||||
if (!pendingNotes.empty()) {
|
||||
bool isOn[DIV_MAX_CHANS];
|
||||
memset(isOn,0,DIV_MAX_CHANS*sizeof(bool));
|
||||
|
||||
for (int i=pendingNotes.size()-1; i>=0; i--) {
|
||||
if (pendingNotes[i].channel<0 || pendingNotes[i].channel>=chans) continue;
|
||||
if (pendingNotes[i].on) {
|
||||
isOn[pendingNotes[i].channel]=true;
|
||||
} else {
|
||||
if (isOn[pendingNotes[i].channel]) {
|
||||
logV("erasing off -> on sequence in %d",pendingNotes[i].channel);
|
||||
pendingNotes.erase(pendingNotes.begin()+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!pendingNotes.empty()) {
|
||||
DivNoteEvent& note=pendingNotes.front();
|
||||
if (note.channel<0 || note.channel>=chans) {
|
||||
pendingNotes.pop();
|
||||
pendingNotes.pop_front();
|
||||
continue;
|
||||
}
|
||||
if (note.on) {
|
||||
|
@ -1033,7 +1050,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel));
|
||||
}
|
||||
}
|
||||
pendingNotes.pop();
|
||||
pendingNotes.pop_front();
|
||||
}
|
||||
|
||||
if (!freelance) {
|
||||
|
@ -1245,7 +1262,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
case TA_MIDI_NOTE_OFF: {
|
||||
if (chan<0 || chan>=chans) break;
|
||||
if (midiIsDirect) {
|
||||
pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
} else {
|
||||
autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]);
|
||||
}
|
||||
|
@ -1260,13 +1277,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
if (chan<0 || chan>=chans) break;
|
||||
if (msg.data[1]==0) {
|
||||
if (midiIsDirect) {
|
||||
pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false));
|
||||
} else {
|
||||
autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]);
|
||||
}
|
||||
} else {
|
||||
if (midiIsDirect) {
|
||||
pendingNotes.push(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true));
|
||||
pendingNotes.push_back(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true));
|
||||
} else {
|
||||
autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]);
|
||||
}
|
||||
|
|
|
@ -569,7 +569,7 @@ struct DivSong {
|
|||
limitSlides(false),
|
||||
linearPitch(2),
|
||||
pitchSlideSpeed(4),
|
||||
loopModality(0),
|
||||
loopModality(2),
|
||||
delayBehavior(2),
|
||||
properNoiseLayout(true),
|
||||
waveDutyIsVol(false),
|
||||
|
|
|
@ -284,6 +284,10 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) {
|
|||
*/
|
||||
}
|
||||
|
||||
const DivSysDef* DivEngine::getSystemDef(DivSystem sys) {
|
||||
return sysDefs[sys];
|
||||
}
|
||||
|
||||
bool DivEngine::isFMSystem(DivSystem sys) {
|
||||
if (sysDefs[sys]==NULL) return false;
|
||||
return sysDefs[sys]->isFM;
|
||||
|
@ -953,7 +957,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
sysDefs[DIV_SYSTEM_OPN_EXT]=new DivSysDef(
|
||||
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false,
|
||||
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
|
||||
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||
|
@ -977,7 +981,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef(
|
||||
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false,
|
||||
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
|
||||
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
|
||||
|
@ -1133,7 +1137,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef(
|
||||
"Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false,
|
||||
"Yamaha YM2610B (OPNB2)", NULL, 0x9e, 0, 16, true, false, 0x151, false,
|
||||
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
|
||||
|
@ -1160,7 +1164,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef(
|
||||
"Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false,
|
||||
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
|
||||
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
|
||||
|
@ -1307,8 +1311,8 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef(
|
||||
"Yamaha YM2610B (OPNB-B) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false,
|
||||
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
|
||||
"Yamaha YM2610B (OPNB2) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false,
|
||||
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
|
||||
|
@ -1366,7 +1370,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
// to Grauw: feel free to change this to 24 during development of OPL4's PCM part.
|
||||
sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef(
|
||||
"Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false,
|
||||
"Yamaha YMF278B (OPL4)", NULL, 0xae, 0, 42, true, true, 0, false,
|
||||
"like OPL3, but this time it also has a 24-channel version of MultiPCM.",
|
||||
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
|
||||
|
@ -1375,7 +1379,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef(
|
||||
"Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false,
|
||||
"Yamaha YMF278B (OPL4) with drums", NULL, 0xaf, 0, 44, true, true, 0, false,
|
||||
"the OPL4 but with drums mode turned on.",
|
||||
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick/FM 16", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
|
||||
|
@ -1447,7 +1451,7 @@ void DivEngine::registerSystems() {
|
|||
{0x21, {DIV_CMD_SU_SWEEP_ENABLE, "21xx: Toggle volume sweep (bit 0-4: speed; bit 5: direciton is up; bit 6: loop; bit 7: alternate)", constVal<1>, effectVal}},
|
||||
{0x22, {DIV_CMD_SU_SWEEP_ENABLE, "22xx: Toggle cutoff sweep (bit 0-6: speed; bit 7: direction is up)", constVal<2>, effectVal}},
|
||||
};
|
||||
const EffectHandler suCutoffHandler(DIV_CMD_C64_FINE_DUTY, "4xxx: Set cutoff (0 to FFF)", effectValLong<12>);
|
||||
const EffectHandler suCutoffHandler(DIV_CMD_C64_FINE_CUTOFF, "4xxx: Set cutoff (0 to FFF)", effectValLong<12>);
|
||||
for (int i=0; i<16; i++) {
|
||||
suEffectHandlerMap.emplace(0x40+i, suCutoffHandler);
|
||||
}
|
||||
|
@ -1562,7 +1566,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef(
|
||||
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0, false,
|
||||
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
|
||||
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE},
|
||||
|
|
|
@ -102,6 +102,7 @@ const char* aboutLine[]={
|
|||
"fd",
|
||||
"GENATARi",
|
||||
"host12prog",
|
||||
"lunathir",
|
||||
"plane",
|
||||
"TheEssem",
|
||||
"",
|
||||
|
@ -133,6 +134,8 @@ const char* aboutLine[]={
|
|||
"puNES (NES, MMC5 and FDS) by FHorse",
|
||||
"NSFPlay (NES and FDS) by Brad Smith and Brezza",
|
||||
"reSID by Dag Lem",
|
||||
"reSIDfp by Dag Lem, Antti Lankila",
|
||||
"and Leandro Nini",
|
||||
"Stella by Stella Team",
|
||||
"QSound emulator by superctr and Valley Bell",
|
||||
"VICE VIC-20 sound core by Rami Rasanen and viznut",
|
||||
|
|
|
@ -616,9 +616,9 @@ void FurnaceGUI::doInterpolate() {
|
|||
}
|
||||
} else {
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (pat->data[j][0]!=0 && pat->data[j][1]!=0) {
|
||||
if (pat->data[j][0]!=0 || pat->data[j][1]!=0) {
|
||||
if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) {
|
||||
points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12);
|
||||
points.emplace(points.end(),j,pat->data[j][0]+(signed char)pat->data[j][1]*12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue