Merge branch 'master' into ZSMv1
This commit is contained in:
commit
941d45ad80
141 changed files with 6303 additions and 1262 deletions
|
|
@ -23,11 +23,84 @@
|
|||
#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
|
||||
|
||||
void DivEngine::initConfDir() {
|
||||
#ifdef _WIN32
|
||||
// maybe move this function in here instead?
|
||||
configPath=getWinConfigPath();
|
||||
#elif defined (IS_MOBILE)
|
||||
configPath=SDL_GetPrefPath();
|
||||
#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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -399,6 +411,12 @@ class DivDispatch {
|
|||
*/
|
||||
virtual bool getDCOffRequired();
|
||||
|
||||
/**
|
||||
* check whether PRE_NOTE command is desired.
|
||||
* @return truth.
|
||||
*/
|
||||
virtual bool getWantPreNote();
|
||||
|
||||
/**
|
||||
* get a description of a dispatch-specific effect.
|
||||
* @param effect the effect.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -64,9 +65,9 @@
|
|||
#include "platform/scc.h"
|
||||
#include "platform/ymz280b.h"
|
||||
#include "platform/rf5c68.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) {
|
||||
|
|
@ -395,6 +396,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
dispatch=new DivPlatformNamcoWSG;
|
||||
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30);
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "dispatch.h"
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "engine.h"
|
||||
#include "instrument.h"
|
||||
|
|
@ -27,15 +28,11 @@
|
|||
#include "../audio/sdlAudio.h"
|
||||
#endif
|
||||
#include <stdexcept>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#ifdef HAVE_JACK
|
||||
#include "../audio/jack.h"
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#ifdef HAVE_SNDFILE
|
||||
#include "sfWrapper.h"
|
||||
#endif
|
||||
|
|
@ -181,6 +178,308 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
}
|
||||
}
|
||||
|
||||
#define EXPORT_BUFSIZE 2048
|
||||
|
||||
double DivEngine::benchmarkPlayback() {
|
||||
float* outBuf[2];
|
||||
outBuf[0]=new float[EXPORT_BUFSIZE];
|
||||
outBuf[1]=new float[EXPORT_BUFSIZE];
|
||||
|
||||
curOrder=0;
|
||||
prevOrder=0;
|
||||
remainingLoops=1;
|
||||
playSub(false);
|
||||
|
||||
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
|
||||
|
||||
// benchmark
|
||||
while (playing) {
|
||||
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
|
||||
|
||||
delete[] outBuf[0];
|
||||
delete[] outBuf[1];
|
||||
|
||||
double t=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
|
||||
printf("[RESULT] %fs\n",t);
|
||||
return t;
|
||||
}
|
||||
|
||||
double DivEngine::benchmarkSeek() {
|
||||
double t[20];
|
||||
curOrder=curSubSong->ordersLen-1;
|
||||
prevOrder=curSubSong->ordersLen-1;
|
||||
|
||||
// benchmark
|
||||
for (int i=0; i<20; i++) {
|
||||
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
|
||||
playSub(false);
|
||||
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
|
||||
t[i]=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
|
||||
printf("[#%d] %fs\n",i+1,t[i]);
|
||||
}
|
||||
|
||||
double tMin=DBL_MAX;
|
||||
double tMax=0.0;
|
||||
double tAvg=0.0;
|
||||
for (int i=0; i<20; i++) {
|
||||
if (t[i]<tMin) tMin=t[i];
|
||||
if (t[i]>tMax) tMax=t[i];
|
||||
tAvg+=t[i];
|
||||
}
|
||||
tAvg/=20.0;
|
||||
|
||||
printf("[RESULT] min %fs max %fs average %fs\n",tMin,tMax,tAvg);
|
||||
return tAvg;
|
||||
}
|
||||
|
||||
#define WRITE_TICK \
|
||||
if (!wroteTick) { \
|
||||
wroteTick=true; \
|
||||
if (binary) { \
|
||||
if (tick-lastTick>255) { \
|
||||
w->writeC(0xfc); \
|
||||
w->writeS(tick-lastTick); \
|
||||
} else if (tick-lastTick>1) { \
|
||||
w->writeC(0xfd); \
|
||||
w->writeC(tick-lastTick); \
|
||||
} else { \
|
||||
w->writeC(0xfe); \
|
||||
} \
|
||||
} else { \
|
||||
w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \
|
||||
} \
|
||||
lastTick=tick; \
|
||||
}
|
||||
|
||||
void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
||||
w->writeC(c.cmd);
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
if (c.value==DIV_NOTE_NULL) {
|
||||
w->writeC(0xff);
|
||||
} else {
|
||||
w->writeC(c.value+60);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
w->writeC(c.value);
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
w->writeC(c.value);
|
||||
w->writeC(c.value2);
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
w->writeC((c.value?0x80:0)|(c.value2?0x40:0));
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
w->writeS((c.value<<12)|(c.value2&0x7ff));
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
w->writeC((c.value?8:0)|(c.value2&0x77));
|
||||
break;
|
||||
default:
|
||||
logW("unimplemented command %s!",cmdName[c.cmd]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveCommand(bool binary) {
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
setOrder(0);
|
||||
BUSY_BEGIN_SOFT;
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
walkSong(loopOrder,loopRow,loopEnd);
|
||||
logI("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
SafeWriter* w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
// write header
|
||||
if (binary) {
|
||||
w->write("FCS",4);
|
||||
} else {
|
||||
w->writeText("# Furnace Command Stream\n\n");
|
||||
|
||||
w->writeText("[Information]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",song.name));
|
||||
w->writeText(fmt::sprintf("author: %s\n",song.author));
|
||||
w->writeText(fmt::sprintf("category: %s\n",song.category));
|
||||
w->writeText(fmt::sprintf("system: %s\n",song.systemName));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SubSongInformation]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",curSubSong->name));
|
||||
w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SysDefinition]\n");
|
||||
// TODO
|
||||
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
// play the song ourselves
|
||||
bool done=false;
|
||||
playSub(false);
|
||||
|
||||
if (!binary) {
|
||||
w->writeText("[Stream]\n");
|
||||
}
|
||||
int tick=0;
|
||||
bool oldCmdStreamEnabled=cmdStreamEnabled;
|
||||
cmdStreamEnabled=true;
|
||||
double curDivider=divider;
|
||||
int lastTick=0;
|
||||
while (!done) {
|
||||
if (nextTick(false,true) || !playing) {
|
||||
done=true;
|
||||
}
|
||||
// get command stream
|
||||
bool wroteTick=false;
|
||||
if (curDivider!=divider) {
|
||||
curDivider=divider;
|
||||
WRITE_TICK;
|
||||
if (binary) {
|
||||
w->writeC(0xfb);
|
||||
w->writeI((int)(curDivider*65536));
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
|
||||
}
|
||||
}
|
||||
for (DivCommand& i: cmdStream) {
|
||||
switch (i.cmd) {
|
||||
// strip away hinted/useless commands
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA:
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
break;
|
||||
default:
|
||||
WRITE_TICK;
|
||||
if (binary) {
|
||||
w->writeC(i.chan);
|
||||
writePackedCommandValues(w,i);
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmdStream.clear();
|
||||
tick++;
|
||||
}
|
||||
cmdStreamEnabled=oldCmdStreamEnabled;
|
||||
|
||||
if (binary) {
|
||||
w->writeC(0xff);
|
||||
} else {
|
||||
if (!playing) {
|
||||
w->writeText(">> END\n");
|
||||
} else {
|
||||
w->writeText(">> LOOP 0\n");
|
||||
}
|
||||
}
|
||||
|
||||
remainingLoops=-1;
|
||||
playing=false;
|
||||
freelance=false;
|
||||
extValuePresent=false;
|
||||
BUSY_END;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void _runExportThread(DivEngine* caller) {
|
||||
caller->runExportThread();
|
||||
}
|
||||
|
|
@ -189,8 +488,6 @@ bool DivEngine::isExporting() {
|
|||
return exporting;
|
||||
}
|
||||
|
||||
#define EXPORT_BUFSIZE 2048
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
void DivEngine::runExportThread() {
|
||||
size_t fadeOutSamples=got.rate*exportFadeOut;
|
||||
|
|
@ -786,7 +1083,7 @@ void DivEngine::initSongWithDesc(const int* description) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivEngine::createNew(const int* description) {
|
||||
void DivEngine::createNew(const int* description, String sysName) {
|
||||
quitDispatch();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
|
|
@ -796,6 +1093,11 @@ void DivEngine::createNew(const int* description) {
|
|||
if (description!=NULL) {
|
||||
initSongWithDesc(description);
|
||||
}
|
||||
if (sysName=="") {
|
||||
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
|
||||
} else {
|
||||
song.systemName=sysName;
|
||||
}
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
|
|
@ -1879,11 +2181,13 @@ void DivEngine::delInstrument(int index) {
|
|||
song.ins.erase(song.ins.begin()+index);
|
||||
song.insLen=song.ins.size();
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<256; j++) {
|
||||
if (curPat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<curSubSong->patLen; k++) {
|
||||
if (curPat[i].data[j]->data[k][2]>index) {
|
||||
curPat[i].data[j]->data[k][2]--;
|
||||
for (size_t j=0; j<song.subsong.size(); j++) {
|
||||
for (int k=0; k<256; k++) {
|
||||
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
|
||||
for (int l=0; l<song.subsong[j]->patLen; l++) {
|
||||
if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) {
|
||||
song.subsong[j]->pat[i].data[k]->data[l][2]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1894,7 +2198,10 @@ void DivEngine::delInstrument(int index) {
|
|||
}
|
||||
|
||||
int DivEngine::addWave() {
|
||||
if (song.wave.size()>=256) return -1;
|
||||
if (song.wave.size()>=256) {
|
||||
lastError="too many wavetables!";
|
||||
return -1;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
|
|
@ -1906,50 +2213,62 @@ int DivEngine::addWave() {
|
|||
return waveCount;
|
||||
}
|
||||
|
||||
bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
|
||||
int DivEngine::addWavePtr(DivWavetable* which) {
|
||||
if (song.wave.size()>=256) {
|
||||
lastError="too many wavetables!";
|
||||
return false;
|
||||
delete which;
|
||||
return -1;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
int waveCount=(int)song.wave.size();
|
||||
song.wave.push_back(which);
|
||||
song.waveLen=waveCount+1;
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
return song.waveLen;
|
||||
}
|
||||
|
||||
DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
lastError=fmt::sprintf("%s",strerror(errno));
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
unsigned char* buf;
|
||||
ssize_t len;
|
||||
if (fseek(f,0,SEEK_END)!=0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
len=ftell(f);
|
||||
if (len<0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
lastError="file size is invalid!";
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
lastError="file is empty";
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
if (fseek(f,0,SEEK_SET)!=0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
buf=new unsigned char[len];
|
||||
if (fread(buf,1,len,f)!=(size_t)len) {
|
||||
logW("did not read entire wavetable file buffer!");
|
||||
delete[] buf;
|
||||
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
|
|
@ -1977,7 +2296,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
|
|||
lastError="invalid wavetable header/data!";
|
||||
delete wave;
|
||||
delete[] buf;
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
|
|
@ -2018,7 +2337,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
|
|||
} else {
|
||||
delete wave;
|
||||
delete[] buf;
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
|
|
@ -2036,7 +2355,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
|
|||
} else {
|
||||
delete wave;
|
||||
delete[] buf;
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2044,17 +2363,10 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
|
|||
delete wave;
|
||||
delete[] buf;
|
||||
lastError="premature end of file";
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
int waveCount=(int)song.wave.size();
|
||||
song.wave.push_back(wave);
|
||||
song.waveLen=waveCount+1;
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
return true;
|
||||
return wave;
|
||||
}
|
||||
|
||||
void DivEngine::delWave(int index) {
|
||||
|
|
@ -2070,7 +2382,10 @@ void DivEngine::delWave(int index) {
|
|||
}
|
||||
|
||||
int DivEngine::addSample() {
|
||||
if (song.sample.size()>=256) return -1;
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return -1;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
DivSample* sample=new DivSample;
|
||||
|
|
@ -2086,11 +2401,28 @@ int DivEngine::addSample() {
|
|||
return sampleCount;
|
||||
}
|
||||
|
||||
int DivEngine::addSampleFromFile(const char* path) {
|
||||
int DivEngine::addSamplePtr(DivSample* which) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
delete which;
|
||||
return -1;
|
||||
}
|
||||
int sampleCount=(int)song.sample.size();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
song.sample.push_back(which);
|
||||
song.sampleLen=sampleCount+1;
|
||||
saveLock.unlock();
|
||||
renderSamples();
|
||||
BUSY_END;
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
DivSample* DivEngine::sampleFromFile(const char* path) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return NULL;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
||||
|
|
@ -2123,7 +2455,6 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
if (extS==".dmc") { // read as .dmc
|
||||
size_t len=0;
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song.sample.size();
|
||||
sample->name=stripPath;
|
||||
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
|
|
@ -2131,7 +2462,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
|
|
@ -2139,7 +2470,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
|
@ -2149,7 +2480,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
|
|
@ -2157,7 +2488,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
|
|
@ -2165,12 +2496,12 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sample->rate=33144;
|
||||
sample->centerRate=33144;
|
||||
sample->depth=1;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
|
||||
sample->init(len*8);
|
||||
|
||||
if (fread(sample->dataDPCM,1,len,f)==0) {
|
||||
|
|
@ -2178,22 +2509,16 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
saveLock.lock();
|
||||
song.sample.push_back(sample);
|
||||
song.sampleLen=sampleCount+1;
|
||||
saveLock.unlock();
|
||||
renderSamples();
|
||||
BUSY_END;
|
||||
return sampleCount;
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef HAVE_SNDFILE
|
||||
lastError="Furnace was not compiled with libsndfile!";
|
||||
return -1;
|
||||
return NULL;
|
||||
#else
|
||||
SF_INFO si;
|
||||
SFWrapper sfWrap;
|
||||
|
|
@ -2205,15 +2530,15 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
if (err==SF_ERR_SYSTEM) {
|
||||
lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno));
|
||||
} else {
|
||||
lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(err));
|
||||
lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err));
|
||||
}
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
if (si.frames>16777215) {
|
||||
lastError="this sample is too big! max sample size is 16777215.";
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
void* buf=NULL;
|
||||
sf_count_t sampleLen=sizeof(short);
|
||||
|
|
@ -2245,9 +2570,9 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
|
||||
int index=0;
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
sample->depth=8;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
} else {
|
||||
sample->depth=16;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
|
||||
}
|
||||
sample->init(si.frames);
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
|
|
@ -2302,6 +2627,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD)
|
||||
{
|
||||
sample->loopStart=inst.loops[0].start;
|
||||
sample->loopEnd=inst.loops[0].end;
|
||||
if(inst.loops[0].end < (unsigned int)sampleCount)
|
||||
sampleCount=inst.loops[0].end;
|
||||
}
|
||||
|
|
@ -2310,16 +2636,181 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
if (sample->centerRate<4000) sample->centerRate=4000;
|
||||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
sfWrap.doClose();
|
||||
saveLock.lock();
|
||||
song.sample.push_back(sample);
|
||||
song.sampleLen=sampleCount+1;
|
||||
saveLock.unlock();
|
||||
renderSamples();
|
||||
BUSY_END;
|
||||
return sampleCount;
|
||||
return sample;
|
||||
#endif
|
||||
}
|
||||
|
||||
DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return NULL;
|
||||
}
|
||||
if (channels<1) {
|
||||
lastError="invalid channel count";
|
||||
return NULL;
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (channels!=1) {
|
||||
lastError="channel count has to be 1 for non-8/16-bit format";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
||||
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
|
||||
if (pathRedux==NULL) {
|
||||
pathRedux=path;
|
||||
} else {
|
||||
pathRedux++;
|
||||
}
|
||||
String stripPath;
|
||||
const char* pathReduxEnd=strrchr(pathRedux,'.');
|
||||
if (pathReduxEnd==NULL) {
|
||||
stripPath=pathRedux;
|
||||
} else {
|
||||
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
|
||||
stripPath+=*i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t len=0;
|
||||
size_t lenDivided=0;
|
||||
DivSample* sample=new DivSample;
|
||||
sample->name=stripPath;
|
||||
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lenDivided=len/channels;
|
||||
|
||||
unsigned int samples=lenDivided;
|
||||
switch (depth) {
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
samples=lenDivided*8;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
samples=lenDivided*2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
samples=lenDivided;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
samples=16*((lenDivided+8)/9);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
samples=(lenDivided+1)/2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (samples>16777215) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="this sample is too big! max sample size is 16777215.";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sample->rate=32000;
|
||||
sample->centerRate=32000;
|
||||
sample->depth=depth;
|
||||
sample->init(samples);
|
||||
|
||||
unsigned char* buf=new unsigned char[len];
|
||||
if (fread(buf,1,len,f)==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete[] buf;
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// import sample
|
||||
size_t pos=0;
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
int accum=0;
|
||||
for (int j=0; j<channels; j++) {
|
||||
if (pos+1>=len) break;
|
||||
if (bigEndian) {
|
||||
accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0));
|
||||
} else {
|
||||
accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0));
|
||||
}
|
||||
pos+=2;
|
||||
}
|
||||
accum/=channels;
|
||||
sample->data16[i]=accum;
|
||||
}
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
int accum=0;
|
||||
for (int j=0; j<channels; j++) {
|
||||
if (pos>=len) break;
|
||||
accum+=(signed char)(buf[pos++]^(unsign?0x80:0));
|
||||
}
|
||||
accum/=channels;
|
||||
sample->data8[i]=accum;
|
||||
}
|
||||
} else {
|
||||
memcpy(sample->getCurBuf(),buf,len);
|
||||
}
|
||||
delete[] buf;
|
||||
|
||||
BUSY_END;
|
||||
return sample;
|
||||
}
|
||||
|
||||
void DivEngine::delSample(int index) {
|
||||
BUSY_BEGIN;
|
||||
sPreview.sample=-1;
|
||||
|
|
@ -2497,13 +2988,15 @@ void DivEngine::moveOrderDown() {
|
|||
|
||||
void DivEngine::exchangeIns(int one, int two) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<256; j++) {
|
||||
if (curPat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<curSubSong->patLen; k++) {
|
||||
if (curPat[i].data[j]->data[k][2]==one) {
|
||||
curPat[i].data[j]->data[k][2]=two;
|
||||
} else if (curPat[i].data[j]->data[k][2]==two) {
|
||||
curPat[i].data[j]->data[k][2]=one;
|
||||
for (size_t j=0; j<song.subsong.size(); j++) {
|
||||
for (int k=0; k<256; k++) {
|
||||
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
|
||||
for (int l=0; l<curSubSong->patLen; l++) {
|
||||
if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) {
|
||||
song.subsong[j]->pat[i].data[k]->data[l][2]=two;
|
||||
} else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) {
|
||||
song.subsong[j]->pat[i].data[k]->data[l][2]=one;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2927,36 +3420,6 @@ void DivEngine::quitDispatch() {
|
|||
BUSY_END;
|
||||
}
|
||||
|
||||
#define CHECK_CONFIG_DIR_MAC() \
|
||||
configPath+="/Library/Application Support/Furnace"; \
|
||||
if (stat(configPath.c_str(),&st)<0) { \
|
||||
logI("creating config dir..."); \
|
||||
if (mkdir(configPath.c_str(),0755)<0) { \
|
||||
logW("could not make config dir! (%s)",strerror(errno)); \
|
||||
configPath="."; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CHECK_CONFIG_DIR() \
|
||||
configPath+="/.config"; \
|
||||
if (stat(configPath.c_str(),&st)<0) { \
|
||||
logI("creating user config dir..."); \
|
||||
if (mkdir(configPath.c_str(),0755)<0) { \
|
||||
logW("could not make user config dir! (%s)",strerror(errno)); \
|
||||
configPath="."; \
|
||||
} \
|
||||
} \
|
||||
if (configPath!=".") { \
|
||||
configPath+="/furnace"; \
|
||||
if (stat(configPath.c_str(),&st)<0) { \
|
||||
logI("creating config dir..."); \
|
||||
if (mkdir(configPath.c_str(),0755)<0) { \
|
||||
logW("could not make config dir! (%s)",strerror(errno)); \
|
||||
configPath="."; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
bool DivEngine::initAudioBackend() {
|
||||
// load values
|
||||
if (audioEngine==DIV_AUDIO_NULL) {
|
||||
|
|
@ -2969,6 +3432,7 @@ bool DivEngine::initAudioBackend() {
|
|||
|
||||
lowQuality=getConfInt("audioQuality",0);
|
||||
forceMono=getConfInt("forceMono",0);
|
||||
clampSamples=getConfInt("clampSamples",0);
|
||||
lowLatency=getConfInt("lowLatency",0);
|
||||
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
||||
if (metroVol<0.0f) metroVol=0.0f;
|
||||
|
|
@ -3065,6 +3529,7 @@ bool DivEngine::initAudioBackend() {
|
|||
|
||||
bool DivEngine::deinitAudioBackend() {
|
||||
if (output!=NULL) {
|
||||
output->quit();
|
||||
if (output->midiIn) {
|
||||
if (output->midiIn->isDeviceOpen()) {
|
||||
logI("closing MIDI input.");
|
||||
|
|
@ -3078,53 +3543,19 @@ bool DivEngine::deinitAudioBackend() {
|
|||
}
|
||||
}
|
||||
output->quitMidi();
|
||||
output->quit();
|
||||
delete output;
|
||||
output=NULL;
|
||||
audioEngine=DIV_AUDIO_NULL;
|
||||
//audioEngine=DIV_AUDIO_NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "winStuff.h"
|
||||
#endif
|
||||
|
||||
bool DivEngine::init() {
|
||||
// register systems
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
|
||||
// init config
|
||||
#ifdef _WIN32
|
||||
configPath=getWinConfigPath();
|
||||
#elif defined(IS_MOBILE)
|
||||
configPath=SDL_GetPrefPath("tildearrow","furnace");
|
||||
#else
|
||||
struct stat st;
|
||||
char* home=getenv("HOME");
|
||||
if (home==NULL) {
|
||||
int uid=getuid();
|
||||
struct passwd* entry=getpwuid(uid);
|
||||
if (entry==NULL) {
|
||||
logW("unable to determine config directory! (%s)",strerror(errno));
|
||||
configPath=".";
|
||||
} else {
|
||||
configPath=entry->pw_dir;
|
||||
#ifdef __APPLE__
|
||||
CHECK_CONFIG_DIR_MAC();
|
||||
#else
|
||||
CHECK_CONFIG_DIR();
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
configPath=home;
|
||||
#ifdef __APPLE__
|
||||
CHECK_CONFIG_DIR_MAC();
|
||||
#else
|
||||
CHECK_CONFIG_DIR();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
initConfDir();
|
||||
logD("config path: %s",configPath.c_str());
|
||||
|
||||
loadConf();
|
||||
|
|
@ -3140,6 +3571,12 @@ bool DivEngine::init() {
|
|||
preset.push_back(0);
|
||||
initSongWithDesc(preset.data());
|
||||
}
|
||||
String sysName=getConfString("initialSysName","");
|
||||
if (sysName=="") {
|
||||
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
|
||||
} else {
|
||||
song.systemName=sysName;
|
||||
}
|
||||
hasLoadedSomething=true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,11 +45,15 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "0.6pre1 (dev101)"
|
||||
#define DIV_ENGINE_VERSION 101
|
||||
#define DIV_VERSION "dev106"
|
||||
#define DIV_ENGINE_VERSION 106
|
||||
|
||||
// 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,7 +64,7 @@ enum DivStatusView {
|
|||
enum DivAudioEngines {
|
||||
DIV_AUDIO_JACK=0,
|
||||
DIV_AUDIO_SDL=1,
|
||||
|
||||
|
||||
DIV_AUDIO_NULL=126,
|
||||
DIV_AUDIO_DUMMY=127
|
||||
};
|
||||
|
|
@ -160,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];
|
||||
|
|
@ -178,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},
|
||||
|
|
@ -276,6 +284,8 @@ enum DivChanTypes {
|
|||
DIV_CH_OP=5
|
||||
};
|
||||
|
||||
extern const char* cmdName[];
|
||||
|
||||
class DivEngine {
|
||||
DivDispatchContainer disCont[32];
|
||||
TAAudio* output;
|
||||
|
|
@ -297,6 +307,7 @@ class DivEngine {
|
|||
bool stopExport;
|
||||
bool halted;
|
||||
bool forceMono;
|
||||
bool clampSamples;
|
||||
bool cmdStreamEnabled;
|
||||
bool softLocked;
|
||||
bool firstTick;
|
||||
|
|
@ -355,6 +366,7 @@ class DivEngine {
|
|||
short vibTable[64];
|
||||
int reversePitchTable[4096];
|
||||
int pitchTable[4096];
|
||||
char c163NameCS[1024];
|
||||
int midiBaseChan;
|
||||
bool midiPoly;
|
||||
size_t midiAgeCounter;
|
||||
|
|
@ -396,6 +408,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);
|
||||
|
|
@ -453,7 +466,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.
|
||||
|
|
@ -465,9 +478,11 @@ 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 to ZSM.
|
||||
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
|
||||
// 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
|
||||
|
|
@ -479,9 +494,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();
|
||||
|
||||
|
|
@ -573,14 +595,14 @@ 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);
|
||||
|
||||
// get japanese system name
|
||||
const char* getSystemNameJ(DivSystem sys);
|
||||
|
||||
|
||||
// convert sample rate format
|
||||
int fileToDivRate(int frate);
|
||||
int divToFileRate(int drate);
|
||||
|
|
@ -657,7 +679,7 @@ class DivEngine {
|
|||
|
||||
// is playing
|
||||
bool isPlaying();
|
||||
|
||||
|
||||
// is running
|
||||
bool isRunning();
|
||||
|
||||
|
|
@ -686,8 +708,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);
|
||||
|
|
@ -695,8 +720,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);
|
||||
|
|
@ -735,7 +766,7 @@ class DivEngine {
|
|||
void autoNoteOn(int chan, int ins, int note, int vol=-1);
|
||||
void autoNoteOff(int chan, int note, int vol=-1);
|
||||
void autoNoteOffAll();
|
||||
|
||||
|
||||
// set whether autoNoteIn is mono or poly
|
||||
void setAutoNotePoly(bool poly);
|
||||
|
||||
|
|
@ -756,7 +787,7 @@ class DivEngine {
|
|||
|
||||
// get dispatch channel state
|
||||
void* getDispatchChanState(int chan);
|
||||
|
||||
|
||||
// get register pool
|
||||
unsigned char* getRegisterPool(int sys, int& size, int& depth);
|
||||
|
||||
|
|
@ -792,7 +823,7 @@ class DivEngine {
|
|||
|
||||
// set the console mode.
|
||||
void setConsoleMode(bool enable);
|
||||
|
||||
|
||||
// get metronome
|
||||
bool getMetronome();
|
||||
|
||||
|
|
@ -853,7 +884,7 @@ class DivEngine {
|
|||
|
||||
// remove system
|
||||
bool removeSystem(int index, bool preserveOrder=true);
|
||||
|
||||
|
||||
// write to register on system
|
||||
void poke(int sys, unsigned int addr, unsigned short val);
|
||||
|
||||
|
|
@ -865,7 +896,7 @@ class DivEngine {
|
|||
|
||||
// get warnings
|
||||
String getWarnings();
|
||||
|
||||
|
||||
// switch master
|
||||
bool switchMaster();
|
||||
|
||||
|
|
|
|||
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,
|
||||
|
|
@ -732,6 +733,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]);
|
||||
}
|
||||
|
|
@ -1262,7 +1265,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));
|
||||
|
|
@ -1280,7 +1283,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
|
||||
|
|
@ -1497,6 +1500,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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -387,8 +387,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
|
||||
|
|
@ -524,6 +528,21 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
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);
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
w->writeI(blockEndSeek-blockStartSeek-4);
|
||||
|
|
@ -932,8 +951,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1067,6 +1090,27 @@ 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();
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -1106,3 +1150,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,12 +261,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 +326,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 +347,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 +360,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 +370,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 +457,14 @@ struct DivInstrumentWaveSynth {
|
|||
param4(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentSoundUnit {
|
||||
bool useSample;
|
||||
bool switchRoles;
|
||||
DivInstrumentSoundUnit():
|
||||
useSample(false),
|
||||
switchRoles(false) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
bool mode;
|
||||
|
|
@ -443,6 +478,7 @@ struct DivInstrument {
|
|||
DivInstrumentFDS fds;
|
||||
DivInstrumentMultiPCM multipcm;
|
||||
DivInstrumentWaveSynth ws;
|
||||
DivInstrumentSoundUnit su;
|
||||
|
||||
/**
|
||||
* save the instrument to a SafeWriter.
|
||||
|
|
@ -464,6 +500,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) {
|
||||
|
|
|
|||
|
|
@ -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,6 +90,10 @@ bool DivDispatch::getDCOffRequired() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DivDispatch::getWantPreNote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* DivDispatch::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,12 +114,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;
|
||||
|
|
|
|||
|
|
@ -513,6 +513,10 @@ bool DivPlatformC64::getDCOffRequired() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformC64::getWantPreNote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformC64::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformC64::Channel();
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch {
|
|||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
bool getWantPreNote();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -84,6 +84,12 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) {
|
|||
|
||||
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 +103,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,8 +158,32 @@ 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) {
|
||||
|
|
@ -180,10 +211,9 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
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 +243,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 +257,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 +328,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 +344,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 +396,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 +408,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 +444,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:
|
||||
|
|
@ -462,7 +605,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
|
||||
|
|
@ -471,12 +614,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) {
|
||||
|
|
@ -489,7 +643,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -507,6 +661,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;
|
||||
|
|
@ -518,7 +690,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,7 +106,9 @@ 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);
|
||||
|
|
@ -88,6 +116,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -153,14 +153,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);
|
||||
}
|
||||
|
|
@ -200,14 +199,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;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
|
||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
return DivPlatformGenesis::dispatch(c);
|
||||
|
|
@ -418,6 +420,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -478,6 +490,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);
|
||||
}
|
||||
|
||||
|
|
@ -525,7 +544,7 @@ 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));
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -158,12 +158,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,12 +62,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 {
|
||||
|
|
@ -172,7 +171,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 +201,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 +282,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 +315,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;
|
||||
|
|
|
|||
|
|
@ -108,12 +108,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 { \
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ 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;
|
||||
}
|
||||
|
|
@ -771,7 +771,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);
|
||||
|
|
@ -807,7 +807,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
|
||||
int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
|
||||
immWrite(16,freq&0xff);
|
||||
immWrite(17,(freq>>8)&0xff);
|
||||
|
|
@ -872,6 +872,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;
|
||||
|
|
|
|||
|
|
@ -90,12 +90,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 +115,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 +133,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 +158,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);
|
||||
|
|
@ -220,8 +233,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -557,10 +574,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) {
|
||||
|
|
@ -579,8 +604,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;
|
||||
}
|
||||
|
|
@ -589,7 +614,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;
|
||||
|
|
|
|||
368
src/engine/platform/pcmdac.cpp
Normal file
368
src/engine/platform/pcmdac.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/**
|
||||
* 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) {
|
||||
if (chan.std.arp.mode) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.std.arp.val);
|
||||
} else {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.note+chan.std.arp.val);
|
||||
}
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
} else {
|
||||
if (chan.std.arp.mode && chan.std.arp.finished) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(chan.note);
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan.useWave && chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) {
|
||||
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
|
||||
|
|
@ -133,8 +133,14 @@ 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)-2;
|
||||
if (chan.freq>65535) chan.freq=65535;
|
||||
|
|
|
|||
|
|
@ -301,7 +301,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;
|
||||
}
|
||||
|
|
@ -358,14 +358,14 @@ 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);
|
||||
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
|
||||
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
|
||||
//logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
// Write sample address. Enable volume
|
||||
if (!chan[i].std.vol.had) {
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4);
|
||||
|
|
|
|||
|
|
@ -142,7 +142,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);
|
||||
|
|
@ -393,7 +393,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;
|
||||
|
|
|
|||
|
|
@ -56,12 +56,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;
|
||||
|
|
@ -202,7 +200,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 +233,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);
|
||||
|
|
|
|||
|
|
@ -1180,11 +1180,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
if ((value & 0x80)) {
|
||||
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
|
||||
reads from it. */
|
||||
/*if (!CGB &&
|
||||
if (!CGB &&
|
||||
gb->apu.is_active[GB_WAVE] &&
|
||||
gb->apu.wave_channel.sample_countdown == 0 &&
|
||||
gb->apu.wave_channel.enable) {
|
||||
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;*/
|
||||
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
|
||||
|
||||
/* This glitch varies between models and even specific instances:
|
||||
DMG-B: Most of them behave as emulated. A few behave differently.
|
||||
|
|
@ -1193,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
|
||||
Additionally, I believe DMGs, including those we behave differently than emulated,
|
||||
are all deterministic. */
|
||||
/*if (offset < 4) {
|
||||
if (offset < 4) {
|
||||
gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
|
||||
gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
|
||||
gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
|
||||
|
|
@ -1206,7 +1206,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
|
||||
8);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
if (!gb->apu.is_active[GB_WAVE]) {
|
||||
gb->apu.is_active[GB_WAVE] = true;
|
||||
update_sample(gb, GB_WAVE,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ extern "C" {
|
|||
|
||||
#define GB_STRUCT_VERSION 13
|
||||
|
||||
#define CGB 0
|
||||
#define CGB (gb->model&GB_MODEL_CGB_FAMILY)
|
||||
|
||||
#define GB_MODEL_FAMILY_MASK 0xF00
|
||||
#define GB_MODEL_DMG_FAMILY 0x000
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
|
|
|
|||
|
|
@ -26,37 +26,11 @@
|
|||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace Lynx
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
|
||||
namespace
|
||||
{
|
||||
#include <intrin.h>
|
||||
|
||||
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
|
||||
|
||||
#if defined ( __cpp_lib_bitops )
|
||||
|
||||
#define popcnt(X) std::popcount(X)
|
||||
|
||||
#elif defined( _MSC_VER )
|
||||
|
||||
# include <intrin.h>
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
{
|
||||
return __popcnt( x );
|
||||
}
|
||||
|
||||
#elif defined( __GNUC__ )
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
{
|
||||
return __builtin_popcount( x );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
static uint32_t popcnt_generic( uint32_t x )
|
||||
{
|
||||
int v = 0;
|
||||
while ( x != 0 )
|
||||
|
|
@ -67,8 +41,61 @@ uint32_t popcnt( uint32_t x )
|
|||
return v;
|
||||
}
|
||||
|
||||
#if defined( _M_IX86 ) || defined( _M_X64 )
|
||||
|
||||
static uint32_t popcnt_intrinsic( uint32_t x )
|
||||
{
|
||||
return __popcnt( x );
|
||||
}
|
||||
|
||||
static uint32_t( *popcnt )( uint32_t );
|
||||
|
||||
//detecting popcnt availability on msvc intel
|
||||
static void selectPOPCNT()
|
||||
{
|
||||
int info[4];
|
||||
__cpuid( info, 1 );
|
||||
if ( ( info[2] & ( (int)1 << 23 ) ) != 0 )
|
||||
{
|
||||
popcnt = &popcnt_intrinsic;
|
||||
}
|
||||
else
|
||||
{
|
||||
popcnt = &popcnt_generic;
|
||||
}
|
||||
}
|
||||
|
||||
#else //defined( _M_IX86 ) || defined( _M_X64 )
|
||||
|
||||
//MSVC non INTEL should use generic implementation
|
||||
inline void selectPOPCNT()
|
||||
{
|
||||
}
|
||||
|
||||
#define popcnt popcnt_generic
|
||||
|
||||
#endif
|
||||
|
||||
#else //defined( _MSC_VER )
|
||||
|
||||
//non MVSC should use builtin implementation
|
||||
|
||||
inline void selectPOPCNT()
|
||||
{
|
||||
}
|
||||
|
||||
#define popcnt __builtin_popcount
|
||||
|
||||
#endif
|
||||
|
||||
namespace Lynx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
|
||||
|
||||
int32_t clamp( int32_t v, int32_t lo, int32_t hi )
|
||||
{
|
||||
return v < lo ? lo : ( v > hi ? hi : v );
|
||||
|
|
@ -513,6 +540,7 @@ private:
|
|||
|
||||
Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate }
|
||||
{
|
||||
selectPOPCNT();
|
||||
enqueueSampling();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
|
||||
|
|
|
|||
|
|
@ -5,9 +5,22 @@
|
|||
#define minval(a,b) (((a)<(b))?(a):(b))
|
||||
#define maxval(a,b) (((a)>(b))?(a):(b))
|
||||
|
||||
#define FILVOL chan[4].special1C
|
||||
#define ILCTRL chan[4].special1D
|
||||
#define ILSIZE chan[5].special1C
|
||||
#define FIL1 chan[5].special1D
|
||||
#define IL1 chan[6].special1C
|
||||
#define IL2 chan[6].special1D
|
||||
#define IL0 chan[7].special1C
|
||||
#define MVOL chan[7].special1D
|
||||
|
||||
void SoundUnit::NextSample(short* l, short* r) {
|
||||
// run channels
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;}
|
||||
if (chan[i].vol==0 && !chan[i].flags.swvol) {
|
||||
fns[i]=0;
|
||||
continue;
|
||||
}
|
||||
if (chan[i].flags.pcm) {
|
||||
ns[i]=pcm[chan[i].pcmpos];
|
||||
} else switch (chan[i].flags.shape) {
|
||||
|
|
@ -48,13 +61,12 @@ void SoundUnit::NextSample(short* l, short* r) {
|
|||
pcmdec[i]-=32768;
|
||||
if (chan[i].pcmpos<chan[i].pcmbnd) {
|
||||
chan[i].pcmpos++;
|
||||
chan[i].wc++;
|
||||
if (chan[i].pcmpos==chan[i].pcmbnd) {
|
||||
if (chan[i].flags.pcmloop) {
|
||||
chan[i].pcmpos=chan[i].pcmrst;
|
||||
}
|
||||
}
|
||||
chan[i].pcmpos&=(SOUNDCHIP_PCM_SIZE-1);
|
||||
chan[i].pcmpos&=(pcmSize-1);
|
||||
} else if (chan[i].flags.pcmloop) {
|
||||
chan[i].pcmpos=chan[i].pcmrst;
|
||||
}
|
||||
|
|
@ -221,16 +233,115 @@ void SoundUnit::NextSample(short* l, short* r) {
|
|||
nsR[i]=0;
|
||||
}
|
||||
}
|
||||
|
||||
// mix
|
||||
tnsL=(nsL[0]+nsL[1]+nsL[2]+nsL[3]+nsL[4]+nsL[5]+nsL[6]+nsL[7])>>2;
|
||||
tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2;
|
||||
|
||||
*l=minval(32767,maxval(-32767,tnsL));
|
||||
*r=minval(32767,maxval(-32767,tnsR));
|
||||
|
||||
IL1=minval(32767,maxval(-32767,tnsL))>>8;
|
||||
IL2=minval(32767,maxval(-32767,tnsR))>>8;
|
||||
|
||||
// write input lines to sample memory
|
||||
if (ILSIZE&64) {
|
||||
if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) {
|
||||
ilBufPeriod=0;
|
||||
unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7);
|
||||
short next;
|
||||
if (ilBufPos<ilLowerBound) ilBufPos=ilLowerBound;
|
||||
switch (ILCTRL&3) {
|
||||
case 0:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL0)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 1:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 2:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 3:
|
||||
ilFeedback0=pcm[ilBufPos];
|
||||
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ILCTRL&4) {
|
||||
if (ILSIZE&128) {
|
||||
tnsL+=ilFeedback1*(signed char)FILVOL;
|
||||
tnsR+=ilFeedback0*(signed char)FILVOL;
|
||||
} else {
|
||||
tnsL+=ilFeedback0*(signed char)FILVOL;
|
||||
tnsR+=ilFeedback1*(signed char)FILVOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dsOut) {
|
||||
tnsL=minval(32767,maxval(-32767,tnsL<<1));
|
||||
tnsR=minval(32767,maxval(-32767,tnsR<<1));
|
||||
|
||||
short accumL=0;
|
||||
short accumR=0;
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if ((tnsL>>8)==0 && dsCounterL>0) dsCounterL=0;
|
||||
dsCounterL+=tnsL>>8;
|
||||
if (dsCounterL>=0) {
|
||||
accumL+=4095;
|
||||
dsCounterL-=127;
|
||||
} else {
|
||||
accumL+=-4095;
|
||||
dsCounterL+=127;
|
||||
}
|
||||
|
||||
if ((tnsR>>8)==0 && dsCounterR>0) dsCounterR=0;
|
||||
dsCounterR+=tnsR>>8;
|
||||
if (dsCounterR>=0) {
|
||||
accumR+=4095;
|
||||
dsCounterR-=127;
|
||||
} else {
|
||||
accumR+=-4095;
|
||||
dsCounterR+=127;
|
||||
}
|
||||
}
|
||||
|
||||
*l=accumL;
|
||||
*r=accumR;
|
||||
} else {
|
||||
*l=minval(32767,maxval(-32767,tnsL));
|
||||
*r=minval(32767,maxval(-32767,tnsR));
|
||||
}
|
||||
}
|
||||
|
||||
void SoundUnit::Init() {
|
||||
void SoundUnit::Init(int sampleMemSize, bool dsOutMode) {
|
||||
pcmSize=sampleMemSize;
|
||||
dsOut=dsOutMode;
|
||||
Reset();
|
||||
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
|
||||
memset(pcm,0,pcmSize);
|
||||
for (int i=0; i<256; i++) {
|
||||
SCsine[i]=sin((i/128.0f)*M_PI)*127;
|
||||
SCtriangle[i]=(i>127)?(255-i):(i);
|
||||
|
|
@ -242,9 +353,6 @@ void SoundUnit::Init() {
|
|||
SCpantabR[128+i]=i-1;
|
||||
}
|
||||
SCpantabR[128]=0;
|
||||
for (int i=0; i<8; i++) {
|
||||
muted[i]=false;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundUnit::Reset() {
|
||||
|
|
@ -272,8 +380,14 @@ void SoundUnit::Reset() {
|
|||
oldflags[i]=0;
|
||||
pcmdec[i]=0;
|
||||
}
|
||||
dsCounterL=0;
|
||||
dsCounterR=0;
|
||||
tnsL=0;
|
||||
tnsR=0;
|
||||
ilBufPos=0;
|
||||
ilBufPeriod=0;
|
||||
ilFeedback0=0;
|
||||
ilFeedback1=0;
|
||||
memset(chan,0,sizeof(SUChannel)*8);
|
||||
}
|
||||
|
||||
|
|
@ -282,6 +396,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) {
|
|||
}
|
||||
|
||||
SoundUnit::SoundUnit() {
|
||||
Init();
|
||||
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
|
||||
Init(65536); // default
|
||||
for (int i=0; i<8; i++) {
|
||||
muted[i]=false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define SOUNDCHIP_PCM_SIZE 8192
|
||||
|
||||
class SoundUnit {
|
||||
signed char SCsine[256];
|
||||
signed char SCtriangle[256];
|
||||
|
|
@ -22,8 +20,15 @@ class SoundUnit {
|
|||
int nshigh[8];
|
||||
int nsband[8];
|
||||
int tnsL, tnsR;
|
||||
unsigned char ilBufPeriod;
|
||||
unsigned short ilBufPos;
|
||||
signed char ilFeedback0;
|
||||
signed char ilFeedback1;
|
||||
unsigned short oldfreq[8];
|
||||
unsigned short oldflags[8];
|
||||
unsigned int pcmSize;
|
||||
bool dsOut;
|
||||
short dsCounterL, dsCounterR;
|
||||
public:
|
||||
unsigned short resetfreq[8];
|
||||
unsigned short voldcycles[8];
|
||||
|
|
@ -81,11 +86,13 @@ class SoundUnit {
|
|||
unsigned char dir: 1;
|
||||
unsigned char bound;
|
||||
} swcut;
|
||||
unsigned short wc;
|
||||
unsigned char special1C;
|
||||
unsigned char special1D;
|
||||
unsigned short restimer;
|
||||
} chan[8];
|
||||
signed char pcm[SOUNDCHIP_PCM_SIZE];
|
||||
signed char pcm[65536];
|
||||
bool muted[8];
|
||||
void SetIL0(unsigned char addr);
|
||||
void Write(unsigned char addr, unsigned char data);
|
||||
void NextSample(short* l, short* r);
|
||||
inline int GetSample(int ch) {
|
||||
|
|
@ -94,7 +101,7 @@ class SoundUnit {
|
|||
if (ret>32767) ret=32767;
|
||||
return ret;
|
||||
}
|
||||
void Init();
|
||||
void Init(int sampleMemSize=8192, bool dsOutMode=false);
|
||||
void Reset();
|
||||
SoundUnit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define chWrite(c,a,v) rWrite(((c)<<5)|(a),v);
|
||||
|
||||
#define CHIP_DIVIDER 2
|
||||
#define CHIP_FREQBASE 524288
|
||||
|
||||
const char** DivPlatformSoundUnit::getRegisterSheet() {
|
||||
|
|
@ -98,6 +99,13 @@ const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
double DivPlatformSoundUnit::NOTE_SU(int ch, int note) {
|
||||
if (chan[ch].switchRoles) {
|
||||
return NOTE_PERIODIC(note);
|
||||
}
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
while (!writes.empty()) {
|
||||
|
|
@ -137,15 +145,15 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_SU(i,chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_SU(i,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].baseFreq=NOTE_SU(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
|
@ -187,9 +195,21 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
chan[i].control=chan[i].std.ex3.val&15;
|
||||
writeControl(i);
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].syncTimer=chan[i].std.ex4.val&65535;
|
||||
chan[i].timerSync=(chan[i].syncTimer>0);
|
||||
if (chan[i].switchRoles) {
|
||||
chWrite(i,0x00,chan[i].syncTimer&0xff);
|
||||
chWrite(i,0x01,chan[i].syncTimer>>8);
|
||||
} else {
|
||||
chWrite(i,0x1e,chan[i].syncTimer&0xff);
|
||||
chWrite(i,0x1f,chan[i].syncTimer>>8);
|
||||
}
|
||||
writeControlUpper(i);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
|
||||
if (chan[i].pcm) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
// TODO: sample map?
|
||||
|
|
@ -206,14 +226,19 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
chWrite(i,0x00,chan[i].freq&0xff);
|
||||
chWrite(i,0x01,chan[i].freq>>8);
|
||||
if (chan[i].switchRoles) {
|
||||
chWrite(i,0x1e,chan[i].freq&0xff);
|
||||
chWrite(i,0x1f,chan[i].freq>>8);
|
||||
} else {
|
||||
chWrite(i,0x00,chan[i].freq&0xff);
|
||||
chWrite(i,0x01,chan[i].freq>>8);
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].pcm) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
|
||||
if (sample!=NULL) {
|
||||
unsigned int sampleEnd=sample->offSU+sample->samples;
|
||||
unsigned int sampleEnd=sample->offSU+(sample->getEndPosition());
|
||||
unsigned int off=sample->offSU+chan[i].hasOffset;
|
||||
chan[i].hasOffset=0;
|
||||
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
|
||||
|
|
@ -221,7 +246,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
chWrite(i,0x0b,off>>8);
|
||||
chWrite(i,0x0c,sampleEnd&0xff);
|
||||
chWrite(i,0x0d,sampleEnd>>8);
|
||||
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) {
|
||||
if (sample->isLoopable()) {
|
||||
unsigned int sampleLoop=sample->offSU+sample->loopStart;
|
||||
if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1;
|
||||
chWrite(i,0x0e,sampleLoop&0xff);
|
||||
|
|
@ -249,14 +274,15 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
|
||||
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
chan[c.chan].switchRoles=ins->su.switchRoles;
|
||||
if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
|
||||
writeControl(c.chan);
|
||||
writeControlUpper(c.chan);
|
||||
}
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
|
|
@ -414,7 +440,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
int destFreq=NOTE_SU(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
|
||||
|
|
@ -446,7 +472,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].baseFreq=NOTE_SU(c.chan,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;
|
||||
|
|
@ -454,7 +480,7 @@ int DivPlatformSoundUnit::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_SU));
|
||||
}
|
||||
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);
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -478,6 +504,11 @@ void DivPlatformSoundUnit::forceIns() {
|
|||
for (int i=0; i<8; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
||||
// restore channel attributes
|
||||
chWrite(i,0x03,chan[i].pan);
|
||||
writeControl(i);
|
||||
writeControlUpper(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -522,6 +553,16 @@ void DivPlatformSoundUnit::reset() {
|
|||
lfoMode=0;
|
||||
lfoSpeed=255;
|
||||
delay=500;
|
||||
|
||||
// set initial IL status
|
||||
ilCtrl=initIlCtrl;
|
||||
ilSize=initIlSize;
|
||||
fil1=initFil1;
|
||||
echoVol=initEchoVol;
|
||||
rWrite(0x9c,echoVol);
|
||||
rWrite(0x9d,ilCtrl);
|
||||
rWrite(0xbc,ilSize);
|
||||
rWrite(0xbd,fil1);
|
||||
}
|
||||
|
||||
bool DivPlatformSoundUnit::isStereo() {
|
||||
|
|
@ -548,6 +589,15 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
initIlCtrl=3|(flags&4);
|
||||
initIlSize=((flags>>8)&63)|((flags&4)?0x40:0)|((flags&8)?0x80:0);
|
||||
initFil1=flags>>16;
|
||||
initEchoVol=flags>>24;
|
||||
|
||||
sampleMemSize=flags&16;
|
||||
|
||||
su->Init(sampleMemSize?65536:8192,flags&32);
|
||||
renderSamples();
|
||||
}
|
||||
|
||||
void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) {
|
||||
|
|
@ -563,7 +613,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) {
|
|||
}
|
||||
|
||||
size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) {
|
||||
return (index==0)?8192:0;
|
||||
return (index==0)?((sampleMemSize?65536:8192)-((initIlSize&64)?((1+(initIlSize&63))<<7):0)):0;
|
||||
}
|
||||
|
||||
size_t DivPlatformSoundUnit::getSampleMemUsage(int index) {
|
||||
|
|
@ -576,6 +626,7 @@ void DivPlatformSoundUnit::renderSamples() {
|
|||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
if (s->data8==NULL) continue;
|
||||
int paddedLen=s->samples;
|
||||
if (memPos>=getSampleMemCapacity(0)) {
|
||||
logW("out of PCM memory for sample %d!",i);
|
||||
|
|
@ -602,9 +653,8 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
su=new SoundUnit();
|
||||
su->Init();
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
int ins, cutoff, baseCutoff, res, control, hasOffset;
|
||||
signed char pan;
|
||||
unsigned char duty;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset, switchRoles;
|
||||
bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep;
|
||||
unsigned short freqSweepP, volSweepP, cutSweepP;
|
||||
unsigned char freqSweepB, volSweepB, cutSweepB;
|
||||
|
|
@ -67,6 +67,7 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
pcm(false),
|
||||
phaseReset(false),
|
||||
filterPhaseReset(false),
|
||||
switchRoles(false),
|
||||
pcmLoop(false),
|
||||
timerSync(false),
|
||||
freqSweep(false),
|
||||
|
|
@ -96,6 +97,10 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan;
|
||||
bool sampleMemSize;
|
||||
unsigned char ilCtrl, ilSize, fil1;
|
||||
unsigned char initIlCtrl, initIlSize, initFil1;
|
||||
signed char echoVol, initEchoVol;
|
||||
|
||||
int cycles, curChan, delay;
|
||||
short tempL;
|
||||
|
|
@ -104,6 +109,7 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
SoundUnit* su;
|
||||
size_t sampleMemLen;
|
||||
unsigned char regPool[128];
|
||||
double NOTE_SU(int ch, int note);
|
||||
void writeControl(int ch);
|
||||
void writeControlUpper(int ch);
|
||||
|
||||
|
|
|
|||
|
|
@ -83,12 +83,10 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
continue;
|
||||
}
|
||||
rWrite(0x09,(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;
|
||||
}
|
||||
if (s->isLoopable() && dacPos>=s->getEndPosition()) {
|
||||
dacPos=s->loopStart;
|
||||
} else if (dacPos>=s->samples) {
|
||||
dacSample=-1;
|
||||
}
|
||||
dacPeriod-=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,13 +98,11 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
rWritePCMData(tmp_r&0xff);
|
||||
}
|
||||
chan[16].pcm.pos++;
|
||||
if (chan[16].pcm.pos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[16].pcm.pos=s->loopStart;
|
||||
} else {
|
||||
chan[16].pcm.sample=-1;
|
||||
break;
|
||||
}
|
||||
if (s->isLoopable() && chan[16].pcm.pos>=s->getEndPosition()) {
|
||||
chan[16].pcm.pos=s->loopStart;
|
||||
} else if (chan[16].pcm.pos>=s->samples) {
|
||||
chan[16].pcm.sample=-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -269,12 +267,12 @@ int DivPlatformVERA::dispatch(DivCommand c) {
|
|||
chan[16].pcm.pos=0;
|
||||
DivSample* s=parent->getSample(chan[16].pcm.sample);
|
||||
unsigned char ctrl=0x90|chan[16].vol; // always stereo
|
||||
if (s->depth==16) {
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
chan[16].pcm.depth16=true;
|
||||
ctrl|=0x20;
|
||||
} else {
|
||||
chan[16].pcm.depth16=false;
|
||||
if (s->depth!=8) chan[16].pcm.sample=-1;
|
||||
if (s->depth!=DIV_SAMPLE_DEPTH_8BIT) chan[16].pcm.sample=-1;
|
||||
}
|
||||
rWritePCMCtrl(ctrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,13 +77,11 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
chWrite(i,0,0x80|chan[i].dacOut);
|
||||
}
|
||||
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;
|
||||
chWrite(i,0,0);
|
||||
}
|
||||
if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else if (chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
chWrite(i,0,0);
|
||||
}
|
||||
chan[i].dacPeriod-=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -761,7 +761,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
immWrite(0x104,(end>>5)&0xff);
|
||||
immWrite(0x105,(end>>13)&0xff);
|
||||
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2);
|
||||
immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
immWrite(0x100,(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);
|
||||
|
|
@ -796,7 +796,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
immWrite(0x104,(end>>5)&0xff);
|
||||
immWrite(0x105,(end>>13)&0xff);
|
||||
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2);
|
||||
immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
|
||||
immWrite(0x109,freq&0xff);
|
||||
immWrite(0x10a,(freq>>8)&0xff);
|
||||
|
|
|
|||
|
|
@ -793,7 +793,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -828,7 +828,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
|
||||
immWrite(0x19,freq&0xff);
|
||||
immWrite(0x1a,(freq>>8)&0xff);
|
||||
|
|
|
|||
|
|
@ -775,7 +775,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -810,7 +810,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
|
||||
immWrite(0x19,freq&0xff);
|
||||
immWrite(0x1a,(freq>>8)&0xff);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include <math.h>
|
||||
#include <map>
|
||||
|
||||
#define CHIP_FREQBASE 98304
|
||||
#define CHIP_FREQBASE 25165824
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
|
||||
|
||||
|
|
@ -136,50 +136,54 @@ void DivPlatformYMZ280B::tick(bool sysTick) {
|
|||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
unsigned char ctrl;
|
||||
switch (s->depth) {
|
||||
case 3: ctrl=0x20; break;
|
||||
case 8: ctrl=0x40; break;
|
||||
case 16: ctrl=0x60; break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: ctrl=0x20; break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x40; break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x60; break;
|
||||
default: ctrl=0;
|
||||
}
|
||||
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1;
|
||||
chan[i].freq=(int)round(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)/256.0)-1;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>511) chan[i].freq=511;
|
||||
// ADPCM has half the range
|
||||
if (s->depth==3 && chan[i].freq>255) chan[i].freq=255;
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8);
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_YMZ_ADPCM && chan[i].freq>255) chan[i].freq=255;
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|(chan[i].freq>>8);
|
||||
if (chan[i].keyOn) {
|
||||
unsigned int start=s->offYMZ280B;
|
||||
unsigned int loop=0;
|
||||
unsigned int loopStart=0;
|
||||
unsigned int loopEnd=0;
|
||||
unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1);
|
||||
if (chan[i].audPos>0) {
|
||||
switch (s->depth) {
|
||||
case 3: start+=chan[i].audPos/2; break;
|
||||
case 8: start+=chan[i].audPos; break;
|
||||
case 16: start+=chan[i].audPos*2; break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; break;
|
||||
default: break;
|
||||
}
|
||||
start=MIN(start,end);
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
if (s->isLoopable()) {
|
||||
switch (s->depth) {
|
||||
case 3: loop=start+s->loopStart/2; break;
|
||||
case 8: loop=start+s->loopStart; break;
|
||||
case 16: loop=start+s->loopStart*2; break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=start+s->loopStart/2; loopEnd=start+s->loopEnd/2; break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: loopStart=start+s->loopStart; loopEnd=start+s->loopEnd; break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: loopStart=start+s->loopStart*2; loopEnd=start+s->loopEnd*2; break;
|
||||
default: break;
|
||||
}
|
||||
loop=MIN(loop,end);
|
||||
loopEnd=MIN(loopEnd,end);
|
||||
loopStart=MIN(loopStart,loopEnd);
|
||||
}
|
||||
rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first
|
||||
rWrite(0x20+i*4,(start>>16)&0xff);
|
||||
rWrite(0x21+i*4,(loop>>16)&0xff);
|
||||
rWrite(0x22+i*4,(end>>16)&0xff);
|
||||
rWrite(0x21+i*4,(loopStart>>16)&0xff);
|
||||
rWrite(0x22+i*4,(loopEnd>>16)&0xff);
|
||||
rWrite(0x23+i*4,(end>>16)&0xff);
|
||||
rWrite(0x40+i*4,(start>>8)&0xff);
|
||||
rWrite(0x41+i*4,(loop>>8)&0xff);
|
||||
rWrite(0x42+i*4,(end>>8)&0xff);
|
||||
rWrite(0x41+i*4,(loopStart>>8)&0xff);
|
||||
rWrite(0x42+i*4,(loopEnd>>8)&0xff);
|
||||
rWrite(0x43+i*4,(end>>8)&0xff);
|
||||
rWrite(0x60+i*4,start&0xff);
|
||||
rWrite(0x61+i*4,loop&0xff);
|
||||
rWrite(0x62+i*4,end&0xff);
|
||||
rWrite(0x61+i*4,loopStart&0xff);
|
||||
rWrite(0x62+i*4,loopEnd&0xff);
|
||||
rWrite(0x63+i*4,end&0xff);
|
||||
if (!chan[i].std.vol.had) {
|
||||
chan[i].outVol=chan[i].vol;
|
||||
|
|
@ -263,14 +267,15 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
int multiplier=(parent->song.linearPitch==2)?1:256;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
chan[c.chan].baseFreq+=c.value*multiplier;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
chan[c.chan].baseFreq-=c.value*multiplier;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
|
|
@ -327,6 +332,8 @@ void DivPlatformYMZ280B::forceIns() {
|
|||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
|
||||
rWrite(0x03+i*4,chan[i].panning);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,16 @@ const char* cmdName[]={
|
|||
"PRE_PORTA",
|
||||
"PRE_NOTE",
|
||||
|
||||
"HINT_VIBRATO",
|
||||
"HINT_VIBRATO_RANGE",
|
||||
"HINT_VIBRATO_SHAPE",
|
||||
"HINT_PITCH",
|
||||
"HINT_ARPEGGIO",
|
||||
"HINT_VOLUME",
|
||||
"HINT_VOL_SLIDE",
|
||||
"HINT_PORTA",
|
||||
"HINT_LEGATO",
|
||||
|
||||
"SAMPLE_MODE",
|
||||
"SAMPLE_FREQ",
|
||||
"SAMPLE_BANK",
|
||||
|
|
@ -201,7 +211,27 @@ const char* formatNote(unsigned char note, unsigned char octave) {
|
|||
|
||||
int DivEngine::dispatchCmd(DivCommand c) {
|
||||
if (view==DIV_STATUS_COMMANDS) {
|
||||
printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2);
|
||||
if (!skipping) {
|
||||
switch (c.cmd) {
|
||||
// strip away hinted/useless commands
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA:
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
break;
|
||||
default:
|
||||
printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2);
|
||||
}
|
||||
}
|
||||
}
|
||||
totalCmds++;
|
||||
if (cmdStreamEnabled && cmdStream.size()<2000) {
|
||||
|
|
@ -330,14 +360,15 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
// instrument
|
||||
bool insChanged=false;
|
||||
if (pat->data[whatRow][2]!=-1) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
|
||||
if (chan[i].lastIns!=pat->data[whatRow][2]) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
|
||||
chan[i].lastIns=pat->data[whatRow][2];
|
||||
insChanged=true;
|
||||
if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) {
|
||||
logV("forcing volume");
|
||||
chan[i].volume=chan[i].volMax;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -350,11 +381,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
|
@ -371,11 +404,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
|
@ -392,6 +427,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (!chan[i].keyOn) {
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) {
|
||||
chan[i].arp=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
|
||||
}
|
||||
}
|
||||
chan[i].doNote=true;
|
||||
|
|
@ -408,6 +444,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
chan[i].volume=pat->data[whatRow][3]<<8;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -452,11 +489,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
chan[i].portaNote=song.limitSlides?0x60:255;
|
||||
chan[i].portaSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=false;
|
||||
|
|
@ -472,11 +511,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60;
|
||||
chan[i].portaSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=false;
|
||||
|
|
@ -490,6 +531,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
|
|
@ -503,6 +545,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].inPorta=true;
|
||||
chan[i].wasShorthandPorta=false;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
if (chan[i].keyOn) chan[i].doNote=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
|
@ -514,6 +557,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0x04: // vibrato
|
||||
chan[i].vibratoDepth=effectVal&15;
|
||||
chan[i].vibratoRate=effectVal>>4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
|
||||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
break;
|
||||
case 0x07: // tremolo
|
||||
|
|
@ -537,12 +581,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x00: // arpeggio
|
||||
chan[i].arp=effectVal;
|
||||
if (chan[i].arp==0 && song.arp0Reset) {
|
||||
chan[i].resetArp=true;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
|
||||
break;
|
||||
case 0x0c: // retrigger
|
||||
if (effectVal!=0) {
|
||||
|
|
@ -558,7 +604,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz
|
||||
divider=(double)(((effect&0x3)<<8)|effectVal);
|
||||
if (divider<10) divider=10;
|
||||
if (divider<1) divider=1;
|
||||
cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider;
|
||||
clockDrift=0;
|
||||
subticks=0;
|
||||
|
|
@ -574,6 +620,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0xe1: // portamento up
|
||||
chan[i].portaNote=chan[i].note+(effectVal&15);
|
||||
chan[i].portaSpeed=(effectVal>>4)*4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
|
@ -592,6 +639,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0xe2: // portamento down
|
||||
chan[i].portaNote=chan[i].note-(effectVal&15);
|
||||
chan[i].portaSpeed=(effectVal>>4)*4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
|
@ -609,9 +657,11 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xe3: // vibrato direction
|
||||
chan[i].vibratoDir=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir));
|
||||
break;
|
||||
case 0xe4: // vibrato fine
|
||||
chan[i].vibratoFine=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_RANGE,i,chan[i].vibratoFine));
|
||||
break;
|
||||
case 0xe5: // pitch
|
||||
chan[i].pitch=effectVal-0x80;
|
||||
|
|
@ -622,6 +672,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
//chan[i].pitch+=globalPitch;
|
||||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch));
|
||||
break;
|
||||
case 0xea: // legato mode
|
||||
chan[i].legato=effectVal;
|
||||
|
|
@ -644,7 +695,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xf0: // set Hz by tempo
|
||||
divider=(double)effectVal*2.0/5.0;
|
||||
if (divider<10) divider=10;
|
||||
if (divider<1) divider=1;
|
||||
cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider;
|
||||
clockDrift=0;
|
||||
subticks=0;
|
||||
|
|
@ -671,17 +722,21 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xf3: // fine volume ramp up
|
||||
chan[i].volSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf4: // fine volume ramp down
|
||||
chan[i].volSpeed=-effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf8: // single volume ramp up
|
||||
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
break;
|
||||
case 0xf9: // single volume ramp down
|
||||
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
break;
|
||||
case 0xfa: // fast volume ramp
|
||||
if (effectVal!=0) {
|
||||
|
|
@ -693,6 +748,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
|
||||
case 0xff: // stop song
|
||||
|
|
@ -723,15 +779,18 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
if (chan[i].legato) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
} else {
|
||||
if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) {
|
||||
if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) {
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
chan[i].wasShorthandPorta=false;
|
||||
chan[i].inPorta=false;
|
||||
} else {
|
||||
chan[i].portaNote=chan[i].note;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
}
|
||||
} else if (!chan[i].noteOnInhibit) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
|
||||
|
|
@ -742,12 +801,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (!chan[i].keyOn && chan[i].scheduledSlideReset) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].scheduledSlideReset=false;
|
||||
chan[i].inPorta=false;
|
||||
}
|
||||
if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
chan[i].keyOn=true;
|
||||
chan[i].keyOff=false;
|
||||
|
|
@ -771,7 +832,7 @@ void DivEngine::nextRow() {
|
|||
static char pb1[4096];
|
||||
static char pb2[4096];
|
||||
static char pb3[4096];
|
||||
if (view==DIV_STATUS_PATTERN) {
|
||||
if (view==DIV_STATUS_PATTERN && !skipping) {
|
||||
strcpy(pb1,"");
|
||||
strcpy(pb3,"");
|
||||
for (int i=0; i<chans; i++) {
|
||||
|
|
@ -866,7 +927,9 @@ void DivEngine::nextRow() {
|
|||
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
|
||||
if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
|
||||
if (!chan[i].legato) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
|
||||
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
|
||||
if (disCont[dispatchOfChan[i]].dispatch->getWantPreNote()) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
|
||||
}
|
||||
|
||||
if (song.oneTickCut) {
|
||||
bool doPrepareCut=true;
|
||||
|
|
@ -896,7 +959,7 @@ void DivEngine::nextRow() {
|
|||
|
||||
bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
||||
bool ret=false;
|
||||
if (divider<10) divider=10;
|
||||
if (divider<1) divider=1;
|
||||
|
||||
if (lowLatency && !skipping && !inhibitLowLat) {
|
||||
tickMult=1000/divider;
|
||||
|
|
@ -914,7 +977,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
|
||||
// MIDI clock
|
||||
if (output) if (!skipping && output->midiOut!=NULL) {
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
|
||||
//output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
|
||||
}
|
||||
|
||||
while (!pendingNotes.empty()) {
|
||||
|
|
@ -985,15 +1048,19 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
chan[i].volSpeed=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
} else if (chan[i].volume<0) {
|
||||
chan[i].volSpeed=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
if (song.legacyVolumeSlides) {
|
||||
chan[i].volume=chan[i].volMax+1;
|
||||
} else {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
} else {
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
|
|
@ -1022,10 +1089,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
|
||||
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
||||
chan[i].portaSpeed=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].oldNote=chan[i].note;
|
||||
chan[i].note=chan[i].portaNote;
|
||||
chan[i].inPorta=false;
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1039,11 +1108,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
|
@ -1057,6 +1128,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
if (chan[i].resetArp) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
chan[i].resetArp=false;
|
||||
}
|
||||
if (song.rowResetsArpPos && firstTick) {
|
||||
|
|
@ -1105,7 +1177,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
}
|
||||
|
||||
if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond);
|
||||
if (consoleMode && subticks<=1 && !skipping) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond);
|
||||
}
|
||||
|
||||
if (haltOn==DIV_HALT_TICK) halted=true;
|
||||
|
|
@ -1204,17 +1276,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
|
||||
samp_prevSample=samp_temp;
|
||||
|
||||
if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) {
|
||||
if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) {
|
||||
if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) {
|
||||
sPreview.pos=s->loopStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) {
|
||||
if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) {
|
||||
if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) {
|
||||
sPreview.pos=s->loopStart;
|
||||
} else {
|
||||
} else if (sPreview.pos>=s->samples) {
|
||||
sPreview.sample=-1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1256,25 +1328,22 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
}
|
||||
|
||||
// logic starts here
|
||||
size_t runtotal[32];
|
||||
size_t runLeft[32];
|
||||
size_t runPos[32];
|
||||
size_t lastAvail[32];
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
lastAvail[i]=blip_samples_avail(disCont[i].bb[0]);
|
||||
if (lastAvail[i]>0) {
|
||||
disCont[i].flush(lastAvail[i]);
|
||||
disCont[i].lastAvail=blip_samples_avail(disCont[i].bb[0]);
|
||||
if (disCont[i].lastAvail>0) {
|
||||
disCont[i].flush(disCont[i].lastAvail);
|
||||
}
|
||||
runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]);
|
||||
if (runtotal[i]>disCont[i].bbInLen) {
|
||||
disCont[i].runtotal=blip_clocks_needed(disCont[i].bb[0],size-disCont[i].lastAvail);
|
||||
if (disCont[i].runtotal>disCont[i].bbInLen) {
|
||||
logV("growing dispatch %d bbIn to %d",i,disCont[i].runtotal+256);
|
||||
delete[] disCont[i].bbIn[0];
|
||||
delete[] disCont[i].bbIn[1];
|
||||
disCont[i].bbIn[0]=new short[runtotal[i]+256];
|
||||
disCont[i].bbIn[1]=new short[runtotal[i]+256];
|
||||
disCont[i].bbInLen=runtotal[i]+256;
|
||||
disCont[i].bbIn[0]=new short[disCont[i].runtotal+256];
|
||||
disCont[i].bbIn[1]=new short[disCont[i].runtotal+256];
|
||||
disCont[i].bbInLen=disCont[i].runtotal+256;
|
||||
}
|
||||
runLeft[i]=runtotal[i];
|
||||
runPos[i]=0;
|
||||
disCont[i].runLeft=disCont[i].runtotal;
|
||||
disCont[i].runPos=0;
|
||||
}
|
||||
|
||||
if (metroTickLen<size) {
|
||||
|
|
@ -1326,10 +1395,10 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
// 3. tick the clock and fill buffers as needed
|
||||
if (cycles<runLeftG) {
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
int total=(cycles*runtotal[i])/(size<<MASTER_CLOCK_PREC);
|
||||
disCont[i].acquire(runPos[i],total);
|
||||
runLeft[i]-=total;
|
||||
runPos[i]+=total;
|
||||
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC);
|
||||
disCont[i].acquire(disCont[i].runPos,total);
|
||||
disCont[i].runLeft-=total;
|
||||
disCont[i].runPos+=total;
|
||||
}
|
||||
runLeftG-=cycles;
|
||||
cycles=0;
|
||||
|
|
@ -1337,8 +1406,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
cycles-=runLeftG;
|
||||
runLeftG=0;
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
disCont[i].acquire(runPos[i],runLeft[i]);
|
||||
runLeft[i]=0;
|
||||
disCont[i].acquire(disCont[i].runPos,disCont[i].runLeft);
|
||||
disCont[i].runLeft=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1359,7 +1428,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
totalProcessed=size-(runLeftG>>MASTER_CLOCK_PREC);
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
disCont[i].fillBuf(runtotal[i],lastAvail[i],size-lastAvail[i]);
|
||||
disCont[i].fillBuf(disCont[i].runtotal,disCont[i].lastAvail,size-disCont[i].lastAvail);
|
||||
}
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
@ -1413,6 +1482,14 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
out[1][i]=out[0][i];
|
||||
}
|
||||
}
|
||||
if (clampSamples) {
|
||||
for (size_t i=0; i<size; i++) {
|
||||
if (out[0][i]<-1.0) out[0][i]=-1.0;
|
||||
if (out[0][i]>1.0) out[0][i]=1.0;
|
||||
if (out[1][i]<-1.0) out[1][i]=-1.0;
|
||||
if (out[1][i]>1.0) out[1][i]=1.0;
|
||||
}
|
||||
}
|
||||
isBusy.unlock();
|
||||
|
||||
std::chrono::steady_clock::time_point ts_processEnd=std::chrono::steady_clock::now();
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ int SafeWriter::writeWString(WString val, bool pascal) {
|
|||
return 2+val.size()*2;
|
||||
}
|
||||
}
|
||||
int SafeWriter::writeText(String val) {
|
||||
return write(val.c_str(),val.size());
|
||||
}
|
||||
|
||||
void SafeWriter::init() {
|
||||
if (operative) return;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class SafeWriter {
|
|||
int writeD_BE(double val);
|
||||
int writeWString(WString val, bool pascal);
|
||||
int writeString(String val, bool pascal);
|
||||
int writeText(String val);
|
||||
|
||||
void init();
|
||||
SafeReader* toReader();
|
||||
|
|
|
|||
|
|
@ -38,6 +38,65 @@ DivSampleHistory::~DivSampleHistory() {
|
|||
if (data!=NULL) delete[] data;
|
||||
}
|
||||
|
||||
bool DivSample::isLoopable() {
|
||||
return (loopStart>=0 && loopStart<loopEnd) && (loopEnd>loopStart && loopEnd<=(int)samples);
|
||||
}
|
||||
|
||||
unsigned int DivSample::getEndPosition(DivSampleDepth depth) {
|
||||
int end=loopEnd;
|
||||
unsigned int len=samples;
|
||||
switch (depth) {
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
end=(loopEnd+7)/8;
|
||||
len=length1;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
end=(loopEnd+7)/8;
|
||||
len=lengthDPCM;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
end=(loopEnd+1)/2;
|
||||
len=lengthZ;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
end=(loopEnd+1)/2;
|
||||
len=lengthQSoundA;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
end=(loopEnd+1)/2;
|
||||
len=lengthA;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
end=(loopEnd+1)/2;
|
||||
len=lengthB;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
end=loopEnd;
|
||||
len=length8;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
end=9*((loopEnd+15)/16);
|
||||
len=lengthBRR;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
end=(loopEnd+1)/2;
|
||||
len=lengthVOX;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
end=loopEnd*2;
|
||||
len=length16;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return isLoopable()?end:len;
|
||||
}
|
||||
|
||||
void DivSample::setSampleCount(unsigned int count) {
|
||||
samples=count;
|
||||
if ((!isLoopable()) || loopEnd<0 || loopEnd>(int)samples) loopEnd=samples;
|
||||
}
|
||||
|
||||
bool DivSample::save(const char* path) {
|
||||
#ifndef HAVE_SNDFILE
|
||||
logE("Furnace was not compiled with libsndfile!");
|
||||
|
|
@ -53,7 +112,7 @@ bool DivSample::save(const char* path) {
|
|||
si.channels=1;
|
||||
si.samplerate=rate;
|
||||
switch (depth) {
|
||||
case 8: // 8-bit
|
||||
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit
|
||||
si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV;
|
||||
break;
|
||||
default: // 16-bit
|
||||
|
|
@ -76,17 +135,17 @@ bool DivSample::save(const char* path) {
|
|||
inst.detune = 50 - (pitch % 100);
|
||||
inst.velocity_hi = 0x7f;
|
||||
inst.key_hi = 0x7f;
|
||||
if(loopStart != -1)
|
||||
if(isLoopable())
|
||||
{
|
||||
inst.loop_count = 1;
|
||||
inst.loops[0].mode = SF_LOOP_FORWARD;
|
||||
inst.loops[0].start = loopStart;
|
||||
inst.loops[0].end = samples;
|
||||
inst.loops[0].end = loopEnd;
|
||||
}
|
||||
sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst));
|
||||
|
||||
switch (depth) {
|
||||
case 8: {
|
||||
case DIV_SAMPLE_DEPTH_8BIT: {
|
||||
// convert from signed to unsigned
|
||||
unsigned char* buf=new unsigned char[length8];
|
||||
for (size_t i=0; i<length8; i++) {
|
||||
|
|
@ -108,65 +167,65 @@ bool DivSample::save(const char* path) {
|
|||
}
|
||||
|
||||
// 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
|
||||
bool DivSample::initInternal(unsigned char d, int count) {
|
||||
bool DivSample::initInternal(DivSampleDepth d, int count) {
|
||||
switch (d) {
|
||||
case 0: // 1-bit
|
||||
case DIV_SAMPLE_DEPTH_1BIT: // 1-bit
|
||||
if (data1!=NULL) delete[] data1;
|
||||
length1=(count+7)/8;
|
||||
data1=new unsigned char[length1];
|
||||
memset(data1,0,length1);
|
||||
break;
|
||||
case 1: // DPCM
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM: // DPCM
|
||||
if (dataDPCM!=NULL) delete[] dataDPCM;
|
||||
lengthDPCM=(count+7)/8;
|
||||
dataDPCM=new unsigned char[lengthDPCM];
|
||||
memset(dataDPCM,0,lengthDPCM);
|
||||
break;
|
||||
case 3: // YMZ ADPCM
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
|
||||
if (dataZ!=NULL) delete[] dataZ;
|
||||
lengthZ=(count+1)/2;
|
||||
// for padding AICA sample
|
||||
dataZ=new unsigned char[(lengthZ+3)&(~0x03)];
|
||||
memset(dataZ,0,(lengthZ+3)&(~0x03));
|
||||
break;
|
||||
case 4: // QSound ADPCM
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
|
||||
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
||||
lengthQSoundA=(count+1)/2;
|
||||
dataQSoundA=new unsigned char[lengthQSoundA];
|
||||
memset(dataQSoundA,0,lengthQSoundA);
|
||||
break;
|
||||
case 5: // ADPCM-A
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A
|
||||
if (dataA!=NULL) delete[] dataA;
|
||||
lengthA=(count+1)/2;
|
||||
dataA=new unsigned char[(lengthA+255)&(~0xff)];
|
||||
memset(dataA,0,(lengthA+255)&(~0xff));
|
||||
break;
|
||||
case 6: // ADPCM-B
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
|
||||
if (dataB!=NULL) delete[] dataB;
|
||||
lengthB=(count+1)/2;
|
||||
dataB=new unsigned char[(lengthB+255)&(~0xff)];
|
||||
memset(dataB,0,(lengthB+255)&(~0xff));
|
||||
break;
|
||||
case 8: // 8-bit
|
||||
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit
|
||||
if (data8!=NULL) delete[] data8;
|
||||
length8=count;
|
||||
// for padding X1-010 sample
|
||||
data8=new signed char[(count+4095)&(~0xfff)];
|
||||
memset(data8,0,(count+4095)&(~0xfff));
|
||||
break;
|
||||
case 9: // BRR
|
||||
case DIV_SAMPLE_DEPTH_BRR: // BRR
|
||||
if (dataBRR!=NULL) delete[] dataBRR;
|
||||
lengthBRR=9*((count+15)/16);
|
||||
dataBRR=new unsigned char[lengthBRR];
|
||||
memset(dataBRR,0,lengthBRR);
|
||||
break;
|
||||
case 10: // VOX
|
||||
case DIV_SAMPLE_DEPTH_VOX: // VOX
|
||||
if (dataVOX!=NULL) delete[] dataVOX;
|
||||
lengthVOX=(count+1)/2;
|
||||
dataVOX=new unsigned char[lengthVOX];
|
||||
memset(dataVOX,0,lengthVOX);
|
||||
break;
|
||||
case 16: // 16-bit
|
||||
case DIV_SAMPLE_DEPTH_16BIT: // 16-bit
|
||||
if (data16!=NULL) delete[] data16;
|
||||
length16=count*2;
|
||||
data16=new short[(count+511)&(~0x1ff)];
|
||||
|
|
@ -180,34 +239,34 @@ bool DivSample::initInternal(unsigned char d, int count) {
|
|||
|
||||
bool DivSample::init(unsigned int count) {
|
||||
if (!initInternal(depth,count)) return false;
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivSample::resize(unsigned int count) {
|
||||
if (depth==8) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
if (data8!=NULL) {
|
||||
signed char* oldData8=data8;
|
||||
data8=NULL;
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
memcpy(data8,oldData8,MIN(count,samples));
|
||||
delete[] oldData8;
|
||||
} else {
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
} else if (depth==16) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (data16!=NULL) {
|
||||
short* oldData16=data16;
|
||||
data16=NULL;
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
memcpy(data16,oldData16,sizeof(short)*MIN(count,samples));
|
||||
delete[] oldData16;
|
||||
} else {
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -218,11 +277,11 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
|
|||
if (end>samples) end=samples;
|
||||
int count=samples-(end-begin);
|
||||
if (count<=0) return resize(0);
|
||||
if (depth==8) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
if (data8!=NULL) {
|
||||
signed char* oldData8=data8;
|
||||
data8=NULL;
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
if (begin>0) {
|
||||
memcpy(data8,oldData8,begin);
|
||||
}
|
||||
|
|
@ -234,13 +293,13 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
|
|||
// do nothing
|
||||
return true;
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
} else if (depth==16) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (data16!=NULL) {
|
||||
short* oldData16=data16;
|
||||
data16=NULL;
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
if (begin>0) {
|
||||
memcpy(data16,oldData16,sizeof(short)*begin);
|
||||
}
|
||||
|
|
@ -252,7 +311,7 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
|
|||
// do nothing
|
||||
return true;
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -262,31 +321,31 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
|
|||
int count=end-begin;
|
||||
if (count==0) return true;
|
||||
if (begin==0 && end==samples) return true;
|
||||
if (depth==8) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
if (data8!=NULL) {
|
||||
signed char* oldData8=data8;
|
||||
data8=NULL;
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
memcpy(data8,oldData8+begin,count);
|
||||
delete[] oldData8;
|
||||
} else {
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
} else if (depth==16) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (data16!=NULL) {
|
||||
short* oldData16=data16;
|
||||
data16=NULL;
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
memcpy(data16,&(oldData16[begin]),sizeof(short)*count);
|
||||
delete[] oldData16;
|
||||
} else {
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -294,11 +353,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
|
|||
|
||||
bool DivSample::insert(unsigned int pos, unsigned int length) {
|
||||
unsigned int count=samples+length;
|
||||
if (depth==8) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
if (data8!=NULL) {
|
||||
signed char* oldData8=data8;
|
||||
data8=NULL;
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
if (pos>0) {
|
||||
memcpy(data8,oldData8,pos);
|
||||
}
|
||||
|
|
@ -307,15 +366,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
|
|||
}
|
||||
delete[] oldData8;
|
||||
} else {
|
||||
initInternal(8,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
} else if (depth==16) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (data16!=NULL) {
|
||||
short* oldData16=data16;
|
||||
data16=NULL;
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
if (pos>0) {
|
||||
memcpy(data16,oldData16,sizeof(short)*pos);
|
||||
}
|
||||
|
|
@ -324,9 +383,9 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
|
|||
}
|
||||
delete[] oldData16;
|
||||
} else {
|
||||
initInternal(16,count);
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
|
||||
}
|
||||
samples=count;
|
||||
setSampleCount(count);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -337,15 +396,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
|
|||
int finalCount=(double)samples*(r/(double)rate); \
|
||||
signed char* oldData8=data8; \
|
||||
short* oldData16=data16; \
|
||||
if (depth==16) { \
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
|
||||
if (data16!=NULL) { \
|
||||
data16=NULL; \
|
||||
initInternal(16,finalCount); \
|
||||
initInternal(DIV_SAMPLE_DEPTH_16BIT,finalCount); \
|
||||
} \
|
||||
} else if (depth==8) { \
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \
|
||||
if (data8!=NULL) { \
|
||||
data8=NULL; \
|
||||
initInternal(8,finalCount); \
|
||||
initInternal(DIV_SAMPLE_DEPTH_8BIT,finalCount); \
|
||||
} \
|
||||
} else { \
|
||||
return false; \
|
||||
|
|
@ -353,19 +412,20 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
|
|||
|
||||
#define RESAMPLE_END \
|
||||
if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \
|
||||
if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \
|
||||
centerRate=(int)((double)centerRate*(r/(double)rate)); \
|
||||
rate=r; \
|
||||
samples=finalCount; \
|
||||
if (depth==16) { \
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
|
||||
delete[] oldData16; \
|
||||
} else if (depth==8) { \
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \
|
||||
delete[] oldData8; \
|
||||
}
|
||||
|
||||
bool DivSample::resampleNone(double r) {
|
||||
RESAMPLE_BEGIN;
|
||||
|
||||
if (depth==16) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
|
||||
if (pos>=samples) {
|
||||
|
|
@ -374,7 +434,7 @@ bool DivSample::resampleNone(double r) {
|
|||
data16[i]=oldData16[pos];
|
||||
}
|
||||
}
|
||||
} else if (depth==8) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
|
||||
if (pos>=samples) {
|
||||
|
|
@ -396,7 +456,7 @@ bool DivSample::resampleLinear(double r) {
|
|||
unsigned int posInt=0;
|
||||
double factor=(double)rate/r;
|
||||
|
||||
if (depth==16) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
short s1=(posInt>=samples)?0:oldData16[posInt];
|
||||
short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1];
|
||||
|
|
@ -409,7 +469,7 @@ bool DivSample::resampleLinear(double r) {
|
|||
posInt++;
|
||||
}
|
||||
}
|
||||
} else if (depth==8) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
short s1=(posInt>=samples)?0:oldData8[posInt];
|
||||
short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1];
|
||||
|
|
@ -436,7 +496,7 @@ bool DivSample::resampleCubic(double r) {
|
|||
double factor=(double)rate/r;
|
||||
float* cubicTable=DivFilterTables::getCubicTable();
|
||||
|
||||
if (depth==16) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
unsigned int n=((unsigned int)(posFrac*1024.0))&1023;
|
||||
float* t=&cubicTable[n<<2];
|
||||
|
|
@ -456,7 +516,7 @@ bool DivSample::resampleCubic(double r) {
|
|||
posInt++;
|
||||
}
|
||||
}
|
||||
} else if (depth==8) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
unsigned int n=((unsigned int)(posFrac*1024.0))&1023;
|
||||
float* t=&cubicTable[n<<2];
|
||||
|
|
@ -493,7 +553,7 @@ bool DivSample::resampleBlep(double r) {
|
|||
|
||||
memset(s,0,16*sizeof(float));
|
||||
|
||||
if (depth==16) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
memset(data16,0,finalCount*sizeof(short));
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
if (posInt<samples) {
|
||||
|
|
@ -529,7 +589,7 @@ bool DivSample::resampleBlep(double r) {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (depth==8) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
memset(data8,0,finalCount);
|
||||
for (int i=0; i<finalCount; i++) {
|
||||
if (posInt<samples) {
|
||||
|
|
@ -582,7 +642,7 @@ bool DivSample::resampleSinc(double r) {
|
|||
|
||||
memset(s,0,16*sizeof(float));
|
||||
|
||||
if (depth==16) {
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (int i=0; i<finalCount+8; i++) {
|
||||
unsigned int n=((unsigned int)(posFrac*8192.0))&8191;
|
||||
float result=0;
|
||||
|
|
@ -607,7 +667,7 @@ bool DivSample::resampleSinc(double r) {
|
|||
s[15]=(posInt>=samples)?0:oldData16[posInt];
|
||||
}
|
||||
}
|
||||
} else if (depth==8) {
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (int i=0; i<finalCount+8; i++) {
|
||||
unsigned int n=((unsigned int)(posFrac*8192.0))&8191;
|
||||
float result=0;
|
||||
|
|
@ -639,7 +699,7 @@ bool DivSample::resampleSinc(double r) {
|
|||
}
|
||||
|
||||
bool DivSample::resample(double r, int filter) {
|
||||
if (depth!=8 && depth!=16) return false;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) return false;
|
||||
switch (filter) {
|
||||
case DIV_RESAMPLE_NONE:
|
||||
return resampleNone(r);
|
||||
|
|
@ -669,15 +729,15 @@ bool DivSample::resample(double r, int filter) {
|
|||
|
||||
void DivSample::render() {
|
||||
// step 1: convert to 16-bit if needed
|
||||
if (depth!=16) {
|
||||
if (!initInternal(16,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_16BIT,samples)) return;
|
||||
switch (depth) {
|
||||
case 0: // 1-bit
|
||||
case DIV_SAMPLE_DEPTH_1BIT: // 1-bit
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data16[i]=((data1[i>>3]>>(i&7))&1)?0x7fff:-0x7fff;
|
||||
}
|
||||
break;
|
||||
case 1: { // DPCM
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM: { // DPCM
|
||||
int accum=0;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
accum+=((dataDPCM[i>>3]>>(i&7))&1)?1:-1;
|
||||
|
|
@ -687,27 +747,27 @@ void DivSample::render() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 3: // YMZ ADPCM
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
|
||||
ymz_decode(dataZ,data16,samples);
|
||||
break;
|
||||
case 4: // QSound ADPCM
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
|
||||
bs_decode(dataQSoundA,data16,samples);
|
||||
break;
|
||||
case 5: // ADPCM-A
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A
|
||||
yma_decode(dataA,data16,samples);
|
||||
break;
|
||||
case 6: // ADPCM-B
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
|
||||
ymb_decode(dataB,data16,samples);
|
||||
break;
|
||||
case 8: // 8-bit PCM
|
||||
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit PCM
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data16[i]=data8[i]<<8;
|
||||
}
|
||||
break;
|
||||
case 9: // BRR
|
||||
case DIV_SAMPLE_DEPTH_BRR: // BRR
|
||||
// TODO!
|
||||
break;
|
||||
case 10: // VOX
|
||||
case DIV_SAMPLE_DEPTH_VOX: // VOX
|
||||
oki_decode(dataVOX,data16,samples);
|
||||
break;
|
||||
default:
|
||||
|
|
@ -716,16 +776,16 @@ void DivSample::render() {
|
|||
}
|
||||
|
||||
// step 2: render to other formats
|
||||
if (depth!=0) { // 1-bit
|
||||
if (!initInternal(0,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_1BIT) { // 1-bit
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT,samples)) return;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
if (data16[i]>0) {
|
||||
data1[i>>3]|=1<<(i&7);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (depth!=1) { // DPCM
|
||||
if (!initInternal(1,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_1BIT_DPCM) { // DPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return;
|
||||
int accum=63;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
int next=((unsigned short)(data16[i]^0x8000))>>9;
|
||||
|
|
@ -739,84 +799,88 @@ void DivSample::render() {
|
|||
if (accum>127) accum=127;
|
||||
}
|
||||
}
|
||||
if (depth!=3) { // YMZ ADPCM
|
||||
if (!initInternal(3,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return;
|
||||
ymz_encode(data16,dataZ,(samples+7)&(~0x7));
|
||||
}
|
||||
if (depth!=4) { // QSound ADPCM
|
||||
if (!initInternal(4,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return;
|
||||
bs_encode(data16,dataQSoundA,samples);
|
||||
}
|
||||
// TODO: pad to 256.
|
||||
if (depth!=5) { // ADPCM-A
|
||||
if (!initInternal(5,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_ADPCM_A) { // ADPCM-A
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_A,samples)) return;
|
||||
yma_encode(data16,dataA,(samples+511)&(~0x1ff));
|
||||
}
|
||||
if (depth!=6) { // ADPCM-B
|
||||
if (!initInternal(6,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_ADPCM_B) { // ADPCM-B
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_B,samples)) return;
|
||||
ymb_encode(data16,dataB,(samples+511)&(~0x1ff));
|
||||
}
|
||||
if (depth!=8) { // 8-bit PCM
|
||||
if (!initInternal(8,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT) { // 8-bit PCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data8[i]=data16[i]>>8;
|
||||
}
|
||||
}
|
||||
// TODO: BRR!
|
||||
if (depth!=10) { // VOX
|
||||
if (!initInternal(10,samples)) return;
|
||||
if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return;
|
||||
oki_encode(data16,dataVOX,samples);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivSample::getCurBuf() {
|
||||
switch (depth) {
|
||||
case 0:
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
return data1;
|
||||
case 1:
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
return dataDPCM;
|
||||
case 3:
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
return dataZ;
|
||||
case 4:
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
return dataQSoundA;
|
||||
case 5:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
return dataA;
|
||||
case 6:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
return dataB;
|
||||
case 8:
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
return data8;
|
||||
case 9:
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
return dataBRR;
|
||||
case 10:
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
return dataVOX;
|
||||
case 16:
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
return data16;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int DivSample::getCurBufLen() {
|
||||
switch (depth) {
|
||||
case 0:
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
return length1;
|
||||
case 1:
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
return lengthDPCM;
|
||||
case 3:
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
return lengthZ;
|
||||
case 4:
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
return lengthQSoundA;
|
||||
case 5:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
return lengthA;
|
||||
case 6:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
return lengthB;
|
||||
case 8:
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
return length8;
|
||||
case 9:
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
return lengthBRR;
|
||||
case 10:
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
return lengthVOX;
|
||||
case 16:
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
return length16;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -831,9 +895,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
|
|||
duplicate=new unsigned char[getCurBufLen()];
|
||||
memcpy(duplicate,getCurBuf(),getCurBufLen());
|
||||
}
|
||||
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart);
|
||||
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd);
|
||||
} else {
|
||||
h=new DivSampleHistory(depth,rate,centerRate,loopStart);
|
||||
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd);
|
||||
}
|
||||
if (!doNotPush) {
|
||||
while (!redoHist.empty()) {
|
||||
|
|
@ -863,7 +927,8 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
|
|||
} \
|
||||
rate=h->rate; \
|
||||
centerRate=h->centerRate; \
|
||||
loopStart=h->loopStart;
|
||||
loopStart=h->loopStart; \
|
||||
loopEnd=h->loopEnd;
|
||||
|
||||
|
||||
int DivSample::undo() {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,28 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _SAMPLE_H
|
||||
#define _SAMPLE_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ta-utils.h"
|
||||
#include <deque>
|
||||
|
||||
enum DivSampleDepth: unsigned char {
|
||||
DIV_SAMPLE_DEPTH_1BIT=0,
|
||||
DIV_SAMPLE_DEPTH_1BIT_DPCM=1,
|
||||
DIV_SAMPLE_DEPTH_YMZ_ADPCM=3,
|
||||
DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4,
|
||||
DIV_SAMPLE_DEPTH_ADPCM_A=5,
|
||||
DIV_SAMPLE_DEPTH_ADPCM_B=6,
|
||||
DIV_SAMPLE_DEPTH_8BIT=8,
|
||||
DIV_SAMPLE_DEPTH_BRR=9,
|
||||
DIV_SAMPLE_DEPTH_VOX=10,
|
||||
DIV_SAMPLE_DEPTH_16BIT=16,
|
||||
DIV_SAMPLE_DEPTH_MAX // boundary for sample depth
|
||||
};
|
||||
|
||||
enum DivResampleFilters {
|
||||
DIV_RESAMPLE_NONE=0,
|
||||
DIV_RESAMPLE_LINEAR,
|
||||
|
|
@ -32,10 +51,10 @@ enum DivResampleFilters {
|
|||
struct DivSampleHistory {
|
||||
unsigned char* data;
|
||||
unsigned int length, samples;
|
||||
unsigned char depth;
|
||||
int rate, centerRate, loopStart;
|
||||
DivSampleDepth depth;
|
||||
int rate, centerRate, loopStart, loopEnd;
|
||||
bool hasSample;
|
||||
DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls):
|
||||
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le):
|
||||
data((unsigned char*)d),
|
||||
length(l),
|
||||
samples(s),
|
||||
|
|
@ -43,8 +62,9 @@ struct DivSampleHistory {
|
|||
rate(r),
|
||||
centerRate(cr),
|
||||
loopStart(ls),
|
||||
loopEnd(le),
|
||||
hasSample(true) {}
|
||||
DivSampleHistory(unsigned char de, int r, int cr, int ls):
|
||||
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le):
|
||||
data(NULL),
|
||||
length(0),
|
||||
samples(0),
|
||||
|
|
@ -52,13 +72,14 @@ struct DivSampleHistory {
|
|||
rate(r),
|
||||
centerRate(cr),
|
||||
loopStart(ls),
|
||||
loopEnd(le),
|
||||
hasSample(false) {}
|
||||
~DivSampleHistory();
|
||||
};
|
||||
|
||||
struct DivSample {
|
||||
String name;
|
||||
int rate, centerRate, loopStart, loopOffP;
|
||||
int rate, centerRate, loopStart, loopEnd, loopOffP;
|
||||
// valid values are:
|
||||
// - 0: ZX Spectrum overlay drum (1-bit)
|
||||
// - 1: 1-bit NES DPCM (1-bit)
|
||||
|
|
@ -70,7 +91,7 @@ struct DivSample {
|
|||
// - 9: BRR (SNES)
|
||||
// - 10: VOX ADPCM
|
||||
// - 16: 16-bit PCM
|
||||
unsigned char depth;
|
||||
DivSampleDepth depth;
|
||||
|
||||
// these are the new data structures.
|
||||
signed char* data8; // 8
|
||||
|
|
@ -93,6 +114,23 @@ struct DivSample {
|
|||
std::deque<DivSampleHistory*> undoHist;
|
||||
std::deque<DivSampleHistory*> redoHist;
|
||||
|
||||
/**
|
||||
* check if sample is loopable.
|
||||
* @return whether it is loopable.
|
||||
*/
|
||||
bool isLoopable();
|
||||
|
||||
/**
|
||||
* get sample end position
|
||||
* @return the samples end position.
|
||||
*/
|
||||
unsigned int getEndPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX);
|
||||
|
||||
/**
|
||||
* @warning DO NOT USE - internal functions
|
||||
*/
|
||||
void setSampleCount(unsigned int count);
|
||||
|
||||
/**
|
||||
* @warning DO NOT USE - internal functions
|
||||
*/
|
||||
|
|
@ -116,7 +154,7 @@ struct DivSample {
|
|||
* @param count number of samples.
|
||||
* @return whether it was successful.
|
||||
*/
|
||||
bool initInternal(unsigned char d, int count);
|
||||
bool initInternal(DivSampleDepth d, int count);
|
||||
|
||||
/**
|
||||
* initialize sample data. make sure you have set `depth` before doing so.
|
||||
|
|
@ -212,8 +250,9 @@ struct DivSample {
|
|||
rate(32000),
|
||||
centerRate(8363),
|
||||
loopStart(-1),
|
||||
loopEnd(-1),
|
||||
loopOffP(0),
|
||||
depth(16),
|
||||
depth(DIV_SAMPLE_DEPTH_16BIT),
|
||||
data8(NULL),
|
||||
data16(NULL),
|
||||
data1(NULL),
|
||||
|
|
@ -253,3 +292,5 @@ struct DivSample {
|
|||
samples(0) {}
|
||||
~DivSample();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "song.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
void DivSubSong::clearData() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
|
|
@ -28,6 +29,34 @@ void DivSubSong::clearData() {
|
|||
ordersLen=1;
|
||||
}
|
||||
|
||||
void DivSubSong::optimizePatterns() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
logD("optimizing channel %d...",i);
|
||||
std::vector<std::pair<int,int>> clearOuts=pat[i].optimize();
|
||||
for (auto& j: clearOuts) {
|
||||
for (int k=0; k<256; k++) {
|
||||
if (orders.ord[i][k]==j.first) {
|
||||
orders.ord[i][k]=j.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivSubSong::rearrangePatterns() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
logD("re-arranging channel %d...",i);
|
||||
std::vector<std::pair<int,int>> clearOuts=pat[i].rearrange();
|
||||
for (auto& j: clearOuts) {
|
||||
for (int k=0; k<256; k++) {
|
||||
if (orders.ord[i][k]==j.first) {
|
||||
orders.ord[i][k]=j.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivSong::clearSongData() {
|
||||
for (DivSubSong* i: subsong) {
|
||||
i->clearData();
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ struct DivSubSong {
|
|||
String chanShortName[DIV_MAX_CHANS];
|
||||
|
||||
void clearData();
|
||||
void optimizePatterns();
|
||||
void rearrangePatterns();
|
||||
|
||||
DivSubSong():
|
||||
hilightA(4),
|
||||
|
|
@ -428,7 +430,7 @@ struct DivSong {
|
|||
unsigned int systemFlags[32];
|
||||
|
||||
// song information
|
||||
String name, author;
|
||||
String name, author, systemName;
|
||||
|
||||
// legacy song information
|
||||
// those will be stored in .fur and mapped to VGM as:
|
||||
|
|
@ -438,7 +440,7 @@ struct DivSong {
|
|||
String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate;
|
||||
|
||||
// more VGM specific stuff
|
||||
String nameJ, authorJ, categoryJ;
|
||||
String nameJ, authorJ, categoryJ, systemNameJ;
|
||||
|
||||
// other things
|
||||
String notes;
|
||||
|
|
@ -541,6 +543,7 @@ struct DivSong {
|
|||
systemLen(2),
|
||||
name(""),
|
||||
author(""),
|
||||
systemName(""),
|
||||
carrier(""),
|
||||
composer(""),
|
||||
vendor(""),
|
||||
|
|
@ -561,7 +564,7 @@ struct DivSong {
|
|||
linearPitch(2),
|
||||
pitchSlideSpeed(4),
|
||||
loopModality(0),
|
||||
properNoiseLayout(false),
|
||||
properNoiseLayout(true),
|
||||
waveDutyIsVol(false),
|
||||
resetMacroOnPorta(false),
|
||||
legacyVolumeSlides(false),
|
||||
|
|
|
|||
|
|
@ -54,14 +54,14 @@ std::vector<DivInstrumentType>& DivEngine::getPossibleInsTypes() {
|
|||
return possibleInsTypes;
|
||||
}
|
||||
|
||||
// TODO: rewrite this function (again). it's an unreliable mess.
|
||||
String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
|
||||
switch (song.systemLen) {
|
||||
// for pre-dev103 modules
|
||||
String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable) {
|
||||
switch (ds.systemLen) {
|
||||
case 0:
|
||||
return "help! what's going on!";
|
||||
case 1:
|
||||
if (song.system[0]==DIV_SYSTEM_AY8910) {
|
||||
switch (song.systemFlags[0]&0x3f) {
|
||||
if (ds.system[0]==DIV_SYSTEM_AY8910) {
|
||||
switch (ds.systemFlags[0]&0x3f) {
|
||||
case 0: // AY-3-8910, 1.79MHz
|
||||
case 1: // AY-3-8910, 1.77MHz
|
||||
case 2: // AY-3-8910, 1.75MHz
|
||||
|
|
@ -88,114 +88,116 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
|
|||
return "Intellivision (PAL)";
|
||||
|
||||
default:
|
||||
if ((song.systemFlags[0]&0x30)==0x00) {
|
||||
if ((ds.systemFlags[0]&0x30)==0x00) {
|
||||
return "AY-3-8910";
|
||||
} else if ((song.systemFlags[0]&0x30)==0x10) {
|
||||
} else if ((ds.systemFlags[0]&0x30)==0x10) {
|
||||
return "Yamaha YM2149";
|
||||
} else if ((song.systemFlags[0]&0x30)==0x20) {
|
||||
} else if ((ds.systemFlags[0]&0x30)==0x20) {
|
||||
return "Overclocked Sunsoft 5B";
|
||||
} else if ((song.systemFlags[0]&0x30)==0x30) {
|
||||
} else if ((ds.systemFlags[0]&0x30)==0x30) {
|
||||
return "Intellivision";
|
||||
}
|
||||
}
|
||||
} else if (song.system[0]==DIV_SYSTEM_SMS) {
|
||||
switch (song.systemFlags[0]&0x0f) {
|
||||
} else if (ds.system[0]==DIV_SYSTEM_SMS) {
|
||||
switch (ds.systemFlags[0]&0x0f) {
|
||||
case 0: case 1:
|
||||
return "Sega Master System";
|
||||
case 6:
|
||||
return "BBC Micro";
|
||||
}
|
||||
} else if (song.system[0]==DIV_SYSTEM_YM2612) {
|
||||
switch (song.systemFlags[0]&3) {
|
||||
} else if (ds.system[0]==DIV_SYSTEM_YM2612) {
|
||||
switch (ds.systemFlags[0]&3) {
|
||||
case 2:
|
||||
return "FM Towns";
|
||||
}
|
||||
} else if (song.system[0]==DIV_SYSTEM_YM2151) {
|
||||
switch (song.systemFlags[0]&3) {
|
||||
} else if (ds.system[0]==DIV_SYSTEM_YM2151) {
|
||||
switch (ds.systemFlags[0]&3) {
|
||||
case 2:
|
||||
return "Sharp X68000";
|
||||
}
|
||||
} else if (song.system[0]==DIV_SYSTEM_SAA1099) {
|
||||
switch (song.systemFlags[0]&3) {
|
||||
} else if (ds.system[0]==DIV_SYSTEM_SAA1099) {
|
||||
switch (ds.systemFlags[0]&3) {
|
||||
case 0:
|
||||
return "SAM Coupé";
|
||||
}
|
||||
}
|
||||
return getSystemName(song.system[0]);
|
||||
return getSystemName(ds.system[0]);
|
||||
case 2:
|
||||
if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) {
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2612 && ds.system[1]==DIV_SYSTEM_SMS) {
|
||||
return "Sega Genesis/Mega Drive";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) {
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2612_EXT && ds.system[1]==DIV_SYSTEM_SMS) {
|
||||
return "Sega Genesis Extended Channel 3";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_SMS) {
|
||||
if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_SMS) {
|
||||
return "NTSC-J Sega Master System";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_SMS) {
|
||||
if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_SMS) {
|
||||
return "NTSC-J Sega Master System + drums";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_AY8910) {
|
||||
if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_AY8910) {
|
||||
return "MSX-MUSIC";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_AY8910) {
|
||||
if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_AY8910) {
|
||||
return "MSX-MUSIC + drums";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_C64_6581 && song.system[1]==DIV_SYSTEM_C64_6581) {
|
||||
if (ds.system[0]==DIV_SYSTEM_C64_6581 && ds.system[1]==DIV_SYSTEM_C64_6581) {
|
||||
return "Commodore 64 with dual 6581";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_C64_8580 && song.system[1]==DIV_SYSTEM_C64_8580) {
|
||||
if (ds.system[0]==DIV_SYSTEM_C64_8580 && ds.system[1]==DIV_SYSTEM_C64_8580) {
|
||||
return "Commodore 64 with dual 8580";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) {
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) {
|
||||
return "YM2151 + SegaPCM Arcade (compatibility)";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM) {
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM) {
|
||||
return "YM2151 + SegaPCM Arcade";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_SAA1099 && song.system[1]==DIV_SYSTEM_SAA1099) {
|
||||
if (ds.system[0]==DIV_SYSTEM_SAA1099 && ds.system[1]==DIV_SYSTEM_SAA1099) {
|
||||
return "Creative Music System";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_GB && song.system[1]==DIV_SYSTEM_AY8910) {
|
||||
if (ds.system[0]==DIV_SYSTEM_GB && ds.system[1]==DIV_SYSTEM_AY8910) {
|
||||
return "Game Boy with AY expansion";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC6) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC6) {
|
||||
return "Famicom + Konami VRC6";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC7) {
|
||||
return "Famicom + Konami VRC7";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_OPLL) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_OPLL) {
|
||||
return "Family Noraebang";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_FDS) {
|
||||
return "Famicom Disk System";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_N163) {
|
||||
return "Famicom + Namco C163";
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_N163) {
|
||||
String ret="Famicom + ";
|
||||
ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME);
|
||||
return ret;
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_MMC5) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_MMC5) {
|
||||
return "Famicom + MMC5";
|
||||
}
|
||||
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_AY8910) {
|
||||
if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_AY8910) {
|
||||
return "Famicom + Sunsoft 5B";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) {
|
||||
if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910) {
|
||||
return "Bally Midway MCR";
|
||||
}
|
||||
|
||||
if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) {
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_VERA) {
|
||||
return "Commander X16";
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_BUBSYS_WSG) {
|
||||
if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910 && ds.system[2]==DIV_SYSTEM_BUBSYS_WSG) {
|
||||
return "Konami Bubble System";
|
||||
}
|
||||
break;
|
||||
|
|
@ -203,9 +205,13 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
|
|||
if (isMultiSystemAcceptable) return "multi-system";
|
||||
|
||||
String ret="";
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
if (i>0) ret+=" + ";
|
||||
ret+=getSystemName(song.system[i]);
|
||||
if (ds.system[i]==DIV_SYSTEM_N163) {
|
||||
ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME);
|
||||
} else {
|
||||
ret+=getSystemName(ds.system[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -213,6 +219,11 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
|
|||
|
||||
const char* DivEngine::getSystemName(DivSystem sys) {
|
||||
if (sysDefs[sys]==NULL) return "Unknown";
|
||||
if (sys==DIV_SYSTEM_N163) {
|
||||
String c1=getConfString("c163Name",DIV_C163_DEFAULT_NAME);
|
||||
strncpy(c163NameCS,c1.c_str(),1023);
|
||||
return c163NameCS;
|
||||
}
|
||||
return sysDefs[sys]->name;
|
||||
}
|
||||
|
||||
|
|
@ -1235,7 +1246,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_N163]=new DivSysDef(
|
||||
"Namco C163", NULL, 0x8c, 0, 8, false, true, 0, false,
|
||||
"Namco 163/C163/129/160/106/whatever", NULL, 0x8c, 0, 8, false, true, 0, false,
|
||||
"an expansion chip for the Famicom, with full wavetable.",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
|
|
@ -1829,7 +1840,7 @@ void DivEngine::registerSystems() {
|
|||
|
||||
sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef(
|
||||
"Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false,
|
||||
"a sample chip used in the Gravis Ultrasound, popular in the PC (DOS) demoscene.",
|
||||
"a sample chip used in the Ensoniq's unique TransWave synthesizers, and SoundScape series PC ISA soundcards (which are yet another (partially) Sound Blaster compatible ones with emulated OPL3 and MIDI ROMpler).",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"},
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"},
|
||||
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
|
||||
|
|
@ -1839,8 +1850,8 @@ void DivEngine::registerSystems() {
|
|||
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
|
||||
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false,
|
||||
"like OPL but with an ADPCM channel.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"},
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "ADPCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "P"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM},
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA},
|
||||
{},
|
||||
|
|
@ -1851,8 +1862,8 @@ void DivEngine::registerSystems() {
|
|||
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
|
||||
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false,
|
||||
"the Y8950 chip, in drums mode.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"},
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "ADPCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "P"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_AMIGA},
|
||||
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_NULL},
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(streamID);
|
||||
w->writeS(write.val); // sample number
|
||||
w->writeC((sample->loopStart==0)|(sampleDir[streamID]?0x10:0)); // flags
|
||||
if (sample->loopStart>0 && !sampleDir[streamID]) {
|
||||
if (sample->isLoopable() && !sampleDir[streamID]) {
|
||||
loopTimer[streamID]=sample->length8;
|
||||
loopSample[streamID]=write.val;
|
||||
}
|
||||
|
|
@ -802,7 +802,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) {
|
||||
if (version<0x150) {
|
||||
lastError="VGM version is too low";
|
||||
return NULL;
|
||||
|
|
@ -1549,7 +1549,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
size_t memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* sample=song.sample[i];
|
||||
unsigned int alignedSize=(sample->length8+0xff)&(~0xff);
|
||||
unsigned int alignedSize=(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff);
|
||||
if (alignedSize>65536) alignedSize=65536;
|
||||
if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) {
|
||||
memPos=(memPos+0xffff)&0xff0000;
|
||||
|
|
@ -1559,8 +1559,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
sample->offSegaPCM=memPos;
|
||||
unsigned int readPos=0;
|
||||
for (unsigned int j=0; j<alignedSize; j++) {
|
||||
if (readPos>=sample->length8) {
|
||||
if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) {
|
||||
if (readPos>=sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
|
||||
if (sample->isLoopable()) {
|
||||
readPos=sample->loopStart;
|
||||
pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
|
||||
} else {
|
||||
|
|
@ -1663,7 +1663,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
memcpy(sampleMem,writeZ280[i]->getSampleMem(),sampleMemLen);
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
if (s->depth==16) {
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
unsigned int pos=s->offYMZ280B;
|
||||
for (unsigned int j=0; j<s->samples; j++) {
|
||||
unsigned char lo=sampleMem[pos+j*2];
|
||||
|
|
@ -1795,6 +1795,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
playSub(false);
|
||||
size_t tickCount=0;
|
||||
bool writeLoop=false;
|
||||
int ord=-1;
|
||||
int exportChans=0;
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!willExport[dispatchOfChan[i]]) continue;
|
||||
exportChans++;
|
||||
}
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==curOrder && loopRow==curRow && ticks==1) {
|
||||
|
|
@ -1820,6 +1826,26 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
writeLoop=false;
|
||||
loopPos=-1;
|
||||
}
|
||||
} else {
|
||||
// check for pattern change
|
||||
if (prevOrder!=ord) {
|
||||
logI("registering order change %d on %d",prevOrder, prevRow);
|
||||
ord=prevOrder;
|
||||
|
||||
if (patternHints) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0xfe);
|
||||
w->writeI(3+exportChans);
|
||||
w->writeC(0x01);
|
||||
w->writeC(prevOrder);
|
||||
w->writeC(prevRow);
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!willExport[dispatchOfChan[i]]) continue;
|
||||
w->writeC(curSubSong->orders.ord[i][prevOrder]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
@ -1871,12 +1897,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
if (loopSample[nextToTouch]<song.sampleLen) {
|
||||
DivSample* sample=song.sample[loopSample[nextToTouch]];
|
||||
// insert loop
|
||||
if (sample->loopStart<(int)sample->length8) {
|
||||
if (sample->loopStart<(int)sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
|
||||
w->writeC(0x93);
|
||||
w->writeC(nextToTouch);
|
||||
w->writeI(sample->off8+sample->loopStart);
|
||||
w->writeC(0x81);
|
||||
w->writeI(sample->length8-sample->loopStart);
|
||||
w->writeI(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->loopStart);
|
||||
}
|
||||
}
|
||||
loopSample[nextToTouch]=-1;
|
||||
|
|
@ -1920,24 +1946,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
WString ws;
|
||||
ws=utf8To16(song.name.c_str());
|
||||
w->writeWString(ws,false); // name
|
||||
w->writeS(0); // japanese name
|
||||
w->writeS(0); // game name
|
||||
w->writeS(0); // japanese game name
|
||||
if (song.systemLen>1) {
|
||||
ws=L"Multiple Systems";
|
||||
} else {
|
||||
ws=utf8To16(getSystemName(song.system[0]));
|
||||
}
|
||||
ws=utf8To16(song.nameJ.c_str());
|
||||
w->writeWString(ws,false); // japanese name
|
||||
ws=utf8To16(song.category.c_str());
|
||||
w->writeWString(ws,false); // game name
|
||||
ws=utf8To16(song.categoryJ.c_str());
|
||||
w->writeWString(ws,false); // japanese game name
|
||||
ws=utf8To16(song.systemName.c_str());
|
||||
w->writeWString(ws,false); // system name
|
||||
if (song.systemLen>1) {
|
||||
ws=L"複数システム";
|
||||
} else {
|
||||
ws=utf8To16(getSystemNameJ(song.system[0]));
|
||||
}
|
||||
ws=utf8To16(song.systemNameJ.c_str());
|
||||
w->writeWString(ws,false); // japanese system name
|
||||
ws=utf8To16(song.author.c_str());
|
||||
w->writeWString(ws,false); // author name
|
||||
w->writeS(0); // japanese author name
|
||||
ws=utf8To16(song.authorJ.c_str());
|
||||
w->writeWString(ws,false); // japanese author name
|
||||
w->writeS(0); // date
|
||||
w->writeWString(L"Furnace Tracker",false); // ripper
|
||||
w->writeS(0); // notes
|
||||
|
|
|
|||
|
|
@ -92,3 +92,65 @@ bool DivWavetable::save(const char* path) {
|
|||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivWavetable::saveDMW(const char* path) {
|
||||
SafeWriter* w=new SafeWriter();
|
||||
w->init();
|
||||
|
||||
// write width
|
||||
w->writeI(len);
|
||||
|
||||
// check height
|
||||
w->writeC(max);
|
||||
if (max==255) {
|
||||
// write as new format (because 0xff means that)
|
||||
w->writeC(1); // format version
|
||||
w->writeC(max); // actual height
|
||||
|
||||
// waveform data
|
||||
for (int i=0; i<len; i++) {
|
||||
w->writeI(data[i]&0xff);
|
||||
}
|
||||
} else {
|
||||
// write as old format
|
||||
for (int i=0; i<len; i++) {
|
||||
w->writeC(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* outFile=ps_fopen(path,"wb");
|
||||
if (outFile==NULL) {
|
||||
logE("could not save wavetable: %s!",strerror(errno));
|
||||
w->finish();
|
||||
return false;
|
||||
}
|
||||
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
|
||||
logW("did not write entire wavetable!");
|
||||
}
|
||||
fclose(outFile);
|
||||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivWavetable::saveRaw(const char* path) {
|
||||
SafeWriter* w=new SafeWriter();
|
||||
w->init();
|
||||
|
||||
// waveform data
|
||||
for (int i=0; i<len; i++) {
|
||||
w->writeC(data[i]);
|
||||
}
|
||||
|
||||
FILE* outFile=ps_fopen(path,"wb");
|
||||
if (outFile==NULL) {
|
||||
logE("could not save wavetable: %s!",strerror(errno));
|
||||
w->finish();
|
||||
return false;
|
||||
}
|
||||
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
|
||||
logW("did not write entire wavetable!");
|
||||
}
|
||||
fclose(outFile);
|
||||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,20 @@ struct DivWavetable {
|
|||
* @return whether it was successful.
|
||||
*/
|
||||
bool save(const char* path);
|
||||
|
||||
/**
|
||||
* save this wavetable to a file in .dmw format.
|
||||
* @param path file path.
|
||||
* @return whether it was successful.
|
||||
*/
|
||||
bool saveDMW(const char* path);
|
||||
|
||||
/**
|
||||
* save this wavetable to a file in raw format.
|
||||
* @param path file path.
|
||||
* @return whether it was successful.
|
||||
*/
|
||||
bool saveRaw(const char* path);
|
||||
DivWavetable():
|
||||
len(32),
|
||||
min(0),
|
||||
|
|
@ -56,4 +70,4 @@ struct DivWavetable {
|
|||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue