furnace/src/engine/engine.cpp
cam900 028adf2c8e Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
* 'master' of https://github.com/tildearrow/furnace: (70 commits)
  whoops
  GUI: AY8930 credits
  GUI: fix inability to close subsongs
  BANK
  OPN: wire up ExtCh system
  fix build failure
  dev95 - multiple songs in a single file (READ)
  DO NOT USE - THIS FAILS - WORK IN PROGRESS
  enforce asset limits
  old .dmf loading improvements
  add AICA and YMZ ADPCM formats
  allocate ID for YMZ280B
  harden .fur file saver
  Fix AY VGM output, Fix presets
  preparations for UI improvements
  GUI: add more presets
  prepare for ExtCh OPN/OPNA
  GUI: clarify that lock layout doesn't work yet
  GUI: remember last state of order edit mode
  GUI: store edit/followOrders/followPattern state
  ...

# Conflicts:
#	src/engine/fileOps.cpp
#	src/engine/platform/ym2610.cpp
#	src/engine/platform/ym2610b.cpp
#	src/engine/sample.cpp
#	src/engine/sample.h
#	src/engine/sysDef.cpp
#	src/gui/doAction.cpp
#	src/gui/sysConf.cpp
2022-05-18 03:09:55 +09:00

2962 lines
73 KiB
C++

/**
* 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 "engine.h"
#include "instrument.h"
#include "safeReader.h"
#include "../ta-log.h"
#include "../fileutils.h"
#include "../audio/sdl.h"
#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 <sndfile.h>
#include <fmt/printf.h>
void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) {
((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size);
}
const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNull) {
switch (effect) {
case 0x00:
return "00xy: Arpeggio";
case 0x01:
return "01xx: Pitch slide up";
case 0x02:
return "02xx: Pitch slide down";
case 0x03:
return "03xx: Portamento";
case 0x04:
return "04xy: Vibrato (x: speed; y: depth)";
case 0x07:
return "07xy: Tremolo (x: speed; y: depth)";
case 0x08:
return "08xy: Set panning (x: left; y: right)";
case 0x09:
return "09xx: Set speed 1";
case 0x0a:
return "0Axy: Volume slide (0y: down; x0: up)";
case 0x0b:
return "0Bxx: Jump to pattern";
case 0x0c:
return "0Cxx: Retrigger";
case 0x0d:
return "0Dxx: Jump to next pattern";
case 0x0f:
return "0Fxx: Set speed 2";
case 0x80:
return "80xx: Set panning (00: left; 80: center; FF: right)";
case 0x81:
return "81xx: Set panning (left channel)";
case 0x82:
return "82xx: Set panning (right channel)";
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
return "Cxxx: Set tick rate (hz)";
case 0xe0:
return "E0xx: Set arp speed";
case 0xe1:
return "E1xy: Note slide up (x: speed; y: semitones)";
case 0xe2:
return "E2xy: Note slide down (x: speed; y: semitones)";
case 0xe3:
return "E3xx: Set vibrato shape (0: up/down; 1: up only; 2: down only)";
case 0xe4:
return "E4xx: Set vibrato range";
case 0xe5:
return "E5xx: Set pitch (80: center)";
case 0xea:
return "EAxx: Legato";
case 0xeb:
return "EBxx: Set sample bank";
case 0xec:
return "ECxx: Note cut";
case 0xed:
return "EDxx: Note delay";
case 0xee:
return "EExx: Send external command";
case 0xef:
return "EFxx: Set global tuning (quirky!)";
case 0xf0:
return "F0xx: Set tick rate (bpm)";
case 0xf1:
return "F1xx: Single tick note slide up";
case 0xf2:
return "F2xx: Single tick note slide down";
case 0xf3:
return "F3xx: Fine volume slide up";
case 0xf4:
return "F4xx: Fine volume slide down";
case 0xf8:
return "F8xx: Single tick volume slide up";
case 0xf9:
return "F9xx: Single tick volume slide down";
case 0xfa:
return "FAxx: Fast volume slide (0y: down; x0: up)";
case 0xff:
return "FFxx: Stop song";
default:
if ((effect&0xf0)==0x90) {
return "9xxx: Set sample offset*256";
} else if (chan>=0 && chan<chans) {
const char* ret=disCont[dispatchOfChan[chan]].dispatch->getEffectName(effect);
if (ret!=NULL) return ret;
}
break;
}
return notNull?"Invalid effect":NULL;
}
void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
loopOrder=0;
loopRow=0;
loopEnd=-1;
int nextOrder=-1;
int nextRow=0;
int effectVal=0;
DivPattern* pat[DIV_MAX_CHANS];
for (int i=0; i<curSubSong->ordersLen; i++) {
for (int j=0; j<chans; j++) {
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
}
for (int j=nextRow; j<curSubSong->patLen; j++) {
nextRow=0;
for (int k=0; k<chans; k++) {
for (int l=0; l<curPat[k].effectCols; l++) {
effectVal=pat[k]->data[j][5+(l<<1)];
if (effectVal<0) effectVal=0;
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
}
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
if (nextOrder==-1) {
nextOrder=effectVal;
nextRow=0;
}
}
}
}
if (nextOrder!=-1) {
if (nextOrder<=i) {
loopOrder=nextOrder;
loopRow=nextRow;
loopEnd=i;
return;
}
i=nextOrder-1;
nextOrder=-1;
break;
}
}
}
}
void _runExportThread(DivEngine* caller) {
caller->runExportThread();
}
bool DivEngine::isExporting() {
return exporting;
}
#define EXPORT_BUFSIZE 2048
void DivEngine::runExportThread() {
switch (exportMode) {
case DIV_EXPORT_MODE_ONE: {
SNDFILE* sf;
SF_INFO si;
si.samplerate=got.rate;
si.channels=2;
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
sf=sf_open(exportPath.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
logE("could not open file for writing! (%s)",sf_strerror(NULL));
exporting=false;
return;
}
float* outBuf[3];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
outBuf[2]=new float[EXPORT_BUFSIZE*2];
// take control of audio output
deinitAudioBackend();
playSub(false);
logI("rendering to file...");
while (playing) {
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
for (int i=0; i<EXPORT_BUFSIZE; i++) {
outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]));
outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]));
}
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
}
if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) {
logE("error: failed to write entire buffer!");
break;
}
}
delete[] outBuf[0];
delete[] outBuf[1];
delete[] outBuf[2];
if (sf_close(sf)!=0) {
logE("could not close audio file!");
}
exporting=false;
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}
if (!output->setRun(true)) {
logE("error while activating audio!");
}
}
logI("done!");
break;
}
case DIV_EXPORT_MODE_MANY_SYS: {
SNDFILE* sf[32];
SF_INFO si[32];
String fname[32];
for (int i=0; i<song.systemLen; i++) {
sf[i]=NULL;
si[i].samplerate=got.rate;
if (disCont[i].dispatch->isStereo()) {
si[i].channels=2;
} else {
si[i].channels=1;
}
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
}
for (int i=0; i<song.systemLen; i++) {
fname[i]=fmt::sprintf("%s_s%02d.wav",exportPath,i+1);
logI("- %s",fname[i].c_str());
sf[i]=sf_open(fname[i].c_str(),SFM_WRITE,&si[i]);
if (sf[i]==NULL) {
logE("could not open file for writing! (%s)",sf_strerror(NULL));
for (int j=0; j<i; j++) {
sf_close(sf[i]);
}
return;
}
}
float* outBuf[2];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
short* sysBuf=new short[EXPORT_BUFSIZE*2];
// take control of audio output
deinitAudioBackend();
playSub(false);
logI("rendering to files...");
while (playing) {
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
for (int i=0; i<song.systemLen; i++) {
for (int j=0; j<EXPORT_BUFSIZE; j++) {
if (!disCont[i].dispatch->isStereo()) {
sysBuf[j]=disCont[i].bbOut[0][j];
} else {
sysBuf[j<<1]=disCont[i].bbOut[0][j];
sysBuf[1+(j<<1)]=disCont[i].bbOut[1][j];
}
}
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! (%d) %d>%d",i,totalProcessed,EXPORT_BUFSIZE);
}
if (sf_writef_short(sf[i],sysBuf,totalProcessed)!=(int)totalProcessed) {
logE("error: failed to write entire buffer! (%d)",i);
break;
}
}
}
delete[] outBuf[0];
delete[] outBuf[1];
delete[] sysBuf;
for (int i=0; i<song.systemLen; i++) {
if (sf_close(sf[i])!=0) {
logE("could not close audio file!");
}
}
exporting=false;
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}
if (!output->setRun(true)) {
logE("error while activating audio!");
}
}
logI("done!");
break;
}
case DIV_EXPORT_MODE_MANY_CHAN: {
// take control of audio output
deinitAudioBackend();
float* outBuf[3];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
outBuf[2]=new float[EXPORT_BUFSIZE*2];
int loopCount=remainingLoops;
logI("rendering to files...");
for (int i=0; i<chans; i++) {
SNDFILE* sf;
SF_INFO si;
String fname=fmt::sprintf("%s_c%02d.wav",exportPath,i+1);
logI("- %s",fname.c_str());
si.samplerate=got.rate;
si.channels=2;
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
sf=sf_open(fname.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
logE("could not open file for writing! (%s)",sf_strerror(NULL));
break;
}
for (int j=0; j<chans; j++) {
bool mute=(j!=i);
isMuted[j]=mute;
}
if (getChannelType(i)==5) {
for (int j=i; j<chans; j++) {
if (getChannelType(j)!=5) break;
isMuted[j]=false;
}
}
for (int j=0; j<chans; j++) {
if (disCont[dispatchOfChan[j]].dispatch!=NULL) {
disCont[dispatchOfChan[j]].dispatch->muteChannel(dispatchChanOfChan[j],isMuted[j]);
}
}
curOrder=0;
remainingLoops=loopCount;
playSub(false);
while (playing) {
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
for (int j=0; j<EXPORT_BUFSIZE; j++) {
outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]));
outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]));
}
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
}
if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) {
logE("error: failed to write entire buffer!");
break;
}
}
if (sf_close(sf)!=0) {
logE("could not close audio file!");
}
if (getChannelType(i)==5) {
i++;
while (true) {
if (i>=chans) break;
if (getChannelType(i)!=5) break;
}
i--;
}
if (stopExport) break;
}
exporting=false;
delete[] outBuf[0];
delete[] outBuf[1];
delete[] outBuf[2];
for (int i=0; i<chans; i++) {
isMuted[i]=false;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],false);
}
}
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}
if (!output->setRun(true)) {
logE("error while activating audio!");
}
}
logI("done!");
break;
}
}
stopExport=false;
}
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) {
exportPath=path;
exportMode=mode;
if (exportMode!=DIV_EXPORT_MODE_ONE) {
// remove extension
String lowerCase=exportPath;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
size_t extPos=lowerCase.rfind(".wav");
if (extPos!=String::npos) {
exportPath=exportPath.substr(0,extPos);
}
}
exporting=true;
stopExport=false;
stop();
repeatPattern=false;
setOrder(0);
remainingLoops=loops;
exportThread=new std::thread(_runExportThread,this);
return true;
}
void DivEngine::waitAudioFile() {
if (exportThread!=NULL) {
exportThread->join();
}
}
bool DivEngine::haltAudioFile() {
stopExport=true;
stop();
return true;
}
void DivEngine::notifyInsChange(int ins) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsChange(ins);
}
BUSY_END;
}
void DivEngine::notifyWaveChange(int wave) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyWaveChange(wave);
}
BUSY_END;
}
int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) {
ret = NULL;
if (path.empty()) {
return 0;
}
logI("loading ROM %s...",path);
FILE* f=ps_fopen(path.c_str(),"rb");
if (f==NULL) {
logE("error: %s",strerror(errno));
lastError=strerror(errno);
return -1;
}
if (fseek(f,0,SEEK_END)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on seek: %s",strerror(errno));
fclose(f);
return -1;
}
ssize_t len=ftell(f);
if (len==(SIZE_MAX>>1)) {
logE("could not get file length: %s",strerror(errno));
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
fclose(f);
return -1;
}
if (len<1) {
if (len==0) {
logE("that file is empty!");
lastError="file is empty";
} else {
logE("tell error: %s",strerror(errno));
lastError=fmt::sprintf("on tell: %s",strerror(errno));
}
fclose(f);
return -1;
}
if (len!=expectedSize) {
logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len);
lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len);
return -1;
}
if (fseek(f,0,SEEK_SET)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on get size: %s",strerror(errno));
fclose(f);
return -1;
}
unsigned char* file=new unsigned char[len];
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
logE("read error: %s",strerror(errno));
lastError=fmt::sprintf("on read: %s",strerror(errno));
fclose(f);
delete[] file;
return -1;
}
fclose(f);
ret = file;
return 0;
}
int DivEngine::loadSampleROMs() {
if (yrw801ROM!=NULL) {
delete[] yrw801ROM;
yrw801ROM=NULL;
}
if (tg100ROM!=NULL) {
delete[] tg100ROM;
tg100ROM=NULL;
}
if (mu5ROM!=NULL) {
delete[] mu5ROM;
mu5ROM=NULL;
}
int error = 0;
error += loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM);
error += loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM);
error += loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM);
return error;
}
void DivEngine::renderSamplesP() {
BUSY_BEGIN;
renderSamples();
BUSY_END;
}
void DivEngine::renderSamples() {
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
// step 1: render samples
for (int i=0; i<song.sampleLen; i++) {
song.sample[i]->render();
}
// step 2: render samples to dispatch
for (int i=0; i<song.systemLen; i++) {
if (disCont[i].dispatch!=NULL) {
disCont[i].dispatch->renderSamples();
}
}
}
String DivEngine::encodeSysDesc(std::vector<int>& desc) {
String ret;
if (desc[0]!=0) {
int index=0;
for (size_t i=0; i<desc.size(); i+=4) {
ret+=fmt::sprintf("%d %d %d %d ",systemToFileFur((DivSystem)desc[i]),desc[i+1],desc[i+2],desc[i+3]);
index++;
if (index>=32) break;
}
}
return ret;
}
std::vector<int> DivEngine::decodeSysDesc(String desc) {
std::vector<int> ret;
bool hasVal=false;
bool negative=false;
int val=0;
int curStage=0;
int sysID=0;
int sysVol=0;
int sysPan=0;
int sysFlags=0;
desc+=' '; // ha
for (char i: desc) {
switch (i) {
case ' ':
if (hasVal) {
if (negative) val=-val;
switch (curStage) {
case 0:
sysID=val;
curStage++;
break;
case 1:
sysVol=val;
curStage++;
break;
case 2:
sysPan=val;
curStage++;
break;
case 3:
sysFlags=val;
if (systemFromFileFur(sysID)!=0) {
if (sysVol<-128) sysVol=-128;
if (sysVol>127) sysVol=127;
if (sysPan<-128) sysPan=-128;
if (sysPan>127) sysPan=127;
ret.push_back(systemFromFileFur(sysID));
ret.push_back(sysVol);
ret.push_back(sysPan);
ret.push_back(sysFlags);
}
curStage=0;
break;
}
hasVal=false;
negative=false;
val=0;
}
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
val=(val*10)+(i-'0');
hasVal=true;
break;
case '-':
if (!hasVal) negative=true;
break;
}
}
return ret;
}
void DivEngine::initSongWithDesc(const int* description) {
int chanCount=0;
if (description[0]!=0) {
int index=0;
for (int i=0; description[i]; i+=4) {
song.system[index]=(DivSystem)description[i];
song.systemVol[index]=description[i+1];
song.systemPan[index]=description[i+2];
song.systemFlags[index]=description[i+3];
index++;
chanCount+=getChannelCount(song.system[index]);
if (chanCount>=63) break;
if (index>=32) break;
}
song.systemLen=index;
}
}
void DivEngine::createNew(const int* description) {
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
song.unload();
song=DivSong();
changeSong(0);
if (description!=NULL) {
initSongWithDesc(description);
}
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
void DivEngine::swapChannels(int src, int dest) {
logV("swapping channel %d with %d",src,dest);
if (src==dest) {
logV("not swapping channels because it's the same channel!",src,dest);
return;
}
for (int i=0; i<256; i++) {
curOrders->ord[dest][i]^=curOrders->ord[src][i];
curOrders->ord[src][i]^=curOrders->ord[dest][i];
curOrders->ord[dest][i]^=curOrders->ord[src][i];
DivPattern* prev=curPat[src].data[i];
curPat[src].data[i]=curPat[dest].data[i];
curPat[dest].data[i]=prev;
}
curPat[src].effectCols^=curPat[dest].effectCols;
curPat[dest].effectCols^=curPat[src].effectCols;
curPat[src].effectCols^=curPat[dest].effectCols;
String prevChanName=curSubSong->chanName[src];
String prevChanShortName=curSubSong->chanShortName[src];
bool prevChanShow=curSubSong->chanShow[src];
bool prevChanCollapse=curSubSong->chanCollapse[src];
curSubSong->chanName[src]=curSubSong->chanName[dest];
curSubSong->chanShortName[src]=curSubSong->chanShortName[dest];
curSubSong->chanShow[src]=curSubSong->chanShow[dest];
curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest];
curSubSong->chanName[dest]=prevChanName;
curSubSong->chanShortName[dest]=prevChanShortName;
curSubSong->chanShow[dest]=prevChanShow;
curSubSong->chanCollapse[dest]=prevChanCollapse;
}
void DivEngine::stompChannel(int ch) {
logV("stomping channel %d",ch);
for (int i=0; i<256; i++) {
curOrders->ord[ch][i]=0;
}
curPat[ch].wipePatterns();
curPat[ch].effectCols=1;
curSubSong->chanName[ch]="";
curSubSong->chanShortName[ch]="";
curSubSong->chanShow[ch]=true;
curSubSong->chanCollapse[ch]=false;
}
void DivEngine::changeSong(size_t songIndex) {
if (songIndex>=song.subsong.size()) return;
curSubSong=song.subsong[songIndex];
curPat=song.subsong[songIndex]->pat;
curOrders=&song.subsong[songIndex]->orders;
curSubSongIndex=songIndex;
curOrder=0;
curRow=0;
}
void DivEngine::swapChannelsP(int src, int dest) {
if (src<0 || src>=chans) return;
if (dest<0 || dest>=chans) return;
BUSY_BEGIN;
saveLock.lock();
swapChannels(src,dest);
saveLock.unlock();
BUSY_END;
}
void DivEngine::changeSongP(size_t index) {
if (index>=song.subsong.size()) return;
if (index==curSubSongIndex) return;
stop();
BUSY_BEGIN;
saveLock.lock();
changeSong(index);
saveLock.unlock();
BUSY_END;
}
int DivEngine::addSubSong() {
if (song.subsong.size()>=127) return -1;
BUSY_BEGIN;
saveLock.lock();
song.subsong.push_back(new DivSubSong);
saveLock.unlock();
BUSY_END;
return song.subsong.size()-1;
}
bool DivEngine::removeSubSong(int index) {
if (song.subsong.size()<=1) return false;
stop();
BUSY_BEGIN;
saveLock.lock();
song.subsong[index]->clearData();
delete song.subsong[index];
song.subsong.erase(song.subsong.begin()+index);
changeSong(0);
saveLock.unlock();
BUSY_END;
return true;
}
void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
int chanCount=chans;
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
if (!preserveOrder) {
int firstChan=0;
int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]);
while (dispatchOfChan[firstChan]!=index) firstChan++;
int lastChan=firstChan+getChannelCount(song.system[index]);
if (chanMovement!=0) {
if (chanMovement>0) {
// add channels
for (int i=chanCount+chanMovement-1; i>=lastChan+chanMovement; i--) {
swapChannels(i,i-chanMovement);
}
for (int i=lastChan; i<lastChan+chanMovement; i++) {
stompChannel(i);
}
} else {
// remove channels
for (int i=lastChan+chanMovement; i<lastChan; i++) {
stompChannel(i);
}
for (int i=lastChan+chanMovement; i<chanCount+chanMovement; i++) {
swapChannels(i,i-chanMovement);
}
}
}
}
song.system[index]=which;
song.systemFlags[index]=0;
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
bool DivEngine::addSystem(DivSystem which) {
if (song.systemLen>32) {
lastError="max number of systems is 32";
return false;
}
// this was DIV_MAX_CHANS but I am setting it to 63 for now due to an ImGui limitation
if (chans+getChannelCount(which)>63) {
lastError="max number of total channels is 63";
return false;
}
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
song.system[song.systemLen]=which;
song.systemVol[song.systemLen]=64;
song.systemPan[song.systemLen]=0;
song.systemFlags[song.systemLen++]=0;
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
return true;
}
// TODO: maybe issue with subsongs?
bool DivEngine::removeSystem(int index, bool preserveOrder) {
if (song.systemLen<=1) {
lastError="cannot remove the last one";
return false;
}
if (index<0 || index>=song.systemLen) {
lastError="invalid index";
return false;
}
int chanCount=chans;
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
if (!preserveOrder) {
int firstChan=0;
while (dispatchOfChan[firstChan]!=index) firstChan++;
for (int i=0; i<getChannelCount(song.system[index]); i++) {
stompChannel(i+firstChan);
}
for (int i=firstChan+getChannelCount(song.system[index]); i<chanCount; i++) {
swapChannels(i,i-getChannelCount(song.system[index]));
}
}
song.system[index]=DIV_SYSTEM_NULL;
song.systemLen--;
for (int i=index; i<song.systemLen; i++) {
song.system[i]=song.system[i+1];
song.systemVol[i]=song.systemVol[i+1];
song.systemPan[i]=song.systemPan[i+1];
song.systemFlags[i]=song.systemFlags[i+1];
}
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
return true;
}
void DivEngine::poke(int sys, unsigned int addr, unsigned short val) {
if (sys<0 || sys>=song.systemLen) return;
BUSY_BEGIN;
disCont[sys].dispatch->poke(addr,val);
BUSY_END;
}
void DivEngine::poke(int sys, std::vector<DivRegWrite>& wlist) {
if (sys<0 || sys>=song.systemLen) return;
BUSY_BEGIN;
disCont[sys].dispatch->poke(wlist);
BUSY_END;
}
String DivEngine::getLastError() {
return lastError;
}
String DivEngine::getWarnings() {
return warnings;
}
DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) {
if (index==-2 && tempIns!=NULL) {
return tempIns;
}
if (index<0 || index>=song.insLen) {
switch (fallbackType) {
case DIV_INS_OPLL:
return &song.nullInsOPLL;
break;
case DIV_INS_OPL:
return &song.nullInsOPL;
break;
default:
break;
}
return &song.nullIns;
}
return song.ins[index];
}
DivWavetable* DivEngine::getWave(int index) {
if (index<0 || index>=song.waveLen) {
if (song.waveLen>0) {
return song.wave[0];
} else {
return &song.nullWave;
}
}
return song.wave[index];
}
DivSample* DivEngine::getSample(int index) {
if (index<0 || index>=song.sampleLen) return &song.nullSample;
return song.sample[index];
}
DivDispatch* DivEngine::getDispatch(int index) {
if (index<0 || index>=song.systemLen) return NULL;
return disCont[index].dispatch;
}
void DivEngine::setLoops(int loops) {
remainingLoops=loops;
}
DivChannelState* DivEngine::getChanState(int ch) {
if (ch<0 || ch>=chans) return NULL;
return &chan[ch];
}
void* DivEngine::getDispatchChanState(int ch) {
if (ch<0 || ch>=chans) return NULL;
return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]);
}
unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) {
if (sys<0 || sys>=song.systemLen) return NULL;
if (disCont[sys].dispatch==NULL) return NULL;
size=disCont[sys].dispatch->getRegisterPoolSize();
depth=disCont[sys].dispatch->getRegisterPoolDepth();
return disCont[sys].dispatch->getRegisterPool();
}
DivMacroInt* DivEngine::getMacroInt(int chan) {
if (chan<0 || chan>=chans) return NULL;
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]);
}
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
if (chan<0 || chan>=chans) return NULL;
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
}
void DivEngine::enableCommandStream(bool enable) {
cmdStreamEnabled=enable;
}
void DivEngine::getCommandStream(std::vector<DivCommand>& where) {
BUSY_BEGIN;
where.clear();
for (DivCommand& i: cmdStream) {
where.push_back(i);
}
cmdStream.clear();
BUSY_END;
}
void DivEngine::playSub(bool preserveDrift, int goalRow) {
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
reset();
if (preserveDrift && curOrder==0) return;
bool oldRepeatPattern=repeatPattern;
repeatPattern=false;
int goal=curOrder;
curOrder=0;
curRow=0;
stepPlay=0;
int prevDrift;
prevDrift=clockDrift;
clockDrift=0;
cycles=0;
if (preserveDrift) {
endOfSong=false;
} else {
ticks=1;
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
}
speedAB=false;
playing=true;
skipping=true;
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
while (playing && curOrder<goal) {
if (nextTick(preserveDrift)) {
skipping=false;
return;
}
}
int oldOrder=curOrder;
while (playing && curRow<goalRow) {
if (nextTick(preserveDrift)) {
skipping=false;
return;
}
if (oldOrder!=curOrder) break;
}
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
if (goal>0 || goalRow>0) {
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->forceIns();
}
for (int i=0; i<chans; i++) {
chan[i].cut=-1;
}
repeatPattern=oldRepeatPattern;
if (preserveDrift) {
clockDrift=prevDrift;
} else {
clockDrift=0;
cycles=0;
}
if (!preserveDrift) {
ticks=1;
subticks=1;
}
skipping=false;
cmdStream.clear();
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
logV("playSub() took %dµs",std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count());
}
/*
int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
return period?
round((clock/base)/divider):
base*(divider/clock);
}*/
double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
if (song.linearPitch==2) { // full linear
return (note<<7);
}
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
return period?
(clock/base)/divider:
base*(divider/clock);
}
#define CONVERT_FNUM_BLOCK(bf,bits,note) \
double tuning=song.tuning; \
if (tuning<400.0) tuning=400.0; \
if (tuning>500.0) tuning=500.0; \
int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \
int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \
int block=(note)/12; \
if (block<0) block=0; \
if (block>7) block=7; \
bf>>=block; \
if (bf<0) bf=0; \
/* octave boundaries */ \
while (bf>0 && bf<boundaryBottom && block>0) { \
bf<<=1; \
block--; \
} \
if (bf>boundaryTop) { \
while (block<7 && bf>boundaryTop) { \
bf>>=1; \
block++; \
} \
if (bf>((1<<bits)-1)) { \
bf=(1<<bits)-1; \
} \
} \
/* logV("f-num: %d block: %d",bf,block); */ \
return bf|(block<<bits);
int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
if (song.linearPitch==2) { // full linear
return (note<<7);
}
int bf=calcBaseFreq(clock,divider,note,false);
CONVERT_FNUM_BLOCK(bf,bits,note)
}
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2, double clock, double divider, int blockBits) {
if (song.linearPitch==2) {
// do frequency calculation here
int nbase=base+pitch+pitch2;
double fbase=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(nbase+384)/(128.0*12.0));
int bf=period?
round((clock/fbase)/divider):
round(fbase*(divider/clock));
if (blockBits>0) {
CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7)
} else {
return bf;
}
}
if (song.linearPitch==1) {
// global pitch multiplier
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
if (song.pitchMacroIsLinear) {
pitch+=pitch2;
}
pitch+=2048;
if (pitch<0) pitch=0;
if (pitch>4095) pitch=4095;
int ret=period?
((base*(reversePitchTable[pitch]))/whatTheFuck):
(((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024;
if (!song.pitchMacroIsLinear) {
ret+=period?(-pitch2):pitch2;
}
return ret;
}
return period?
base-pitch-pitch2:
base+((pitch*octave)>>1)+pitch2;
}
int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) {
int panL=val>>bits;
int panR=val&((1<<bits)-1);
int diff=panR-panL;
float pan=0.5f;
if (diff!=0) {
pan=(1.0f+((float)diff/(float)MAX(panL,panR)))*0.5f;
}
return pan*range;
}
int DivEngine::convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range) {
return convertPanSplitToLinear((left<<8)|right,8,range);
}
unsigned int DivEngine::convertPanLinearToSplit(int val, unsigned char bits, int range) {
if (val<0) val=0;
if (val>range) val=range;
int maxV=(1<<bits)-1;
int panL=(((range-val)*maxV*2))/range;
int panR=((val)*maxV*2)/range;
if (panL>maxV) panL=maxV;
if (panR>maxV) panR=maxV;
return (panL<<bits)|panR;
}
void DivEngine::play() {
BUSY_BEGIN_SOFT;
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
if (stepPlay==0) {
freelance=false;
playSub(false);
} else {
stepPlay=0;
}
for (int i=0; i<DIV_MAX_CHANS; i++) {
keyHit[i]=false;
}
if (output) if (!skipping && output->midiOut!=NULL) {
int pos=totalTicksR/6;
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f));
output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0));
}
BUSY_END;
}
void DivEngine::playToRow(int row) {
BUSY_BEGIN_SOFT;
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
freelance=false;
playSub(false,row);
for (int i=0; i<DIV_MAX_CHANS; i++) {
keyHit[i]=false;
}
BUSY_END;
}
void DivEngine::stepOne(int row) {
if (!isPlaying()) {
BUSY_BEGIN_SOFT;
freelance=false;
playSub(false,row);
for (int i=0; i<DIV_MAX_CHANS; i++) {
keyHit[i]=false;
}
} else {
BUSY_BEGIN;
}
stepPlay=2;
ticks=1;
BUSY_END;
}
void DivEngine::stop() {
BUSY_BEGIN;
freelance=false;
playing=false;
extValuePresent=false;
stepPlay=0;
remainingLoops=-1;
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyPlaybackStop();
}
if (output) if (output->midiOut!=NULL) {
output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0));
for (int i=0; i<chans; i++) {
if (chan[i].curMidiNote>=0) {
output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0));
}
}
}
BUSY_END;
}
void DivEngine::halt() {
BUSY_BEGIN;
halted=true;
BUSY_END;
}
void DivEngine::resume() {
BUSY_BEGIN;
halted=false;
haltOn=DIV_HALT_NONE;
BUSY_END;
}
void DivEngine::haltWhen(DivHaltPositions when) {
BUSY_BEGIN;
halted=false;
haltOn=when;
BUSY_END;
}
bool DivEngine::isHalted() {
return halted;
}
const char** DivEngine::getRegisterSheet(int sys) {
if (sys<0 || sys>=song.systemLen) return NULL;
return disCont[sys].dispatch->getRegisterSheet();
}
void DivEngine::recalcChans() {
bool isInsTypePossible[DIV_INS_MAX];
chans=0;
int chanIndex=0;
memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool));
for (int i=0; i<song.systemLen; i++) {
int chanCount=getChannelCount(song.system[i]);
chans+=chanCount;
for (int j=0; j<chanCount; j++) {
sysOfChan[chanIndex]=song.system[i];
dispatchOfChan[chanIndex]=i;
dispatchChanOfChan[chanIndex]=j;
chanIndex++;
if (sysDefs[song.system[i]]!=NULL) {
if (sysDefs[song.system[i]]->chanInsType[j][0]!=DIV_INS_NULL) {
isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][0]]=true;
}
if (sysDefs[song.system[i]]->chanInsType[j][1]!=DIV_INS_NULL) {
isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][1]]=true;
}
}
}
}
possibleInsTypes.clear();
for (int i=0; i<DIV_INS_MAX; i++) {
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
}
hasLoadedSomething=true;
}
void DivEngine::reset() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
chan[i]=DivChannelState();
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
chan[i].volume=chan[i].volMax;
if (song.linearPitch==0) chan[i].vibratoFine=4;
}
extValue=0;
extValuePresent=0;
speed1=curSubSong->speed1;
speed2=curSubSong->speed2;
firstTick=false;
nextSpeed=speed1;
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
}
}
globalPitch=0;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->reset();
disCont[i].clear();
}
}
void DivEngine::syncReset() {
BUSY_BEGIN;
reset();
BUSY_END;
}
const int sampleRates[6]={
4000, 8000, 11025, 16000, 22050, 32000
};
int DivEngine::fileToDivRate(int frate) {
if (frate<0) frate=0;
if (frate>5) frate=5;
return sampleRates[frate];
}
int DivEngine::divToFileRate(int drate) {
if (drate>26000) {
return 5;
} else if (drate>18000) {
return 4;
} else if (drate>14000) {
return 3;
} else if (drate>9500) {
return 2;
} else if (drate>6000) {
return 1;
} else {
return 0;
}
return 4;
}
int DivEngine::getEffectiveSampleRate(int rate) {
if (rate<1) return 0;
switch (song.system[0]) {
case DIV_SYSTEM_YMU759:
return 8000;
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT:
return 1278409/(1280000/rate);
case DIV_SYSTEM_PCE:
return 1789773/(1789773/rate);
case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT:
return (31250*MIN(255,(rate*255/31250)))/255;
case DIV_SYSTEM_QSOUND:
return (24038*MIN(65535,(rate*4096/24038)))/4096;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT:
return 18518;
case DIV_SYSTEM_VERA:
return (48828*MIN(128,(rate*128/48828)))/128;
case DIV_SYSTEM_X1_010:
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
case DIV_SYSTEM_ES5506:
return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case
default:
break;
}
return rate;
}
void DivEngine::previewSample(int sample, int note) {
BUSY_BEGIN;
if (sample<0 || sample>=(int)song.sample.size()) {
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
return;
}
blip_clear(samp_bb);
double rate=song.sample[sample]->rate;
if (note>=0) {
rate=(song.tuning*pow(2.0,(double)(note+3)/12.0)*((double)song.sample[sample]->centerRate/8363.0));
if (rate<=0) rate=song.sample[sample]->rate;
}
if (rate<100) rate=100;
blip_set_rates(samp_bb,rate,got.rate);
samp_prevSample=0;
sPreview.pos=0;
sPreview.sample=sample;
sPreview.wave=-1;
sPreview.dir=false;
BUSY_END;
}
void DivEngine::stopSamplePreview() {
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
}
void DivEngine::previewWave(int wave, int note) {
BUSY_BEGIN;
if (wave<0 || wave>=(int)song.wave.size()) {
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
return;
}
if (song.wave[wave]->len<=0) {
BUSY_END;
return;
}
blip_clear(samp_bb);
double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0));
if (rate<100) rate=100;
blip_set_rates(samp_bb,rate,got.rate);
samp_prevSample=0;
sPreview.pos=0;
sPreview.sample=-1;
sPreview.wave=wave;
sPreview.dir=false;
BUSY_END;
}
void DivEngine::stopWavePreview() {
BUSY_BEGIN;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
}
String DivEngine::getConfigPath() {
return configPath;
}
int DivEngine::getMaxVolumeChan(int ch) {
return chan[ch].volMax>>8;
}
unsigned char DivEngine::getOrder() {
return curOrder;
}
int DivEngine::getRow() {
return curRow;
}
size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
unsigned char DivEngine::getSpeed1() {
return speed1;
}
unsigned char DivEngine::getSpeed2() {
return speed2;
}
float DivEngine::getHz() {
if (curSubSong->customTempo) {
return curSubSong->hz;
} else if (curSubSong->pal) {
return 60.0;
} else {
return 50.0;
}
return 60.0;
}
float DivEngine::getCurHz() {
return divider;
}
int DivEngine::getTotalSeconds() {
return totalSeconds;
}
int DivEngine::getTotalTicks() {
return totalTicks;
}
bool DivEngine::getRepeatPattern() {
return repeatPattern;
}
void DivEngine::setRepeatPattern(bool value) {
BUSY_BEGIN;
repeatPattern=value;
BUSY_END;
}
bool DivEngine::hasExtValue() {
return extValuePresent;
}
unsigned char DivEngine::getExtValue() {
return extValue;
}
bool DivEngine::isPlaying() {
return (playing && !freelance);
}
bool DivEngine::isStepping() {
return !(stepPlay==0);
}
bool DivEngine::isChannelMuted(int chan) {
return isMuted[chan];
}
void DivEngine::toggleMute(int chan) {
muteChannel(chan,!isMuted[chan]);
}
void DivEngine::toggleSolo(int chan) {
bool solo=false;
for (int i=0; i<chans; i++) {
if (i==chan) {
solo=true;
continue;
} else {
if (!isMuted[i]) {
solo=false;
break;
}
}
}
BUSY_BEGIN;
if (!solo) {
for (int i=0; i<chans; i++) {
isMuted[i]=(i!=chan);
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],isMuted[i]);
}
}
} else {
for (int i=0; i<chans; i++) {
isMuted[i]=false;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],isMuted[i]);
}
}
}
BUSY_END;
}
void DivEngine::muteChannel(int chan, bool mute) {
BUSY_BEGIN;
isMuted[chan]=mute;
if (disCont[dispatchOfChan[chan]].dispatch!=NULL) {
disCont[dispatchOfChan[chan]].dispatch->muteChannel(dispatchChanOfChan[chan],isMuted[chan]);
}
BUSY_END;
}
void DivEngine::unmuteAll() {
BUSY_BEGIN;
for (int i=0; i<chans; i++) {
isMuted[i]=false;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],isMuted[i]);
}
}
BUSY_END;
}
int DivEngine::addInstrument(int refChan) {
if (song.ins.size()>=256) return -1;
BUSY_BEGIN;
DivInstrument* ins=new DivInstrument;
int insCount=(int)song.ins.size();
DivInstrumentType prefType=getPreferInsType(refChan);
switch (prefType) {
case DIV_INS_OPLL:
*ins=song.nullInsOPLL;
break;
case DIV_INS_OPL:
*ins=song.nullInsOPL;
break;
default:
break;
}
if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) {
*ins=song.nullInsQSound;
}
ins->name=fmt::sprintf("Instrument %d",insCount);
if (prefType!=DIV_INS_NULL) {
ins->type=prefType;
}
saveLock.lock();
song.ins.push_back(ins);
song.insLen=insCount+1;
saveLock.unlock();
BUSY_END;
return insCount;
}
int DivEngine::addInstrumentPtr(DivInstrument* which) {
if (song.ins.size()>=256) {
delete which;
return -1;
}
BUSY_BEGIN;
saveLock.lock();
song.ins.push_back(which);
song.insLen=song.ins.size();
saveLock.unlock();
BUSY_END;
return song.insLen;
}
void DivEngine::loadTempIns(DivInstrument* which) {
BUSY_BEGIN;
if (tempIns==NULL) {
tempIns=new DivInstrument;
}
*tempIns=*which;
BUSY_END;
}
void DivEngine::delInstrument(int index) {
BUSY_BEGIN;
saveLock.lock();
if (index>=0 && index<(int)song.ins.size()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsDeletion(song.ins[index]);
}
delete song.ins[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]--;
}
}
}
}
}
saveLock.unlock();
BUSY_END;
}
int DivEngine::addWave() {
if (song.wave.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivWavetable* wave=new DivWavetable;
int waveCount=(int)song.wave.size();
song.wave.push_back(wave);
song.waveLen=waveCount+1;
saveLock.unlock();
BUSY_END;
return waveCount;
}
bool DivEngine::addWaveFromFile(const char* path) {
if (song.wave.size()>=256) {
lastError="too many wavetables!";
return false;
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=fmt::sprintf("%s",strerror(errno));
return false;
}
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;
}
len=ftell(f);
if (len<0) {
fclose(f);
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
return false;
}
if (len==(SIZE_MAX>>1)) {
fclose(f);
lastError="file size is invalid!";
return false;
}
if (len==0) {
fclose(f);
lastError="file is empty";
return false;
}
if (fseek(f,0,SEEK_SET)!=0) {
fclose(f);
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
return false;
}
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;
}
fclose(f);
SafeReader reader=SafeReader(buf,len);
unsigned char magic[16];
bool isFurnaceTable=false;
try {
reader.read(magic,16);
if (memcmp("-Furnace waveta-",magic,16)==0) {
isFurnaceTable=true;
}
} catch (EndOfFileException& e) {
reader.seek(0,SEEK_SET);
}
DivWavetable* wave=new DivWavetable;
try {
if (isFurnaceTable) {
reader.seek(16,SEEK_SET);
short version=reader.readS();
reader.readS(); // reserved
reader.seek(20,SEEK_SET);
if (wave->readWaveData(reader,version)!=DIV_DATA_SUCCESS) {
lastError="invalid wavetable header/data!";
delete wave;
delete[] buf;
return false;
}
} else {
try {
// read as .dmw
reader.seek(0,SEEK_SET);
int len=reader.readI();
wave->max=(unsigned char)reader.readC();
if (wave->max==255) { // new wavetable format
unsigned char waveVersion=reader.readC();
logI("reading modern .dmw...");
logD("wave version %d",waveVersion);
wave->max=reader.readC();
for (int i=0; i<len; i++) {
wave->data[i]=reader.readI();
}
} else if (reader.size()==(size_t)(len+5)) {
// read as .dmw
logI("reading .dmw...");
if (len>256) len=256;
for (int i=0; i<len; i++) {
wave->data[i]=(unsigned char)reader.readC();
}
} else {
// read as binary
logI("reading binary...");
len=reader.size();
if (len>256) len=256;
reader.seek(0,SEEK_SET);
for (int i=0; i<len; i++) {
wave->data[i]=(unsigned char)reader.readC();
if (wave->max<wave->data[i]) wave->max=wave->data[i];
}
wave->len=len;
}
} catch (EndOfFileException& e) {
// read as binary
len=reader.size();
logI("reading binary for being too small...");
if (len>256) len=256;
reader.seek(0,SEEK_SET);
for (int i=0; i<len; i++) {
wave->data[i]=(unsigned char)reader.readC();
if (wave->max<wave->data[i]) wave->max=wave->data[i];
}
wave->len=len;
}
}
} catch (EndOfFileException& e) {
delete wave;
delete[] buf;
lastError="premature end of file";
return false;
}
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;
}
void DivEngine::delWave(int index) {
BUSY_BEGIN;
saveLock.lock();
if (index>=0 && index<(int)song.wave.size()) {
delete song.wave[index];
song.wave.erase(song.wave.begin()+index);
song.waveLen=song.wave.size();
}
saveLock.unlock();
BUSY_END;
}
int DivEngine::addSample() {
if (song.sample.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size();
sample->name=fmt::sprintf("Sample %d",sampleCount);
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END;
return sampleCount;
}
int DivEngine::addSampleFromFile(const char* path) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return -1;
}
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;
}
}
const char* ext=strrchr(path,'.');
if (ext!=NULL) {
String extS;
for (; *ext; ext++) {
char i=*ext;
if (i>='A' && i<='Z') {
i+='a'-'A';
}
extS+=i;
}
if (extS==String(".dmc")) { // read as .dmc
size_t len=0;
DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size();
sample->name=stripPath;
FILE* f=fopen(path,"rb");
if (f==NULL) {
BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
delete sample;
return -1;
}
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 -1;
}
len=ftell(f);
if (len==0) {
fclose(f);
BUSY_END;
lastError="file is empty!";
delete sample;
return -1;
}
if (len==(SIZE_MAX>>1)) {
fclose(f);
BUSY_END;
lastError="file is invalid!";
delete sample;
return -1;
}
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 -1;
}
sample->rate=33144;
sample->centerRate=33144;
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8);
if (fread(sample->dataDPCM,1,len,f)==0) {
fclose(f);
BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
delete sample;
return -1;
}
saveLock.lock();
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END;
return sampleCount;
}
}
SF_INFO si;
SNDFILE* f=sf_open(path,SFM_READ,&si);
if (f==NULL) {
BUSY_END;
int err=sf_error(NULL);
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));
}
return -1;
}
if (si.frames>16777215) {
lastError="this sample is too big! max sample size is 16777215.";
sf_close(f);
BUSY_END;
return -1;
}
short* buf=new short[si.channels*si.frames];
if (sf_readf_short(f,buf,si.frames)!=si.frames) {
logW("sample read size mismatch!");
}
DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size();
sample->name=stripPath;
int index=0;
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
} else {
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
}
sample->init(si.frames);
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=buf[i+j];
}
averaged/=si.channels;
if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) {
sample->data8[index++]=averaged>>8;
} else {
sample->data16[index++]=averaged;
}
}
delete[] buf;
sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000;
if (sample->rate>96000) sample->rate=96000;
sample->centerRate=si.samplerate;
SF_INSTRUMENT inst;
if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE)
{
// There's no documentation on libsndfile detune range, but the code
// implies -50..50. Yet when loading a file you can get a >50 value.
if(inst.detune > 50)
inst.detune = inst.detune - 100;
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD)
{
sample->loopStart=inst.loops[0].start;
sample->loopEnd=inst.loops[0].end;
sample->loopMode=DivSampleLoopMode(int(inst.loops[0].mode-SF_LOOP_NONE));
if(inst.loops[0].end < (unsigned int)sampleCount)
sampleCount=inst.loops[0].end;
}
}
if (sample->centerRate<4000) sample->centerRate=4000;
if (sample->centerRate>64000) sample->centerRate=64000;
sf_close(f);
saveLock.lock();
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END;
return sampleCount;
}
void DivEngine::delSample(int index) {
BUSY_BEGIN;
saveLock.lock();
if (index>=0 && index<(int)song.sample.size()) {
delete song.sample[index];
song.sample.erase(song.sample.begin()+index);
song.sampleLen=song.sample.size();
renderSamples();
}
saveLock.unlock();
BUSY_END;
}
void DivEngine::addOrder(bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=0xff) return;
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=curOrders->ord[i][curOrder];
}
} else {
bool used[256];
for (int i=0; i<chans; i++) {
memset(used,0,sizeof(bool)*256);
for (int j=0; j<curSubSong->ordersLen; j++) {
used[curOrders->ord[i][j]]=true;
}
order[i]=0xff;
for (int j=0; j<256; j++) {
if (!used[j]) {
order[i]=j;
break;
}
}
}
}
if (where) { // at the end
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
playSub(false);
}
}
BUSY_END;
}
void DivEngine::deepCloneOrder(bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=0xff) return;
warnings="";
BUSY_BEGIN_SOFT;
for (int i=0; i<chans; i++) {
bool didNotFind=true;
logD("channel %d",i);
order[i]=curOrders->ord[i][curOrder];
// find free slot
for (int j=0; j<256; j++) {
logD("finding free slot in %d...",j);
if (curPat[i].data[j]==NULL) {
int origOrd=order[i];
order[i]=j;
DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
DivPattern* pat=curPat[i].getPattern(j,true);
memcpy(pat->data,oldPat->data,256*32*sizeof(short));
logD("found at %d",j);
didNotFind=false;
break;
}
}
if (didNotFind) {
addWarning(fmt::sprintf("no free patterns in channel %d!",i));
}
}
if (where) { // at the end
saveLock.lock();
for (int i=0; i<chans; i++) {
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<chans; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
playSub(false);
}
}
BUSY_END;
}
void DivEngine::deleteOrder() {
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
curOrders->ord[i][j]=curOrders->ord[i][j+1];
}
}
curSubSong->ordersLen--;
saveLock.unlock();
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
void DivEngine::moveOrderUp() {
BUSY_BEGIN_SOFT;
if (curOrder<1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
}
saveLock.unlock();
curOrder--;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
void DivEngine::moveOrderDown() {
BUSY_BEGIN_SOFT;
if (curOrder>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
}
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
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;
}
}
}
}
}
bool DivEngine::moveInsUp(int which) {
if (which<1 || which>=(int)song.ins.size()) return false;
BUSY_BEGIN;
DivInstrument* prev=song.ins[which];
saveLock.lock();
song.ins[which]=song.ins[which-1];
song.ins[which-1]=prev;
exchangeIns(which,which-1);
saveLock.unlock();
BUSY_END;
return true;
}
bool DivEngine::moveWaveUp(int which) {
if (which<1 || which>=(int)song.wave.size()) return false;
BUSY_BEGIN;
DivWavetable* prev=song.wave[which];
saveLock.lock();
song.wave[which]=song.wave[which-1];
song.wave[which-1]=prev;
saveLock.unlock();
BUSY_END;
return true;
}
bool DivEngine::moveSampleUp(int which) {
if (which<1 || which>=(int)song.sample.size()) return false;
BUSY_BEGIN;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which-1];
song.sample[which-1]=prev;
saveLock.unlock();
BUSY_END;
return true;
}
bool DivEngine::moveInsDown(int which) {
if (which<0 || which>=((int)song.ins.size())-1) return false;
BUSY_BEGIN;
DivInstrument* prev=song.ins[which];
saveLock.lock();
song.ins[which]=song.ins[which+1];
song.ins[which+1]=prev;
exchangeIns(which,which+1);
saveLock.unlock();
BUSY_END;
return true;
}
bool DivEngine::moveWaveDown(int which) {
if (which<0 || which>=((int)song.wave.size())-1) return false;
BUSY_BEGIN;
DivWavetable* prev=song.wave[which];
saveLock.lock();
song.wave[which]=song.wave[which+1];
song.wave[which+1]=prev;
saveLock.unlock();
BUSY_END;
return true;
}
bool DivEngine::moveSampleDown(int which) {
if (which<0 || which>=((int)song.sample.size())-1) return false;
BUSY_BEGIN;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which+1];
song.sample[which+1]=prev;
saveLock.unlock();
BUSY_END;
return true;
}
void DivEngine::noteOn(int chan, int ins, int note, int vol) {
if (chan<0 || chan>=chans) return;
BUSY_BEGIN;
pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true));
if (!playing) {
reset();
freelance=true;
playing=true;
}
BUSY_END;
}
void DivEngine::noteOff(int chan) {
if (chan<0 || chan>=chans) return;
BUSY_BEGIN;
pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false));
if (!playing) {
reset();
freelance=true;
playing=true;
}
BUSY_END;
}
void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
bool isViable[DIV_MAX_CHANS];
bool canPlayAnyway=false;
bool notInViableChannel=false;
if (midiBaseChan<0) midiBaseChan=0;
if (midiBaseChan>=chans) midiBaseChan=chans-1;
int finalChan=midiBaseChan;
int finalChanType=getChannelType(finalChan);
if (!playing) {
reset();
freelance=true;
playing=true;
}
// 1. check which channels are viable for this instrument
DivInstrument* insInst=getIns(ins);
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true;
for (int i=0; i<chans; i++) {
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) {
if (insInst->type==DIV_INS_OPL) {
if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) {
isViable[i]=true;
canPlayAnyway=true;
} else {
isViable[i]=false;
}
} else {
isViable[i]=true;
canPlayAnyway=true;
}
} else {
isViable[i]=false;
}
}
if (!canPlayAnyway) return;
// 2. find a free channel
do {
if (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel)) {
chan[finalChan].midiNote=note;
chan[finalChan].midiAge=midiAgeCounter++;
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
return;
}
if (++finalChan>=chans) {
finalChan=0;
}
} while (finalChan!=midiBaseChan);
// 3. find the oldest channel
int candidate=finalChan;
do {
if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge<chan[candidate].midiAge) {
candidate=finalChan;
}
if (++finalChan>=chans) {
finalChan=0;
}
} while (finalChan!=midiBaseChan);
chan[candidate].midiNote=note;
chan[candidate].midiAge=midiAgeCounter++;
pendingNotes.push(DivNoteEvent(candidate,ins,note,vol,true));
}
void DivEngine::autoNoteOff(int ch, int note, int vol) {
if (!playing) {
reset();
freelance=true;
playing=true;
}
//if (ch<0 || ch>=chans) return;
for (int i=0; i<chans; i++) {
if (chan[i].midiNote==note) {
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
chan[i].midiNote=-1;
}
}
}
void DivEngine::autoNoteOffAll() {
if (!playing) {
reset();
freelance=true;
playing=true;
}
for (int i=0; i<chans; i++) {
if (chan[i].midiNote!=-1) {
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
chan[i].midiNote=-1;
}
}
}
void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT;
curOrder=order;
if (order>=curSubSong->ordersLen) curOrder=0;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
BUSY_BEGIN_SOFT;
saveLock.lock();
song.systemFlags[system]=flags;
saveLock.unlock();
disCont[system].dispatch->setFlags(song.systemFlags[system]);
disCont[system].setRates(got.rate);
if (restart && isPlaying()) {
playSub(false);
}
BUSY_END;
}
void DivEngine::setSongRate(float hz, bool pal) {
BUSY_BEGIN;
saveLock.lock();
curSubSong->pal=!pal;
curSubSong->hz=hz;
// what?
curSubSong->customTempo=true;
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
}
}
saveLock.unlock();
BUSY_END;
}
void DivEngine::setAudio(DivAudioEngines which) {
audioEngine=which;
}
void DivEngine::setView(DivStatusView which) {
view=which;
}
bool DivEngine::getMetronome() {
return metronome;
}
void DivEngine::setMetronome(bool enable) {
metronome=enable;
metroAmp=0;
}
void DivEngine::setMetronomeVol(float vol) {
metroVol=vol;
}
void DivEngine::setConsoleMode(bool enable) {
consoleMode=enable;
}
bool DivEngine::switchMaster() {
deinitAudioBackend();
quitDispatch();
initDispatch();
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}
if (!output->setRun(true)) {
logE("error while activating audio!");
return false;
}
} else {
return false;
}
renderSamples();
return true;
}
void DivEngine::setMidiBaseChan(int chan) {
if (chan<0 || chan>=chans) chan=0;
midiBaseChan=chan;
}
void DivEngine::setMidiDirect(bool value) {
midiIsDirect=value;
}
void DivEngine::setMidiCallback(std::function<int(const TAMidiMessage&)> what) {
midiCallback=what;
}
bool DivEngine::sendMidiMessage(TAMidiMessage& msg) {
if (output==NULL) {
logW("output is NULL!");
return false;
}
if (output->midiOut==NULL) {
logW("MIDI output is NULL!");
return false;
}
BUSY_BEGIN;
logD("sending MIDI message...");
bool ret=(output->midiOut->send(msg));
BUSY_END;
return ret;
}
void DivEngine::synchronized(const std::function<void()>& what) {
BUSY_BEGIN;
what();
BUSY_END;
}
void DivEngine::lockSave(const std::function<void()>& what) {
saveLock.lock();
what();
saveLock.unlock();
}
void DivEngine::lockEngine(const std::function<void()>& what) {
BUSY_BEGIN;
saveLock.lock();
what();
saveLock.unlock();
BUSY_END;
}
TAAudioDesc& DivEngine::getAudioDescWant() {
return want;
}
TAAudioDesc& DivEngine::getAudioDescGot() {
return got;
}
std::vector<String>& DivEngine::getAudioDevices() {
return audioDevs;
}
std::vector<String>& DivEngine::getMidiIns() {
return midiIns;
}
std::vector<String>& DivEngine::getMidiOuts() {
return midiOuts;
}
void DivEngine::rescanAudioDevices() {
audioDevs.clear();
if (output!=NULL) {
audioDevs=output->listAudioDevices();
if (output->midiIn!=NULL) {
midiIns=output->midiIn->listDevices();
}
if (output->midiOut!=NULL) {
midiOuts=output->midiOut->listDevices();
}
}
}
void DivEngine::initDispatch() {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlags[i]);
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}
recalcChans();
BUSY_END;
}
void DivEngine::quitDispatch() {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].quit();
}
cycles=0;
clockDrift=0;
chans=0;
playing=false;
speedAB=false;
endOfSong=false;
ticks=0;
curRow=0;
curOrder=0;
nextSpeed=3;
changeOrd=-1;
changePos=0;
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
totalCmds=0;
lastCmds=0;
cmdsPerSecond=0;
for (int i=0; i<DIV_MAX_CHANS; i++) {
isMuted[i]=0;
}
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) {
if (getConfString("audioEngine","SDL")=="JACK") {
audioEngine=DIV_AUDIO_JACK;
} else {
audioEngine=DIV_AUDIO_SDL;
}
}
lowQuality=getConfInt("audioQuality",0);
forceMono=getConfInt("forceMono",0);
lowLatency=getConfInt("lowLatency",0);
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
if (metroVol<0.0f) metroVol=0.0f;
if (metroVol>2.0f) metroVol=2.0f;
if (lowLatency) logI("using low latency mode.");
switch (audioEngine) {
case DIV_AUDIO_JACK:
#ifndef HAVE_JACK
logE("Furnace was not compiled with JACK support!");
setConf("audioEngine","SDL");
saveConf();
output=new TAAudioSDL;
#else
output=new TAAudioJACK;
#endif
break;
case DIV_AUDIO_SDL:
output=new TAAudioSDL;
break;
case DIV_AUDIO_DUMMY:
output=new TAAudio;
break;
default:
logE("invalid audio engine!");
return false;
}
audioDevs=output->listAudioDevices();
want.deviceName=getConfString("audioDevice","");
want.bufsize=getConfInt("audioBufSize",1024);
want.rate=getConfInt("audioRate",44100);
want.fragments=2;
want.inChans=0;
want.outChans=2;
want.outFormat=TA_AUDIO_FORMAT_F32;
want.name="Furnace";
output->setCallback(process,this);
if (!output->init(want,got)) {
logE("error while initializing audio!");
delete output;
output=NULL;
audioEngine=DIV_AUDIO_NULL;
return false;
}
if (output->initMidi(false)) {
midiIns=output->midiIn->listDevices();
midiOuts=output->midiOut->listDevices();
} else {
logW("error while initializing MIDI!");
}
if (output->midiIn) {
String inName=getConfString("midiInDevice","");
if (!inName.empty()) {
// try opening device
logI("opening MIDI input.");
if (!output->midiIn->openDevice(inName)) {
logW("could not open MIDI input device!");
}
}
}
if (output->midiOut) {
String outName=getConfString("midiOutDevice","");
if (!outName.empty()) {
// try opening device
logI("opening MIDI output.");
if (!output->midiOut->openDevice(outName)) {
logW("could not open MIDI output device!");
}
}
}
return true;
}
bool DivEngine::deinitAudioBackend() {
if (output!=NULL) {
if (output->midiIn) {
if (output->midiIn->isDeviceOpen()) {
logI("closing MIDI input.");
output->midiIn->closeDevice();
}
}
if (output->midiOut) {
if (output->midiOut->isDeviceOpen()) {
logI("closing MIDI output.");
output->midiOut->closeDevice();
}
}
output->quitMidi();
output->quit();
delete output;
output=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(ANDROID)
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
logD("config path: %s",configPath.c_str());
loadConf();
loadSampleROMs();
// set default system preset
if (!hasLoadedSomething) {
logD("setting default preset");
std::vector<int> preset=decodeSysDesc(getConfString("initialSys",""));
logD("preset size %ld",preset.size());
if (preset.size()>0 && (preset.size()&3)==0) {
preset.push_back(0);
initSongWithDesc(preset.data());
}
hasLoadedSomething=true;
}
// init the rest of engine
bool haveAudio=false;
if (!initAudioBackend()) {
logE("no audio output available!");
} else {
haveAudio=true;
}
samp_bb=blip_new(32768);
if (samp_bb==NULL) {
logE("not enough memory!");
return false;
}
samp_bbOut=new short[32768];
samp_bbIn=new short[32768];
samp_bbInLen=32768;
blip_set_rates(samp_bb,44100,got.rate);
for (int i=0; i<64; i++) {
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
}
for (int i=0; i<4096; i++) {
reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0)));
pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0)));
}
for (int i=0; i<DIV_MAX_CHANS; i++) {
isMuted[i]=0;
keyHit[i]=false;
}
oscBuf[0]=new float[32768];
oscBuf[1]=new float[32768];
initDispatch();
renderSamples();
reset();
active=true;
if (!haveAudio) {
return false;
} else {
if (!output->setRun(true)) {
logE("error while activating!");
return false;
}
}
return true;
}
bool DivEngine::quit() {
deinitAudioBackend();
quitDispatch();
logI("saving config.");
saveConf();
active=false;
delete[] oscBuf[0];
delete[] oscBuf[1];
if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM;
song.unload();
return true;
}