Merge branch 'master' of https://github.com/tildearrow/furnace into snes
This commit is contained in:
commit
0ee6d761f5
1085 changed files with 31936 additions and 7804 deletions
|
|
@ -23,11 +23,92 @@
|
|||
#include <fmt/printf.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "winStuff.h"
|
||||
#define CONFIG_FILE "\\furnace.cfg"
|
||||
#else
|
||||
#ifdef __HAIKU__
|
||||
#include <support/SupportDefs.h>
|
||||
#include <storage/FindDirectory.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#define CONFIG_FILE "/furnace.cfg"
|
||||
#endif
|
||||
|
||||
#ifdef IS_MOBILE
|
||||
#ifdef HAVE_SDL2
|
||||
#include <SDL.h>
|
||||
#else
|
||||
#error "Furnace mobile requires SDL2!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void DivEngine::initConfDir() {
|
||||
#ifdef _WIN32
|
||||
// maybe move this function in here instead?
|
||||
configPath=getWinConfigPath();
|
||||
#elif defined(IS_MOBILE)
|
||||
configPath=SDL_GetPrefPath("tildearrow","furnace");
|
||||
#else
|
||||
#ifdef __HAIKU__
|
||||
char userSettingsDir[PATH_MAX];
|
||||
status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX);
|
||||
if (findUserDir==B_OK) {
|
||||
configPath=userSettingsDir;
|
||||
} else {
|
||||
logW("unable to find/create user settings directory (%s)!",strerror(findUserDir));
|
||||
configPath=".";
|
||||
return;
|
||||
}
|
||||
#else
|
||||
// TODO this should check XDG_CONFIG_HOME first
|
||||
char* home=getenv("HOME");
|
||||
if (home==NULL) {
|
||||
int uid=getuid();
|
||||
struct passwd* entry=getpwuid(uid);
|
||||
if (entry==NULL) {
|
||||
logW("unable to determine home directory (%s)!",strerror(errno));
|
||||
configPath=".";
|
||||
return;
|
||||
} else {
|
||||
configPath=entry->pw_dir;
|
||||
}
|
||||
} else {
|
||||
configPath=home;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
configPath+="/Library/Application Support";
|
||||
#else
|
||||
// FIXME this doesn't honour XDG_CONFIG_HOME *at all*
|
||||
configPath+="/.config";
|
||||
#endif // __APPLE__
|
||||
#endif // __HAIKU__
|
||||
#ifdef __APPLE__
|
||||
configPath+="/Furnace";
|
||||
#else
|
||||
configPath+="/furnace";
|
||||
#endif // __APPLE__
|
||||
struct stat st;
|
||||
std::string pathSep="/";
|
||||
configPath+=pathSep;
|
||||
size_t sepPos=configPath.find(pathSep,1);
|
||||
while (sepPos!=std::string::npos) {
|
||||
std::string subpath=configPath.substr(0,sepPos++);
|
||||
if (stat(subpath.c_str(),&st)!=0) {
|
||||
logI("creating config path element %s ...",subpath.c_str());
|
||||
if (mkdir(subpath.c_str(),0755)!=0) {
|
||||
logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno));
|
||||
configPath=".";
|
||||
return;
|
||||
}
|
||||
}
|
||||
sepPos=configPath.find(pathSep,sepPos);
|
||||
}
|
||||
configPath.resize(configPath.length()-pathSep.length());
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
bool DivEngine::saveConf() {
|
||||
configFile=configPath+String(CONFIG_FILE);
|
||||
FILE* f=ps_fopen(configFile.c_str(),"wb");
|
||||
|
|
@ -158,6 +239,10 @@ void DivEngine::setConf(String key, double value) {
|
|||
conf[key]=fmt::sprintf("%f",value);
|
||||
}
|
||||
|
||||
void DivEngine::setConf(String key, const char* value) {
|
||||
conf[key]=String(value);
|
||||
}
|
||||
|
||||
void DivEngine::setConf(String key, String value) {
|
||||
conf[key]=value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide)
|
||||
DIV_CMD_PRE_NOTE, // used in C64 (note)
|
||||
|
||||
// these will be used in ROM export.
|
||||
// do NOT implement!
|
||||
DIV_CMD_HINT_VIBRATO, // (speed, depth)
|
||||
DIV_CMD_HINT_VIBRATO_RANGE, // (range)
|
||||
DIV_CMD_HINT_VIBRATO_SHAPE, // (shape)
|
||||
DIV_CMD_HINT_PITCH, // (pitch)
|
||||
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
||||
DIV_CMD_HINT_VOLUME, // (vol)
|
||||
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
||||
DIV_CMD_HINT_PORTA, // (target, speed)
|
||||
DIV_CMD_HINT_LEGATO, // (note)
|
||||
|
||||
DIV_CMD_SAMPLE_MODE, // (enabled)
|
||||
DIV_CMD_SAMPLE_FREQ, // (frequency)
|
||||
DIV_CMD_SAMPLE_BANK, // (bank)
|
||||
|
|
@ -400,11 +412,10 @@ class DivDispatch {
|
|||
virtual bool getDCOffRequired();
|
||||
|
||||
/**
|
||||
* get a description of a dispatch-specific effect.
|
||||
* @param effect the effect.
|
||||
* @return the description, or NULL if effect is invalid.
|
||||
* check whether PRE_NOTE command is desired.
|
||||
* @return truth.
|
||||
*/
|
||||
virtual const char* getEffectName(unsigned char effect);
|
||||
virtual bool getWantPreNote();
|
||||
|
||||
/**
|
||||
* set the chip flags.
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include "platform/su.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/zxbeeper.h"
|
||||
#include "platform/bubsyswsg.h"
|
||||
#include "platform/n163.h"
|
||||
#include "platform/pet.h"
|
||||
|
|
@ -65,9 +66,9 @@
|
|||
#include "platform/ymz280b.h"
|
||||
#include "platform/rf5c68.h"
|
||||
#include "platform/snes.h"
|
||||
#include "platform/pcmdac.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "platform/zxbeeper.h"
|
||||
#include "song.h"
|
||||
|
||||
void DivDispatchContainer::setRates(double gotRate) {
|
||||
|
|
@ -218,10 +219,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_C64_6581:
|
||||
dispatch=new DivPlatformC64;
|
||||
((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1);
|
||||
((DivPlatformC64*)dispatch)->setChipModel(true);
|
||||
break;
|
||||
case DIV_SYSTEM_C64_8580:
|
||||
dispatch=new DivPlatformC64;
|
||||
((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1);
|
||||
((DivPlatformC64*)dispatch)->setChipModel(false);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
|
|
@ -399,6 +402,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SNES:
|
||||
dispatch=new DivPlatformSNES;
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -32,7 +32,8 @@
|
|||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <deque>
|
||||
|
||||
#define addWarning(x) \
|
||||
if (warnings.empty()) { \
|
||||
|
|
@ -45,11 +46,14 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev99"
|
||||
#define DIV_ENGINE_VERSION 99
|
||||
|
||||
#define DIV_VERSION "dev114"
|
||||
#define DIV_ENGINE_VERSION 114
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
|
||||
// "Namco C163"
|
||||
#define DIV_C163_DEFAULT_NAME "Namco 163"
|
||||
|
||||
enum DivStatusView {
|
||||
DIV_STATUS_NOTHING=0,
|
||||
|
|
@ -60,8 +64,9 @@ enum DivStatusView {
|
|||
enum DivAudioEngines {
|
||||
DIV_AUDIO_JACK=0,
|
||||
DIV_AUDIO_SDL=1,
|
||||
DIV_AUDIO_NULL=2,
|
||||
DIV_AUDIO_DUMMY=3
|
||||
|
||||
DIV_AUDIO_NULL=126,
|
||||
DIV_AUDIO_DUMMY=127
|
||||
};
|
||||
|
||||
enum DivAudioExportModes {
|
||||
|
|
@ -83,11 +88,11 @@ struct DivChannelState {
|
|||
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
||||
int volume, volSpeed, cut, rowDelay, volMax;
|
||||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
|
||||
int tremoloDepth, tremoloRate, tremoloPos;
|
||||
unsigned char arp, arpStage, arpTicks, panL, panR;
|
||||
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
|
||||
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp;
|
||||
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
|
||||
|
||||
int midiNote, curMidiNote, midiPitch;
|
||||
size_t midiAge;
|
||||
|
|
@ -112,6 +117,7 @@ struct DivChannelState {
|
|||
vibratoDepth(0),
|
||||
vibratoRate(0),
|
||||
vibratoPos(0),
|
||||
vibratoPosGiant(0),
|
||||
vibratoDir(0),
|
||||
vibratoFine(15),
|
||||
tremoloDepth(0),
|
||||
|
|
@ -134,6 +140,7 @@ struct DivChannelState {
|
|||
inPorta(false),
|
||||
scheduledSlideReset(false),
|
||||
shorthandPorta(false),
|
||||
wasShorthandPorta(false),
|
||||
noteOnInhibit(false),
|
||||
resetArp(false),
|
||||
midiNote(-1),
|
||||
|
|
@ -157,7 +164,7 @@ struct DivNoteEvent {
|
|||
struct DivDispatchContainer {
|
||||
DivDispatch* dispatch;
|
||||
blip_buffer_t* bb[2];
|
||||
size_t bbInLen;
|
||||
size_t bbInLen, runtotal, runLeft, runPos, lastAvail;
|
||||
int temp[2], prevSample[2];
|
||||
short* bbIn[2];
|
||||
short* bbOut[2];
|
||||
|
|
@ -175,6 +182,10 @@ struct DivDispatchContainer {
|
|||
dispatch(NULL),
|
||||
bb{NULL,NULL},
|
||||
bbInLen(0),
|
||||
runtotal(0),
|
||||
runLeft(0),
|
||||
runPos(0),
|
||||
lastAvail(0),
|
||||
temp{0,0},
|
||||
prevSample{0,0},
|
||||
bbIn{NULL,NULL},
|
||||
|
|
@ -183,7 +194,29 @@ struct DivDispatchContainer {
|
|||
dcOffCompensation(false) {}
|
||||
};
|
||||
|
||||
typedef std::function<bool(int,unsigned char,unsigned char)> EffectProcess;
|
||||
typedef int EffectValConversion(unsigned char,unsigned char);
|
||||
|
||||
struct EffectHandler {
|
||||
DivDispatchCmds dispatchCmd;
|
||||
const char* description;
|
||||
EffectValConversion* val;
|
||||
EffectValConversion* val2;
|
||||
EffectHandler(
|
||||
DivDispatchCmds dispatchCmd_,
|
||||
const char* description_,
|
||||
EffectValConversion val_=NULL,
|
||||
EffectValConversion val2_=NULL
|
||||
):
|
||||
dispatchCmd(dispatchCmd_),
|
||||
description(description_),
|
||||
val(val_),
|
||||
val2(val2_) {}
|
||||
};
|
||||
|
||||
struct DivDoNotHandleEffect {
|
||||
};
|
||||
|
||||
typedef std::unordered_map<unsigned char,const EffectHandler> EffectHandlerMap;
|
||||
|
||||
struct DivSysDef {
|
||||
const char* name;
|
||||
|
|
@ -200,8 +233,8 @@ struct DivSysDef {
|
|||
// 0: primary
|
||||
// 1: alternate (usually PCM)
|
||||
DivInstrumentType chanInsType[DIV_MAX_CHANS][2];
|
||||
EffectProcess effectFunc;
|
||||
EffectProcess postEffectFunc;
|
||||
const EffectHandlerMap effectHandlers;
|
||||
const EffectHandlerMap postEffectHandlers;
|
||||
DivSysDef(
|
||||
const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans,
|
||||
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, const char* desc,
|
||||
|
|
@ -210,8 +243,8 @@ struct DivSysDef {
|
|||
std::initializer_list<int> chTypes,
|
||||
std::initializer_list<DivInstrumentType> chInsType1,
|
||||
std::initializer_list<DivInstrumentType> chInsType2={},
|
||||
EffectProcess fxHandler=[](int,unsigned char,unsigned char) -> bool {return false;},
|
||||
EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}):
|
||||
const EffectHandlerMap fxHandlers_={},
|
||||
const EffectHandlerMap postFxHandlers_={}):
|
||||
name(sysName),
|
||||
nameJ(sysNameJ),
|
||||
description(desc),
|
||||
|
|
@ -222,8 +255,8 @@ struct DivSysDef {
|
|||
isSTD(isSTDChip),
|
||||
isCompound(compound),
|
||||
vgmVersion(vgmVer),
|
||||
effectFunc(fxHandler),
|
||||
postEffectFunc(postFxHandler) {
|
||||
effectHandlers(fxHandlers_),
|
||||
postEffectHandlers(postFxHandlers_) {
|
||||
memset(chanNames,0,DIV_MAX_CHANS*sizeof(void*));
|
||||
memset(chanShortNames,0,DIV_MAX_CHANS*sizeof(void*));
|
||||
memset(chanTypes,0,DIV_MAX_CHANS*sizeof(int));
|
||||
|
|
@ -273,6 +306,8 @@ enum DivChanTypes {
|
|||
DIV_CH_OP=5
|
||||
};
|
||||
|
||||
extern const char* cmdName[];
|
||||
|
||||
class DivEngine {
|
||||
DivDispatchContainer disCont[32];
|
||||
TAAudio* output;
|
||||
|
|
@ -294,6 +329,7 @@ class DivEngine {
|
|||
bool stopExport;
|
||||
bool halted;
|
||||
bool forceMono;
|
||||
bool clampSamples;
|
||||
bool cmdStreamEnabled;
|
||||
bool softLocked;
|
||||
bool firstTick;
|
||||
|
|
@ -320,7 +356,9 @@ class DivEngine {
|
|||
DivAudioExportModes exportMode;
|
||||
double exportFadeOut;
|
||||
std::map<String,String> conf;
|
||||
std::queue<DivNoteEvent> pendingNotes;
|
||||
std::deque<DivNoteEvent> pendingNotes;
|
||||
// bitfield
|
||||
unsigned char walked[8192];
|
||||
bool isMuted[DIV_MAX_CHANS];
|
||||
std::mutex isBusy, saveLock;
|
||||
String configPath;
|
||||
|
|
@ -352,6 +390,7 @@ class DivEngine {
|
|||
short vibTable[64];
|
||||
int reversePitchTable[4096];
|
||||
int pitchTable[4096];
|
||||
char c163NameCS[1024];
|
||||
int midiBaseChan;
|
||||
bool midiPoly;
|
||||
size_t midiAgeCounter;
|
||||
|
|
@ -393,6 +432,7 @@ class DivEngine {
|
|||
bool loadFur(unsigned char* file, size_t len);
|
||||
bool loadMod(unsigned char* file, size_t len);
|
||||
bool loadFTM(unsigned char* file, size_t len);
|
||||
bool loadFC(unsigned char* file, size_t len);
|
||||
|
||||
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
|
|
@ -412,7 +452,7 @@ class DivEngine {
|
|||
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
||||
|
||||
bool initAudioBackend();
|
||||
bool deinitAudioBackend();
|
||||
bool deinitAudioBackend(bool dueToSwitchMaster=false);
|
||||
|
||||
void registerSystems();
|
||||
void initSongWithDesc(const int* description);
|
||||
|
|
@ -450,7 +490,7 @@ class DivEngine {
|
|||
String encodeSysDesc(std::vector<int>& desc);
|
||||
std::vector<int> decodeSysDesc(String desc);
|
||||
// start fresh
|
||||
void createNew(const int* description);
|
||||
void createNew(const int* description, String sysName);
|
||||
// load a file.
|
||||
bool load(unsigned char* f, size_t length);
|
||||
// save as .dmf.
|
||||
|
|
@ -462,7 +502,9 @@ class DivEngine {
|
|||
// specify system to build ROM for.
|
||||
SafeWriter* buildROM(int sys);
|
||||
// dump to VGM.
|
||||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171);
|
||||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false);
|
||||
// dump command stream.
|
||||
SafeWriter* saveCommand(bool binary=false);
|
||||
// export to an audio file
|
||||
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
|
||||
// wait for audio export to finish
|
||||
|
|
@ -474,9 +516,16 @@ class DivEngine {
|
|||
// notify wavetable change
|
||||
void notifyWaveChange(int wave);
|
||||
|
||||
// benchmark (returns time in seconds)
|
||||
double benchmarkPlayback();
|
||||
double benchmarkSeek();
|
||||
|
||||
// returns the minimum VGM version which may carry the specified system, or 0 if none.
|
||||
int minVGMVersion(DivSystem which);
|
||||
|
||||
// determine and setup config dir
|
||||
void initConfDir();
|
||||
|
||||
// save config
|
||||
bool saveConf();
|
||||
|
||||
|
|
@ -495,6 +544,7 @@ class DivEngine {
|
|||
void setConf(String key, int value);
|
||||
void setConf(String key, float value);
|
||||
void setConf(String key, double value);
|
||||
void setConf(String key, const char* value);
|
||||
void setConf(String key, String value);
|
||||
|
||||
// calculate base frequency/period
|
||||
|
|
@ -506,6 +556,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);
|
||||
|
|
@ -568,7 +621,7 @@ class DivEngine {
|
|||
DivInstrumentType getPreferInsSecondType(int ch);
|
||||
|
||||
// get song system name
|
||||
String getSongSystemName(bool isMultiSystemAcceptable=true);
|
||||
String getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable=true);
|
||||
|
||||
// get sys name
|
||||
const char* getSystemName(DivSystem sys);
|
||||
|
|
@ -576,6 +629,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);
|
||||
|
|
@ -652,6 +708,9 @@ class DivEngine {
|
|||
|
||||
// is playing
|
||||
bool isPlaying();
|
||||
|
||||
// is running
|
||||
bool isRunning();
|
||||
|
||||
// is stepping
|
||||
bool isStepping();
|
||||
|
|
@ -678,8 +737,11 @@ class DivEngine {
|
|||
// add wavetable
|
||||
int addWave();
|
||||
|
||||
// add wavetable from file
|
||||
bool addWaveFromFile(const char* path, bool loadRaw=true);
|
||||
// add wavetable from pointer
|
||||
int addWavePtr(DivWavetable* which);
|
||||
|
||||
// get wavetable from file
|
||||
DivWavetable* waveFromFile(const char* path, bool loadRaw=true);
|
||||
|
||||
// delete wavetable
|
||||
void delWave(int index);
|
||||
|
|
@ -687,8 +749,14 @@ class DivEngine {
|
|||
// add sample
|
||||
int addSample();
|
||||
|
||||
// add sample from file
|
||||
int addSampleFromFile(const char* path);
|
||||
// add sample from pointer
|
||||
int addSamplePtr(DivSample* which);
|
||||
|
||||
// get sample from file
|
||||
DivSample* sampleFromFile(const char* path);
|
||||
|
||||
// get raw sample
|
||||
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign);
|
||||
|
||||
// delete sample
|
||||
void delSample(int index);
|
||||
|
|
@ -845,6 +913,9 @@ class DivEngine {
|
|||
|
||||
// remove system
|
||||
bool removeSystem(int index, bool preserveOrder=true);
|
||||
|
||||
// move system
|
||||
bool swapSystem(int src, int dest, bool preserveOrder=true);
|
||||
|
||||
// write to register on system
|
||||
void poke(int sys, unsigned int addr, unsigned short val);
|
||||
|
|
@ -1003,6 +1074,7 @@ class DivEngine {
|
|||
memset(reversePitchTable,0,4096*sizeof(int));
|
||||
memset(pitchTable,0,4096*sizeof(int));
|
||||
memset(sysDefs,0,256*sizeof(void*));
|
||||
memset(walked,0,8192);
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,7 @@
|
|||
#include "../ta-log.h"
|
||||
#include "../fileutils.h"
|
||||
#include <fmt/printf.h>
|
||||
#include <limits.h>
|
||||
|
||||
enum DivInsFormats {
|
||||
DIV_INSFORMAT_DMP,
|
||||
|
|
@ -149,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->type=DIV_INS_FM;
|
||||
logD("instrument type is Arcade");
|
||||
break;
|
||||
case 9: // Neo Geo
|
||||
ins->type=DIV_INS_FM;
|
||||
logD("instrument type is Neo Geo");
|
||||
break;
|
||||
default:
|
||||
logD("instrument type is unknown");
|
||||
lastError="unknown instrument type!";
|
||||
lastError=fmt::sprintf("unknown instrument type %d!",sys);
|
||||
delete ins;
|
||||
return;
|
||||
break;
|
||||
|
|
@ -170,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
mode=reader.readC();
|
||||
logD("instrument mode is %d",mode);
|
||||
if (mode==0) {
|
||||
if (version<11) {
|
||||
ins->type=DIV_INS_STD;
|
||||
if (ins->type==DIV_INS_FM) {
|
||||
if (sys==9) {
|
||||
ins->type=DIV_INS_AY;
|
||||
} else {
|
||||
ins->type=DIV_INS_STD;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ins->type=DIV_INS_FM;
|
||||
if (sys==3 || sys==6) {
|
||||
ins->type=DIV_INS_OPLL;
|
||||
} else if (sys==1) {
|
||||
ins->type=DIV_INS_OPL;
|
||||
} else {
|
||||
ins->type=DIV_INS_FM;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ins->type=DIV_INS_FM;
|
||||
|
|
@ -192,7 +207,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->fm.ops=reader.readC()?4:2;
|
||||
}
|
||||
} else {
|
||||
ins->fm.ops=reader.readC()?2:4;
|
||||
// HELP
|
||||
if (reader.size()==49) {
|
||||
ins->fm.ops=4;
|
||||
reader.readC();
|
||||
} else {
|
||||
ins->fm.ops=reader.readC()?2:4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ins->fm.ops=4;
|
||||
|
|
@ -225,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->fm.op[j].dvb=reader.readC();
|
||||
ins->fm.op[j].dam=reader.readC();
|
||||
} else {
|
||||
ins->fm.op[j].rs=reader.readC();
|
||||
ins->fm.op[j].dt=reader.readC();
|
||||
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
|
||||
ins->fm.op[j].dt&=15;
|
||||
ins->fm.op[j].d2r=reader.readC();
|
||||
ins->fm.op[j].ssgEnv=reader.readC();
|
||||
if (sys==3 || sys==6) { // OPLL/VRC7
|
||||
ins->fm.op[j].ksr=reader.readC()?1:0;
|
||||
ins->fm.op[j].vib=reader.readC();
|
||||
if (j==0) {
|
||||
ins->fm.opllPreset=ins->fm.op[j].vib>>4;
|
||||
}
|
||||
ins->fm.op[j].vib=ins->fm.op[j].vib?1:0;
|
||||
ins->fm.op[j].ksl=reader.readC()?1:0;
|
||||
ins->fm.op[j].ssgEnv=reader.readC();
|
||||
} else {
|
||||
ins->fm.op[j].rs=reader.readC();
|
||||
ins->fm.op[j].dt=reader.readC();
|
||||
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
|
||||
ins->fm.op[j].dt&=15;
|
||||
ins->fm.op[j].d2r=reader.readC();
|
||||
ins->fm.op[j].ssgEnv=reader.readC();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // STD
|
||||
|
|
@ -240,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
if (version>5) {
|
||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||
ins->std.volMacro.val[i]=reader.readI();
|
||||
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
|
||||
ins->type=DIV_INS_FDS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||
|
|
@ -726,6 +761,8 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
|||
ins = new DivInstrument;
|
||||
ins->type = DIV_INS_OPL;
|
||||
ins->name = fmt::sprintf("%s (2)", insName);
|
||||
ins->fm.alg = (feedConnect2nd & 0x1);
|
||||
ins->fm.fb = ((feedConnect2nd >> 1) & 0xF);
|
||||
for (int i : {1,0}) {
|
||||
readOpliOp(reader, ins->fm.op[i]);
|
||||
}
|
||||
|
|
@ -796,7 +833,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
|||
|
||||
op.mult = dtMul & 0xF;
|
||||
op.dt = ((dtMul >> 4) & 0x7);
|
||||
op.tl = totalLevel & 0x3F;
|
||||
op.tl = totalLevel & 0x7F;
|
||||
op.rs = ((arRateScale >> 6) & 0x3);
|
||||
op.ar = arRateScale & 0x1F;
|
||||
op.dr = drAmpEnable & 0x1F;
|
||||
|
|
@ -835,8 +872,9 @@ void DivEngine::loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
DivInstrumentFM::Operator& insOp = ins->fm.op[i];
|
||||
uint8_t tmp = reader.readC();
|
||||
insOp.mult = tmp & 0xF;
|
||||
insOp.dt = ((tmp >> 4) & 0x7);
|
||||
insOp.tl = (reader.readC() & 0x3F);
|
||||
// ???
|
||||
insOp.dt = ((3 + (tmp >> 4)) & 0x7);
|
||||
insOp.tl = (reader.readC() & 0x7F);
|
||||
tmp = reader.readC();
|
||||
insOp.rs = ((tmp >> 6) & 0x3);
|
||||
insOp.ar = tmp & 0x1F;
|
||||
|
|
@ -1255,7 +1293,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false;
|
||||
newPatch = NULL;
|
||||
};
|
||||
auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) -> int {
|
||||
auto readIntStrWithinRange = [](String&& input, int limitLow = INT_MIN, int limitHigh = INT_MAX) -> int {
|
||||
int x = std::stoi(input.c_str());
|
||||
if (x > limitHigh || x < limitLow) {
|
||||
throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh));
|
||||
|
|
@ -1273,7 +1311,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15);
|
||||
op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7));
|
||||
op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3);
|
||||
op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1);
|
||||
op.am = readIntStrWithinRange(reader.readStringToken(), 0) > 0 ? 1 : 0;
|
||||
};
|
||||
auto seekGroupValStart = [](SafeReader& reader, int pos) {
|
||||
// Seek to position then move to next ':' character
|
||||
|
|
@ -1490,6 +1528,8 @@ void DivEngine::loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
|||
ins = new DivInstrument;
|
||||
ins->type = DIV_INS_OPL;
|
||||
ins->name = fmt::sprintf("%s (2)", insName);
|
||||
ins->fm.alg = (feedConnect2nd & 0x1);
|
||||
ins->fm.fb = ((feedConnect2nd >> 1) & 0xF);
|
||||
for (int i : {1,0}) {
|
||||
patchSum += readWoplOp(reader, ins->fm.op[i]);
|
||||
}
|
||||
|
|
@ -1631,7 +1671,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
|||
|
||||
total += (op.mult = dtMul & 0xF);
|
||||
total += (op.dt = ((dtMul >> 4) & 0x7));
|
||||
total += (op.tl = totalLevel & 0x3F);
|
||||
total += (op.tl = totalLevel & 0x7F);
|
||||
total += (op.rs = ((arRateScale >> 6) & 0x3));
|
||||
total += (op.ar = arRateScale & 0x1F);
|
||||
total += (op.dr = drAmpEnable & 0x1F);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@
|
|||
#include "../fileutils.h"
|
||||
|
||||
void DivInstrument::putInsData(SafeWriter* w) {
|
||||
size_t blockStartSeek, blockEndSeek;
|
||||
|
||||
w->write("INST",4);
|
||||
blockStartSeek=w->tell();
|
||||
w->writeI(0);
|
||||
|
||||
w->writeS(DIV_ENGINE_VERSION);
|
||||
|
|
@ -68,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeC(op.ws);
|
||||
w->writeC(op.ksr);
|
||||
|
||||
w->writeC(op.enable);
|
||||
|
||||
// reserved
|
||||
for (int k=0; k<12; k++) {
|
||||
for (int k=0; k<11; k++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -129,7 +134,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);
|
||||
|
|
@ -384,8 +389,12 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
// sample map
|
||||
w->writeC(amiga.useNoteMap);
|
||||
if (amiga.useNoteMap) {
|
||||
w->write(amiga.noteFreq,120*sizeof(unsigned int));
|
||||
w->write(amiga.noteMap,120*sizeof(short));
|
||||
for (int note=0; note<120; note++) {
|
||||
w->writeI(amiga.noteMap[note].freq);
|
||||
}
|
||||
for (int note=0; note<120; note++) {
|
||||
w->writeS(amiga.noteMap[note].map);
|
||||
}
|
||||
}
|
||||
|
||||
// N163
|
||||
|
|
@ -520,8 +529,142 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
for (int j=0; j<23; j++) { // reserved
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
// Sound Unit
|
||||
w->writeC(su.useSample);
|
||||
w->writeC(su.switchRoles);
|
||||
|
||||
// GB hardware sequence
|
||||
w->writeC(gb.hwSeqLen);
|
||||
for (int i=0; i<gb.hwSeqLen; i++) {
|
||||
w->writeC(gb.hwSeq[i].cmd);
|
||||
w->writeS(gb.hwSeq[i].data);
|
||||
}
|
||||
|
||||
// GB additional flags
|
||||
w->writeC(gb.softEnv);
|
||||
w->writeC(gb.alwaysInit);
|
||||
|
||||
// ES5506
|
||||
w->writeC(es5506.filter.mode);
|
||||
w->writeS(es5506.filter.k1);
|
||||
w->writeS(es5506.filter.k2);
|
||||
w->writeS(es5506.envelope.ecount);
|
||||
w->writeC(es5506.envelope.lVRamp);
|
||||
w->writeC(es5506.envelope.rVRamp);
|
||||
w->writeC(es5506.envelope.k1Ramp);
|
||||
w->writeC(es5506.envelope.k2Ramp);
|
||||
w->writeC(es5506.envelope.k1Slow);
|
||||
w->writeC(es5506.envelope.k2Slow);
|
||||
|
||||
// SNES
|
||||
w->writeC(snes.useEnv);
|
||||
w->writeC(snes.gainMode);
|
||||
w->writeC(snes.gain);
|
||||
w->writeC(snes.a);
|
||||
w->writeC(snes.d);
|
||||
w->writeC(snes.s);
|
||||
w->writeC(snes.r);
|
||||
|
||||
// macro speed/delay
|
||||
w->writeC(std.volMacro.speed);
|
||||
w->writeC(std.arpMacro.speed);
|
||||
w->writeC(std.dutyMacro.speed);
|
||||
w->writeC(std.waveMacro.speed);
|
||||
w->writeC(std.pitchMacro.speed);
|
||||
w->writeC(std.ex1Macro.speed);
|
||||
w->writeC(std.ex2Macro.speed);
|
||||
w->writeC(std.ex3Macro.speed);
|
||||
w->writeC(std.algMacro.speed);
|
||||
w->writeC(std.fbMacro.speed);
|
||||
w->writeC(std.fmsMacro.speed);
|
||||
w->writeC(std.amsMacro.speed);
|
||||
w->writeC(std.panLMacro.speed);
|
||||
w->writeC(std.panRMacro.speed);
|
||||
w->writeC(std.phaseResetMacro.speed);
|
||||
w->writeC(std.ex4Macro.speed);
|
||||
w->writeC(std.ex5Macro.speed);
|
||||
w->writeC(std.ex6Macro.speed);
|
||||
w->writeC(std.ex7Macro.speed);
|
||||
w->writeC(std.ex8Macro.speed);
|
||||
|
||||
w->writeC(std.volMacro.delay);
|
||||
w->writeC(std.arpMacro.delay);
|
||||
w->writeC(std.dutyMacro.delay);
|
||||
w->writeC(std.waveMacro.delay);
|
||||
w->writeC(std.pitchMacro.delay);
|
||||
w->writeC(std.ex1Macro.delay);
|
||||
w->writeC(std.ex2Macro.delay);
|
||||
w->writeC(std.ex3Macro.delay);
|
||||
w->writeC(std.algMacro.delay);
|
||||
w->writeC(std.fbMacro.delay);
|
||||
w->writeC(std.fmsMacro.delay);
|
||||
w->writeC(std.amsMacro.delay);
|
||||
w->writeC(std.panLMacro.delay);
|
||||
w->writeC(std.panRMacro.delay);
|
||||
w->writeC(std.phaseResetMacro.delay);
|
||||
w->writeC(std.ex4Macro.delay);
|
||||
w->writeC(std.ex5Macro.delay);
|
||||
w->writeC(std.ex6Macro.delay);
|
||||
w->writeC(std.ex7Macro.delay);
|
||||
w->writeC(std.ex8Macro.delay);
|
||||
|
||||
// op macro speed/delay
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentSTD::OpMacro& op=std.opMacros[i];
|
||||
|
||||
w->writeC(op.amMacro.speed);
|
||||
w->writeC(op.arMacro.speed);
|
||||
w->writeC(op.drMacro.speed);
|
||||
w->writeC(op.multMacro.speed);
|
||||
w->writeC(op.rrMacro.speed);
|
||||
w->writeC(op.slMacro.speed);
|
||||
w->writeC(op.tlMacro.speed);
|
||||
w->writeC(op.dt2Macro.speed);
|
||||
w->writeC(op.rsMacro.speed);
|
||||
w->writeC(op.dtMacro.speed);
|
||||
w->writeC(op.d2rMacro.speed);
|
||||
w->writeC(op.ssgMacro.speed);
|
||||
w->writeC(op.damMacro.speed);
|
||||
w->writeC(op.dvbMacro.speed);
|
||||
w->writeC(op.egtMacro.speed);
|
||||
w->writeC(op.kslMacro.speed);
|
||||
w->writeC(op.susMacro.speed);
|
||||
w->writeC(op.vibMacro.speed);
|
||||
w->writeC(op.wsMacro.speed);
|
||||
w->writeC(op.ksrMacro.speed);
|
||||
|
||||
w->writeC(op.amMacro.delay);
|
||||
w->writeC(op.arMacro.delay);
|
||||
w->writeC(op.drMacro.delay);
|
||||
w->writeC(op.multMacro.delay);
|
||||
w->writeC(op.rrMacro.delay);
|
||||
w->writeC(op.slMacro.delay);
|
||||
w->writeC(op.tlMacro.delay);
|
||||
w->writeC(op.dt2Macro.delay);
|
||||
w->writeC(op.rsMacro.delay);
|
||||
w->writeC(op.dtMacro.delay);
|
||||
w->writeC(op.d2rMacro.delay);
|
||||
w->writeC(op.ssgMacro.delay);
|
||||
w->writeC(op.damMacro.delay);
|
||||
w->writeC(op.dvbMacro.delay);
|
||||
w->writeC(op.egtMacro.delay);
|
||||
w->writeC(op.kslMacro.delay);
|
||||
w->writeC(op.susMacro.delay);
|
||||
w->writeC(op.vibMacro.delay);
|
||||
w->writeC(op.wsMacro.delay);
|
||||
w->writeC(op.ksrMacro.delay);
|
||||
}
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
w->writeI(blockEndSeek-blockStartSeek-4);
|
||||
w->seek(0,SEEK_END);
|
||||
}
|
||||
|
||||
#define READ_MACRO_VALS(x,y) \
|
||||
for (int macroValPos=0; macroValPos<y; macroValPos++) x[macroValPos]=reader.readI();
|
||||
|
||||
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
||||
char magic[4];
|
||||
reader.read(magic,4);
|
||||
|
|
@ -575,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
op.ws=reader.readC();
|
||||
op.ksr=reader.readC();
|
||||
|
||||
if (version>=114) {
|
||||
op.enable=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
|
||||
// reserved
|
||||
for (int k=0; k<12; k++) reader.readC();
|
||||
for (int k=0; k<11; k++) reader.readC();
|
||||
}
|
||||
|
||||
// GB
|
||||
|
|
@ -647,10 +796,10 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
int oldVolHeight=reader.readC();
|
||||
int oldDutyHeight=reader.readC();
|
||||
reader.readC(); // oldWaveHeight
|
||||
reader.read(std.volMacro.val,4*std.volMacro.len);
|
||||
reader.read(std.arpMacro.val,4*std.arpMacro.len);
|
||||
reader.read(std.dutyMacro.val,4*std.dutyMacro.len);
|
||||
reader.read(std.waveMacro.val,4*std.waveMacro.len);
|
||||
READ_MACRO_VALS(std.volMacro.val,std.volMacro.len);
|
||||
READ_MACRO_VALS(std.arpMacro.val,std.arpMacro.len);
|
||||
READ_MACRO_VALS(std.dutyMacro.val,std.dutyMacro.len);
|
||||
READ_MACRO_VALS(std.waveMacro.val,std.waveMacro.len);
|
||||
if (version<31) {
|
||||
if (!std.arpMacro.mode) for (int j=0; j<std.arpMacro.len; j++) {
|
||||
std.arpMacro.val[j]-=12;
|
||||
|
|
@ -665,10 +814,10 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
}
|
||||
}
|
||||
if (version>=17) {
|
||||
reader.read(std.pitchMacro.val,4*std.pitchMacro.len);
|
||||
reader.read(std.ex1Macro.val,4*std.ex1Macro.len);
|
||||
reader.read(std.ex2Macro.val,4*std.ex2Macro.len);
|
||||
reader.read(std.ex3Macro.val,4*std.ex3Macro.len);
|
||||
READ_MACRO_VALS(std.pitchMacro.val,std.pitchMacro.len);
|
||||
READ_MACRO_VALS(std.ex1Macro.val,std.ex1Macro.len);
|
||||
READ_MACRO_VALS(std.ex2Macro.val,std.ex2Macro.len);
|
||||
READ_MACRO_VALS(std.ex3Macro.val,std.ex3Macro.len);
|
||||
} else {
|
||||
if (type==DIV_INS_STD) {
|
||||
if (oldVolHeight==31) {
|
||||
|
|
@ -703,10 +852,10 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
std.fmsMacro.open=reader.readC();
|
||||
std.amsMacro.open=reader.readC();
|
||||
|
||||
reader.read(std.algMacro.val,4*std.algMacro.len);
|
||||
reader.read(std.fbMacro.val,4*std.fbMacro.len);
|
||||
reader.read(std.fmsMacro.val,4*std.fmsMacro.len);
|
||||
reader.read(std.amsMacro.val,4*std.amsMacro.len);
|
||||
READ_MACRO_VALS(std.algMacro.val,std.algMacro.len);
|
||||
READ_MACRO_VALS(std.fbMacro.val,std.fbMacro.len);
|
||||
READ_MACRO_VALS(std.fmsMacro.val,std.fmsMacro.len);
|
||||
READ_MACRO_VALS(std.amsMacro.val,std.amsMacro.len);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentSTD::OpMacro& op=std.opMacros[i];
|
||||
|
|
@ -909,23 +1058,27 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
// clear noise macro if PCE instrument and version<63
|
||||
if (version<63 && type==DIV_INS_PCE) {
|
||||
std.dutyMacro.len=0;
|
||||
std.dutyMacro.loop=-1;
|
||||
std.dutyMacro.rel=-1;
|
||||
std.dutyMacro.loop=255;
|
||||
std.dutyMacro.rel=255;
|
||||
}
|
||||
|
||||
// clear wave macro if OPLL instrument and version<70
|
||||
if (version<70 && type==DIV_INS_OPLL) {
|
||||
std.waveMacro.len=0;
|
||||
std.waveMacro.loop=-1;
|
||||
std.waveMacro.rel=-1;
|
||||
std.waveMacro.loop=255;
|
||||
std.waveMacro.rel=255;
|
||||
}
|
||||
|
||||
// sample map
|
||||
if (version>=67) {
|
||||
amiga.useNoteMap=reader.readC();
|
||||
if (amiga.useNoteMap) {
|
||||
reader.read(amiga.noteFreq,120*sizeof(unsigned int));
|
||||
reader.read(amiga.noteMap,120*sizeof(short));
|
||||
for (int note=0; note<120; note++) {
|
||||
amiga.noteMap[note].freq=reader.readI();
|
||||
}
|
||||
for (int note=0; note<120; note++) {
|
||||
amiga.noteMap[note].map=reader.readS();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -976,14 +1129,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
std.ex7Macro.open=reader.readC();
|
||||
std.ex8Macro.open=reader.readC();
|
||||
|
||||
reader.read(std.panLMacro.val,4*std.panLMacro.len);
|
||||
reader.read(std.panRMacro.val,4*std.panRMacro.len);
|
||||
reader.read(std.phaseResetMacro.val,4*std.phaseResetMacro.len);
|
||||
reader.read(std.ex4Macro.val,4*std.ex4Macro.len);
|
||||
reader.read(std.ex5Macro.val,4*std.ex5Macro.len);
|
||||
reader.read(std.ex6Macro.val,4*std.ex6Macro.len);
|
||||
reader.read(std.ex7Macro.val,4*std.ex7Macro.len);
|
||||
reader.read(std.ex8Macro.val,4*std.ex8Macro.len);
|
||||
READ_MACRO_VALS(std.panLMacro.val,std.panLMacro.len);
|
||||
READ_MACRO_VALS(std.panRMacro.val,std.panRMacro.len);
|
||||
READ_MACRO_VALS(std.phaseResetMacro.val,std.phaseResetMacro.len);
|
||||
READ_MACRO_VALS(std.ex4Macro.val,std.ex4Macro.len);
|
||||
READ_MACRO_VALS(std.ex5Macro.val,std.ex5Macro.len);
|
||||
READ_MACRO_VALS(std.ex6Macro.val,std.ex6Macro.len);
|
||||
READ_MACRO_VALS(std.ex7Macro.val,std.ex7Macro.len);
|
||||
READ_MACRO_VALS(std.ex8Macro.val,std.ex8Macro.len);
|
||||
|
||||
// FDS
|
||||
fds.modSpeed=reader.readI();
|
||||
|
|
@ -1059,6 +1212,157 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
for (int k=0; k<23; k++) reader.readC();
|
||||
}
|
||||
|
||||
// Sound Unit
|
||||
if (version>=104) {
|
||||
su.useSample=reader.readC();
|
||||
su.switchRoles=reader.readC();
|
||||
}
|
||||
|
||||
// GB hardware sequence
|
||||
if (version>=105) {
|
||||
gb.hwSeqLen=reader.readC();
|
||||
for (int i=0; i<gb.hwSeqLen; i++) {
|
||||
gb.hwSeq[i].cmd=reader.readC();
|
||||
gb.hwSeq[i].data=reader.readS();
|
||||
}
|
||||
}
|
||||
|
||||
// GB additional flags
|
||||
if (version>=106) {
|
||||
gb.softEnv=reader.readC();
|
||||
gb.alwaysInit=reader.readC();
|
||||
}
|
||||
|
||||
// ES5506
|
||||
if (version>=107) {
|
||||
es5506.filter.mode=(DivInstrumentES5506::Filter::FilterMode)reader.readC();
|
||||
es5506.filter.k1=reader.readS();
|
||||
es5506.filter.k2=reader.readS();
|
||||
es5506.envelope.ecount=reader.readS();
|
||||
es5506.envelope.lVRamp=reader.readC();
|
||||
es5506.envelope.rVRamp=reader.readC();
|
||||
es5506.envelope.k1Ramp=reader.readC();
|
||||
es5506.envelope.k2Ramp=reader.readC();
|
||||
es5506.envelope.k1Slow=reader.readC();
|
||||
es5506.envelope.k2Slow=reader.readC();
|
||||
}
|
||||
|
||||
// SNES
|
||||
if (version>=109) {
|
||||
snes.useEnv=reader.readC();
|
||||
snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC();
|
||||
snes.gain=reader.readC();
|
||||
snes.a=reader.readC();
|
||||
snes.d=reader.readC();
|
||||
snes.s=reader.readC();
|
||||
snes.r=reader.readC();
|
||||
}
|
||||
|
||||
// macro speed/delay
|
||||
if (version>=111) {
|
||||
std.volMacro.speed=reader.readC();
|
||||
std.arpMacro.speed=reader.readC();
|
||||
std.dutyMacro.speed=reader.readC();
|
||||
std.waveMacro.speed=reader.readC();
|
||||
std.pitchMacro.speed=reader.readC();
|
||||
std.ex1Macro.speed=reader.readC();
|
||||
std.ex2Macro.speed=reader.readC();
|
||||
std.ex3Macro.speed=reader.readC();
|
||||
std.algMacro.speed=reader.readC();
|
||||
std.fbMacro.speed=reader.readC();
|
||||
std.fmsMacro.speed=reader.readC();
|
||||
std.amsMacro.speed=reader.readC();
|
||||
std.panLMacro.speed=reader.readC();
|
||||
std.panRMacro.speed=reader.readC();
|
||||
std.phaseResetMacro.speed=reader.readC();
|
||||
std.ex4Macro.speed=reader.readC();
|
||||
std.ex5Macro.speed=reader.readC();
|
||||
std.ex6Macro.speed=reader.readC();
|
||||
std.ex7Macro.speed=reader.readC();
|
||||
std.ex8Macro.speed=reader.readC();
|
||||
|
||||
std.volMacro.delay=reader.readC();
|
||||
std.arpMacro.delay=reader.readC();
|
||||
std.dutyMacro.delay=reader.readC();
|
||||
std.waveMacro.delay=reader.readC();
|
||||
std.pitchMacro.delay=reader.readC();
|
||||
std.ex1Macro.delay=reader.readC();
|
||||
std.ex2Macro.delay=reader.readC();
|
||||
std.ex3Macro.delay=reader.readC();
|
||||
std.algMacro.delay=reader.readC();
|
||||
std.fbMacro.delay=reader.readC();
|
||||
std.fmsMacro.delay=reader.readC();
|
||||
std.amsMacro.delay=reader.readC();
|
||||
std.panLMacro.delay=reader.readC();
|
||||
std.panRMacro.delay=reader.readC();
|
||||
std.phaseResetMacro.delay=reader.readC();
|
||||
std.ex4Macro.delay=reader.readC();
|
||||
std.ex5Macro.delay=reader.readC();
|
||||
std.ex6Macro.delay=reader.readC();
|
||||
std.ex7Macro.delay=reader.readC();
|
||||
std.ex8Macro.delay=reader.readC();
|
||||
|
||||
// op macro speed/delay
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentSTD::OpMacro& op=std.opMacros[i];
|
||||
|
||||
op.amMacro.speed=reader.readC();
|
||||
op.arMacro.speed=reader.readC();
|
||||
op.drMacro.speed=reader.readC();
|
||||
op.multMacro.speed=reader.readC();
|
||||
op.rrMacro.speed=reader.readC();
|
||||
op.slMacro.speed=reader.readC();
|
||||
op.tlMacro.speed=reader.readC();
|
||||
op.dt2Macro.speed=reader.readC();
|
||||
op.rsMacro.speed=reader.readC();
|
||||
op.dtMacro.speed=reader.readC();
|
||||
op.d2rMacro.speed=reader.readC();
|
||||
op.ssgMacro.speed=reader.readC();
|
||||
op.damMacro.speed=reader.readC();
|
||||
op.dvbMacro.speed=reader.readC();
|
||||
op.egtMacro.speed=reader.readC();
|
||||
op.kslMacro.speed=reader.readC();
|
||||
op.susMacro.speed=reader.readC();
|
||||
op.vibMacro.speed=reader.readC();
|
||||
op.wsMacro.speed=reader.readC();
|
||||
op.ksrMacro.speed=reader.readC();
|
||||
|
||||
op.amMacro.delay=reader.readC();
|
||||
op.arMacro.delay=reader.readC();
|
||||
op.drMacro.delay=reader.readC();
|
||||
op.multMacro.delay=reader.readC();
|
||||
op.rrMacro.delay=reader.readC();
|
||||
op.slMacro.delay=reader.readC();
|
||||
op.tlMacro.delay=reader.readC();
|
||||
op.dt2Macro.delay=reader.readC();
|
||||
op.rsMacro.delay=reader.readC();
|
||||
op.dtMacro.delay=reader.readC();
|
||||
op.d2rMacro.delay=reader.readC();
|
||||
op.ssgMacro.delay=reader.readC();
|
||||
op.damMacro.delay=reader.readC();
|
||||
op.dvbMacro.delay=reader.readC();
|
||||
op.egtMacro.delay=reader.readC();
|
||||
op.kslMacro.delay=reader.readC();
|
||||
op.susMacro.delay=reader.readC();
|
||||
op.vibMacro.delay=reader.readC();
|
||||
op.wsMacro.delay=reader.readC();
|
||||
op.ksrMacro.delay=reader.readC();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -1098,3 +1402,148 @@ bool DivInstrument::save(const char* path) {
|
|||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivInstrument::saveDMP(const char* path) {
|
||||
SafeWriter* w=new SafeWriter();
|
||||
w->init();
|
||||
|
||||
// write version
|
||||
w->writeC(11);
|
||||
|
||||
// guess the system
|
||||
switch (type) {
|
||||
case DIV_INS_FM:
|
||||
// we can't tell between Genesis, Neo Geo and Arcade ins type yet
|
||||
w->writeC(0x02);
|
||||
w->writeC(1);
|
||||
break;
|
||||
case DIV_INS_STD:
|
||||
// we can't tell between SMS and NES ins type yet
|
||||
w->writeC(0x03);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_INS_GB:
|
||||
w->writeC(0x04);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_INS_C64:
|
||||
w->writeC(0x07);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_INS_PCE:
|
||||
w->writeC(0x06);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_INS_OPLL:
|
||||
// ???
|
||||
w->writeC(0x13);
|
||||
w->writeC(1);
|
||||
break;
|
||||
case DIV_INS_OPZ:
|
||||
// data will be lost
|
||||
w->writeC(0x08);
|
||||
w->writeC(1);
|
||||
break;
|
||||
case DIV_INS_FDS:
|
||||
// ???
|
||||
w->writeC(0x06);
|
||||
w->writeC(0);
|
||||
break;
|
||||
default:
|
||||
// not supported by .dmp
|
||||
w->finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type==DIV_INS_FM || type==DIV_INS_OPLL || type==DIV_INS_OPZ) {
|
||||
w->writeC(fm.fms);
|
||||
w->writeC(fm.fb);
|
||||
w->writeC(fm.alg);
|
||||
w->writeC(fm.ams);
|
||||
|
||||
// TODO: OPLL params
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=fm.op[i];
|
||||
w->writeC(op.mult);
|
||||
w->writeC(op.tl);
|
||||
w->writeC(op.ar);
|
||||
w->writeC(op.dr);
|
||||
w->writeC(op.sl);
|
||||
w->writeC(op.rr);
|
||||
w->writeC(op.am);
|
||||
w->writeC(op.rs);
|
||||
w->writeC(op.dt|(op.dt2<<4));
|
||||
w->writeC(op.d2r);
|
||||
w->writeC(op.ssgEnv);
|
||||
}
|
||||
} else {
|
||||
if (type!=DIV_INS_GB) {
|
||||
w->writeC(std.volMacro.len);
|
||||
for (int i=0; i<std.volMacro.len; i++) {
|
||||
w->writeI(std.volMacro.val[i]);
|
||||
}
|
||||
if (std.volMacro.len>0) w->writeC(std.volMacro.loop);
|
||||
}
|
||||
|
||||
w->writeC(std.arpMacro.len);
|
||||
for (int i=0; i<std.arpMacro.len; i++) {
|
||||
w->writeI(std.arpMacro.val[i]+12);
|
||||
}
|
||||
if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop);
|
||||
w->writeC(std.arpMacro.mode);
|
||||
|
||||
w->writeC(std.dutyMacro.len);
|
||||
for (int i=0; i<std.dutyMacro.len; i++) {
|
||||
w->writeI(std.dutyMacro.val[i]+12);
|
||||
}
|
||||
if (std.dutyMacro.len>0) w->writeC(std.dutyMacro.loop);
|
||||
|
||||
w->writeC(std.waveMacro.len);
|
||||
for (int i=0; i<std.waveMacro.len; i++) {
|
||||
w->writeI(std.waveMacro.val[i]+12);
|
||||
}
|
||||
if (std.waveMacro.len>0) w->writeC(std.waveMacro.loop);
|
||||
|
||||
if (type==DIV_INS_C64) {
|
||||
w->writeC(c64.triOn);
|
||||
w->writeC(c64.sawOn);
|
||||
w->writeC(c64.pulseOn);
|
||||
w->writeC(c64.noiseOn);
|
||||
w->writeC(c64.a);
|
||||
w->writeC(c64.d);
|
||||
w->writeC(c64.s);
|
||||
w->writeC(c64.r);
|
||||
w->writeC((c64.duty*100)/4095);
|
||||
w->writeC(c64.ringMod);
|
||||
w->writeC(c64.oscSync);
|
||||
w->writeC(c64.toFilter);
|
||||
w->writeC(c64.volIsCutoff);
|
||||
w->writeC(c64.initFilter);
|
||||
w->writeC(c64.res);
|
||||
w->writeC((c64.cut*100)/2047);
|
||||
w->writeC(c64.hp);
|
||||
w->writeC(c64.lp);
|
||||
w->writeC(c64.bp);
|
||||
w->writeC(c64.ch3off);
|
||||
}
|
||||
if (type==DIV_INS_GB) {
|
||||
w->writeC(gb.envVol);
|
||||
w->writeC(gb.envDir);
|
||||
w->writeC(gb.envLen);
|
||||
w->writeC(gb.soundLen);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* outFile=ps_fopen(path,"wb");
|
||||
if (outFile==NULL) {
|
||||
logE("could not save instrument: %s!",strerror(errno));
|
||||
w->finish();
|
||||
return false;
|
||||
}
|
||||
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
|
||||
logW("did not write entire instrument!");
|
||||
}
|
||||
fclose(outFile);
|
||||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,21 +165,20 @@ struct DivInstrumentMacro {
|
|||
int val[256];
|
||||
unsigned int mode;
|
||||
bool open;
|
||||
unsigned char len;
|
||||
signed char loop;
|
||||
signed char rel;
|
||||
unsigned char len, delay, speed, loop, rel;
|
||||
|
||||
// the following variables are used by the GUI and not saved in the file
|
||||
int vScroll, vZoom;
|
||||
|
||||
|
||||
explicit DivInstrumentMacro(const String& n, bool initOpen=false):
|
||||
name(n),
|
||||
mode(0),
|
||||
open(initOpen),
|
||||
len(0),
|
||||
loop(-1),
|
||||
rel(-1),
|
||||
delay(0),
|
||||
speed(1),
|
||||
loop(255),
|
||||
rel(255),
|
||||
vScroll(0),
|
||||
vZoom(-1) {
|
||||
memset(val,0,256*sizeof(int));
|
||||
|
|
@ -261,12 +260,32 @@ struct DivInstrumentSTD {
|
|||
};
|
||||
|
||||
struct DivInstrumentGB {
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
|
||||
bool softEnv, alwaysInit;
|
||||
enum HWSeqCommands: unsigned char {
|
||||
DIV_GB_HWCMD_ENVELOPE=0,
|
||||
DIV_GB_HWCMD_SWEEP,
|
||||
DIV_GB_HWCMD_WAIT,
|
||||
DIV_GB_HWCMD_WAIT_REL,
|
||||
DIV_GB_HWCMD_LOOP,
|
||||
DIV_GB_HWCMD_LOOP_REL,
|
||||
|
||||
DIV_GB_HWCMD_MAX
|
||||
};
|
||||
struct HWSeqCommand {
|
||||
unsigned char cmd;
|
||||
unsigned short data;
|
||||
} hwSeq[256];
|
||||
DivInstrumentGB():
|
||||
envVol(15),
|
||||
envDir(0),
|
||||
envLen(2),
|
||||
soundLen(64) {}
|
||||
soundLen(64),
|
||||
hwSeqLen(0),
|
||||
softEnv(false),
|
||||
alwaysInit(false) {
|
||||
memset(hwSeq,0,256*sizeof(int));
|
||||
}
|
||||
};
|
||||
|
||||
struct DivInstrumentC64 {
|
||||
|
|
@ -306,12 +325,18 @@ struct DivInstrumentC64 {
|
|||
};
|
||||
|
||||
struct DivInstrumentAmiga {
|
||||
struct SampleMap {
|
||||
int freq;
|
||||
short map;
|
||||
SampleMap(int f=0, short m=-1):
|
||||
freq(f),
|
||||
map(m) {}
|
||||
};
|
||||
short initSample;
|
||||
bool useNoteMap;
|
||||
bool useWave;
|
||||
unsigned char waveLen;
|
||||
int noteFreq[120];
|
||||
short noteMap[120];
|
||||
SampleMap noteMap[120];
|
||||
|
||||
/**
|
||||
* get the sample at specified note.
|
||||
|
|
@ -321,7 +346,7 @@ struct DivInstrumentAmiga {
|
|||
if (useNoteMap) {
|
||||
if (note<0) note=0;
|
||||
if (note>119) note=119;
|
||||
return noteMap[note];
|
||||
return noteMap[note].map;
|
||||
}
|
||||
return initSample;
|
||||
}
|
||||
|
|
@ -334,7 +359,7 @@ struct DivInstrumentAmiga {
|
|||
if (useNoteMap) {
|
||||
if (note<0) note=0;
|
||||
if (note>119) note=119;
|
||||
return noteFreq[note];
|
||||
return noteMap[note].freq;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -344,8 +369,9 @@ struct DivInstrumentAmiga {
|
|||
useNoteMap(false),
|
||||
useWave(false),
|
||||
waveLen(31) {
|
||||
memset(noteMap,-1,120*sizeof(short));
|
||||
memset(noteFreq,0,120*sizeof(int));
|
||||
for (SampleMap& elem: noteMap) {
|
||||
elem=SampleMap();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -430,6 +456,72 @@ struct DivInstrumentWaveSynth {
|
|||
param4(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentSoundUnit {
|
||||
bool useSample;
|
||||
bool switchRoles;
|
||||
DivInstrumentSoundUnit():
|
||||
useSample(false),
|
||||
switchRoles(false) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentES5506 {
|
||||
struct Filter {
|
||||
enum FilterMode: unsigned char { // filter mode for pole 4,3
|
||||
FILTER_MODE_HPK2_HPK2=0,
|
||||
FILTER_MODE_HPK2_LPK1,
|
||||
FILTER_MODE_LPK2_LPK2,
|
||||
FILTER_MODE_LPK2_LPK1,
|
||||
};
|
||||
FilterMode mode;
|
||||
unsigned short k1, k2;
|
||||
Filter():
|
||||
mode(FILTER_MODE_LPK2_LPK1),
|
||||
k1(0xffff),
|
||||
k2(0xffff) {}
|
||||
};
|
||||
struct Envelope {
|
||||
unsigned short ecount;
|
||||
signed char lVRamp, rVRamp;
|
||||
signed char k1Ramp, k2Ramp;
|
||||
bool k1Slow, k2Slow;
|
||||
Envelope():
|
||||
ecount(0),
|
||||
lVRamp(0),
|
||||
rVRamp(0),
|
||||
k1Ramp(0),
|
||||
k2Ramp(0),
|
||||
k1Slow(false),
|
||||
k2Slow(false) {}
|
||||
};
|
||||
Filter filter;
|
||||
Envelope envelope;
|
||||
DivInstrumentES5506():
|
||||
filter(Filter()),
|
||||
envelope(Envelope()) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentSNES {
|
||||
enum GainMode: unsigned char {
|
||||
GAIN_MODE_DIRECT=0,
|
||||
GAIN_MODE_DEC_LINEAR=4,
|
||||
GAIN_MODE_DEC_LOG=5,
|
||||
GAIN_MODE_INC_LINEAR=6,
|
||||
GAIN_MODE_INC_INVLOG=7
|
||||
};
|
||||
bool useEnv;
|
||||
GainMode gainMode;
|
||||
unsigned char gain;
|
||||
unsigned char a, d, s, r;
|
||||
DivInstrumentSNES():
|
||||
useEnv(true),
|
||||
gainMode(GAIN_MODE_DIRECT),
|
||||
gain(127),
|
||||
a(15),
|
||||
d(7),
|
||||
s(7),
|
||||
r(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
bool mode;
|
||||
|
|
@ -443,6 +535,9 @@ struct DivInstrument {
|
|||
DivInstrumentFDS fds;
|
||||
DivInstrumentMultiPCM multipcm;
|
||||
DivInstrumentWaveSynth ws;
|
||||
DivInstrumentSoundUnit su;
|
||||
DivInstrumentES5506 es5506;
|
||||
DivInstrumentSNES snes;
|
||||
|
||||
/**
|
||||
* save the instrument to a SafeWriter.
|
||||
|
|
@ -464,6 +559,13 @@ struct DivInstrument {
|
|||
* @return whether it was successful.
|
||||
*/
|
||||
bool save(const char* path);
|
||||
|
||||
/**
|
||||
* save this instrument to a file in .dmp format.
|
||||
* @param path file path.
|
||||
* @return whether it was successful.
|
||||
*/
|
||||
bool saveDMP(const char* path);
|
||||
DivInstrument():
|
||||
name(""),
|
||||
type(DIV_INS_FM) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,18 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
had=false;
|
||||
return;
|
||||
}
|
||||
if (delay>0) {
|
||||
delay--;
|
||||
return;
|
||||
}
|
||||
if (began && source.delay>0) {
|
||||
delay=source.delay;
|
||||
} else {
|
||||
delay=source.speed-1;
|
||||
}
|
||||
if (began) {
|
||||
began=false;
|
||||
}
|
||||
if (finished) {
|
||||
finished=false;
|
||||
}
|
||||
|
|
@ -41,16 +53,17 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
actualHad=has;
|
||||
had=actualHad;
|
||||
if (has) {
|
||||
lastPos=pos;
|
||||
val=source.val[pos++];
|
||||
if (source.rel>=0 && pos>source.rel && !released) {
|
||||
if (source.loop<source.len && source.loop>=0 && source.loop<source.rel) {
|
||||
if (pos>source.rel && !released) {
|
||||
if (source.loop<source.len && source.loop<source.rel) {
|
||||
pos=source.loop;
|
||||
} else {
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
if (pos>=source.len) {
|
||||
if (source.loop<source.len && source.loop>=0 && (source.loop>=source.rel || source.rel>=source.len)) {
|
||||
if (source.loop<source.len && (source.loop>=source.rel || source.rel>=source.len)) {
|
||||
pos=source.loop;
|
||||
} else if (linger) {
|
||||
pos--;
|
||||
|
|
@ -239,7 +252,7 @@ void DivMacroInt::init(DivInstrument* which) {
|
|||
for (size_t i=0; i<macroListLen; i++) {
|
||||
if (macroSource[i]!=NULL) {
|
||||
macroList[i]->prepare(*macroSource[i],e);
|
||||
hasRelease=(macroSource[i]->rel>=0 && macroSource[i]->rel<macroSource[i]->len);
|
||||
hasRelease=(macroSource[i]->rel<macroSource[i]->len);
|
||||
} else {
|
||||
hasRelease=false;
|
||||
}
|
||||
|
|
@ -251,3 +264,110 @@ void DivMacroInt::notifyInsDeletion(DivInstrument* which) {
|
|||
init(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// randomly-generated
|
||||
constexpr unsigned int hashTable[256]={
|
||||
0x0718657, 0xe904eb33, 0x14b2da2b, 0x0ef67ca9,
|
||||
0x0f0559a, 0x4142065a, 0x4d9ab4ba, 0x3cdd601a,
|
||||
0x6635aca, 0x2c41ab72, 0xf98e8d31, 0x1003ee63,
|
||||
0x3fd9fb5, 0x30734d16, 0xe8964431, 0x29bb9b79,
|
||||
0x817f580, 0xfe083b9e, 0x974b5e85, 0x3b5729c2,
|
||||
0x2afea96, 0xf1573b4b, 0x308a1024, 0xaa94b92d,
|
||||
0x693fa93, 0x547ba3da, 0xac4f206c, 0x93f72ea9,
|
||||
0xcc44001, 0x37e27670, 0xf35a63d0, 0xd1cdbb92,
|
||||
0x7c7ee24, 0xfa267ee9, 0xf9cd9956, 0x6a6768d4,
|
||||
0x9e6a108, 0xf6ca4bd0, 0xa53cba9f, 0x526a523a,
|
||||
0xf46f0c8, 0xf131bd4c, 0x82800d48, 0xabff9214,
|
||||
0x40eabd4, 0xea0ef8f7, 0xdc3968d6, 0x54c3cb63,
|
||||
0x8855023, 0xaab73861, 0xff0bea2c, 0x139b9765,
|
||||
0x4a21279, 0x6b2aa29a, 0xf147cc3f, 0xc42edc1a,
|
||||
0xfe2f86f, 0x6d352047, 0xd3cac3e4, 0x35e5c389,
|
||||
0xe923727, 0x12fe3b32, 0x204295c5, 0x254a8b7a,
|
||||
0xc1d995d, 0x26a512d2, 0xa3e34033, 0x9a968df0,
|
||||
0x53447ed, 0x36cf4077, 0x189b03a7, 0x558790e8,
|
||||
0x01f921a, 0x840f260c, 0x93dd2b86, 0x12f69cb0,
|
||||
0x117d93a, 0xcb2cbc2b, 0xd41e3aed, 0x5ff6ec75,
|
||||
0x607290d, 0xd41adb92, 0x64f94ba7, 0xaff720f7,
|
||||
0x6bf1d5d, 0xc8e36c6d, 0x7095bab5, 0xdfbf7b0d,
|
||||
0x01ddeea, 0xe8f262da, 0xf589512f, 0xc2ecac5d,
|
||||
0xbe29d98, 0xff8b5a2e, 0x18e7279e, 0x6ad24dcb,
|
||||
0x2b3b9b1, 0x6f5227d8, 0x076d7553, 0x6c5856e2,
|
||||
0x995f655, 0xe9fcf5a6, 0x83671b70, 0xaf3aed1e,
|
||||
0xac340f0, 0x5c7008b4, 0x14651282, 0x8bf855b9,
|
||||
0x4a933af, 0x829b87f1, 0x9a673070, 0xb19da64f,
|
||||
0x77d8f36, 0x584c9fdc, 0xa9e52c0d, 0x6da5e13d,
|
||||
0xae1051f, 0xe85e976f, 0xfeac2d9a, 0x19c46754,
|
||||
0x1cba6f3, 0xaf21bc31, 0x16b6a8d4, 0xe08b0fdb,
|
||||
0x97e6e54, 0x5da499ae, 0xab472e19, 0xc2491f2e,
|
||||
0xc08c563, 0xe91b131b, 0xc8e22451, 0x6995c8fe,
|
||||
0x7042718, 0x01043738, 0xc7d88b28, 0x2d9f330f,
|
||||
0x4b3aae5, 0xf1e705ba, 0xc5b8ee59, 0xa8ba4e8f,
|
||||
0x55f65a2, 0xa1899e41, 0x296243c8, 0x1e502bf2,
|
||||
0x20080de, 0x841d2239, 0x37b082af, 0xbdd7f7da,
|
||||
0x4075090, 0x1dc7dc49, 0x5cd3c69a, 0x7fb13b62,
|
||||
0xb382bf1, 0xa0cfbc2f, 0x9eca4dc1, 0xb9355453,
|
||||
0x5d0dd24, 0x834f4d8e, 0xe9b136b2, 0xe7b8738d,
|
||||
0x1c91d41, 0x8cb3ddb5, 0xdc600590, 0x607cff55,
|
||||
0x2ca7675, 0x4622a8e4, 0x9340e414, 0xcb44928a,
|
||||
0xa9e791c, 0x68849920, 0xc5b5fcd8, 0xbc352269,
|
||||
0x3ab13cf, 0xaa3cbbd0, 0x1abacc64, 0x623b5b49,
|
||||
0xcc8c4c3, 0x3c8f2f70, 0x3e584a28, 0x9316d24d,
|
||||
0xfe315a2, 0x10f0ba7a, 0xed15a523, 0x4f987369,
|
||||
0x7aa4a4a, 0x90eaf98f, 0xcf0af610, 0x1b38f4e7,
|
||||
0x19df72d, 0xd8306808, 0xd54e25ac, 0x76b79c6d,
|
||||
0x58110cf, 0x06a3e5f2, 0x873a6039, 0xf52684e3,
|
||||
0xecf39c3, 0x7cbb2759, 0xe280d361, 0x91e8471a,
|
||||
0xa67cdd3, 0x17cac3be, 0xfc9eff1f, 0x71abdf49,
|
||||
0x6168624, 0xb68f86f7, 0x67a8e72a, 0xe746911d,
|
||||
0xca48fd7, 0x8f3cc436, 0x3a3851a8, 0x30a7e26e,
|
||||
0xca49308, 0xb598ef74, 0x49ef167a, 0xa9e17632,
|
||||
0x0f7308a, 0xf156efed, 0xcf799645, 0xbae4b85a,
|
||||
0xecba3fe, 0xd97f861d, 0xc164af62, 0xb1aca42f,
|
||||
0xf249576, 0x83d1bf4e, 0x2f486a9c, 0xd3b53cc2,
|
||||
0x17d7c26, 0xd95ddae1, 0x76c1a2f5, 0xf8af6782,
|
||||
0xdbaece4, 0x010b2b53, 0x049be200, 0xd9fd0d1a,
|
||||
0x37d7e6c, 0x5b848651, 0x203c98c7, 0x669681b0,
|
||||
0x683086f, 0xdd0ee8ab, 0x5dbe008b, 0xe5d0690d,
|
||||
0x23dd758, 0x6b34acbc, 0x4b2b3e65, 0xcc7b56c1,
|
||||
0x196b0a0, 0x7b065105, 0xb731b01a, 0xd37daa16,
|
||||
0xf77816b, 0x3c9fa546, 0x81dfadb8, 0x39b1fb8b
|
||||
};
|
||||
|
||||
constexpr unsigned int NAME_HASH(const char* name) {
|
||||
unsigned int nameHash=0xffffffff;
|
||||
for (const char* i=name; *i; i++) {
|
||||
nameHash=(nameHash>>8)^hashTable[(unsigned char)*i];
|
||||
}
|
||||
return nameHash;
|
||||
}
|
||||
|
||||
#define CONSIDER(x) case NAME_HASH(#x): return &x; break;
|
||||
|
||||
DivMacroStruct* DivMacroInt::structByName(const String& name) {
|
||||
unsigned int hash=NAME_HASH(name.c_str());
|
||||
|
||||
switch (hash) {
|
||||
CONSIDER(vol)
|
||||
CONSIDER(arp)
|
||||
CONSIDER(duty)
|
||||
CONSIDER(wave)
|
||||
CONSIDER(pitch)
|
||||
CONSIDER(ex1)
|
||||
CONSIDER(ex2)
|
||||
CONSIDER(ex3)
|
||||
CONSIDER(alg)
|
||||
CONSIDER(fb)
|
||||
CONSIDER(fms)
|
||||
CONSIDER(ams)
|
||||
CONSIDER(panL)
|
||||
CONSIDER(panR)
|
||||
CONSIDER(phaseReset)
|
||||
CONSIDER(ex4)
|
||||
CONSIDER(ex5)
|
||||
CONSIDER(ex6)
|
||||
CONSIDER(ex7)
|
||||
CONSIDER(ex8)
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,21 +25,24 @@
|
|||
class DivEngine;
|
||||
|
||||
struct DivMacroStruct {
|
||||
int pos;
|
||||
int pos, lastPos, delay;
|
||||
int val;
|
||||
bool has, had, actualHad, finished, will, linger;
|
||||
bool has, had, actualHad, finished, will, linger, began;
|
||||
unsigned int mode;
|
||||
void doMacro(DivInstrumentMacro& source, bool released, bool tick);
|
||||
void init() {
|
||||
pos=mode=0;
|
||||
pos=lastPos=mode=delay=0;
|
||||
has=had=actualHad=will=false;
|
||||
linger=false;
|
||||
began=true;
|
||||
// TODO: test whether this breaks anything?
|
||||
val=0;
|
||||
}
|
||||
void prepare(DivInstrumentMacro& source, DivEngine* e);
|
||||
DivMacroStruct():
|
||||
pos(0),
|
||||
lastPos(0),
|
||||
delay(0),
|
||||
val(0),
|
||||
has(false),
|
||||
had(false),
|
||||
|
|
@ -47,6 +50,7 @@ struct DivMacroStruct {
|
|||
finished(false),
|
||||
will(false),
|
||||
linger(false),
|
||||
began(true),
|
||||
mode(0) {}
|
||||
};
|
||||
|
||||
|
|
@ -127,6 +131,13 @@ class DivMacroInt {
|
|||
*/
|
||||
void notifyInsDeletion(DivInstrument* which);
|
||||
|
||||
/**
|
||||
* get DivMacroStruct by macro name.
|
||||
* @param which the macro name.
|
||||
* @return a DivMacroStruct, or NULL if none found.
|
||||
*/
|
||||
DivMacroStruct* structByName(const String& name);
|
||||
|
||||
DivMacroInt():
|
||||
e(NULL),
|
||||
ins(NULL),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
static DivPattern emptyPat;
|
||||
|
||||
|
|
@ -40,6 +41,44 @@ DivPattern* DivChannelData::getPattern(int index, bool create) {
|
|||
return data[index];
|
||||
}
|
||||
|
||||
std::vector<std::pair<int,int>> DivChannelData::optimize() {
|
||||
std::vector<std::pair<int,int>> ret;
|
||||
for (int i=0; i<256; i++) {
|
||||
if (data[i]!=NULL) {
|
||||
// compare
|
||||
for (int j=0; j<256; j++) {
|
||||
if (j==i) continue;
|
||||
if (data[j]==NULL) continue;
|
||||
if (memcmp(data[i]->data,data[j]->data,256*32*sizeof(short))==0) {
|
||||
delete data[j];
|
||||
data[j]=NULL;
|
||||
logV("%d == %d",i,j);
|
||||
ret.push_back(std::pair<int,int>(j,i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int,int>> DivChannelData::rearrange() {
|
||||
std::vector<std::pair<int,int>> ret;
|
||||
for (int i=0; i<256; i++) {
|
||||
if (data[i]==NULL) {
|
||||
for (int j=i; j<256; j++) {
|
||||
if (data[j]!=NULL) {
|
||||
data[i]=data[j];
|
||||
data[j]=NULL;
|
||||
logV("%d -> %d",j,i);
|
||||
ret.push_back(std::pair<int,int>(j,i));
|
||||
if (++i>=256) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DivChannelData::wipePatterns() {
|
||||
for (int i=0; i<256; i++) {
|
||||
if (data[i]!=NULL) {
|
||||
|
|
@ -54,81 +93,6 @@ void DivPattern::copyOn(DivPattern* dest) {
|
|||
memcpy(dest->data,data,sizeof(data));
|
||||
}
|
||||
|
||||
SafeReader* DivPattern::compile(int len, int fxRows) {
|
||||
SafeWriter w;
|
||||
w.init();
|
||||
short lastNote, lastOctave, lastInstr, lastVolume, lastEffect[8], lastEffectVal[8];
|
||||
unsigned char rows=0;
|
||||
|
||||
lastNote=0;
|
||||
lastOctave=0;
|
||||
lastInstr=-1;
|
||||
lastVolume=-1;
|
||||
memset(lastEffect,-1,8*sizeof(short));
|
||||
memset(lastEffectVal,-1,8*sizeof(short));
|
||||
|
||||
for (int i=0; i<len; i++) {
|
||||
unsigned char mask=0;
|
||||
if (data[i][0]!=-1) {
|
||||
lastNote=data[i][0];
|
||||
lastOctave=data[i][1];
|
||||
mask|=128;
|
||||
}
|
||||
if (data[i][2]!=-1 && data[i][2]!=lastInstr) {
|
||||
lastInstr=data[i][2];
|
||||
mask|=32;
|
||||
}
|
||||
if (data[i][3]!=-1 && data[i][3]!=lastVolume) {
|
||||
lastVolume=data[i][3];
|
||||
mask|=64;
|
||||
}
|
||||
for (int j=0; j<fxRows; j++) {
|
||||
if (data[i][4+(j<<1)]!=-1) {
|
||||
lastEffect[j]=data[i][4+(j<<1)];
|
||||
lastEffectVal[j]=data[i][5+(j<<1)];
|
||||
mask=(mask&0xf8)|j;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mask) {
|
||||
rows++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rows!=0) {
|
||||
w.writeC(rows);
|
||||
}
|
||||
rows=1;
|
||||
|
||||
w.writeC(mask);
|
||||
if (mask&128) {
|
||||
if (lastNote==100) {
|
||||
w.writeC(-128);
|
||||
} else {
|
||||
w.writeC(lastNote+(lastOctave*12));
|
||||
}
|
||||
}
|
||||
if (mask&64) {
|
||||
w.writeC(lastVolume);
|
||||
}
|
||||
if (mask&32) {
|
||||
w.writeC(lastInstr);
|
||||
}
|
||||
for (int j=0; j<(mask&7); j++) {
|
||||
w.writeC(lastEffect[j]);
|
||||
if (lastEffectVal[j]==-1) {
|
||||
w.writeC(0);
|
||||
} else {
|
||||
w.writeC(lastEffectVal[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
w.writeC(rows);
|
||||
w.writeC(0);
|
||||
|
||||
return w.toReader();
|
||||
}
|
||||
|
||||
DivChannelData::DivChannelData():
|
||||
effectCols(1) {
|
||||
memset(data,0,256*sizeof(void*));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "safeReader.h"
|
||||
#include <vector>
|
||||
|
||||
struct DivPattern {
|
||||
String name;
|
||||
|
|
@ -28,14 +29,6 @@ struct DivPattern {
|
|||
* @param dest the destination pattern.
|
||||
*/
|
||||
void copyOn(DivPattern* dest);
|
||||
|
||||
/**
|
||||
* don't use yet!
|
||||
* @param len the pattern length
|
||||
* @param fxRows number of effect ...columns
|
||||
* @return a SafeReader.
|
||||
*/
|
||||
SafeReader* compile(int len=256, int fxRows=1);
|
||||
DivPattern();
|
||||
};
|
||||
|
||||
|
|
@ -59,6 +52,20 @@ struct DivChannelData {
|
|||
*/
|
||||
DivPattern* getPattern(int index, bool create);
|
||||
|
||||
/**
|
||||
* optimize pattern data.
|
||||
* not thread-safe! use a mutex!
|
||||
* @return a list of From -> To pairs
|
||||
*/
|
||||
std::vector<std::pair<int,int>> optimize();
|
||||
|
||||
/**
|
||||
* re-arrange NULLs.
|
||||
* not thread-safe! use a mutex!
|
||||
* @return a list of From -> To pairs
|
||||
*/
|
||||
std::vector<std::pair<int,int>> rearrange();
|
||||
|
||||
/**
|
||||
* destroy all patterns on this DivChannelData.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ bool DivDispatch::getDCOffRequired() {
|
|||
return false;
|
||||
}
|
||||
|
||||
const char* DivDispatch::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
bool DivDispatch::getWantPreNote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DivDispatch::setFlags(unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -64,21 +64,6 @@ const char** DivPlatformAmiga::getRegisterSheet() {
|
|||
return regCheatSheetAmiga;
|
||||
}
|
||||
|
||||
const char* DivPlatformAmiga::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Toggle filter (0 disables; 1 enables)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle AM with next channel";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Toggle period modulation with next channel";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define writeAudDat(x) \
|
||||
chan[i].audDat=x; \
|
||||
if (i<3 && chan[i].useV) { \
|
||||
|
|
@ -114,12 +99,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
if (chan[i].audPos<s->samples) {
|
||||
writeAudDat(s->data8[chan[i].audPos++]);
|
||||
}
|
||||
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].audPos>=MIN(131071,s->getEndPosition())) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else if (chan[i].audPos>=MIN(131071,s->samples)) {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
|
|
@ -180,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()) {
|
||||
|
|
@ -355,6 +328,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,38 +22,6 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "fmshared_OPM.h"
|
||||
|
||||
static unsigned short chanOffs[8]={
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
|
||||
};
|
||||
static unsigned short opOffs[4]={
|
||||
0x00, 0x08, 0x10, 0x18
|
||||
};
|
||||
static bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
|
||||
|
||||
const char* regCheatSheetOPM[]={
|
||||
"Test", "00",
|
||||
"NoteCtl", "08",
|
||||
|
|
@ -82,111 +50,6 @@ const char** DivPlatformArcade::getRegisterSheet() {
|
|||
return regCheatSheetOPM;
|
||||
}
|
||||
|
||||
const char* DivPlatformArcade::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set noise frequency (xx: value; 0 disables noise)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Set LFO speed";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)";
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x1e:
|
||||
return "1Exx: Set AM depth (0 to 7F)";
|
||||
break;
|
||||
case 0x1f:
|
||||
return "1Fxx: Set PM depth (0 to 7F)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int o[2];
|
||||
|
||||
|
|
@ -198,13 +61,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
OPM_Write(&fm,1,w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
} else {
|
||||
OPM_Write(&fm,0,w.addr);
|
||||
w.addrOrVal=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
|
|
@ -214,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
|
||||
}
|
||||
|
||||
|
||||
if (o[0]<-32768) o[0]=-32768;
|
||||
if (o[0]>32767) o[0]=32767;
|
||||
|
||||
if (o[1]<-32768) o[1]=-32768;
|
||||
if (o[1]>32767) o[1]=32767;
|
||||
|
||||
|
||||
bufL[h]=o[0];
|
||||
bufR[h]=o[1];
|
||||
}
|
||||
|
|
@ -239,11 +102,11 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
|||
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
|
||||
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
delay=1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fm_ymfm->generate(&out_ymfm);
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
@ -257,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
|||
os[1]=out_ymfm.data[1];
|
||||
if (os[1]<-32768) os[1]=-32768;
|
||||
if (os[1]>32767) os[1]=32767;
|
||||
|
||||
|
||||
bufL[h]=os[0];
|
||||
bufR[h]=os[1];
|
||||
}
|
||||
|
|
@ -298,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) {
|
||||
|
|
@ -401,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
chan[i].state.ams=chan[i].std.ams.val;
|
||||
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
||||
}
|
||||
if (chan[i].std.ex4.had && chan[i].active) {
|
||||
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||
chan[i].opMaskChanged=true;
|
||||
}
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
|
|
@ -493,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
immWrite(i+0x30,chan[i].freq<<2);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x08,0x78|i);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x08,(chan[i].opMask<<3)|i);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -516,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
chan[c.chan].opMask=
|
||||
(chan[c.chan].state.op[0].enable?1:0)|
|
||||
(chan[c.chan].state.op[2].enable?2:0)|
|
||||
(chan[c.chan].state.op[1].enable?4:0)|
|
||||
(chan[c.chan].state.op[3].enable?8:0);
|
||||
}
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -648,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
if(c.value==0) {
|
||||
rWrite(0x01,0x02);
|
||||
}
|
||||
else {
|
||||
rWrite(0x01,0x00);
|
||||
}
|
||||
rWrite(0x18,c.value);
|
||||
break;
|
||||
}
|
||||
|
|
@ -859,6 +729,7 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
return 127;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -938,7 +809,7 @@ void DivPlatformArcade::poke(std::vector<DivRegWrite>& wlist) {
|
|||
}
|
||||
|
||||
void DivPlatformArcade::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
while (!writes.empty()) writes.pop_front();
|
||||
memset(regPool,0,256);
|
||||
if (useYMFM) {
|
||||
fm_ymfm->reset();
|
||||
|
|
@ -970,6 +841,8 @@ void DivPlatformArcade::reset() {
|
|||
pmDepth=0x7f;
|
||||
|
||||
//rWrite(0x18,0x10);
|
||||
immWrite(0x01,0x02); // LFO Off
|
||||
immWrite(0x18,0x00); // LFO Freq Off
|
||||
immWrite(0x19,amDepth);
|
||||
immWrite(0x19,0x80|pmDepth);
|
||||
//rWrite(0x1b,0x00);
|
||||
|
|
@ -978,15 +851,20 @@ void DivPlatformArcade::reset() {
|
|||
}
|
||||
|
||||
void DivPlatformArcade::setFlags(unsigned int flags) {
|
||||
if (flags==2) {
|
||||
chipClock=4000000.0;
|
||||
baseFreqOff=-122;
|
||||
} else if (flags==1) {
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
baseFreqOff=12;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
baseFreqOff=0;
|
||||
switch (flags&0xff) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=COLOR_NTSC;
|
||||
baseFreqOff=0;
|
||||
break;
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
baseFreqOff=12;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=4000000.0;
|
||||
baseFreqOff=-122;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/64;
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
|
|||
|
|
@ -19,19 +19,23 @@
|
|||
|
||||
#ifndef _ARCADE_H
|
||||
#define _ARCADE_H
|
||||
#include "../dispatch.h"
|
||||
#include "fmshared_OPM.h"
|
||||
#include "../macroInt.h"
|
||||
#include "../instrument.h"
|
||||
#include <queue>
|
||||
#include "../../../extern/opm/opm.h"
|
||||
#include "sound/ymfm/ymfm_opm.h"
|
||||
#include "../macroInt.h"
|
||||
|
||||
class DivArcadeInterface: public ymfm::ymfm_interface {
|
||||
|
||||
};
|
||||
|
||||
class DivPlatformArcade: public DivDispatch {
|
||||
class DivPlatformArcade: public DivPlatformOPM {
|
||||
protected:
|
||||
const unsigned short chanOffs[8]={
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
|
|
@ -39,9 +43,9 @@ class DivPlatformArcade: public DivDispatch {
|
|||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged;
|
||||
int vol, outVol;
|
||||
unsigned char chVolL, chVolR;
|
||||
unsigned char chVolL, chVolR, opMask;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
|
|
@ -64,38 +68,27 @@ class DivPlatformArcade: public DivDispatch {
|
|||
portaPause(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
chVolL(127),
|
||||
chVolR(127) {}
|
||||
chVolR(127),
|
||||
opMask(15) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
opm_t fm;
|
||||
int delay, baseFreqOff;
|
||||
int baseFreqOff;
|
||||
int pcmL, pcmR, pcmCycles;
|
||||
unsigned char lastBusy;
|
||||
unsigned char amDepth, pmDepth;
|
||||
|
||||
ymfm::ym2151* fm_ymfm;
|
||||
ymfm::ym2151::output_data out_ymfm;
|
||||
DivArcadeInterface iface;
|
||||
|
||||
unsigned char regPool[256];
|
||||
|
||||
bool extMode, useYMFM;
|
||||
|
||||
bool isMuted[8];
|
||||
|
||||
short oldWrites[256];
|
||||
short pendingWrites[256];
|
||||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
|
|
@ -124,7 +117,6 @@ class DivPlatformArcade: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformArcade();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
|
||||
|
||||
#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8)
|
||||
#define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8))
|
||||
|
||||
const char* regCheatSheetAY[]={
|
||||
"FreqL_A", "0",
|
||||
|
|
@ -69,46 +69,18 @@ const char* regCheatSheetAY8914[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
// taken from ay8910.cpp
|
||||
const int sunsoftVolTable[32]={
|
||||
103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451,
|
||||
18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796,
|
||||
5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737,
|
||||
1397, 1123, 925, 762, 578, 438, 332, 251
|
||||
};
|
||||
|
||||
const char** DivPlatformAY8910::getRegisterSheet() {
|
||||
return intellivision?regCheatSheetAY8914:regCheatSheetAY;
|
||||
}
|
||||
|
||||
const char* DivPlatformAY8910::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Set noise frequency (0 to 1F)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xy: Set envelope mode (x: shape, y: enable for this channel)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period low byte";
|
||||
break;
|
||||
case 0x24:
|
||||
return "24xx: Set envelope period high byte";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
case 0x2e:
|
||||
return "2Exx: Write to I/O port A";
|
||||
break;
|
||||
case 0x2f:
|
||||
return "2Fxx: Write to I/O port B";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (ayBufLen<len) {
|
||||
ayBufLen=len;
|
||||
|
|
@ -129,27 +101,33 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
regPool[w.addr&0x0f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
ay->sound_stream_update(ayBuf,len);
|
||||
if (sunsoft) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i];
|
||||
ay->sound_stream_update(ayBuf,1);
|
||||
bufL[i+start]=ayBuf[0][0];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
} else if (stereo) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i];
|
||||
bufR[i+start]=ayBuf[1][i]+ayBuf[2][i];
|
||||
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3;
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
ay->sound_stream_update(ayBuf,len);
|
||||
if (stereo) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i];
|
||||
bufR[i+start]=ayBuf[1][i]+ayBuf[2][i];
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,18 +173,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);
|
||||
|
|
@ -471,9 +440,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
return 15;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
// TODO: FIX wtr_envelope.dmf
|
||||
// the brokenPortaArp update broke it
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -489,9 +461,9 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
if (isMuted[ch]) {
|
||||
rWrite(0x08+ch,0);
|
||||
} else if (intellivision && (chan[ch].psgMode&4)) {
|
||||
} else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
|
||||
} else {
|
||||
} else if (chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
|
||||
}
|
||||
}
|
||||
|
|
@ -565,8 +537,6 @@ void DivPlatformAY8910::reset() {
|
|||
|
||||
delay=0;
|
||||
|
||||
extMode=false;
|
||||
|
||||
ioPortA=false;
|
||||
ioPortB=false;
|
||||
portAVal=0;
|
||||
|
|
@ -595,50 +565,69 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||
clockSel=(flags>>7)&1;
|
||||
switch (flags&15) {
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=1750000;
|
||||
break;
|
||||
case 3:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 4:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
case 5:
|
||||
chipClock=1000000;
|
||||
break;
|
||||
case 6:
|
||||
chipClock=COLOR_NTSC/4.0;
|
||||
break;
|
||||
case 7:
|
||||
chipClock=COLOR_PAL*3.0/8.0;
|
||||
break;
|
||||
case 8:
|
||||
chipClock=COLOR_PAL*3.0/16.0;
|
||||
break;
|
||||
case 9:
|
||||
chipClock=COLOR_PAL/4.0;
|
||||
break;
|
||||
case 10:
|
||||
chipClock=2097152;
|
||||
break;
|
||||
case 11:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 12:
|
||||
chipClock=3600000;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) {
|
||||
if (extMode) {
|
||||
extClock=eclk;
|
||||
extDiv=ediv;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||
if (extMode) {
|
||||
chipClock=extClock;
|
||||
rate=chipClock/extDiv;
|
||||
} else {
|
||||
clockSel=(flags>>7)&1;
|
||||
switch (flags&15) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=1750000;
|
||||
break;
|
||||
case 3:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 4:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
case 5:
|
||||
chipClock=1000000;
|
||||
break;
|
||||
case 6:
|
||||
chipClock=COLOR_NTSC/4.0;
|
||||
break;
|
||||
case 7:
|
||||
chipClock=COLOR_PAL*3.0/8.0;
|
||||
break;
|
||||
case 8:
|
||||
chipClock=COLOR_PAL*3.0/16.0;
|
||||
break;
|
||||
case 9:
|
||||
chipClock=COLOR_PAL/4.0;
|
||||
break;
|
||||
case 10:
|
||||
chipClock=2097152;
|
||||
break;
|
||||
case 11:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 12:
|
||||
chipClock=3600000;
|
||||
break;
|
||||
case 13:
|
||||
chipClock=20000000/16;
|
||||
break;
|
||||
case 14:
|
||||
chipClock=1536000;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
int delay;
|
||||
|
||||
bool extMode;
|
||||
unsigned int extClock;
|
||||
unsigned char extDiv;
|
||||
|
||||
bool stereo, sunsoft, intellivision, clockSel;
|
||||
bool ioPortA, ioPortB;
|
||||
unsigned char portAVal, portBVal;
|
||||
|
|
@ -88,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
|
|
@ -108,8 +112,12 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
|
||||
DivDispatch(),
|
||||
extMode(useExtMode),
|
||||
extClock(eclk),
|
||||
extDiv(ediv) {}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -77,54 +77,6 @@ const char** DivPlatformAY8930::getRegisterSheet() {
|
|||
return regCheatSheetAY8930;
|
||||
}
|
||||
|
||||
const char* DivPlatformAY8930::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle (0 to 8)";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Set noise frequency (0 to 1F)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xy: Set envelope mode (x: shape, y: enable for this channel)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period low byte";
|
||||
break;
|
||||
case 0x24:
|
||||
return "24xx: Set envelope period high byte";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x27:
|
||||
return "27xx: Set noise AND mask";
|
||||
break;
|
||||
case 0x28:
|
||||
return "28xx: Set noise OR mask";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
case 0x2d:
|
||||
return "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER";
|
||||
break;
|
||||
case 0x2e:
|
||||
return "2Exx: Write to I/O port A";
|
||||
break;
|
||||
case 0x2f:
|
||||
return "2Fxx: Write to I/O port B";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (ayBufLen<len) {
|
||||
ayBufLen=len;
|
||||
|
|
@ -215,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);
|
||||
|
|
@ -506,6 +449,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -521,7 +465,7 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
if (isMuted[ch]) {
|
||||
rWrite(0x08+ch,0);
|
||||
} else {
|
||||
} else if (chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,15 +39,6 @@ const char** DivPlatformBubSysWSG::getRegisterSheet() {
|
|||
return regCheatSheetBubSysWSG;
|
||||
}
|
||||
|
||||
const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int chanOut=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
|
|
@ -101,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()) {
|
||||
|
|
@ -250,6 +232,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformBubSysWSG();
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
#include "c64.h"
|
||||
#include "../engine.h"
|
||||
#include "sound/c64_fp/siddefs-fp.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {if (isFP) {sid_fp.write(a,v);} else {sid.write(a,v);}; regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_FREQBASE 524288
|
||||
|
||||
|
|
@ -62,61 +63,26 @@ const char** DivPlatformC64::getRegisterSheet() {
|
|||
return regCheatSheetSID;
|
||||
}
|
||||
|
||||
const char* DivPlatformC64::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set coarse cutoff (not recommended; use 4xxx instead)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set coarse pulse width (not recommended; use 3xxx instead)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set resonance (0 to F)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set filter mode (bit 0: low pass; bit 1: band pass; bit 2: high pass)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set envelope reset time";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Disable envelope reset for this channel (1 disables; 0 enables)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxy: Reset cutoff (x: on new note; y: now)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxy: Reset pulse width (x: on new note; y: now)";
|
||||
break;
|
||||
case 0x1e:
|
||||
return "1Exy: Change additional parameters";
|
||||
break;
|
||||
case 0x30: case 0x31: case 0x32: case 0x33:
|
||||
case 0x34: case 0x35: case 0x36: case 0x37:
|
||||
case 0x38: case 0x39: case 0x3a: case 0x3b:
|
||||
case 0x3c: case 0x3d: case 0x3e: case 0x3f:
|
||||
return "3xxx: Set pulse width (0 to FFF)";
|
||||
break;
|
||||
case 0x40: case 0x41: case 0x42: case 0x43:
|
||||
case 0x44: case 0x45: case 0x46: case 0x47:
|
||||
return "4xxx: Set cutoff (0 to 7FF)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int dcOff=isFP?0:sid.get_dc(0);
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
sid.clock();
|
||||
bufL[i]=sid.output();
|
||||
if (++writeOscBuf>=8) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=sid.last_chan_out[0]>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=sid.last_chan_out[1]>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=sid.last_chan_out[2]>>5;
|
||||
if (isFP) {
|
||||
sid_fp.clock(4,&bufL[i]);
|
||||
if (++writeOscBuf>=4) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5;
|
||||
}
|
||||
} else {
|
||||
sid.clock();
|
||||
bufL[i]=sid.output();
|
||||
if (++writeOscBuf>=16) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,18 +115,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);
|
||||
|
|
@ -368,6 +325,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -458,7 +416,11 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformC64::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
sid.set_is_muted(ch,mute);
|
||||
if (isFP) {
|
||||
sid_fp.mute(ch,mute);
|
||||
} else {
|
||||
sid.set_is_muted(ch,mute);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC64::forceIns() {
|
||||
|
|
@ -511,13 +473,25 @@ bool DivPlatformC64::getDCOffRequired() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformC64::getWantPreNote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
float DivPlatformC64::getPostAmp() {
|
||||
return isFP?3.0f:1.0f;
|
||||
}
|
||||
|
||||
void DivPlatformC64::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformC64::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
|
||||
sid.reset();
|
||||
if (isFP) {
|
||||
sid_fp.reset();
|
||||
} else {
|
||||
sid.reset();
|
||||
}
|
||||
memset(regPool,0,32);
|
||||
|
||||
rWrite(0x18,0x0f);
|
||||
|
|
@ -539,12 +513,24 @@ void DivPlatformC64::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformC64::setChipModel(bool is6581) {
|
||||
if (is6581) {
|
||||
sid.set_chip_model(MOS6581);
|
||||
if (isFP) {
|
||||
sid_fp.setChipModel(reSIDfp::MOS6581);
|
||||
} else {
|
||||
sid.set_chip_model(MOS6581);
|
||||
}
|
||||
} else {
|
||||
sid.set_chip_model(MOS8580);
|
||||
if (isFP) {
|
||||
sid_fp.setChipModel(reSIDfp::MOS8580);
|
||||
} else {
|
||||
sid.set_chip_model(MOS8580);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC64::setFP(bool fp) {
|
||||
isFP=fp;
|
||||
}
|
||||
|
||||
void DivPlatformC64::setFlags(unsigned int flags) {
|
||||
switch (flags&0xf) {
|
||||
case 0x0: // NTSC C64
|
||||
|
|
@ -562,6 +548,10 @@ void DivPlatformC64::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate/16;
|
||||
}
|
||||
if (isFP) {
|
||||
rate/=4;
|
||||
sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/c64/sid.h"
|
||||
#include "sound/c64_fp/SID.h"
|
||||
|
||||
class DivPlatformC64: public DivDispatch {
|
||||
struct Channel {
|
||||
|
|
@ -76,12 +77,17 @@ class DivPlatformC64: public DivDispatch {
|
|||
unsigned char filtControl, filtRes, vol;
|
||||
unsigned char writeOscBuf;
|
||||
int filtCut, resetTime;
|
||||
bool isFP;
|
||||
|
||||
SID sid;
|
||||
reSIDfp::SID sid_fp;
|
||||
unsigned char regPool[32];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_classic(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_fp(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
||||
void updateFilter();
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
|
@ -97,14 +103,16 @@ class DivPlatformC64: public DivDispatch {
|
|||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
bool getWantPreNote();
|
||||
float getPostAmp();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void setChipModel(bool is6581);
|
||||
void setFP(bool fp);
|
||||
void quit();
|
||||
~DivPlatformC64();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,30 +55,6 @@ const char** DivPlatformFDS::getRegisterSheet() {
|
|||
return regCheatSheetFDS;
|
||||
}
|
||||
|
||||
const char* DivPlatformFDS::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set modulation depth";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xy: Set modulation speed high byte (x: enable; y: value)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set modulation speed low byte";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set modulator position";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set modulator table to waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
extcl_apu_tick_FDS(fds);
|
||||
|
|
@ -145,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) {
|
||||
|
|
@ -406,6 +373,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ class DivPlatformFDS: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformFDS();
|
||||
|
|
|
|||
|
|
@ -17,13 +17,32 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define ADDR_MULT_DT 0x40
|
||||
#define ADDR_TL 0x60
|
||||
#define ADDR_RS_AR 0x80
|
||||
#define ADDR_AM_DR 0xa0
|
||||
#define ADDR_DT2_D2R 0xc0
|
||||
#define ADDR_SL_RR 0xe0
|
||||
#define ADDR_NOTE 0x28
|
||||
#define ADDR_KF 0x30
|
||||
#define ADDR_FMS_AMS 0x38
|
||||
#define ADDR_LR_FB_ALG 0x20
|
||||
#ifndef _FMSHARED_OPM_H
|
||||
#define _FMSHARED_OPM_H
|
||||
|
||||
#include "fmsharedbase.h"
|
||||
|
||||
#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
|
||||
|
||||
class DivPlatformOPM: public DivPlatformFMBase {
|
||||
protected:
|
||||
const unsigned short ADDR_MULT_DT=0x40;
|
||||
const unsigned short ADDR_TL=0x60;
|
||||
const unsigned short ADDR_RS_AR=0x80;
|
||||
const unsigned short ADDR_AM_DR=0xa0;
|
||||
const unsigned short ADDR_DT2_D2R=0xc0;
|
||||
const unsigned short ADDR_SL_RR=0xe0;
|
||||
const unsigned short ADDR_NOTE=0x28;
|
||||
const unsigned short ADDR_KF=0x30;
|
||||
const unsigned short ADDR_FMS_AMS=0x38;
|
||||
const unsigned short ADDR_LR_FB_ALG=0x20;
|
||||
|
||||
const unsigned short opOffs[4]={
|
||||
0x00, 0x08, 0x10, 0x18
|
||||
};
|
||||
|
||||
DivPlatformOPM():
|
||||
DivPlatformFMBase() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -20,17 +20,7 @@
|
|||
#ifndef _FMSHARED_OPN_H
|
||||
#define _FMSHARED_OPN_H
|
||||
|
||||
#define ADDR_MULT_DT 0x30
|
||||
#define ADDR_TL 0x40
|
||||
#define ADDR_RS_AR 0x50
|
||||
#define ADDR_AM_DR 0x60
|
||||
#define ADDR_DT2_D2R 0x70
|
||||
#define ADDR_SL_RR 0x80
|
||||
#define ADDR_SSG 0x90
|
||||
#define ADDR_FREQ 0xa0
|
||||
#define ADDR_FREQH 0xa4
|
||||
#define ADDR_FB_ALG 0xb0
|
||||
#define ADDR_LRAF 0xb4
|
||||
#include "fmsharedbase.h"
|
||||
|
||||
#define PLEASE_HELP_ME(_targetChan) \
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \
|
||||
|
|
@ -93,4 +83,36 @@
|
|||
return 2; \
|
||||
}
|
||||
|
||||
#endif
|
||||
class DivPlatformOPN: public DivPlatformFMBase {
|
||||
protected:
|
||||
const unsigned short ADDR_MULT_DT=0x30;
|
||||
const unsigned short ADDR_TL=0x40;
|
||||
const unsigned short ADDR_RS_AR=0x50;
|
||||
const unsigned short ADDR_AM_DR=0x60;
|
||||
const unsigned short ADDR_DT2_D2R=0x70;
|
||||
const unsigned short ADDR_SL_RR=0x80;
|
||||
const unsigned short ADDR_SSG=0x90;
|
||||
const unsigned short ADDR_FREQ=0xa0;
|
||||
const unsigned short ADDR_FREQH=0xa4;
|
||||
const unsigned short ADDR_FB_ALG=0xb0;
|
||||
const unsigned short ADDR_LRAF=0xb4;
|
||||
|
||||
const unsigned short opOffs[4]={
|
||||
0x00, 0x04, 0x08, 0x0c
|
||||
};
|
||||
|
||||
double fmFreqBase;
|
||||
unsigned int fmDivBase;
|
||||
unsigned int ayDiv;
|
||||
bool extSys;
|
||||
|
||||
DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false):
|
||||
DivPlatformFMBase(),
|
||||
fmFreqBase(f),
|
||||
fmDivBase(d),
|
||||
ayDiv(a),
|
||||
extSys(isExtSys) {}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
96
src/engine/platform/fmsharedbase.h
Normal file
96
src/engine/platform/fmsharedbase.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* 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 _FMSHARED_BASE_H
|
||||
#define _FMSHARED_BASE_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <deque>
|
||||
|
||||
class DivPlatformFMBase: public DivDispatch {
|
||||
protected:
|
||||
const bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
const unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
const int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::deque<QueuedWrite> writes;
|
||||
|
||||
unsigned char lastBusy;
|
||||
int delay;
|
||||
|
||||
unsigned char regPool[512];
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
inline void rWrite(unsigned short a, short v) {
|
||||
if (!skipRegisterWrites) {
|
||||
pendingWrites[a]=v;
|
||||
}
|
||||
}
|
||||
inline void immWrite(unsigned short a, unsigned char v) {
|
||||
if (!skipRegisterWrites) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
if (dumpWrites) {
|
||||
addWrite(a,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void urgentWrite(unsigned short a, unsigned char v) {
|
||||
if (!skipRegisterWrites) {
|
||||
if (writes.empty()) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
} else if (writes.size()>16 || writes.front().addrOrVal) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
} else {
|
||||
writes.push_front(QueuedWrite(a,v));
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(a,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformFMBase():
|
||||
DivDispatch(),
|
||||
lastBusy(0),
|
||||
delay(0) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
|
|
@ -61,29 +61,14 @@ const char** DivPlatformGB::getRegisterSheet() {
|
|||
return regCheatSheetGB;
|
||||
}
|
||||
|
||||
const char* DivPlatformGB::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set noise length (0: long; 1: short)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle (0 to 3)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xy: Setup sweep (x: time; y: shift)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set sweep direction (0: up; 1: down)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite& w=writes.front();
|
||||
GB_apu_write(gb,w.addr,w.val);
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
GB_advance_cycles(gb,16);
|
||||
bufL[i]=gb->apu_output.final_sample.left;
|
||||
bufR[i]=gb->apu_output.final_sample.right;
|
||||
|
|
@ -97,10 +82,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
void DivPlatformGB::updateWave() {
|
||||
rWrite(0x1a,0);
|
||||
for (int i=0; i<16; i++) {
|
||||
int nibble1=15-ws.output[i<<1];
|
||||
int nibble2=15-ws.output[1+(i<<1)];
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
antiClickWavePos&=31;
|
||||
}
|
||||
|
||||
static unsigned char chanMuteMask[4]={
|
||||
|
|
@ -151,39 +137,47 @@ static unsigned char noiseTable[256]={
|
|||
};
|
||||
|
||||
void DivPlatformGB::tick(bool sysTick) {
|
||||
if (antiClickEnabled && sysTick && chan[2].freq>0) {
|
||||
antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f));
|
||||
antiClickWavePos+=antiClickPeriodCount/chan[2].freq;
|
||||
antiClickPeriodCount%=chan[2].freq;
|
||||
}
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].softEnv) {
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
|
||||
if (i==2) {
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
|
||||
chan[i].soundLen=64;
|
||||
} else {
|
||||
chan[i].envLen=0;
|
||||
chan[i].envDir=1;
|
||||
chan[i].envVol=chan[i].outVol;
|
||||
chan[i].soundLen=64;
|
||||
|
||||
if (!chan[i].keyOn) chan[i].killIt=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i!=2) {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
} else {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
} else if (!chan[i].softEnv) {
|
||||
if (parent->song.waveDutyIsVol) {
|
||||
rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]);
|
||||
}
|
||||
|
|
@ -213,6 +207,10 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
if (i==2) {
|
||||
antiClickWavePos=0;
|
||||
antiClickPeriodCount=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i==2) {
|
||||
|
|
@ -223,14 +221,64 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// run hardware sequence
|
||||
if (chan[i].active) {
|
||||
if (--chan[i].hwSeqDelay<=0) {
|
||||
chan[i].hwSeqDelay=0;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
int hwSeqCount=0;
|
||||
while (chan[i].hwSeqPos<ins->gb.hwSeqLen && hwSeqCount<4) {
|
||||
bool leave=false;
|
||||
unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data;
|
||||
switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) {
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE:
|
||||
if (!chan[i].softEnv) {
|
||||
chan[i].envLen=data&7;
|
||||
chan[i].envDir=(data&8)?1:0;
|
||||
chan[i].envVol=(data>>4)&15;
|
||||
chan[i].soundLen=data>>8;
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_SWEEP:
|
||||
chan[i].sweep=data;
|
||||
chan[i].sweepChanged=true;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT:
|
||||
chan[i].hwSeqDelay=data+1;
|
||||
leave=true;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL:
|
||||
if (!chan[i].released) {
|
||||
chan[i].hwSeqPos--;
|
||||
leave=true;
|
||||
}
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP:
|
||||
chan[i].hwSeqPos=data-1;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL:
|
||||
if (!chan[i].released) {
|
||||
chan[i].hwSeqPos=data-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
chan[i].hwSeqPos++;
|
||||
if (leave) break;
|
||||
hwSeqCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].sweepChanged) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
if (ntPos<0) ntPos=0;
|
||||
|
|
@ -244,10 +292,11 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].keyOn) {
|
||||
if (i==2) { // wave
|
||||
rWrite(16+i*5,0x80);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].vol]);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
|
||||
} else {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3));
|
||||
chan[i].lastKill=chan[i].envVol;
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
|
|
@ -259,15 +308,35 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
if (i==3) { // noise
|
||||
rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0));
|
||||
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6));
|
||||
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6));
|
||||
} else {
|
||||
rWrite(16+i*5+3,(2048-chan[i].freq)&0xff);
|
||||
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6));
|
||||
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].killIt) {
|
||||
if (i!=2) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
int killDelta=chan[i].lastKill-chan[i].outVol+1;
|
||||
if (killDelta<0) killDelta+=16;
|
||||
chan[i].lastKill=chan[i].outVol;
|
||||
|
||||
if (killDelta!=1) {
|
||||
rWrite(16+i*5+2,((chan[i].envVol<<4))|8);
|
||||
for (int j=0; j<killDelta; j++) {
|
||||
rWrite(16+i*5+2,0x09);
|
||||
rWrite(16+i*5+2,0x11);
|
||||
rWrite(16+i*5+2,0x08);
|
||||
}
|
||||
}
|
||||
}
|
||||
chan[i].killIt=false;
|
||||
}
|
||||
|
||||
chan[i].soManyHacksToMakeItDefleCompatible=false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,6 +360,10 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].hwSeqPos=0;
|
||||
chan[c.chan].hwSeqDelay=0;
|
||||
chan[c.chan].released=false;
|
||||
chan[c.chan].softEnv=ins->gb.softEnv;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (c.chan==2) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
|
|
@ -299,17 +372,35 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
ws.init(ins,32,15,chan[c.chan].insChanged);
|
||||
}
|
||||
if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) {
|
||||
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
}
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
chan[c.chan].outVol=chan[c.chan].envVol;
|
||||
}
|
||||
}
|
||||
if (c.chan==2 && chan[c.chan].softEnv) {
|
||||
chan[c.chan].soundLen=64;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].hwSeqPos=0;
|
||||
chan[c.chan].hwSeqDelay=0;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
chan[c.chan].released=true;
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
|
|
@ -317,17 +408,33 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=true;
|
||||
if (c.chan!=2) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
chan[c.chan].vol=ins->gb.envVol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
if (!ins->gb.softEnv) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
chan[c.chan].vol=c.value;
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (c.chan==2) {
|
||||
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]);
|
||||
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]);
|
||||
}
|
||||
if (!chan[c.chan].softEnv) {
|
||||
chan[c.chan].envVol=chan[c.chan].vol;
|
||||
chan[c.chan].soManyHacksToMakeItDefleCompatible=true;
|
||||
} else if (c.chan!=2) {
|
||||
chan[c.chan].envVol=chan[c.chan].vol;
|
||||
if (!chan[c.chan].keyOn) chan[c.chan].killIt=true;
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
|
@ -393,6 +500,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
|
|
@ -461,7 +569,7 @@ void DivPlatformGB::reset() {
|
|||
}
|
||||
memset(gb,0,sizeof(GB_gameboy_t));
|
||||
memset(regPool,0,128);
|
||||
gb->model=GB_MODEL_DMG_B;
|
||||
gb->model=model;
|
||||
GB_apu_init(gb);
|
||||
GB_set_sample_rate(gb,rate);
|
||||
// enable all channels
|
||||
|
|
@ -470,12 +578,23 @@ void DivPlatformGB::reset() {
|
|||
lastPan=0xff;
|
||||
immWrite(0x25,procMute());
|
||||
immWrite(0x24,0x77);
|
||||
|
||||
antiClickPeriodCount=0;
|
||||
antiClickWavePos=0;
|
||||
}
|
||||
|
||||
int DivPlatformGB::getPortaFloor(int ch) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::getDCOffRequired() {
|
||||
return (model==GB_MODEL_AGB);
|
||||
}
|
||||
|
||||
void DivPlatformGB::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
|
|
@ -488,7 +607,7 @@ void DivPlatformGB::notifyWaveChange(int wave) {
|
|||
if (chan[2].wave==wave) {
|
||||
ws.changeWave1(wave);
|
||||
updateWave();
|
||||
if (!chan[2].keyOff) chan[2].keyOn=true;
|
||||
if (!chan[2].keyOff && chan[2].active) chan[2].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -506,6 +625,24 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformGB::setFlags(unsigned int flags) {
|
||||
antiClickEnabled=!(flags&8);
|
||||
switch (flags&3) {
|
||||
case 0:
|
||||
model=GB_MODEL_DMG_B;
|
||||
break;
|
||||
case 1:
|
||||
model=GB_MODEL_CGB_C;
|
||||
break;
|
||||
case 2:
|
||||
model=GB_MODEL_CGB_E;
|
||||
break;
|
||||
case 3:
|
||||
model=GB_MODEL_AGB;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
chipClock=4194304;
|
||||
rate=chipClock/16;
|
||||
|
|
@ -517,7 +654,9 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
|
|||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
model=GB_MODEL_DMG_B;
|
||||
gb=new GB_gameboy_t;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,18 @@
|
|||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include "sound/gb/gb.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformGB: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt;
|
||||
bool soManyHacksToMakeItDefleCompatible;
|
||||
signed char vol, outVol, wave, lastKill;
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
unsigned short hwSeqPos;
|
||||
short hwSeqDelay;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
|
@ -52,17 +57,38 @@ class DivPlatformGB: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
released(false),
|
||||
softEnv(false),
|
||||
killIt(false),
|
||||
soManyHacksToMakeItDefleCompatible(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
wave(-1),
|
||||
lastKill(0),
|
||||
envVol(0),
|
||||
envDir(0),
|
||||
envLen(0),
|
||||
soundLen(0),
|
||||
hwSeqPos(0),
|
||||
hwSeqDelay(0) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool antiClickEnabled;
|
||||
unsigned char lastPan;
|
||||
DivWaveSynth ws;
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
|
||||
int antiClickPeriodCount, antiClickWavePos;
|
||||
|
||||
GB_gameboy_t* gb;
|
||||
GB_model_t model;
|
||||
unsigned char regPool[128];
|
||||
|
||||
unsigned char procMute();
|
||||
|
|
@ -80,14 +106,16 @@ class DivPlatformGB: public DivDispatch {
|
|||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
int getPortaFloor(int ch);
|
||||
bool isStereo();
|
||||
bool getDCOffRequired();
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setFlags(unsigned int flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformGB();
|
||||
|
|
|
|||
|
|
@ -22,119 +22,11 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "genesisshared.h"
|
||||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
|
||||
static unsigned char konOffs[6]={
|
||||
0, 1, 2, 4, 5, 6
|
||||
};
|
||||
|
||||
#define CHIP_DIVIDER 72
|
||||
#define CHIP_FREQBASE 9440540
|
||||
|
||||
const char* DivPlatformGenesis::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xy: Setup LFO (x: enable; y: speed)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Enable channel 6 DAC";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Toggle extended channel 3 mode";
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0xdf:
|
||||
return "DFxx: Set sample playback direction (0: normal; 1: reverse)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformGenesis::processDAC() {
|
||||
if (softPCM) {
|
||||
softPCMTimer+=chipClock/576;
|
||||
|
|
@ -159,14 +51,13 @@ void DivPlatformGenesis::processDAC() {
|
|||
if (chan[i].dacPeriod>=(chipClock/576)) {
|
||||
if (s->samples>0) {
|
||||
while (chan[i].dacPeriod>=(chipClock/576)) {
|
||||
if (++chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[i].dacDirection) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
chan[i].dacPeriod=0;
|
||||
break;
|
||||
}
|
||||
++chan[i].dacPos;
|
||||
if (!chan[i].dacDirection && (s->isLoopable() && chan[i].dacPos>=s->getEndPosition())) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else if (chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
chan[i].dacPeriod=0;
|
||||
break;
|
||||
}
|
||||
chan[i].dacPeriod-=(chipClock/576);
|
||||
}
|
||||
|
|
@ -206,14 +97,13 @@ void DivPlatformGenesis::processDAC() {
|
|||
chan[5].dacReady=false;
|
||||
}
|
||||
}
|
||||
if (++chan[5].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[5].dacDirection) {
|
||||
chan[5].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[5].dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
chan[5].dacPos++;
|
||||
if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=s->getEndPosition())) {
|
||||
chan[5].dacPos=s->loopStart;
|
||||
} else if (chan[5].dacPos>=s->samples) {
|
||||
chan[5].dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
}
|
||||
while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate;
|
||||
|
|
@ -348,13 +238,17 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
for (int i=0; i<(softPCM?7:6); i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
chan[i].std.next();
|
||||
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,chan[i].std.vol.val),127);
|
||||
for (int j=0; j<4; j++) {
|
||||
int inVol=chan[i].std.vol.val;
|
||||
if (chan[i].furnaceDac && inVol>0) {
|
||||
inVol+=63;
|
||||
}
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,inVol),127);
|
||||
if (i<6) for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (isMuted[i]) {
|
||||
|
|
@ -369,25 +263,41 @@ void DivPlatformGenesis::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);
|
||||
if (i>=5 && chan[i].furnaceDac) {
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,parent->calcArp(chan[i].note,chan[i].std.arp.val),false);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
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);
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan=chan[i].std.panL.val&3;
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (i>=5 && chan[i].furnaceDac) {
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[5].pan&=1;
|
||||
chan[5].pan|=chan[i].std.panL.val?2:0;
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[5].pan&=2;
|
||||
chan[5].pan|=chan[i].std.panR.val?1:0;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
rWrite(chanOffs[5]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[5].pan<<6))|(chan[5].state.fms&7)|((chan[5].state.ams&3)<<4));
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan=chan[i].std.panL.val&3;
|
||||
if (i<6) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
|
|
@ -400,6 +310,8 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (i>=6) continue;
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
|
|
@ -435,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
chan[i].state.ams=chan[i].std.ams.val;
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
if (chan[i].std.ex4.had && chan[i].active) {
|
||||
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||
chan[i].opMaskChanged=true;
|
||||
}
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
|
|
@ -567,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -640,9 +557,20 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].dacPeriod=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].furnaceDac=true;
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
||||
// ???
|
||||
//chan[c.chan].keyOn=true;
|
||||
chan[c.chan].active=true;
|
||||
} else { // compatible mode
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
|
|
@ -668,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
chan[c.chan].opMask=
|
||||
(chan[c.chan].state.op[0].enable?1:0)|
|
||||
(chan[c.chan].state.op[2].enable?2:0)|
|
||||
(chan[c.chan].state.op[1].enable?4:0)|
|
||||
(chan[c.chan].state.op[3].enable?8:0);
|
||||
}
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -892,6 +825,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH: {
|
||||
if (extSys) {
|
||||
extMode=c.value;
|
||||
immWrite(0x27,extMode?0x40:0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
if (c.chan>=6) break;
|
||||
lfoValue=(c.value&7)|((c.value>>4)<<3);
|
||||
|
|
@ -1247,12 +1187,13 @@ void DivPlatformGenesis::setSoftPCM(bool value) {
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::setFlags(unsigned int flags) {
|
||||
switch (flags) {
|
||||
switch (flags&(~0x80000000)) {
|
||||
default:
|
||||
case 0: chipClock=COLOR_NTSC*15.0/7.0; break;
|
||||
case 1: chipClock=COLOR_PAL*12.0/7.0; break;
|
||||
case 2: chipClock=8000000.0; break;
|
||||
case 3: chipClock=COLOR_NTSC*12.0/7.0; break;
|
||||
case 4: chipClock=COLOR_NTSC*9.0/4.0; break;
|
||||
default: chipClock=COLOR_NTSC*15.0/7.0; break;
|
||||
}
|
||||
ladder=flags&0x80000000;
|
||||
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);
|
||||
|
|
|
|||
|
|
@ -19,28 +19,35 @@
|
|||
|
||||
#ifndef _GENESIS_H
|
||||
#define _GENESIS_H
|
||||
#include "../dispatch.h"
|
||||
#include <deque>
|
||||
#include "fmshared_OPN.h"
|
||||
#include "../macroInt.h"
|
||||
#include "../../../extern/Nuked-OPN2/ym3438.h"
|
||||
#include "sound/ymfm/ymfm_opn.h"
|
||||
|
||||
#include "sms.h"
|
||||
|
||||
class DivYM2612Interface: public ymfm::ymfm_interface {
|
||||
|
||||
};
|
||||
|
||||
class DivPlatformGenesis: public DivDispatch {
|
||||
class DivPlatformGenesis: public DivPlatformOPN {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
const unsigned char konOffs[6]={
|
||||
0, 1, 2, 4, 5, 6
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||
int ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
unsigned char pan, opMask;
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
|
|
@ -75,9 +82,11 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
furnaceDac(false),
|
||||
inPorta(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
pan(3),
|
||||
opMask(15),
|
||||
dacMode(false),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
|
|
@ -92,21 +101,11 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
Channel chan[10];
|
||||
DivDispatchOscBuffer* oscBuf[10];
|
||||
bool isMuted[10];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::deque<QueuedWrite> writes;
|
||||
ym3438_t fm;
|
||||
int delay;
|
||||
unsigned char lastBusy;
|
||||
|
||||
ymfm::ym2612* fm_ymfm;
|
||||
ymfm::ym2612::output_data out_ymfm;
|
||||
DivYM2612Interface iface;
|
||||
unsigned char regPool[512];
|
||||
|
||||
unsigned char lfoValue;
|
||||
|
||||
|
|
@ -115,9 +114,6 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
bool extMode, softPCM, useYMFM;
|
||||
bool ladder;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
unsigned char dacVolTable[128];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
|
@ -150,9 +146,10 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
DivPlatformGenesis():
|
||||
DivPlatformOPN(9440540.0, 72, 32) {}
|
||||
~DivPlatformGenesis();
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "genesisshared.h"
|
||||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define CHIP_DIVIDER 72
|
||||
#define CHIP_FREQBASE 9440540
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
|
||||
|
||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
|
|
@ -36,6 +37,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
}
|
||||
int ch=c.chan-2;
|
||||
int ordch=orderedOps[ch];
|
||||
if (!extMode) {
|
||||
c.chan=2;
|
||||
return DivPlatformGenesis::dispatch(c);
|
||||
}
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
|
|
@ -65,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
}
|
||||
opChan[ch].insChanged=false;
|
||||
|
||||
|
|
@ -119,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[i].pan=opChan[ch].pan;
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
|
|
@ -175,6 +181,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH: {
|
||||
extMode=c.value;
|
||||
immWrite(0x27,extMode?0x40:0);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
lfoValue=(c.value&7)|((c.value>>4)<<3);
|
||||
rWrite(0x22,lfoValue);
|
||||
|
|
@ -388,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
|||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
}
|
||||
|
||||
static int opChanOffsL[4]={
|
||||
|
|
@ -403,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
for (int i=0; i<4; i++) {
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
|
@ -411,6 +424,16 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (writeSomething) {
|
||||
if (chan[7].active) { // CSM
|
||||
writeMask^=0xf0;
|
||||
}
|
||||
/*printf(
|
||||
"Mask: %c %c %c %c\n",
|
||||
(writeMask&0x10)?'1':'-',
|
||||
(writeMask&0x20)?'2':'-',
|
||||
(writeMask&0x40)?'3':'-',
|
||||
(writeMask&0x80)?'4':'-'
|
||||
);*/
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
|
@ -440,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
writeNoteOn=true;
|
||||
writeMask|=1<<(4+i);
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -471,6 +496,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
if (chan[7].active) { // CSM
|
||||
writeMask^=0xf0;
|
||||
}
|
||||
/*printf(
|
||||
"Mask: %c %c %c %c\n",
|
||||
(writeMask&0x10)?'1':'-',
|
||||
(writeMask&0x20)?'2':'-',
|
||||
(writeMask&0x40)?'3':'-',
|
||||
(writeMask&0x80)?'4':'-'
|
||||
);*/
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
|
||||
|
|
@ -491,7 +523,7 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (i==2) { // extended channel
|
||||
if (i==2 && extMode) { // extended channel
|
||||
if (isOpMuted[j]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[chan[i].state.alg][j]) {
|
||||
|
|
@ -518,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (i==2) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
} else {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
|
@ -535,6 +571,11 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
opChan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (extMode && softPCM && chan[7].active) { // CSM
|
||||
chan[7].insChanged=true;
|
||||
chan[7].freqChanged=true;
|
||||
chan[7].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformGenesisExt::getChanState(int ch) {
|
||||
|
|
@ -594,6 +635,7 @@ int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, un
|
|||
for (int i=0; i<4; i++) {
|
||||
isOpMuted[i]=false;
|
||||
}
|
||||
extSys=true;
|
||||
|
||||
reset();
|
||||
return 13;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel():
|
||||
|
|
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
keyOff(false),
|
||||
portaPause(false),
|
||||
inPorta(false),
|
||||
mask(true),
|
||||
vol(0),
|
||||
pan(3) {}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
static unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
static unsigned short opOffs[4]={
|
||||
0x00, 0x04, 0x08, 0x0c
|
||||
};
|
||||
static bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define urgentWrite(a,v) if (!skipRegisterWrites) { \
|
||||
if (writes.empty()) { \
|
||||
writes.push_back(QueuedWrite(a,v)); \
|
||||
} else if (writes.size()>16 || writes.front().addrOrVal) { \
|
||||
writes.push_back(QueuedWrite(a,v)); \
|
||||
} else { \
|
||||
writes.push_front(QueuedWrite(a,v)); \
|
||||
} \
|
||||
if (dumpWrites) { \
|
||||
addWrite(a,v); \
|
||||
} \
|
||||
}
|
||||
|
||||
#include "fmshared_OPN.h"
|
||||
|
|
@ -129,19 +129,6 @@ const char** DivPlatformLynx::getRegisterSheet() {
|
|||
return regCheatSheetLynx;
|
||||
}
|
||||
|
||||
const char* DivPlatformLynx::getEffectName(unsigned char effect) {
|
||||
switch (effect)
|
||||
{
|
||||
case 0x30: case 0x31: case 0x32: case 0x33:
|
||||
case 0x34: case 0x35: case 0x36: case 0x37:
|
||||
case 0x38: case 0x39: case 0x3a: case 0x3b:
|
||||
case 0x3c: case 0x3d: case 0x3e: case 0x3f:
|
||||
return "3xxx: Load LFSR (0 to FFF)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
for (int i=0; i<4; i++) {
|
||||
|
|
@ -158,12 +145,10 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7);
|
||||
}
|
||||
|
||||
if (chan[i].samplePos>=(int)s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].samplePos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].samplePos>=(int)s->getEndPosition()) {
|
||||
chan[i].samplePos=s->loopStart;
|
||||
} else if (chan[i].samplePos>=(int)s->samples) {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -387,6 +359,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ class DivPlatformLynx: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName( unsigned char effect );
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformLynx();
|
||||
|
|
|
|||
|
|
@ -43,15 +43,6 @@ const char** DivPlatformMMC5::getRegisterSheet() {
|
|||
return regCheatSheetMMC5;
|
||||
}
|
||||
|
||||
const char* DivPlatformMMC5::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (dacSample!=-1) {
|
||||
|
|
@ -62,12 +53,11 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (!isMuted[2]) {
|
||||
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
dacPos++;
|
||||
if (s->isLoopable() && dacPos>=s->getEndPosition()) {
|
||||
dacPos=s->loopStart;
|
||||
} else if (dacPos>=s->samples) {
|
||||
dacSample=-1;
|
||||
}
|
||||
dacPeriod-=rate;
|
||||
} else {
|
||||
|
|
@ -111,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;
|
||||
|
|
@ -172,7 +154,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
|
||||
// PCM
|
||||
if (chan[2].freqChanged) {
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false);
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1);
|
||||
if (chan[2].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
|
|
@ -202,7 +184,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f));
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
|
|
@ -283,7 +265,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
|
@ -316,7 +298,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
if (c.chan==2) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
|
|
@ -324,6 +310,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ class DivPlatformMMC5: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformMMC5();
|
||||
|
|
|
|||
|
|
@ -30,18 +30,6 @@ const char** DivPlatformMSM6258::getRegisterSheet() {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const char* DivPlatformMSM6258::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set frequency divider (0-2)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Select clock rate (0: full; 1: half)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformMSM6258::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short* outs[2]={
|
||||
&msmOut,
|
||||
|
|
@ -121,6 +109,7 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
sample=ins->amiga.getSample(c.value);
|
||||
samplePos=0;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
//DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -144,8 +133,8 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
|
|||
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
sample=12*sampleBank+c.value%12;
|
||||
samplePos=0;
|
||||
msm->ctrl_w(1);
|
||||
msm->ctrl_w(2);
|
||||
rWrite(0,1);
|
||||
rWrite(0,2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -380,7 +369,7 @@ void DivPlatformMSM6258::setFlags(unsigned int flags) {
|
|||
chipClock=4000000;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/128;
|
||||
rate=chipClock/256;
|
||||
for (int i=0; i<1; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@
|
|||
|
||||
class DivPlatformMSM6258: public DivDispatch {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
|
|
@ -77,12 +73,10 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
okim6258_device* msm;
|
||||
unsigned char regPool[512];
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char* adpcmMem;
|
||||
|
|
@ -93,11 +87,6 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
|
||||
int delay, updateOsc, sample, samplePos;
|
||||
|
||||
bool extMode;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
|
|
@ -120,7 +109,6 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
void setFlags(unsigned int flags);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -24,22 +24,17 @@
|
|||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char** DivPlatformMSM6295::getRegisterSheet() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* DivPlatformMSM6295::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set chip output rate (0: clock/132; 1: clock/165)";
|
||||
break;
|
||||
u8 DivPlatformMSM6295::read_byte(u32 address) {
|
||||
if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) {
|
||||
return 0;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u8 DivMSM6295Interface::read_byte(u32 address) {
|
||||
return adpcmMem[address&0xffff];
|
||||
return adpcmMem[address&0x3ffff];
|
||||
}
|
||||
|
||||
void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
|
|
@ -49,7 +44,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
QueuedWrite& w=writes.front();
|
||||
switch (w.addr) {
|
||||
case 0: // command
|
||||
msm->command_w(w.val);
|
||||
msm.command_w(w.val);
|
||||
break;
|
||||
case 8: // chip clock select (VGM)
|
||||
case 9:
|
||||
|
|
@ -57,7 +52,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
case 11:
|
||||
break;
|
||||
case 12: // rate select
|
||||
msm->ss_w(!w.val);
|
||||
msm.ss_w(!w.val);
|
||||
break;
|
||||
case 14: // enable bankswitch
|
||||
break;
|
||||
|
|
@ -70,21 +65,21 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
break;
|
||||
}
|
||||
writes.pop();
|
||||
delay=32;
|
||||
delay=w.delay;
|
||||
}
|
||||
} else {
|
||||
delay--;
|
||||
}
|
||||
|
||||
msm->tick();
|
||||
msm.tick();
|
||||
|
||||
bufL[h]=msm->out()<<4;
|
||||
bufL[h]=msm.out()<<4;
|
||||
|
||||
if (++updateOsc>=22) {
|
||||
updateOsc=0;
|
||||
// TODO: per-channel osc
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=msm->m_voice[i].m_muted?0:(msm->m_voice[i].m_out<<6);
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +113,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
rWrite(0,0x80|chan[c.chan].sample); // set phrase
|
||||
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
|
||||
} else {
|
||||
|
|
@ -133,7 +128,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
}
|
||||
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
chan[c.chan].sample=12*sampleBank+c.value%12;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
rWrite(0,0x80|chan[c.chan].sample); // set phrase
|
||||
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
|
||||
}
|
||||
|
|
@ -143,14 +138,14 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
|
@ -188,7 +183,6 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
iface.sampleBank=sampleBank;
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
break;
|
||||
|
|
@ -212,7 +206,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformMSM6295::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
msm->m_voice[ch].m_muted=mute;
|
||||
msm.m_voice[ch].m_muted=mute;
|
||||
}
|
||||
|
||||
void DivPlatformMSM6295::forceIns() {
|
||||
|
|
@ -253,8 +247,8 @@ void DivPlatformMSM6295::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformMSM6295::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
msm->reset();
|
||||
msm->ss_w(false);
|
||||
msm.reset();
|
||||
msm.ss_w(rateSelInit);
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
|
@ -268,7 +262,8 @@ void DivPlatformMSM6295::reset() {
|
|||
}
|
||||
|
||||
sampleBank=0;
|
||||
rateSel=false;
|
||||
rateSel=rateSelInit;
|
||||
rWrite(12,!rateSelInit);
|
||||
|
||||
delay=0;
|
||||
}
|
||||
|
|
@ -343,7 +338,9 @@ void DivPlatformMSM6295::renderSamples() {
|
|||
}
|
||||
|
||||
void DivPlatformMSM6295::setFlags(unsigned int flags) {
|
||||
switch (flags) {
|
||||
rateSelInit=(flags>>7)&1;
|
||||
switch (flags&0x7f) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=4000000/4;
|
||||
break;
|
||||
|
|
@ -383,22 +380,27 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) {
|
|||
case 12:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
default:
|
||||
chipClock=4000000/4;
|
||||
case 13:
|
||||
chipClock=3000000;
|
||||
break;
|
||||
case 14:
|
||||
chipClock=COLOR_NTSC/3.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/3;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate/22;
|
||||
}
|
||||
if (rateSel!=rateSelInit) {
|
||||
rWrite(12,!rateSelInit);
|
||||
rateSel=rateSelInit;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
adpcmMem=new unsigned char[getSampleMemCapacity(0)];
|
||||
adpcmMemLen=0;
|
||||
iface.adpcmMem=adpcmMem;
|
||||
iface.sampleBank=0;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
updateOsc=0;
|
||||
|
|
@ -406,7 +408,6 @@ int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
msm=new msm6295_core(iface);
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
|
|
@ -416,7 +417,6 @@ void DivPlatformMSM6295::quit() {
|
|||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete msm;
|
||||
delete[] adpcmMem;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,60 +24,31 @@
|
|||
#include <queue>
|
||||
#include "sound/oki/msm6295.hpp"
|
||||
|
||||
class DivMSM6295Interface: public vgsound_emu_mem_intf {
|
||||
public:
|
||||
unsigned char* adpcmMem;
|
||||
int sampleBank;
|
||||
u8 read_byte(u32 address);
|
||||
DivMSM6295Interface(): adpcmMem(NULL), sampleBank(0) {}
|
||||
};
|
||||
|
||||
class DivPlatformMSM6295: public DivDispatch {
|
||||
class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
int note, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, furnacePCM, hardReset;
|
||||
int vol, outVol;
|
||||
int sample;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
psgMode(1),
|
||||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
portaPause(false),
|
||||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
sample(-1) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
|
|
@ -85,57 +56,58 @@ class DivPlatformMSM6295: public DivDispatch {
|
|||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
unsigned short delay;
|
||||
QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32):
|
||||
addr(a),
|
||||
val(v),
|
||||
delay(d) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
msm6295_core* msm;
|
||||
unsigned char regPool[512];
|
||||
msm6295_core msm;
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char* adpcmMem;
|
||||
size_t adpcmMemLen;
|
||||
DivMSM6295Interface iface;
|
||||
unsigned char sampleBank;
|
||||
|
||||
int delay, updateOsc;
|
||||
|
||||
bool extMode;
|
||||
bool rateSel;
|
||||
bool rateSel=false, rateSelInit=false;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
float getPostAmp();
|
||||
void notifyInsChange(int ins);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
void setFlags(unsigned int flags);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
void renderSamples();
|
||||
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
virtual u8 read_byte(u32 address) override;
|
||||
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
|
||||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
virtual void reset() override;
|
||||
virtual void forceIns() override;
|
||||
virtual void tick(bool sysTick=true) override;
|
||||
virtual void muteChannel(int ch, bool mute) override;
|
||||
virtual bool keyOffAffectsArp(int ch) override;
|
||||
virtual float getPostAmp() override;
|
||||
virtual void notifyInsChange(int ins) override;
|
||||
virtual void notifyInsDeletion(void* ins) override;
|
||||
virtual void poke(unsigned int addr, unsigned short val) override;
|
||||
virtual void poke(std::vector<DivRegWrite>& wlist) override;
|
||||
virtual void setFlags(unsigned int flags) override;
|
||||
virtual const char** getRegisterSheet() override;
|
||||
virtual const void* getSampleMem(int index) override;
|
||||
virtual size_t getSampleMemCapacity(int index) override;
|
||||
virtual size_t getSampleMemUsage(int index) override;
|
||||
virtual void renderSamples() override;
|
||||
|
||||
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
|
||||
virtual void quit() override;
|
||||
DivPlatformMSM6295():
|
||||
DivDispatch(),
|
||||
vgsound_emu_mem_intf(),
|
||||
msm(*this) {}
|
||||
~DivPlatformMSM6295();
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -108,51 +108,6 @@ const char** DivPlatformN163::getRegisterSheet() {
|
|||
return regCheatSheetN163;
|
||||
}
|
||||
|
||||
const char* DivPlatformN163::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Select waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set waveform position in RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Select waveform for load to RAM";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set waveform position for load to RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
|
||||
break;
|
||||
case 0x18:
|
||||
return "180x: Change channel limits (0 to 7, x + 1)";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: (Global) Select waveform for load to RAM";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
n163.tick();
|
||||
|
|
@ -234,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) {
|
||||
|
|
@ -562,6 +508,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -695,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate/(initChanMax+1);
|
||||
}
|
||||
|
||||
// needed to make sure changing channel count won't trigger glitches
|
||||
reset();
|
||||
}
|
||||
|
||||
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ class DivPlatformN163: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformN163();
|
||||
|
|
|
|||
|
|
@ -151,22 +151,7 @@ const char** DivPlatformNamcoWSG::getRegisterSheet() {
|
|||
return regCheatSheetNamcoWSG;
|
||||
}
|
||||
|
||||
const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle noise mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short* buf[2]={
|
||||
bufL+start, bufR+start
|
||||
};
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
switch (devType) {
|
||||
|
|
@ -186,7 +171,15 @@ void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
regPool[w.addr&0x3f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
namco->sound_stream_update(buf,len);
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
short* buf[2]={
|
||||
bufL+h, bufR+h
|
||||
};
|
||||
namco->sound_stream_update(buf,1);
|
||||
for (int i=0; i<chans; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
|
|
@ -213,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()) {
|
||||
|
|
@ -317,7 +301,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x04,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>15)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
case 30:
|
||||
|
|
@ -331,7 +315,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x103,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>15)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -437,6 +421,7 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformNamcoWSG: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformNamcoWSG();
|
||||
|
|
|
|||
|
|
@ -62,27 +62,6 @@ const char** DivPlatformNES::getRegisterSheet() {
|
|||
return regCheatSheetNES;
|
||||
}
|
||||
|
||||
const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x11:
|
||||
return "Write to delta modulation counter (0 to 7F)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xy: Sweep up (x: time; y: shift)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xy: Sweep down (x: time; y: shift)";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
||||
if (useNP) {
|
||||
nes1_NP->Write(addr,data);
|
||||
|
|
@ -108,12 +87,11 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
rWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
if (++dacPos>=s->samples) { \
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \
|
||||
dacPos=s->loopStart; \
|
||||
} else { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
dacPos++; \
|
||||
if (s->isLoopable() && dacPos>=s->getEndPosition()) { \
|
||||
dacPos=s->loopStart; \
|
||||
} else if (dacPos>=s->samples) { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
dacPeriod-=rate; \
|
||||
} else { \
|
||||
|
|
@ -137,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_
|
|||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11;
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11;
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11);
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11);
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11);
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11);
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -241,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;
|
||||
|
|
@ -573,6 +538,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ class DivPlatformNES: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -152,98 +152,6 @@ const int orderedOpsL[4]={
|
|||
#define ADDR_FREQH 0xb0
|
||||
#define ADDR_LR_FB_ALG 0xc0
|
||||
|
||||
const char* DivPlatformOPL::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set global AM depth (0: 1dB, 1: 4.8dB)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Set global vibrato depth (0: normal, 1: double)";
|
||||
break;
|
||||
case 0x18:
|
||||
if (properDrumsSys) {
|
||||
return "18xx: Toggle drums mode (1: enabled; 0: disabled)";
|
||||
}
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x2a:
|
||||
return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static short o[2];
|
||||
static int os[2];
|
||||
|
|
@ -277,8 +185,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
regPool[w.addr&511]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
|
||||
if (downsample) {
|
||||
OPL3_GenerateResampled(&fm,o);
|
||||
} else {
|
||||
OPL3_Generate(&fm,o);
|
||||
}
|
||||
os[0]+=o[0]; os[1]+=o[1];
|
||||
|
||||
if (adpcmChan>=0) {
|
||||
adpcmB->clock();
|
||||
|
|
@ -288,24 +201,45 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (!isMuted[adpcmChan]) {
|
||||
os[0]-=aOut.data[0]>>3;
|
||||
os[1]-=aOut.data[0]>>3;
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0];
|
||||
} else {
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
if (fm.rhy&0x20) {
|
||||
for (int i=0; i<melodicChans+1; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
// special
|
||||
oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*6;
|
||||
oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*6;
|
||||
oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*6;
|
||||
oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*6;
|
||||
} else {
|
||||
for (int i=0; i<chans; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
|
|
@ -315,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (os[1]>32767) os[1]=32767;
|
||||
|
||||
bufL[h]=os[0];
|
||||
bufR[h]=os[1];
|
||||
if (oplType==3 || oplType==759) {
|
||||
bufR[h]=os[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,18 +299,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) {
|
||||
|
|
@ -557,18 +484,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) {
|
||||
|
|
@ -596,8 +514,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
chan[i].freqH=freqt>>8;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
||||
|
|
@ -677,6 +596,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
|||
fm.channel[outChanMap[ch]].muted=mute;
|
||||
}
|
||||
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
|
||||
if (ch&1 && ch<12) {
|
||||
if (chan[ch-1].fourOp) return;
|
||||
}
|
||||
chan[ch].fourOp=(ops==4);
|
||||
update4OpMask=true;
|
||||
for (int i=0; i<ops; i++) {
|
||||
|
|
@ -741,7 +663,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -777,8 +699,8 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/(double)rate;
|
||||
immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
|
||||
immWrite(16,freq&0xff);
|
||||
immWrite(17,(freq>>8)&0xff);
|
||||
}
|
||||
|
|
@ -842,6 +764,13 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
chan[c.chan].fourOp=(ops==4);
|
||||
if (chan[c.chan].fourOp) {
|
||||
/*
|
||||
if (chan[c.chan+1].active) {
|
||||
chan[c.chan+1].keyOff=true;
|
||||
chan[c.chan+1].keyOn=false;
|
||||
chan[c.chan+1].active=false;
|
||||
}*/
|
||||
chan[c.chan+1].insChanged=true;
|
||||
chan[c.chan+1].macroInit(NULL);
|
||||
}
|
||||
update4OpMask=true;
|
||||
|
|
@ -1404,6 +1333,9 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
return 63;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) {
|
||||
chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -1508,7 +1440,12 @@ void DivPlatformOPL::reset() {
|
|||
fm_ymfm->reset();
|
||||
}
|
||||
*/
|
||||
OPL3_Reset(&fm,rate);
|
||||
if (downsample) {
|
||||
const unsigned int downsampledRate=(unsigned int)((double)rate*rate/chipRateBase);
|
||||
OPL3_Reset(&fm,downsampledRate);
|
||||
} else {
|
||||
OPL3_Reset(&fm,rate);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
|
@ -1585,7 +1522,7 @@ void DivPlatformOPL::reset() {
|
|||
}
|
||||
|
||||
bool DivPlatformOPL::isStereo() {
|
||||
return true;
|
||||
return (oplType==3 || oplType==759);
|
||||
}
|
||||
|
||||
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
||||
|
|
@ -1625,6 +1562,7 @@ void DivPlatformOPL::setYMFM(bool use) {
|
|||
|
||||
void DivPlatformOPL::setOPLType(int type, bool drums) {
|
||||
pretendYMU=false;
|
||||
downsample=false;
|
||||
adpcmChan=-1;
|
||||
switch (type) {
|
||||
case 1: case 2: case 8950:
|
||||
|
|
@ -1633,7 +1571,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL2Drums:chanMapOPL2;
|
||||
outChanMap=outChanMapOPL2;
|
||||
chipFreqBase=9440540*0.25;
|
||||
chipFreqBase=32768*72;
|
||||
chans=9;
|
||||
melodicChans=drums?6:9;
|
||||
totalChans=drums?11:9;
|
||||
|
|
@ -1641,23 +1579,27 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
adpcmChan=drums?11:9;
|
||||
}
|
||||
break;
|
||||
case 3: case 759:
|
||||
case 3: case 4: case 759:
|
||||
slotsNonDrums=slotsOPL3;
|
||||
slotsDrums=slotsOPL3Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL3Drums:chanMapOPL3;
|
||||
outChanMap=outChanMapOPL3;
|
||||
chipFreqBase=9440540;
|
||||
chipFreqBase=32768*288;
|
||||
chans=18;
|
||||
melodicChans=drums?15:18;
|
||||
totalChans=drums?20:18;
|
||||
if (type==759) {
|
||||
pretendYMU=true;
|
||||
adpcmChan=16;
|
||||
} else if (type==4) {
|
||||
chipFreqBase=32768*684;
|
||||
downsample=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (type==759) {
|
||||
chipType=type;
|
||||
if (type==759 || type==4) {
|
||||
oplType=3;
|
||||
} else if (type==8950) {
|
||||
oplType=1;
|
||||
|
|
@ -1692,20 +1634,76 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
|
|||
rate=chipClock/36;
|
||||
}*/
|
||||
|
||||
if (oplType==3) {
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
rate=chipClock/288;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
rate=chipClock/72;
|
||||
switch (chipType) {
|
||||
default:
|
||||
case 1: case 2: case 8950:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=4000000.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=3000000.0;
|
||||
break;
|
||||
case 0x04:
|
||||
chipClock=38400*13*8; // 31948800/8
|
||||
break;
|
||||
case 0x05:
|
||||
chipClock=3500000.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/72;
|
||||
chipRateBase=rate;
|
||||
break;
|
||||
case 3:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*16.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=14000000.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=16000000.0;
|
||||
break;
|
||||
case 0x04:
|
||||
chipClock=15000000.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/288;
|
||||
chipRateBase=rate;
|
||||
break;
|
||||
case 4:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*32.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=33868800.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC*8.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/768;
|
||||
chipRateBase=chipClock/684;
|
||||
break;
|
||||
case 759:
|
||||
rate=48000;
|
||||
chipRateBase=rate;
|
||||
chipClock=rate*288;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pretendYMU) {
|
||||
rate=48000;
|
||||
chipClock=rate*288;
|
||||
}
|
||||
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
|
@ -1756,7 +1754,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
for (int i=0; i<20; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
|
@ -1774,7 +1772,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformOPL::quit() {
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (adpcmChan>=0) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
}
|
||||
};
|
||||
Channel chan[20];
|
||||
DivDispatchOscBuffer* oscBuf[18];
|
||||
DivDispatchOscBuffer* oscBuf[20];
|
||||
bool isMuted[20];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
|
@ -95,8 +95,8 @@ class DivPlatformOPL: public DivDispatch {
|
|||
const unsigned char** slots;
|
||||
const unsigned short* chanMap;
|
||||
const unsigned char* outChanMap;
|
||||
double chipFreqBase;
|
||||
int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
|
||||
int chipFreqBase, chipRateBase;
|
||||
int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
|
||||
unsigned char lastBusy;
|
||||
unsigned char drumState;
|
||||
unsigned char drumVol[5];
|
||||
|
|
@ -107,7 +107,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
|
||||
unsigned char lfoValue;
|
||||
|
||||
bool useYMFM, update4OpMask, pretendYMU;
|
||||
bool useYMFM, update4OpMask, pretendYMU, downsample;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
|
@ -145,7 +145,6 @@ class DivPlatformOPL: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -27,68 +27,6 @@
|
|||
|
||||
#define CHIP_FREQBASE 1180068
|
||||
|
||||
const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)";
|
||||
break;
|
||||
case 0x18:
|
||||
if (properDrumsSys) {
|
||||
return "18xx: Toggle drums mode (1: enabled; 0: disabled)";
|
||||
}
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 2 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned char cycleMapOPLL[18]={
|
||||
8, 7, 6, 7, 8, 7, 8, 6, 0, 1, 2, 7, 8, 9, 3, 4, 5, 9
|
||||
};
|
||||
|
|
@ -97,6 +35,10 @@ const unsigned char drumSlot[11]={
|
|||
0, 0, 0, 0, 0, 0, 6, 7, 8, 8, 7
|
||||
};
|
||||
|
||||
const unsigned char visMapOPLL[9]={
|
||||
6, 7, 8, 3, 4, 5, 0, 1, 2
|
||||
};
|
||||
|
||||
void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int o[2];
|
||||
static int os;
|
||||
|
|
@ -124,10 +66,18 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size
|
|||
OPLL_Clock(&fm,o);
|
||||
unsigned char nextOut=cycleMapOPLL[fm.cycles];
|
||||
if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
|
||||
os+=(o[0]+o[1]);
|
||||
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
|
||||
} else {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
|
||||
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
|
||||
}
|
||||
}
|
||||
if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) {
|
||||
unsigned char ch=visMapOPLL[i];
|
||||
if ((i>=6 && properDrums) || !isMuted[ch]) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6;
|
||||
} else {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=0;
|
||||
}
|
||||
}
|
||||
os*=50;
|
||||
|
|
@ -157,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) {
|
||||
|
|
@ -819,6 +760,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformOPLL();
|
||||
|
|
|
|||
|
|
@ -53,27 +53,6 @@ const char** DivPlatformPCE::getRegisterSheet() {
|
|||
return regCheatSheetPCE;
|
||||
}
|
||||
|
||||
const char* DivPlatformPCE::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle noise mode";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set LFO speed";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// PCM part
|
||||
|
|
@ -90,12 +69,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
chWrite(i,0x04,0xdf);
|
||||
chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
|
||||
chan[i].dacPos++;
|
||||
if (chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else if (chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
}
|
||||
chan[i].dacPeriod-=rate;
|
||||
}
|
||||
|
|
@ -117,7 +94,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
pce->ResetTS(0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767);
|
||||
}
|
||||
|
||||
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);
|
||||
|
|
@ -135,14 +112,22 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
|
||||
void DivPlatformPCE::updateWave(int ch) {
|
||||
if (chan[ch].pcm) {
|
||||
chan[ch].deferredWaveUpdate=true;
|
||||
return;
|
||||
}
|
||||
chWrite(ch,0x04,0x5f);
|
||||
chWrite(ch,0x04,0x1f);
|
||||
for (int i=0; i<32; i++) {
|
||||
chWrite(ch,0x06,chan[ch].ws.output[i]);
|
||||
chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]);
|
||||
}
|
||||
chan[ch].antiClickWavePos&=31;
|
||||
if (chan[ch].active) {
|
||||
chWrite(ch,0x04,0x80|chan[ch].outVol);
|
||||
}
|
||||
if (chan[ch].deferredWaveUpdate) {
|
||||
chan[ch].deferredWaveUpdate=false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in octave 6 the noise table changes to a tonal one
|
||||
|
|
@ -152,6 +137,13 @@ static unsigned char noiseFreq[12]={
|
|||
|
||||
void DivPlatformPCE::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
// anti-click
|
||||
if (antiClickEnabled && sysTick && chan[i].freq>0) {
|
||||
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
|
||||
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
|
||||
chan[i].antiClickPeriodCount%=chan[i].freq;
|
||||
}
|
||||
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31);
|
||||
|
|
@ -170,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()) {
|
||||
|
|
@ -220,8 +196,12 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
|
||||
chan[i].antiClickWavePos=0;
|
||||
chan[i].antiClickPeriodCount=0;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -445,6 +425,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -556,10 +537,18 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
|
|||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
// flags&4 will be chip revision
|
||||
antiClickEnabled=!(flags&8);
|
||||
rate=chipClock/12;
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280);
|
||||
}
|
||||
|
||||
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
|
||||
|
|
@ -578,8 +567,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
pce=NULL;
|
||||
setFlags(flags);
|
||||
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
|
@ -588,7 +577,10 @@ void DivPlatformPCE::quit() {
|
|||
for (int i=0; i<6; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete pce;
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformPCE::~DivPlatformPCE() {
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@
|
|||
|
||||
class DivPlatformPCE: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample, ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
|
|
@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch {
|
|||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
antiClickPeriodCount(0),
|
||||
antiClickWavePos(0),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
dacPos(0),
|
||||
|
|
@ -62,6 +64,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
noise(false),
|
||||
pcm(false),
|
||||
furnaceDac(false),
|
||||
deferredWaveUpdate(false),
|
||||
vol(31),
|
||||
outVol(31),
|
||||
wave(-1) {}
|
||||
|
|
@ -69,6 +72,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
Channel chan[6];
|
||||
DivDispatchOscBuffer* oscBuf[6];
|
||||
bool isMuted[6];
|
||||
bool antiClickEnabled;
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
|
|
@ -105,7 +109,6 @@ class DivPlatformPCE: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPCE();
|
||||
|
|
|
|||
359
src/engine/platform/pcmdac.cpp
Normal file
359
src/engine/platform/pcmdac.cpp
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* 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 _USE_MATH_DEFINES
|
||||
#include "pcmdac.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
// to ease the driver, freqency register is a 8.16 counter relative to output sample rate
|
||||
#define CHIP_FREQBASE 65536
|
||||
|
||||
void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
const int depthScale=(15-outDepth);
|
||||
int output=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!chan.active || isMuted) {
|
||||
bufL[h]=0;
|
||||
bufR[h]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
continue;
|
||||
}
|
||||
if (chan.useWave || (chan.sample>=0 && chan.sample<parent->song.sampleLen)) {
|
||||
chan.audPos+=chan.freq>>16;
|
||||
chan.audSub+=(chan.freq&0xffff);
|
||||
if (chan.audSub>=0x10000) {
|
||||
chan.audSub-=0x10000;
|
||||
chan.audPos+=1;
|
||||
}
|
||||
if (chan.useWave) {
|
||||
if (chan.audPos>=(unsigned int)(chan.audLen<<1)) {
|
||||
chan.audPos=0;
|
||||
}
|
||||
output=(chan.ws.output[chan.audPos]^0x80)<<8;
|
||||
} else {
|
||||
DivSample* s=parent->getSample(chan.sample);
|
||||
if (s->samples>0) {
|
||||
if (s->isLoopable() && chan.audPos>=s->getEndPosition()) {
|
||||
chan.audPos=s->loopStart;
|
||||
} else if (chan.audPos>=s->samples) {
|
||||
chan.sample=-1;
|
||||
}
|
||||
if (chan.audPos<s->samples) {
|
||||
output=s->data16[chan.audPos];
|
||||
}
|
||||
} else {
|
||||
chan.sample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
output=output*chan.vol*chan.envVol/16384;
|
||||
oscBuf->data[oscBuf->needle++]=output;
|
||||
if (outStereo) {
|
||||
bufL[h]=((output*chan.panL)>>(depthScale+8))<<depthScale;
|
||||
bufR[h]=((output*chan.panR)>>(depthScale+8))<<depthScale;
|
||||
} else {
|
||||
output=(output>>depthScale)<<depthScale;
|
||||
bufL[h]=output;
|
||||
bufR[h]=output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::tick(bool sysTick) {
|
||||
chan.std.next();
|
||||
if (chan.std.vol.had) {
|
||||
chan.envVol=chan.std.vol.val;
|
||||
}
|
||||
if (chan.std.arp.had) {
|
||||
if (!chan.inPorta) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(parent->calcArp(chan.note,chan.std.arp.val));
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.useWave && chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) {
|
||||
chan.wave=chan.std.wave.val;
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
if (!chan.keyOff) chan.keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan.useWave && chan.active) {
|
||||
chan.ws.tick();
|
||||
}
|
||||
if (chan.std.pitch.had) {
|
||||
if (chan.std.pitch.mode) {
|
||||
chan.pitch2+=chan.std.pitch.val;
|
||||
CLAMP_VAR(chan.pitch2,-32768,32767);
|
||||
} else {
|
||||
chan.pitch2=chan.std.pitch.val;
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.std.panL.had) {
|
||||
int val=chan.std.panL.val&0x7f;
|
||||
chan.panL=val*2;
|
||||
}
|
||||
if (chan.std.panR.had) {
|
||||
int val=chan.std.panR.val&0x7f;
|
||||
chan.panR=val*2;
|
||||
}
|
||||
if (chan.std.phaseReset.had) {
|
||||
if (chan.std.phaseReset.val==1) {
|
||||
chan.audPos=0;
|
||||
}
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
double off=1.0;
|
||||
if (!chan.useWave && chan.sample>=0 && chan.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan.sample);
|
||||
off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
}
|
||||
chan.freq=off*parent->calcFreq(chan.baseFreq,chan.pitch,false,2,chan.pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan.freq>16777215) chan.freq=16777215;
|
||||
if (chan.keyOn) {
|
||||
if (!chan.std.vol.had) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
chan.keyOn=false;
|
||||
}
|
||||
if (chan.keyOff) {
|
||||
chan.keyOff=false;
|
||||
}
|
||||
chan.freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformPCMDAC::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
if (ins->amiga.useWave) {
|
||||
chan.useWave=true;
|
||||
chan.audLen=(ins->amiga.waveLen+1)>>1;
|
||||
if (chan.insChanged) {
|
||||
if (chan.wave<0) {
|
||||
chan.wave=0;
|
||||
chan.ws.setWidth(chan.audLen<<1);
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan.sample=ins->amiga.getSample(c.value);
|
||||
chan.useWave=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan.baseFreq=round(NOTE_FREQUENCY(c.value));
|
||||
}
|
||||
if (chan.useWave || chan.sample<0 || chan.sample>=parent->song.sampleLen) {
|
||||
chan.sample=-1;
|
||||
}
|
||||
if (chan.setPos) {
|
||||
chan.setPos=false;
|
||||
} else {
|
||||
chan.audPos=0;
|
||||
}
|
||||
chan.audSub=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan.freqChanged=true;
|
||||
chan.note=c.value;
|
||||
}
|
||||
chan.active=true;
|
||||
chan.keyOn=true;
|
||||
chan.macroInit(ins);
|
||||
if (!parent->song.brokenOutVol && !chan.std.vol.will) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
if (chan.useWave) {
|
||||
chan.ws.init(ins,chan.audLen<<1,255,chan.insChanged);
|
||||
}
|
||||
chan.insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan.sample=-1;
|
||||
chan.active=false;
|
||||
chan.keyOff=true;
|
||||
chan.macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan.std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan.ins!=c.value || c.value2==1) {
|
||||
chan.ins=c.value;
|
||||
chan.insChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan.vol!=c.value) {
|
||||
chan.vol=c.value;
|
||||
if (!chan.std.vol.has) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
return chan.vol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan.panL=c.value;
|
||||
chan.panR=c.value2;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan.pitch=c.value;
|
||||
chan.freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
if (!chan.useWave) break;
|
||||
chan.wave=c.value;
|
||||
chan.keyOn=true;
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
chan.sample=ins->amiga.getSample(c.value2);
|
||||
int destFreq=round(NOTE_FREQUENCY(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan.baseFreq) {
|
||||
chan.baseFreq+=c.value;
|
||||
if (chan.baseFreq>=destFreq) {
|
||||
chan.baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan.baseFreq-=c.value;
|
||||
if (chan.baseFreq<=destFreq) {
|
||||
chan.baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
if (return2) {
|
||||
chan.inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan.baseFreq=round(NOTE_FREQUENCY(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0))));
|
||||
chan.freqChanged=true;
|
||||
chan.note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan.active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan.inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (chan.useWave) break;
|
||||
chan.audPos=c.value;
|
||||
chan.setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::muteChannel(int ch, bool mute) {
|
||||
isMuted=mute;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::forceIns() {
|
||||
chan.insChanged=true;
|
||||
chan.freqChanged=true;
|
||||
chan.audPos=0;
|
||||
chan.sample=-1;
|
||||
}
|
||||
|
||||
void* DivPlatformPCMDAC::getChanState(int ch) {
|
||||
return &chan;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) {
|
||||
return oscBuf;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::reset() {
|
||||
chan=DivPlatformPCMDAC::Channel();
|
||||
chan.std.setEngine(parent);
|
||||
chan.ws.setEngine(parent);
|
||||
chan.ws.init(NULL,32,255);
|
||||
}
|
||||
|
||||
bool DivPlatformPCMDAC::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
|
||||
return &chan.std;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsChange(int ins) {
|
||||
if (chan.ins==ins) {
|
||||
chan.insChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyWaveChange(int wave) {
|
||||
if (chan.useWave && chan.wave==wave) {
|
||||
chan.ws.changeWave1(wave);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsDeletion(void* ins) {
|
||||
chan.std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::setFlags(unsigned int flags) {
|
||||
// default to 44100Hz 16-bit stereo
|
||||
if (!flags) flags=0x1f0000|44099;
|
||||
rate=(flags&0xffff)+1;
|
||||
// rate can't be too low or the resampler will break
|
||||
if (rate<1000) rate=1000;
|
||||
chipClock=rate;
|
||||
outDepth=(flags>>16)&0xf;
|
||||
outStereo=(flags>>20)&1;
|
||||
}
|
||||
|
||||
int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
oscBuf=new DivDispatchOscBuffer;
|
||||
isMuted=false;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::quit() {
|
||||
delete oscBuf;
|
||||
}
|
||||
99
src/engine/platform/pcmdac.h
Normal file
99
src/engine/platform/pcmdac.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* 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 _PCM_DAC_H
|
||||
#define _PCM_DAC_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
|
||||
class DivPlatformPCMDAC: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audLoc;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int audSub;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panL, panR;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
|
||||
int vol, envVol;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audLoc(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
audSub(0),
|
||||
sample(-1),
|
||||
wave(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panL(255),
|
||||
panR(255),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
useWave(false),
|
||||
setPos(false),
|
||||
vol(255),
|
||||
envVol(64) {}
|
||||
};
|
||||
Channel chan;
|
||||
DivDispatchOscBuffer* oscBuf;
|
||||
bool isMuted;
|
||||
int outDepth;
|
||||
bool outStereo;
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -27,11 +27,17 @@
|
|||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#ifdef HAVE_LINUX_INPUT
|
||||
#include <linux/input.h>
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_KD
|
||||
#include <linux/kd.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#ifdef HAVE_SYS_IO
|
||||
#include <sys/io.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define PCSPKR_DIVIDER 4
|
||||
#define CHIP_DIVIDER 1
|
||||
|
|
@ -80,6 +86,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
if (beepFD>=0) {
|
||||
switch (realOutMethod) {
|
||||
#ifdef HAVE_LINUX_INPUT
|
||||
case 0: { // evdev
|
||||
static struct input_event ie;
|
||||
ie.time.tv_sec=r.tv_sec;
|
||||
|
|
@ -98,11 +105,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_KD
|
||||
case 1: // KIOCSOUND (on tty)
|
||||
if (ioctl(beepFD,KIOCSOUND,r.val)<0) {
|
||||
logW("ioctl error! %s",strerror(errno));
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case 2: { // /dev/port
|
||||
unsigned char bOut;
|
||||
bOut=0;
|
||||
|
|
@ -144,11 +154,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_LINUX_KD
|
||||
case 3: // KIOCSOUND (on stdout)
|
||||
if (ioctl(beepFD,KIOCSOUND,r.val)<0) {
|
||||
logW("ioctl error! %s",strerror(errno));
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_SYS_IO
|
||||
case 4: // outb()
|
||||
if (r.val==0) {
|
||||
outb(inb(0x61)&(~3),0x61);
|
||||
|
|
@ -163,6 +176,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
//logV("not writing because fd is less than 0");
|
||||
|
|
@ -176,10 +190,6 @@ const char** DivPlatformPCSpeaker::getRegisterSheet() {
|
|||
return regCheatSheetPCSpeaker;
|
||||
}
|
||||
|
||||
const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const float cut=0.05;
|
||||
const float reso=0.06;
|
||||
|
||||
|
|
@ -337,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) {
|
||||
|
|
@ -457,6 +458,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -544,6 +546,7 @@ void DivPlatformPCSpeaker::reset() {
|
|||
break;
|
||||
case 4: // outb()
|
||||
beepFD=-1;
|
||||
#ifdef HAVE_SYS_IO
|
||||
if (ioperm(0x61,8,1)<0) {
|
||||
logW("ioperm 0x61: %s",strerror(errno));
|
||||
break;
|
||||
|
|
@ -557,6 +560,9 @@ void DivPlatformPCSpeaker::reset() {
|
|||
break;
|
||||
}
|
||||
beepFD=STDOUT_FILENO;
|
||||
#else
|
||||
errno=ENOSYS;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
if (beepFD<0) {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ class DivPlatformPCSpeaker: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPCSpeaker();
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}}
|
||||
|
||||
#define CHIP_DIVIDER 16
|
||||
#define SAMP_DIVIDER 4
|
||||
|
||||
const char* regCheatSheet6522[]={
|
||||
"T2L", "08",
|
||||
"T2H", "09",
|
||||
"SR", "0A",
|
||||
"ACR", "0B",
|
||||
"PCR", "0C",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -37,35 +37,45 @@ const char** DivPlatformPET::getRegisterSheet() {
|
|||
return regCheatSheet6522;
|
||||
}
|
||||
|
||||
const char* DivPlatformPET::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
// high-level emulation of 6522 shift register and driver software for now
|
||||
void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) {
|
||||
bool hwSROutput=((regPool[11]>>2)&7)==4;
|
||||
switch (addr) {
|
||||
case 9:
|
||||
// simulate phase reset from switching between hw/sw shift registers
|
||||
if ((regPool[9]==0)^(val==0)) {
|
||||
chan.sreg=chan.wave;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
chan.sreg=val;
|
||||
if (hwSROutput) chan.cnt=2;
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
regPool[addr]=val;
|
||||
}
|
||||
|
||||
void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
// high-level emulation of 6522 shift register for now
|
||||
int t2=regPool[8]*2+4;
|
||||
if (((regPool[11]>>2)&7)==4) {
|
||||
bool hwSROutput=((regPool[11]>>2)&7)==4;
|
||||
if (chan.enable) {
|
||||
int reload=regPool[8]*2+4;
|
||||
if (!hwSROutput) {
|
||||
reload+=regPool[9]*512;
|
||||
}
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
int cycs=SAMP_DIVIDER;
|
||||
while (cycs>0) {
|
||||
int adv=MIN(cycs,chan.cnt);
|
||||
chan.cnt-=adv;
|
||||
cycs-=adv;
|
||||
if (chan.cnt==0) {
|
||||
chan.out=(chan.sreg&1)*32767;
|
||||
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
|
||||
chan.cnt=t2;
|
||||
}
|
||||
if (SAMP_DIVIDER>chan.cnt) {
|
||||
chan.out=(chan.sreg&1)*32767;
|
||||
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
|
||||
chan.cnt+=reload-SAMP_DIVIDER;
|
||||
} else {
|
||||
chan.cnt-=SAMP_DIVIDER;
|
||||
}
|
||||
bufL[h]=chan.out;
|
||||
bufR[h]=chan.out;
|
||||
oscBuf->data[oscBuf->needle++]=chan.out;
|
||||
}
|
||||
// emulate driver writes to PCR
|
||||
if (!hwSROutput) regPool[12]=chan.out?0xe0:0xc0;
|
||||
} else {
|
||||
chan.out=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
|
|
@ -78,11 +88,10 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
|
||||
void DivPlatformPET::writeOutVol() {
|
||||
if (chan.active && !isMuted && chan.outVol>0) {
|
||||
if (regPool[11]!=16) {
|
||||
rWrite(11,16);
|
||||
rWrite(10,chan.wave);
|
||||
}
|
||||
chan.enable=true;
|
||||
rWrite(11,regPool[9]==0?16:0);
|
||||
} else {
|
||||
chan.enable=false;
|
||||
rWrite(11,0);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,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) {
|
||||
|
|
@ -115,24 +115,31 @@ void DivPlatformPET::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan.std.pitch.had) {
|
||||
chan.freqChanged=true;
|
||||
if (chan.std.pitch.mode) {
|
||||
chan.pitch2+=chan.std.pitch.val;
|
||||
CLAMP_VAR(chan.pitch2,-32768,32767);
|
||||
} else {
|
||||
chan.pitch2=chan.std.pitch.val;
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan.freq>257) chan.freq=257;
|
||||
if (chan.freq<2) chan.freq=2;
|
||||
rWrite(8,chan.freq-2);
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2;
|
||||
if (chan.freq>65535) chan.freq=65535;
|
||||
if (chan.freq<0) chan.freq=0;
|
||||
rWrite(8,chan.freq&0xff);
|
||||
rWrite(9,chan.freq>>8);
|
||||
if (chan.keyOn) {
|
||||
if (!chan.std.vol.will) {
|
||||
chan.outVol=chan.vol;
|
||||
writeOutVol();
|
||||
}
|
||||
chan.keyOn=false;
|
||||
}
|
||||
if (chan.keyOff) {
|
||||
rWrite(11,0);
|
||||
chan.keyOff=false;
|
||||
}
|
||||
// update mode setting and channel enable
|
||||
writeOutVol();
|
||||
chan.freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -220,6 +227,7 @@ int DivPlatformPET::dispatch(DivCommand c) {
|
|||
if (chan.active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_PET));
|
||||
}
|
||||
if (!chan.inPorta && c.value && !parent->song.brokenPortaArp && chan.std.arp.will) chan.baseFreq=NOTE_PERIODIC(chan.note);
|
||||
chan.inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
class DivPlatformPET: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, enable;
|
||||
int vol, outVol, wave;
|
||||
unsigned char sreg;
|
||||
int cnt;
|
||||
|
|
@ -49,6 +49,7 @@ class DivPlatformPET: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
enable(false),
|
||||
vol(1),
|
||||
outVol(1),
|
||||
wave(0b00001111),
|
||||
|
|
@ -79,12 +80,12 @@ class DivPlatformPET: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPET();
|
||||
private:
|
||||
void writeOutVol();
|
||||
void rWrite(unsigned int addr, unsigned char val);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -249,24 +249,6 @@ const char** DivPlatformQSound::getRegisterSheet() {
|
|||
return regCheatSheetQSound;
|
||||
}
|
||||
|
||||
const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set echo feedback level (00 to FF)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set channel echo level (00 to FF)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)";
|
||||
break;
|
||||
default:
|
||||
if ((effect & 0xf0) == 0x30) {
|
||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
qsound_update(&chip);
|
||||
|
|
@ -301,7 +283,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
||||
qsound_addr = s->offQSound & 0xffff;
|
||||
|
||||
int length = s->samples;
|
||||
int length = s->getEndPosition();
|
||||
if (length > 65536 - 16) {
|
||||
length = 65536 - 16;
|
||||
}
|
||||
|
|
@ -315,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) {
|
||||
|
|
@ -358,7 +331,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].freq>0xefff) chan[i].freq=0xefff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
|
||||
|
|
@ -496,6 +469,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformQSound: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ const char** DivPlatformRF5C68::getRegisterSheet() {
|
|||
return regCheatSheetRF5C68;
|
||||
}
|
||||
|
||||
const char* DivPlatformRF5C68::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::chWrite(unsigned char ch, unsigned int addr, unsigned char val) {
|
||||
if (!skipRegisterWrites) {
|
||||
if (curChan!=ch) {
|
||||
|
|
@ -88,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) {
|
||||
|
|
@ -120,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
|
||||
|
|
@ -142,7 +129,7 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
if (chan[i].audPos>0) {
|
||||
start=start+MIN(chan[i].audPos,s->length8);
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
if (s->isLoopable()) {
|
||||
loop=start+s->loopStart;
|
||||
}
|
||||
start=MIN(start,getSampleMemCapacity()-31);
|
||||
|
|
@ -265,6 +252,7 @@ int DivPlatformRF5C68::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
|
|
@ -392,7 +380,7 @@ void DivPlatformRF5C68::renderSamples() {
|
|||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->length8;
|
||||
int length=s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
|
||||
if (actualLength>0) {
|
||||
s->offRF5C68=memPos;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ class DivPlatformRF5C68: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
|
|
|
|||
|
|
@ -56,21 +56,6 @@ const char** DivPlatformSAA1099::getRegisterSheet() {
|
|||
return regCheatSheetSAA;
|
||||
}
|
||||
|
||||
const char* DivPlatformSAA1099::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xy: Set channel mode (x: noise; y: tone)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set noise frequency";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Setup envelope (refer to docs for more information)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSAA1099::acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (saaBufLen<len*2) {
|
||||
saaBufLen=len*2;
|
||||
|
|
@ -114,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;
|
||||
|
|
@ -335,6 +311,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -80,15 +80,6 @@ const char** DivPlatformSCC::getRegisterSheet() {
|
|||
return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC;
|
||||
}
|
||||
|
||||
const char* DivPlatformSCC::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
for (int i=0; i<16; i++) {
|
||||
|
|
@ -124,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()) {
|
||||
|
|
@ -267,6 +249,7 @@ int DivPlatformSCC::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -377,6 +360,27 @@ void DivPlatformSCC::setChipModel(bool isplus) {
|
|||
isPlus=isplus;
|
||||
}
|
||||
|
||||
void DivPlatformSCC::setFlags(unsigned int flags) {
|
||||
switch (flags&0x7f) {
|
||||
case 0x00:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=3000000.0/2.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=4000000.0/2.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
@ -386,11 +390,7 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
setFlags(flags);
|
||||
if (isPlus) {
|
||||
scc=new k052539_scc_core;
|
||||
regBase=0xa0;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class DivPlatformSCC: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setFlags(unsigned int flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void setChipModel(bool isPlus);
|
||||
void quit();
|
||||
|
|
|
|||
|
|
@ -26,15 +26,6 @@
|
|||
//#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
//#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char* DivPlatformSegaPCM::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set PCM frequency";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int os[2];
|
||||
|
||||
|
|
@ -56,12 +47,10 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
|
||||
}
|
||||
chan[i].pcm.pos+=chan[i].pcm.freq;
|
||||
if (chan[i].pcm.pos>=(s->samples<<8)) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].pcm.pos=s->loopStart<<8;
|
||||
} else {
|
||||
chan[i].pcm.sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].pcm.pos>=(s->getEndPosition()<<8)) {
|
||||
chan[i].pcm.pos=s->loopStart<<8;
|
||||
} else if (chan[i].pcm.pos>=(s->samples<<8)) {
|
||||
chan[i].pcm.sample=-1;
|
||||
}
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -99,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) {
|
||||
|
|
@ -202,7 +182,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].macroInit(ins);
|
||||
if (dumpWrites) { // Sega PCM writes
|
||||
DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
|
||||
int actualLength=(int)s->length8;
|
||||
int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>0xfeff) actualLength=0xfeff;
|
||||
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
|
||||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
|
|
@ -235,7 +215,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].furnacePCM=false;
|
||||
if (dumpWrites) { // Sega PCM writes
|
||||
DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
|
||||
int actualLength=(int)s->length8;
|
||||
int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>65536) actualLength=65536;
|
||||
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
|
||||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
|
|
@ -365,6 +345,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
return 127;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=(chan[c.chan].note<<6);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
bool isStereo();
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformSegaPCM();
|
||||
|
|
|
|||
|
|
@ -21,32 +21,34 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(v) {if (!skipRegisterWrites) {writes.push(v); if (dumpWrites) {addWrite(0x200,v);}}}
|
||||
#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}}
|
||||
|
||||
const char* regCheatSheetSN[]={
|
||||
"DATA", "0",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformSMS::getRegisterSheet() {
|
||||
return regCheatSheetSN;
|
||||
}
|
||||
const char* regCheatSheetGG[]={
|
||||
"DATA", "0",
|
||||
"Stereo", "1",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* DivPlatformSMS::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xy: Set noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
const char** DivPlatformSMS::getRegisterSheet() {
|
||||
return stereo?regCheatSheetGG:regCheatSheetSN;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int o=0;
|
||||
int oL=0;
|
||||
int oR=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!writes.empty()) {
|
||||
unsigned char w=writes.front();
|
||||
YMPSG_Write(&sn_nuked,w);
|
||||
QueuedWrite w=writes.front();
|
||||
if (w.addr==0) {
|
||||
YMPSG_Write(&sn_nuked,w.val);
|
||||
} else if (w.addr==1) {
|
||||
YMPSG_WriteStereo(&sn_nuked,w.val);
|
||||
}
|
||||
writes.pop();
|
||||
}
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
|
|
@ -65,10 +67,13 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
YMPSG_Clock(&sn_nuked);
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
o=YMPSG_GetOutput(&sn_nuked);
|
||||
if (o<-32768) o=-32768;
|
||||
if (o>32767) o=32767;
|
||||
bufL[h]=o;
|
||||
YMPSG_GetOutput(&sn_nuked,&oL,&oR);
|
||||
if (oL<-32768) oL=-32768;
|
||||
if (oL>32767) oL=32767;
|
||||
if (oR<-32768) oR=-32768;
|
||||
if (oR>32767) oR=32767;
|
||||
bufL[h]=oL;
|
||||
bufR[h]=oR;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -81,12 +86,20 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
|
||||
void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
while (!writes.empty()) {
|
||||
unsigned char w=writes.front();
|
||||
sn->write(w);
|
||||
QueuedWrite w=writes.front();
|
||||
if (stereo && (w.addr==1))
|
||||
sn->stereo_w(w.val);
|
||||
else if (w.addr==0) {
|
||||
sn->write(w.val);
|
||||
}
|
||||
writes.pop();
|
||||
}
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
sn->sound_stream_update(bufL+h,1);
|
||||
short* outs[2]={
|
||||
&bufL[h],
|
||||
&bufR[h]
|
||||
};
|
||||
sn->sound_stream_update(outs,1);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -105,42 +118,26 @@ void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
}
|
||||
|
||||
int DivPlatformSMS::acquireOne() {
|
||||
short v;
|
||||
sn->sound_stream_update(&v,1);
|
||||
return v;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (i==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
double CHIP_DIVIDER=toneDivider;
|
||||
if (i==3) CHIP_DIVIDER=noiseDivider;
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15));
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].std.vol.val,chan[i].vol,15);
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
// old formula
|
||||
// ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4;
|
||||
rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
|
||||
rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -160,6 +157,13 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (stereo) {
|
||||
if (chan[i].std.panL.had) {
|
||||
lastPan&=~(0x11<<i);
|
||||
lastPan|=((chan[i].std.panL.val&1)<<i)|(((chan[i].std.panL.val>>1)&1)<<(i+4));
|
||||
rWrite(1,lastPan);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
|
|
@ -172,12 +176,16 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,toneDivider);
|
||||
if (chan[i].freq>1023) chan[i].freq=1023;
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
if (parent->song.snNoLowPeriods) {
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
} else {
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||
rWrite(0x80|i<<5|(chan[i].freq&15));
|
||||
rWrite(chan[i].freq>>4);
|
||||
rWrite(0,0x80|i<<5|(chan[i].freq&15));
|
||||
rWrite(0,chan[i].freq>>4);
|
||||
// what?
|
||||
/*if (i==2 && snNoiseMode&2) {
|
||||
chan[3].baseFreq=chan[2].baseFreq;
|
||||
|
|
@ -187,41 +195,39 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[3].freqChanged || updateSNMode) {
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,noiseDivider);
|
||||
if (chan[3].freq>1023) chan[3].freq=1023;
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
if (parent->song.snNoLowPeriods) {
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
}
|
||||
if (snNoiseMode&2) { // take period from channel 3
|
||||
if (updateSNMode || resetPhase) {
|
||||
if (snNoiseMode&1) {
|
||||
rWrite(0xe7);
|
||||
rWrite(0,0xe7);
|
||||
} else {
|
||||
rWrite(0xe3);
|
||||
rWrite(0,0xe3);
|
||||
}
|
||||
if (updateSNMode) {
|
||||
rWrite(0xdf);
|
||||
rWrite(0,0xdf);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[3].freqChanged) {
|
||||
rWrite(0xc0|(chan[3].freq&15));
|
||||
rWrite(chan[3].freq>>4);
|
||||
rWrite(0,0xc0|(chan[3].freq&15));
|
||||
rWrite(0,chan[3].freq>>4);
|
||||
}
|
||||
} 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) {
|
||||
value=2-value;
|
||||
if (value!=oldValue || updateSNMode || resetPhase) {
|
||||
oldValue=value;
|
||||
rWrite(0xe0|value|((snNoiseMode&1)<<2));
|
||||
rWrite(0,0xe0|value|((snNoiseMode&1)<<2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,8 +237,8 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
|
||||
int DivPlatformSMS::dispatch(DivCommand c) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (c.chan==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
double CHIP_DIVIDER=toneDivider;
|
||||
if (c.chan==3) CHIP_DIVIDER=noiseDivider;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -242,7 +248,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
chan[c.chan].actualNote=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
|
|
@ -250,7 +256,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0x9f|c.chan<<5);
|
||||
rWrite(0,0x9f|c.chan<<5);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
|
|
@ -267,7 +273,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
|
@ -307,6 +313,19 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
snNoiseMode=(c.value&1)|((c.value&16)>>3);
|
||||
updateSNMode=true;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (stereo) {
|
||||
if (c.chan>3) c.chan=3;
|
||||
lastPan&=~(0x11<<c.chan);
|
||||
int pan=0;
|
||||
if (c.value>0) pan|=0x10;
|
||||
if (c.value2>0) pan|=0x01;
|
||||
if (pan==0) pan=0x11;
|
||||
lastPan|=pan<<c.chan;
|
||||
rWrite(1,lastPan);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
@ -317,9 +336,8 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
// TODO: pre porta cancel arp compat flag
|
||||
//if (chan[c.chan].inPorta) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
|
|
@ -335,7 +353,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformSMS::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (chan[ch].active) rWrite(0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
|
||||
if (chan[ch].active) rWrite(0,0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
|
||||
}
|
||||
|
||||
void DivPlatformSMS::forceIns() {
|
||||
|
|
@ -370,11 +388,19 @@ void DivPlatformSMS::reset() {
|
|||
addWrite(0xffffffff,0);
|
||||
}
|
||||
sn->device_start();
|
||||
YMPSG_Init(&sn_nuked,isRealSN);
|
||||
YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767);
|
||||
snNoiseMode=3;
|
||||
rWrite(0xe7);
|
||||
rWrite(0,0xe7);
|
||||
updateSNMode=false;
|
||||
oldValue=0xff;
|
||||
lastPan=0xff;
|
||||
if (stereo) {
|
||||
rWrite(1,0xff);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformSMS::isStereo() {
|
||||
return stereo;
|
||||
}
|
||||
|
||||
bool DivPlatformSMS::keyOffAffectsArp(int ch) {
|
||||
|
|
@ -396,45 +422,109 @@ void DivPlatformSMS::notifyInsDeletion(void* ins) {
|
|||
}
|
||||
|
||||
void DivPlatformSMS::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(val);
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformSMS::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.val);
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformSMS::setFlags(unsigned int flags) {
|
||||
if ((flags&3)==3) {
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
} else if ((flags&3)==2) {
|
||||
chipClock=4000000;
|
||||
} else if ((flags&3)==1) {
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
switch (flags&0xff03) {
|
||||
default:
|
||||
case 0x0000:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 0x0001:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
break;
|
||||
case 0x0002:
|
||||
chipClock=4000000;
|
||||
break;
|
||||
case 0x0003:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 0x0100:
|
||||
chipClock=3000000;
|
||||
break;
|
||||
case 0x0101:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 0x0102:
|
||||
chipClock=COLOR_NTSC/8.0;
|
||||
break;
|
||||
}
|
||||
resetPhase=!(flags&16);
|
||||
|
||||
divider=16;
|
||||
toneDivider=64.0;
|
||||
noiseDivider=64.0;
|
||||
if (sn!=NULL) delete sn;
|
||||
switch ((flags>>2)&3) {
|
||||
case 1: // TI
|
||||
sn=new sn76496_base_device(0x4000, 0x4000, 0x01, 0x02, true, 1, false, true);
|
||||
isRealSN=true;
|
||||
break;
|
||||
case 2: // TI+Atari
|
||||
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, 1, false, true);
|
||||
isRealSN=true;
|
||||
break;
|
||||
case 3: // Game Gear (not fully emulated yet!)
|
||||
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
|
||||
isRealSN=false;
|
||||
break;
|
||||
switch (flags&0xcc) {
|
||||
default: // Sega
|
||||
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
|
||||
case 0x00:
|
||||
sn=new segapsg_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
break;
|
||||
case 0x04: // TI SN76489
|
||||
sn=new sn76489_device();
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
noiseDivider=60.0; // 64 for match to tone frequency on non-Sega PSG but compatibility
|
||||
break;
|
||||
case 0x08: // TI+Atari
|
||||
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, false, 1/*8*/, false, true);
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x0c: // Game Gear (not fully emulated yet!)
|
||||
sn=new gamegear_device();
|
||||
isRealSN=false;
|
||||
stereo=true;
|
||||
break;
|
||||
case 0x40: // TI SN76489A
|
||||
sn=new sn76489a_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x44: // TI SN76496
|
||||
sn=new sn76496_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x48: // NCR 8496
|
||||
sn=new ncr8496_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x4c: // Tandy PSSJ 3-voice sound
|
||||
sn=new pssj3_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x80: // TI SN94624
|
||||
sn=new sn94624_device();
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
divider=2;
|
||||
toneDivider=8.0;
|
||||
noiseDivider=7.5;
|
||||
break;
|
||||
case 0x84: // TI SN76494
|
||||
sn=new sn76494_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
divider=2;
|
||||
toneDivider=8.0;
|
||||
noiseDivider=7.5;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/16;
|
||||
rate=chipClock/divider;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
@ -450,6 +540,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
skipRegisterWrites=false;
|
||||
resetPhase=false;
|
||||
oldValue=0xff;
|
||||
lastPan=0xff;
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
|
|
|
|||
|
|
@ -58,21 +58,31 @@ class DivPlatformSMS: public DivDispatch {
|
|||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
unsigned char lastPan;
|
||||
unsigned char oldValue;
|
||||
unsigned char snNoiseMode;
|
||||
int divider=16;
|
||||
double toneDivider=64.0;
|
||||
double noiseDivider=64.0;
|
||||
bool updateSNMode;
|
||||
bool resetPhase;
|
||||
bool isRealSN;
|
||||
bool stereo;
|
||||
bool nuked;
|
||||
sn76496_base_device* sn;
|
||||
ympsg_t sn_nuked;
|
||||
std::queue<unsigned char> writes;
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_mame(short* bufL, short* bufR, size_t start, size_t len);
|
||||
public:
|
||||
int acquireOne();
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
|
|
@ -82,6 +92,7 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
int getPortaFloor(int ch);
|
||||
|
|
@ -90,7 +101,6 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setNuked(bool value);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
|
|
|
|||
|
|
@ -924,6 +924,7 @@ float ay8910_device::mix_3D()
|
|||
indx |= tone_mask | (m_vol_enabled[chan] ? tone_volume(tone) << (chan*5) : 0);
|
||||
}
|
||||
}
|
||||
lastIndx=indx;
|
||||
return m_vol3d_table[indx];
|
||||
}
|
||||
|
||||
|
|
@ -1359,6 +1360,7 @@ unsigned char ay8910_device::ay8910_read_ym()
|
|||
|
||||
void ay8910_device::device_reset()
|
||||
{
|
||||
lastIndx=0;
|
||||
ay8910_reset_ym();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ public:
|
|||
double m_Kn[32];
|
||||
};
|
||||
|
||||
int lastIndx;
|
||||
|
||||
// internal interface for PSG component of YM device
|
||||
// FIXME: these should be private, but vector06 accesses them directly
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,13 @@ SID::~SID()
|
|||
delete[] fir;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Get DC offset of channel.
|
||||
// ----------------------------------------------------------------------------
|
||||
sound_sample SID::get_dc(int ch) {
|
||||
return voice[ch].getDC();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mute/unmute channel.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public:
|
|||
|
||||
sound_sample last_chan_out[3];
|
||||
|
||||
sound_sample get_dc(int ch);
|
||||
void set_is_muted(int ch, bool val);
|
||||
void set_chip_model(chip_model model);
|
||||
void enable_filter(bool enable);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public:
|
|||
// Amplitude modulated waveform output.
|
||||
// Range [-2048*255, 2047*255].
|
||||
RESID_INLINE sound_sample output();
|
||||
RESID_INLINE sound_sample getDC();
|
||||
|
||||
protected:
|
||||
WaveformGenerator wave;
|
||||
|
|
@ -72,6 +73,12 @@ sound_sample Voice::output()
|
|||
return (wave.output() - wave_zero)*envelope.output() + voice_DC;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
sound_sample Voice::getDC()
|
||||
{
|
||||
return voice_DC;
|
||||
}
|
||||
|
||||
#endif // RESID_INLINING || defined(__VOICE_CC__)
|
||||
|
||||
#endif // not __VOICE_H__
|
||||
|
|
|
|||
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
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue