2022-02-15 03:12:20 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2022-01-21 23:17:05 +00:00
|
|
|
#include "dataErrors.h"
|
2022-01-29 06:22:32 +00:00
|
|
|
#include "song.h"
|
2022-01-17 23:18:28 +00:00
|
|
|
#define _USE_MATH_DEFINES
|
2021-05-11 20:08:08 +00:00
|
|
|
#include "engine.h"
|
2021-12-09 07:38:55 +00:00
|
|
|
#include "instrument.h"
|
2021-05-11 20:08:08 +00:00
|
|
|
#include "safeReader.h"
|
|
|
|
#include "../ta-log.h"
|
2022-01-20 10:04:03 +00:00
|
|
|
#include "../fileutils.h"
|
2021-05-11 20:08:08 +00:00
|
|
|
#include "../audio/sdl.h"
|
2021-12-19 21:52:04 +00:00
|
|
|
#include <stdexcept>
|
2021-12-19 08:16:24 +00:00
|
|
|
#ifndef _WIN32
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#endif
|
2021-06-09 08:33:03 +00:00
|
|
|
#ifdef HAVE_JACK
|
|
|
|
#include "../audio/jack.h"
|
|
|
|
#endif
|
2021-05-14 19:16:48 +00:00
|
|
|
#include <math.h>
|
2021-12-07 17:21:23 +00:00
|
|
|
#include <sndfile.h>
|
2021-12-17 08:33:12 +00:00
|
|
|
#include <fmt/printf.h>
|
2021-05-11 20:08:08 +00:00
|
|
|
|
2021-05-12 08:58:55 +00:00
|
|
|
void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) {
|
|
|
|
((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size);
|
2021-05-11 20:08:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-20 18:48:20 +00:00
|
|
|
const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
|
|
|
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:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "04xy: Vibrato (x: speed; y: depth)";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0x08:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "08xy: Set panning (x: left; y: right)";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0x09:
|
|
|
|
return "09xx: Set speed 1";
|
|
|
|
case 0x0a:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "0Axy: Volume slide (0y: down; x0: up)";
|
2022-01-20 18:48:20 +00:00
|
|
|
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";
|
2022-02-15 06:46:03 +00:00
|
|
|
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
|
|
|
return "Cxxx: Set tick rate";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0xe0:
|
|
|
|
return "E0xx: Set arp speed";
|
|
|
|
case 0xe1:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "E1xy: Note slide up (x: speed; y: semitones)";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0xe2:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "E2xy: Note slide down (x: speed; y: semitones)";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0xe3:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "E3xx: Set vibrato shape (0: up/down; 1: up only; 2: down only)";
|
2022-01-20 18:48:20 +00:00
|
|
|
case 0xe4:
|
|
|
|
return "E4xx: Set vibrato range";
|
|
|
|
case 0xe5:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "E5xx: Set pitch (80: center)";
|
2022-01-20 18:48:20 +00:00
|
|
|
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:
|
2022-02-15 06:46:03 +00:00
|
|
|
return "EFxx: Set global tuning (quirky!)";
|
2022-02-04 19:43:57 +00:00
|
|
|
case 0xff:
|
2022-02-17 08:15:51 +00:00
|
|
|
return "FFxx: Stop song";
|
2022-01-20 18:48:20 +00:00
|
|
|
default:
|
|
|
|
if (chan>=0 && chan<chans) {
|
|
|
|
const char* ret=disCont[dispatchOfChan[chan]].dispatch->getEffectName(effect);
|
|
|
|
if (ret!=NULL) return ret;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return "Invalid effect";
|
|
|
|
}
|
|
|
|
|
2022-01-27 06:04:26 +00:00
|
|
|
void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
2022-01-25 20:06:29 +00:00
|
|
|
loopOrder=0;
|
|
|
|
loopRow=0;
|
2022-01-27 06:04:26 +00:00
|
|
|
loopEnd=-1;
|
2022-01-25 20:06:29 +00:00
|
|
|
int nextOrder=-1;
|
|
|
|
int nextRow=0;
|
|
|
|
int effectVal=0;
|
|
|
|
DivPattern* pat[DIV_MAX_CHANS];
|
|
|
|
for (int i=0; i<song.ordersLen; i++) {
|
|
|
|
for (int j=0; j<chans; j++) {
|
2022-01-25 21:44:21 +00:00
|
|
|
pat[j]=song.pat[j].getPattern(song.orders.ord[j][i],false);
|
2022-01-25 20:06:29 +00:00
|
|
|
}
|
|
|
|
for (int j=nextRow; j<song.patLen; j++) {
|
|
|
|
nextRow=0;
|
|
|
|
for (int k=0; k<chans; k++) {
|
|
|
|
for (int l=0; l<song.pat[k].effectRows; l++) {
|
|
|
|
effectVal=pat[k]->data[j][5+(l<<1)];
|
|
|
|
if (effectVal<0) effectVal=0;
|
|
|
|
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
2022-01-27 06:04:26 +00:00
|
|
|
if (nextOrder==-1 && i<song.ordersLen-1) {
|
|
|
|
nextOrder=i+1;
|
|
|
|
nextRow=effectVal;
|
|
|
|
}
|
2022-01-25 20:06:29 +00:00
|
|
|
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
2022-01-27 06:04:26 +00:00
|
|
|
if (nextOrder==-1) {
|
|
|
|
nextOrder=effectVal;
|
|
|
|
nextRow=0;
|
|
|
|
}
|
2022-01-25 20:06:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nextOrder!=-1) {
|
|
|
|
if (nextOrder<=i) {
|
|
|
|
loopOrder=nextOrder;
|
|
|
|
loopRow=nextRow;
|
2022-01-27 06:04:26 +00:00
|
|
|
loopEnd=i;
|
2022-01-25 20:06:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
i=nextOrder-1;
|
|
|
|
nextOrder=-1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 04:34:29 +00:00
|
|
|
void _runExportThread(DivEngine* caller) {
|
|
|
|
caller->runExportThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::isExporting() {
|
|
|
|
return exporting;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define EXPORT_BUFSIZE 2048
|
|
|
|
|
|
|
|
void DivEngine::runExportThread() {
|
2022-01-18 06:26:22 +00:00
|
|
|
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) {
|
2022-01-23 04:50:49 +00:00
|
|
|
logE("could not open file for writing! (%s)\n",sf_strerror(NULL));
|
2022-01-18 06:26:22 +00:00
|
|
|
exporting=false;
|
|
|
|
return;
|
|
|
|
}
|
2022-01-18 04:34:29 +00:00
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
float* outBuf[3];
|
|
|
|
outBuf[0]=new float[EXPORT_BUFSIZE];
|
|
|
|
outBuf[1]=new float[EXPORT_BUFSIZE];
|
|
|
|
outBuf[2]=new float[EXPORT_BUFSIZE*2];
|
2022-01-18 04:34:29 +00:00
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
// take control of audio output
|
|
|
|
deinitAudioBackend();
|
|
|
|
playSub(false);
|
2022-01-18 04:34:29 +00:00
|
|
|
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("rendering to file...\n");
|
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
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\n",totalProcessed,EXPORT_BUFSIZE);
|
|
|
|
}
|
|
|
|
if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) {
|
|
|
|
logE("error: failed to write entire buffer!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 07:04:03 +00:00
|
|
|
delete[] outBuf[0];
|
|
|
|
delete[] outBuf[1];
|
|
|
|
delete[] outBuf[2];
|
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
if (sf_close(sf)!=0) {
|
|
|
|
logE("could not close audio file!\n");
|
|
|
|
}
|
|
|
|
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!\n");
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("done!\n");
|
2022-01-18 04:34:29 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-01-18 06:26:22 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-01-18 04:34:29 +00:00
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
2022-02-16 02:15:19 +00:00
|
|
|
fname[i]=fmt::sprintf("%s_s%02d.wav",exportPath,i+1);
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("- %s\n",fname[i].c_str());
|
2022-01-18 06:26:22 +00:00
|
|
|
sf[i]=sf_open(fname[i].c_str(),SFM_WRITE,&si[i]);
|
|
|
|
if (sf[i]==NULL) {
|
2022-01-23 04:50:49 +00:00
|
|
|
logE("could not open file for writing! (%s)\n",sf_strerror(NULL));
|
2022-01-18 06:26:22 +00:00
|
|
|
for (int j=0; j<i; j++) {
|
|
|
|
sf_close(sf[i]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2022-01-18 04:34:29 +00:00
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
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);
|
|
|
|
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("rendering to files...\n");
|
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
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\n",i,totalProcessed,EXPORT_BUFSIZE);
|
|
|
|
}
|
|
|
|
if (sf_writef_short(sf[i],sysBuf,totalProcessed)!=(int)totalProcessed) {
|
|
|
|
logE("error: failed to write entire buffer! (%d)\n",i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 07:04:03 +00:00
|
|
|
delete[] outBuf[0];
|
|
|
|
delete[] outBuf[1];
|
|
|
|
delete[] sysBuf;
|
|
|
|
|
2022-01-18 06:26:22 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
if (sf_close(sf[i])!=0) {
|
|
|
|
logE("could not close audio file!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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!\n");
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("done!\n");
|
2022-01-18 06:26:22 +00:00
|
|
|
break;
|
2022-01-18 04:34:29 +00:00
|
|
|
}
|
2022-01-18 06:26:22 +00:00
|
|
|
case DIV_EXPORT_MODE_MANY_CHAN: {
|
2022-01-18 07:04:03 +00:00
|
|
|
// 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;
|
2022-01-23 04:50:49 +00:00
|
|
|
|
|
|
|
logI("rendering to files...\n");
|
2022-01-18 07:04:03 +00:00
|
|
|
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
SNDFILE* sf;
|
|
|
|
SF_INFO si;
|
2022-02-16 02:15:19 +00:00
|
|
|
String fname=fmt::sprintf("%s_c%02d.wav",exportPath,i+1);
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("- %s\n",fname.c_str());
|
2022-01-18 07:04:03 +00:00
|
|
|
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) {
|
2022-01-23 04:50:49 +00:00
|
|
|
logE("could not open file for writing! (%s)\n",sf_strerror(NULL));
|
2022-01-18 07:04:03 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int j=0; j<chans; j++) {
|
|
|
|
bool mute=(j!=i);
|
|
|
|
isMuted[j]=mute;
|
|
|
|
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\n",totalProcessed,EXPORT_BUFSIZE);
|
|
|
|
}
|
|
|
|
if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) {
|
|
|
|
logE("error: failed to write entire buffer!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sf_close(sf)!=0) {
|
|
|
|
logE("could not close audio file!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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!\n");
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 04:50:49 +00:00
|
|
|
logI("done!\n");
|
2022-01-18 06:26:22 +00:00
|
|
|
break;
|
2022-01-18 04:34:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) {
|
|
|
|
exportPath=path;
|
2022-01-18 06:26:22 +00:00
|
|
|
exportMode=mode;
|
2022-01-18 04:34:29 +00:00
|
|
|
exporting=true;
|
|
|
|
stop();
|
|
|
|
setOrder(0);
|
|
|
|
remainingLoops=loops;
|
|
|
|
exportThread=new std::thread(_runExportThread,this);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::waitAudioFile() {
|
|
|
|
if (exportThread!=NULL) {
|
|
|
|
exportThread->join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::haltAudioFile() {
|
|
|
|
stop();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-18 04:59:52 +00:00
|
|
|
void DivEngine::notifyInsChange(int ins) {
|
|
|
|
isBusy.lock();
|
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].dispatch->notifyInsChange(ins);
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-18 05:25:10 +00:00
|
|
|
void DivEngine::notifyWaveChange(int wave) {
|
|
|
|
isBusy.lock();
|
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].dispatch->notifyWaveChange(wave);
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-10 09:22:13 +00:00
|
|
|
// ADPCM code attribution: https://wiki.neogeodev.org/index.php?title=ADPCM_codecs
|
|
|
|
|
|
|
|
static short adSteps[49]={
|
|
|
|
16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
|
|
|
|
41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
|
|
|
|
107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
|
|
|
|
279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
|
|
|
|
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
|
|
|
|
};
|
|
|
|
|
|
|
|
static int adStepSeek[16]={
|
|
|
|
-1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9
|
|
|
|
};
|
|
|
|
|
2021-05-13 07:39:26 +00:00
|
|
|
static double samplePitches[11]={
|
|
|
|
0.1666666666, 0.2, 0.25, 0.333333333, 0.5,
|
|
|
|
1,
|
|
|
|
2, 3, 4, 5, 6
|
|
|
|
};
|
|
|
|
|
2021-12-17 08:33:12 +00:00
|
|
|
void DivEngine::renderSamplesP() {
|
|
|
|
isBusy.lock();
|
|
|
|
renderSamples();
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-05-13 07:39:26 +00:00
|
|
|
void DivEngine::renderSamples() {
|
2021-12-21 21:02:31 +00:00
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.pos=0;
|
2021-12-10 09:22:13 +00:00
|
|
|
if (jediTable==NULL) {
|
|
|
|
jediTable=new int[16*49];
|
2021-12-10 20:16:58 +00:00
|
|
|
for (int step=0; step<49; step++) {
|
|
|
|
for (int nib=0; nib<16; nib++) {
|
2021-12-10 09:22:13 +00:00
|
|
|
int value=(2*(nib&0x07)+1)*adSteps[step]/8;
|
|
|
|
jediTable[step*16+nib]=((nib&0x08)!=0)?-value:value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 07:39:26 +00:00
|
|
|
for (int i=0; i<song.sampleLen; i++) {
|
|
|
|
DivSample* s=song.sample[i];
|
2021-12-10 09:22:13 +00:00
|
|
|
if (s->rendLength!=0) {
|
|
|
|
delete[] s->rendData;
|
|
|
|
delete[] s->adpcmRendData;
|
|
|
|
}
|
2021-05-13 07:39:26 +00:00
|
|
|
s->rendLength=(double)s->length/samplePitches[s->pitch];
|
2021-12-17 08:33:12 +00:00
|
|
|
if (s->rendLength==0) {
|
|
|
|
s->adpcmRendLength=0;
|
|
|
|
continue;
|
|
|
|
}
|
2021-05-13 07:39:26 +00:00
|
|
|
s->rendData=new short[s->rendLength];
|
2021-12-10 09:22:13 +00:00
|
|
|
size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00;
|
2022-01-24 17:47:18 +00:00
|
|
|
if (adpcmLen>1048576) adpcmLen=1048576;
|
2021-12-11 03:51:50 +00:00
|
|
|
s->adpcmRendLength=adpcmLen;
|
2021-12-10 09:22:13 +00:00
|
|
|
s->adpcmRendData=new unsigned char[adpcmLen];
|
|
|
|
memset(s->adpcmRendData,0,adpcmLen);
|
|
|
|
|
|
|
|
// step 1: render to PCM
|
2021-12-14 17:33:26 +00:00
|
|
|
unsigned int k=0;
|
2021-12-15 05:37:27 +00:00
|
|
|
float mult=(float)(s->vol)/50.0f;
|
2021-05-13 07:39:26 +00:00
|
|
|
for (double j=0; j<s->length; j+=samplePitches[s->pitch]) {
|
|
|
|
if (k>=s->rendLength) {
|
|
|
|
break;
|
|
|
|
}
|
2021-05-18 07:29:17 +00:00
|
|
|
if (s->depth==8) {
|
|
|
|
float next=(float)(s->data[(unsigned int)j]-0x80)*mult;
|
|
|
|
s->rendData[k++]=fmin(fmax(next,-128),127);
|
|
|
|
} else {
|
|
|
|
float next=(float)s->data[(unsigned int)j]*mult;
|
|
|
|
s->rendData[k++]=fmin(fmax(next,-32768),32767);
|
|
|
|
}
|
2021-05-13 07:39:26 +00:00
|
|
|
}
|
2021-12-10 09:22:13 +00:00
|
|
|
|
|
|
|
// step 2: render to ADPCM
|
|
|
|
int acc=0;
|
|
|
|
int decstep=0;
|
|
|
|
int diff=0;
|
|
|
|
int step=0;
|
|
|
|
int predsample=0;
|
|
|
|
int index=0;
|
|
|
|
int prevsample=0;
|
|
|
|
int previndex=0;
|
2021-12-22 17:00:40 +00:00
|
|
|
for (unsigned int j=0; j<s->adpcmRendLength*2; j++) {
|
2021-12-10 09:22:13 +00:00
|
|
|
unsigned char encoded=0;
|
|
|
|
int tempstep=0;
|
|
|
|
|
|
|
|
predsample=prevsample;
|
|
|
|
index=previndex;
|
|
|
|
step=adSteps[index];
|
|
|
|
|
2021-12-22 17:00:40 +00:00
|
|
|
short sample=(j<s->rendLength)?((s->depth==16)?(s->rendData[j]>>4):(s->rendData[j]<<4)):0;
|
2021-12-27 20:22:57 +00:00
|
|
|
if (sample>0x7d0) sample=0x7d0;
|
|
|
|
if (sample<-0x7d0) sample=-0x7d0;
|
2021-12-10 09:22:13 +00:00
|
|
|
diff=sample-predsample;
|
|
|
|
if (diff>=0) {
|
|
|
|
encoded=0;
|
|
|
|
} else {
|
|
|
|
encoded=8;
|
|
|
|
diff=-diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
tempstep=step;
|
|
|
|
if (diff>=tempstep) {
|
|
|
|
encoded|=4;
|
|
|
|
diff-=tempstep;
|
|
|
|
}
|
|
|
|
tempstep>>=1;
|
|
|
|
if (diff>=tempstep) {
|
|
|
|
encoded|=2;
|
|
|
|
diff-=tempstep;
|
|
|
|
}
|
|
|
|
tempstep>>=1;
|
|
|
|
if (diff>=tempstep) encoded|=1;
|
|
|
|
|
|
|
|
acc+=jediTable[decstep+encoded];
|
2021-12-27 20:22:57 +00:00
|
|
|
if (acc>0x7ff || acc<-0x800) {
|
|
|
|
logW("clipping! %d\n",acc);
|
|
|
|
}
|
2021-12-10 09:22:13 +00:00
|
|
|
acc&=0xfff;
|
|
|
|
if (acc&0x800) acc|=~0xfff;
|
2021-12-10 20:16:58 +00:00
|
|
|
decstep+=adStepSeek[encoded&7]*16;
|
2021-12-10 09:22:13 +00:00
|
|
|
if (decstep<0) decstep=0;
|
|
|
|
if (decstep>48*16) decstep=48*16;
|
|
|
|
predsample=(short)acc;
|
|
|
|
|
2021-12-10 20:16:58 +00:00
|
|
|
index+=adStepSeek[encoded];
|
2021-12-10 09:22:13 +00:00
|
|
|
if (index<0) index=0;
|
|
|
|
if (index>48) index=48;
|
|
|
|
|
|
|
|
prevsample=predsample;
|
|
|
|
previndex=index;
|
|
|
|
|
|
|
|
if (j&1) {
|
|
|
|
s->adpcmRendData[j>>1]|=encoded;
|
|
|
|
} else {
|
|
|
|
s->adpcmRendData[j>>1]=encoded<<4;
|
|
|
|
}
|
|
|
|
}
|
2021-05-13 07:39:26 +00:00
|
|
|
}
|
2021-12-11 04:41:00 +00:00
|
|
|
|
2022-01-08 08:02:04 +00:00
|
|
|
// step 3: allocate ADPCM samples
|
|
|
|
if (adpcmMem==NULL) adpcmMem=new unsigned char[16777216];
|
2021-12-11 04:41:00 +00:00
|
|
|
|
2022-01-08 08:02:04 +00:00
|
|
|
size_t memPos=0;
|
|
|
|
for (int i=0; i<song.sampleLen; i++) {
|
|
|
|
DivSample* s=song.sample[i];
|
|
|
|
if ((memPos&0xf00000)!=((memPos+s->adpcmRendLength)&0xf00000)) {
|
|
|
|
memPos=(memPos+0xfffff)&0xf00000;
|
2021-12-11 04:41:00 +00:00
|
|
|
}
|
2022-01-24 17:47:18 +00:00
|
|
|
if (memPos>=16777216) {
|
|
|
|
logW("out of ADPCM memory for sample %d!\n",i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (memPos+s->adpcmRendLength>=16777216) {
|
|
|
|
memcpy(adpcmMem+memPos,s->adpcmRendData,16777216-memPos);
|
|
|
|
logW("out of ADPCM memory for sample %d!\n",i);
|
|
|
|
} else {
|
|
|
|
memcpy(adpcmMem+memPos,s->adpcmRendData,s->adpcmRendLength);
|
|
|
|
}
|
2022-01-08 08:02:04 +00:00
|
|
|
s->rendOff=memPos;
|
|
|
|
memPos+=s->adpcmRendLength;
|
2021-12-11 04:41:00 +00:00
|
|
|
}
|
2022-01-24 07:55:01 +00:00
|
|
|
adpcmMemLen=memPos+256;
|
2021-05-13 07:39:26 +00:00
|
|
|
}
|
|
|
|
|
2021-12-24 23:23:01 +00:00
|
|
|
void DivEngine::createNew() {
|
2022-01-08 08:02:04 +00:00
|
|
|
DivSystem sys=song.system[0];
|
2021-12-24 23:23:01 +00:00
|
|
|
quitDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
song.unload();
|
|
|
|
song=DivSong();
|
2022-01-08 08:02:04 +00:00
|
|
|
song.system[0]=sys;
|
2022-01-08 21:03:32 +00:00
|
|
|
recalcChans();
|
2021-12-24 23:23:01 +00:00
|
|
|
renderSamples();
|
|
|
|
isBusy.unlock();
|
|
|
|
initDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
reset();
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-08 21:03:32 +00:00
|
|
|
void DivEngine::changeSystem(int index, DivSystem which) {
|
2021-12-18 03:14:41 +00:00
|
|
|
quitDispatch();
|
|
|
|
isBusy.lock();
|
2022-01-08 21:03:32 +00:00
|
|
|
song.system[index]=which;
|
|
|
|
recalcChans();
|
2021-12-18 03:14:41 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
initDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
renderSamples();
|
|
|
|
reset();
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-08 23:18:23 +00:00
|
|
|
bool DivEngine::addSystem(DivSystem which) {
|
|
|
|
if (song.systemLen>32) {
|
2022-01-09 21:36:47 +00:00
|
|
|
lastError="cannot add more than 32";
|
2022-01-08 23:18:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-19 23:24:37 +00:00
|
|
|
// 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";
|
2022-01-08 23:18:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
quitDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
song.system[song.systemLen++]=which;
|
|
|
|
recalcChans();
|
|
|
|
isBusy.unlock();
|
|
|
|
initDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
renderSamples();
|
|
|
|
reset();
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-09 21:36:47 +00:00
|
|
|
bool DivEngine::removeSystem(int index) {
|
|
|
|
if (song.systemLen<=1) {
|
|
|
|
lastError="cannot remove the last one";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (index<0 || index>=song.systemLen) {
|
|
|
|
lastError="invalid index";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
quitDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
song.system[index]=DIV_SYSTEM_NULL;
|
|
|
|
song.systemLen--;
|
|
|
|
for (int i=index; i<song.systemLen; i++) {
|
2022-01-19 03:02:04 +00:00
|
|
|
song.system[i]=song.system[i+1];
|
2022-01-09 21:36:47 +00:00
|
|
|
}
|
|
|
|
recalcChans();
|
|
|
|
isBusy.unlock();
|
|
|
|
initDispatch();
|
|
|
|
isBusy.lock();
|
|
|
|
renderSamples();
|
|
|
|
reset();
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-01 23:08:19 +00:00
|
|
|
void DivEngine::poke(int sys, unsigned int addr, unsigned short val) {
|
|
|
|
if (sys<0 || sys>=song.systemLen) return;
|
|
|
|
isBusy.lock();
|
|
|
|
disCont[sys].dispatch->poke(addr,val);
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::poke(int sys, std::vector<DivRegWrite>& wlist) {
|
|
|
|
if (sys<0 || sys>=song.systemLen) return;
|
|
|
|
isBusy.lock();
|
|
|
|
disCont[sys].dispatch->poke(wlist);
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-21 04:20:30 +00:00
|
|
|
String DivEngine::getLastError() {
|
|
|
|
return lastError;
|
|
|
|
}
|
|
|
|
|
2022-01-29 06:22:32 +00:00
|
|
|
String DivEngine::getWarnings() {
|
|
|
|
return warnings;
|
|
|
|
}
|
|
|
|
|
2021-05-17 01:49:54 +00:00
|
|
|
DivInstrument* DivEngine::getIns(int index) {
|
|
|
|
if (index<0 || index>=song.insLen) return &song.nullIns;
|
|
|
|
return song.ins[index];
|
|
|
|
}
|
|
|
|
|
2021-05-27 18:30:37 +00:00
|
|
|
DivWavetable* DivEngine::getWave(int index) {
|
2021-06-09 03:21:05 +00:00
|
|
|
if (index<0 || index>=song.waveLen) {
|
|
|
|
if (song.waveLen>0) {
|
|
|
|
return song.wave[0];
|
|
|
|
} else {
|
|
|
|
return &song.nullWave;
|
|
|
|
}
|
|
|
|
}
|
2021-05-27 18:30:37 +00:00
|
|
|
return song.wave[index];
|
|
|
|
}
|
|
|
|
|
2021-12-07 09:22:36 +00:00
|
|
|
void DivEngine::setLoops(int loops) {
|
|
|
|
remainingLoops=loops;
|
|
|
|
}
|
|
|
|
|
2022-01-27 05:29:16 +00:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
2022-02-16 21:11:15 +00:00
|
|
|
void DivEngine::enableCommandStream(bool enable) {
|
|
|
|
cmdStreamEnabled=enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::getCommandStream(std::vector<DivCommand>& where) {
|
|
|
|
isBusy.lock();
|
|
|
|
where.clear();
|
|
|
|
for (DivCommand& i: cmdStream) {
|
|
|
|
where.push_back(i);
|
|
|
|
}
|
|
|
|
cmdStream.clear();
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-06 05:07:35 +00:00
|
|
|
void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
2022-02-21 04:03:42 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
|
2021-12-11 21:51:34 +00:00
|
|
|
reset();
|
2021-12-21 07:02:25 +00:00
|
|
|
if (preserveDrift && curOrder==0) return;
|
2021-12-21 22:42:27 +00:00
|
|
|
bool oldRepeatPattern=repeatPattern;
|
|
|
|
repeatPattern=false;
|
2021-12-21 06:29:07 +00:00
|
|
|
int goal=curOrder;
|
|
|
|
curOrder=0;
|
2021-12-11 08:34:43 +00:00
|
|
|
curRow=0;
|
2022-02-06 05:42:07 +00:00
|
|
|
stepPlay=0;
|
2022-01-12 22:45:07 +00:00
|
|
|
int prevDrift;
|
|
|
|
prevDrift=clockDrift;
|
|
|
|
clockDrift=0;
|
|
|
|
cycles=0;
|
2021-12-21 07:02:25 +00:00
|
|
|
if (preserveDrift) {
|
|
|
|
endOfSong=false;
|
|
|
|
} else {
|
|
|
|
ticks=1;
|
2021-12-21 07:30:09 +00:00
|
|
|
totalTicks=0;
|
2022-01-12 07:45:26 +00:00
|
|
|
totalSeconds=0;
|
|
|
|
totalTicksR=0;
|
2021-12-21 07:02:25 +00:00
|
|
|
}
|
2021-12-12 09:21:09 +00:00
|
|
|
speedAB=false;
|
2021-12-11 21:51:34 +00:00
|
|
|
playing=true;
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
2022-02-17 08:20:08 +00:00
|
|
|
while (playing && curOrder<goal) {
|
2022-02-21 04:03:42 +00:00
|
|
|
if (nextTick(preserveDrift)) return;
|
2021-12-21 06:29:07 +00:00
|
|
|
}
|
2022-02-06 05:07:35 +00:00
|
|
|
int oldOrder=curOrder;
|
2022-02-17 08:20:08 +00:00
|
|
|
while (playing && curRow<goalRow) {
|
2022-02-21 04:03:42 +00:00
|
|
|
if (nextTick(preserveDrift)) return;
|
2022-02-06 05:07:35 +00:00
|
|
|
if (oldOrder!=curOrder) break;
|
|
|
|
}
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
|
2022-02-06 05:07:35 +00:00
|
|
|
if (goal>0 || goalRow>0) {
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->forceIns();
|
2021-12-21 21:02:31 +00:00
|
|
|
}
|
2022-02-14 07:43:56 +00:00
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
chan[i].cut=-1;
|
|
|
|
}
|
2021-12-21 22:42:27 +00:00
|
|
|
repeatPattern=oldRepeatPattern;
|
2022-01-12 22:45:07 +00:00
|
|
|
if (preserveDrift) {
|
|
|
|
clockDrift=prevDrift;
|
|
|
|
} else {
|
|
|
|
clockDrift=0;
|
|
|
|
cycles=0;
|
2021-12-21 07:02:25 +00:00
|
|
|
}
|
|
|
|
if (!preserveDrift) {
|
|
|
|
ticks=1;
|
|
|
|
}
|
2022-02-16 21:11:15 +00:00
|
|
|
cmdStream.clear();
|
2021-12-21 06:29:07 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 05:55:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-02-03 07:24:11 +00:00
|
|
|
int DivEngine::calcFreq(int base, int pitch, bool period, int octave) {
|
2022-02-03 05:52:50 +00:00
|
|
|
if (song.linearPitch) {
|
|
|
|
return period?
|
2022-02-18 02:59:48 +00:00
|
|
|
round(base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0):
|
|
|
|
(round(base*pow(2,(double)pitch/(12.0*128.0))*(98+globalPitch*6))/98);
|
2022-02-03 05:52:50 +00:00
|
|
|
}
|
2021-12-28 05:51:38 +00:00
|
|
|
return period?
|
2022-02-03 05:52:50 +00:00
|
|
|
base-pitch:
|
2022-02-03 07:24:11 +00:00
|
|
|
base+((pitch*octave)>>1);
|
2021-12-28 05:51:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 06:29:07 +00:00
|
|
|
void DivEngine::play() {
|
|
|
|
isBusy.lock();
|
2022-02-06 21:29:30 +00:00
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.wave=-1;
|
|
|
|
sPreview.pos=0;
|
2022-02-06 05:42:07 +00:00
|
|
|
if (stepPlay==0) {
|
|
|
|
freelance=false;
|
|
|
|
playSub(false);
|
|
|
|
} else {
|
|
|
|
stepPlay=0;
|
|
|
|
}
|
2022-02-10 08:15:39 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
|
|
|
keyHit[i]=false;
|
|
|
|
}
|
2021-12-11 08:34:43 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-06 05:07:35 +00:00
|
|
|
void DivEngine::playToRow(int row) {
|
|
|
|
isBusy.lock();
|
2022-02-06 21:29:30 +00:00
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.wave=-1;
|
|
|
|
sPreview.pos=0;
|
2022-02-06 05:07:35 +00:00
|
|
|
freelance=false;
|
|
|
|
playSub(false,row);
|
2022-02-10 08:15:39 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
|
|
|
keyHit[i]=false;
|
|
|
|
}
|
2022-02-06 05:07:35 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-06 05:42:07 +00:00
|
|
|
void DivEngine::stepOne(int row) {
|
|
|
|
isBusy.lock();
|
|
|
|
if (!isPlaying()) {
|
|
|
|
freelance=false;
|
|
|
|
playSub(false,row);
|
2022-02-10 08:15:39 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
|
|
|
keyHit[i]=false;
|
|
|
|
}
|
2022-02-06 05:42:07 +00:00
|
|
|
}
|
|
|
|
stepPlay=2;
|
|
|
|
ticks=1;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-11 08:34:43 +00:00
|
|
|
void DivEngine::stop() {
|
|
|
|
isBusy.lock();
|
2021-12-28 23:23:57 +00:00
|
|
|
freelance=false;
|
2021-12-11 08:34:43 +00:00
|
|
|
playing=false;
|
2021-12-21 00:46:49 +00:00
|
|
|
extValuePresent=false;
|
2022-02-06 05:42:07 +00:00
|
|
|
stepPlay=0;
|
2022-01-18 04:34:29 +00:00
|
|
|
remainingLoops=-1;
|
2022-02-06 21:29:30 +00:00
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.wave=-1;
|
|
|
|
sPreview.pos=0;
|
2021-12-11 08:34:43 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-03 23:38:57 +00:00
|
|
|
void DivEngine::halt() {
|
|
|
|
isBusy.lock();
|
|
|
|
halted=true;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::resume() {
|
|
|
|
isBusy.lock();
|
|
|
|
halted=false;
|
|
|
|
haltOn=DIV_HALT_NONE;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::haltWhen(DivHaltPositions when) {
|
|
|
|
isBusy.lock();
|
|
|
|
halted=false;
|
|
|
|
haltOn=when;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::isHalted() {
|
|
|
|
return halted;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char** DivEngine::getRegisterSheet(int sys) {
|
|
|
|
if (sys<0 || sys>=song.systemLen) return NULL;
|
|
|
|
return disCont[sys].dispatch->getRegisterSheet();
|
|
|
|
}
|
|
|
|
|
2022-01-08 21:03:32 +00:00
|
|
|
void DivEngine::recalcChans() {
|
|
|
|
chans=0;
|
|
|
|
int chanIndex=0;
|
|
|
|
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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 21:51:34 +00:00
|
|
|
void DivEngine::reset() {
|
2022-01-08 06:57:37 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-11 21:51:34 +00:00
|
|
|
chan[i]=DivChannelState();
|
2022-01-08 21:03:32 +00:00
|
|
|
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
|
2021-12-11 21:51:34 +00:00
|
|
|
chan[i].volume=chan[i].volMax;
|
|
|
|
}
|
2021-12-21 00:46:49 +00:00
|
|
|
extValue=0;
|
|
|
|
extValuePresent=0;
|
2021-12-21 07:30:09 +00:00
|
|
|
speed1=song.speed1;
|
|
|
|
speed2=song.speed2;
|
2021-12-29 07:08:50 +00:00
|
|
|
nextSpeed=speed1;
|
2022-01-12 07:45:26 +00:00
|
|
|
divider=60;
|
|
|
|
if (song.customTempo) {
|
|
|
|
divider=song.hz;
|
|
|
|
} else {
|
|
|
|
if (song.pal) {
|
|
|
|
divider=60;
|
|
|
|
} else {
|
|
|
|
divider=50;
|
|
|
|
}
|
|
|
|
}
|
2021-12-27 20:22:57 +00:00
|
|
|
globalPitch=0;
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].dispatch->reset();
|
2022-01-20 02:04:51 +00:00
|
|
|
disCont[i].clear();
|
2022-01-08 21:03:32 +00:00
|
|
|
}
|
2021-12-11 21:51:34 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 02:11:23 +00:00
|
|
|
void DivEngine::syncReset() {
|
|
|
|
isBusy.lock();
|
|
|
|
reset();
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-21 18:06:14 +00:00
|
|
|
const int sampleRates[6]={
|
|
|
|
4000, 8000, 11025, 16000, 22050, 32000
|
|
|
|
};
|
|
|
|
|
2021-12-23 23:04:44 +00:00
|
|
|
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;
|
2022-01-08 08:02:04 +00:00
|
|
|
switch (song.system[0]) {
|
2021-12-23 23:04:44 +00:00
|
|
|
case DIV_SYSTEM_YMU759:
|
|
|
|
return 8000;
|
|
|
|
case DIV_SYSTEM_GENESIS: case DIV_SYSTEM_GENESIS_EXT:
|
|
|
|
return 1278409/(1280000/rate);
|
|
|
|
case DIV_SYSTEM_PCE:
|
|
|
|
return 1789773/(1789773/rate);
|
|
|
|
case DIV_SYSTEM_ARCADE:
|
|
|
|
return (31250*MIN(255,(rate*255/31250)))/255;
|
2022-02-10 08:28:20 +00:00
|
|
|
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT:
|
2021-12-23 23:04:44 +00:00
|
|
|
return 18518;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return rate;
|
|
|
|
}
|
|
|
|
|
2022-01-20 21:51:31 +00:00
|
|
|
void DivEngine::previewSample(int sample, int note) {
|
2021-12-21 18:06:14 +00:00
|
|
|
isBusy.lock();
|
2021-12-29 04:10:13 +00:00
|
|
|
if (sample<0 || sample>=(int)song.sample.size()) {
|
2021-12-21 18:06:14 +00:00
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.pos=0;
|
|
|
|
isBusy.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-01-08 21:03:32 +00:00
|
|
|
blip_clear(samp_bb);
|
2022-01-20 21:51:31 +00:00
|
|
|
double rate=song.sample[sample]->rate;
|
|
|
|
if (note>=0) {
|
2022-02-04 08:29:40 +00:00
|
|
|
rate=(song.tuning*pow(2.0,(double)(note+3)/12.0)*((double)song.sample[sample]->centerRate/8363.0));
|
2022-01-20 21:51:31 +00:00
|
|
|
if (rate<=0) rate=song.sample[sample]->rate;
|
|
|
|
}
|
|
|
|
blip_set_rates(samp_bb,rate,got.rate);
|
2022-01-08 21:03:32 +00:00
|
|
|
samp_prevSample=0;
|
2021-12-21 18:06:14 +00:00
|
|
|
sPreview.pos=0;
|
|
|
|
sPreview.sample=sample;
|
2022-01-20 05:07:53 +00:00
|
|
|
sPreview.wave=-1;
|
2021-12-21 18:06:14 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-20 21:51:31 +00:00
|
|
|
void DivEngine::stopSamplePreview() {
|
|
|
|
isBusy.lock();
|
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.pos=0;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-20 05:07:53 +00:00
|
|
|
void DivEngine::previewWave(int wave, int note) {
|
|
|
|
isBusy.lock();
|
|
|
|
if (wave<0 || wave>=(int)song.wave.size()) {
|
|
|
|
sPreview.wave=-1;
|
|
|
|
sPreview.pos=0;
|
|
|
|
isBusy.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-01-20 05:43:08 +00:00
|
|
|
if (song.wave[wave]->len<=0) {
|
|
|
|
isBusy.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-01-20 05:07:53 +00:00
|
|
|
blip_clear(samp_bb);
|
2022-01-28 05:55:51 +00:00
|
|
|
blip_set_rates(samp_bb,song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)),got.rate);
|
2022-01-20 05:07:53 +00:00
|
|
|
samp_prevSample=0;
|
|
|
|
sPreview.pos=0;
|
|
|
|
sPreview.sample=-1;
|
|
|
|
sPreview.wave=wave;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::stopWavePreview() {
|
2022-01-20 21:51:31 +00:00
|
|
|
isBusy.lock();
|
2022-01-20 05:07:53 +00:00
|
|
|
sPreview.wave=-1;
|
|
|
|
sPreview.pos=0;
|
2022-01-20 21:51:31 +00:00
|
|
|
isBusy.unlock();
|
2022-01-20 05:07:53 +00:00
|
|
|
}
|
|
|
|
|
2021-12-19 08:16:24 +00:00
|
|
|
String DivEngine::getConfigPath() {
|
|
|
|
return configPath;
|
|
|
|
}
|
|
|
|
|
2021-12-13 22:09:46 +00:00
|
|
|
int DivEngine::getMaxVolumeChan(int ch) {
|
|
|
|
return chan[ch].volMax>>8;
|
|
|
|
}
|
|
|
|
|
2021-12-11 08:34:43 +00:00
|
|
|
unsigned char DivEngine::getOrder() {
|
|
|
|
return curOrder;
|
|
|
|
}
|
|
|
|
|
2021-12-13 22:09:46 +00:00
|
|
|
int DivEngine::getRow() {
|
|
|
|
return curRow;
|
|
|
|
}
|
|
|
|
|
2021-12-21 07:30:09 +00:00
|
|
|
unsigned char DivEngine::getSpeed1() {
|
|
|
|
return speed1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char DivEngine::getSpeed2() {
|
|
|
|
return speed2;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DivEngine::getHz() {
|
|
|
|
if (song.customTempo) {
|
|
|
|
return song.hz;
|
|
|
|
} else if (song.pal) {
|
|
|
|
return 60;
|
|
|
|
} else {
|
|
|
|
return 50;
|
|
|
|
}
|
|
|
|
return 60;
|
|
|
|
}
|
|
|
|
|
2022-01-12 07:45:26 +00:00
|
|
|
int DivEngine::getCurHz() {
|
|
|
|
return divider;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DivEngine::getTotalSeconds() {
|
|
|
|
return totalSeconds;
|
|
|
|
}
|
|
|
|
|
2021-12-21 07:30:09 +00:00
|
|
|
int DivEngine::getTotalTicks() {
|
|
|
|
return totalTicks;
|
|
|
|
}
|
|
|
|
|
2021-12-21 22:42:27 +00:00
|
|
|
bool DivEngine::getRepeatPattern() {
|
|
|
|
return repeatPattern;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::setRepeatPattern(bool value) {
|
|
|
|
isBusy.lock();
|
|
|
|
repeatPattern=value;
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-21 00:46:49 +00:00
|
|
|
bool DivEngine::hasExtValue() {
|
|
|
|
return extValuePresent;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char DivEngine::getExtValue() {
|
|
|
|
return extValue;
|
|
|
|
}
|
|
|
|
|
2021-12-13 22:09:46 +00:00
|
|
|
bool DivEngine::isPlaying() {
|
2021-12-28 23:23:57 +00:00
|
|
|
return (playing && !freelance);
|
2021-12-13 22:09:46 +00:00
|
|
|
}
|
|
|
|
|
2022-02-06 05:42:07 +00:00
|
|
|
bool DivEngine::isStepping() {
|
|
|
|
return !(stepPlay==0);
|
|
|
|
}
|
|
|
|
|
2021-12-18 08:25:42 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isBusy.lock();
|
|
|
|
if (!solo) {
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
isMuted[i]=(i!=chan);
|
2022-01-08 21:03:32 +00:00
|
|
|
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
|
|
|
|
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],isMuted[i]);
|
2021-12-18 08:25:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
isMuted[i]=false;
|
2022-01-08 21:03:32 +00:00
|
|
|
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
|
|
|
|
disCont[dispatchOfChan[i]].dispatch->muteChannel(dispatchChanOfChan[i],isMuted[i]);
|
2021-12-18 08:25:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::muteChannel(int chan, bool mute) {
|
|
|
|
isBusy.lock();
|
|
|
|
isMuted[chan]=mute;
|
2022-01-08 21:03:32 +00:00
|
|
|
if (disCont[dispatchOfChan[chan]].dispatch!=NULL) {
|
|
|
|
disCont[dispatchOfChan[chan]].dispatch->muteChannel(dispatchChanOfChan[chan],isMuted[chan]);
|
2021-12-18 08:25:42 +00:00
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-11 23:20:39 +00:00
|
|
|
void DivEngine::unmuteAll() {
|
|
|
|
isBusy.lock();
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-01-10 04:50:26 +00:00
|
|
|
int DivEngine::addInstrument(int refChan) {
|
2021-12-17 08:33:12 +00:00
|
|
|
isBusy.lock();
|
|
|
|
DivInstrument* ins=new DivInstrument;
|
|
|
|
int insCount=(int)song.ins.size();
|
|
|
|
ins->name=fmt::sprintf("Instrument %d",insCount);
|
2022-01-16 03:11:40 +00:00
|
|
|
ins->type=getPreferInsType(refChan);
|
2021-12-17 08:33:12 +00:00
|
|
|
song.ins.push_back(ins);
|
|
|
|
song.insLen=insCount+1;
|
|
|
|
isBusy.unlock();
|
|
|
|
return insCount;
|
|
|
|
}
|
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
enum DivInsFormats {
|
|
|
|
DIV_INSFORMAT_DMP,
|
|
|
|
DIV_INSFORMAT_TFI,
|
|
|
|
DIV_INSFORMAT_VGI,
|
|
|
|
DIV_INSFORMAT_FTI,
|
|
|
|
DIV_INSFORMAT_BTI
|
|
|
|
};
|
|
|
|
|
2022-01-22 05:14:48 +00:00
|
|
|
bool DivEngine::addInstrumentFromFile(const char *path) {
|
2022-01-29 06:22:32 +00:00
|
|
|
warnings="";
|
2022-02-21 06:44:51 +00:00
|
|
|
|
|
|
|
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
|
|
|
|
if (pathRedux==NULL) {
|
|
|
|
pathRedux="Instrument";
|
|
|
|
} else {
|
|
|
|
pathRedux++;
|
|
|
|
}
|
|
|
|
|
2022-01-22 05:14:48 +00:00
|
|
|
FILE* f=ps_fopen(path,"rb");
|
|
|
|
if (f==NULL) {
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError=strerror(errno);
|
2022-01-22 05:14:48 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
unsigned char* buf;
|
|
|
|
ssize_t len;
|
|
|
|
if (fseek(f,0,SEEK_END)!=0) {
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError=strerror(errno);
|
2022-01-22 05:14:48 +00:00
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
len=ftell(f);
|
|
|
|
if (len<0) {
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError=strerror(errno);
|
2022-01-22 05:14:48 +00:00
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (len==0) {
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError=strerror(errno);
|
2022-01-22 05:14:48 +00:00
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (fseek(f,0,SEEK_SET)!=0) {
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError=strerror(errno);
|
2022-01-22 05:14:48 +00:00
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
buf=new unsigned char[len];
|
|
|
|
if (fread(buf,1,len,f)!=(size_t)len) {
|
|
|
|
logW("did not read entire instrument file buffer!\n");
|
2022-01-29 09:25:55 +00:00
|
|
|
lastError="did not read entire instrument file!";
|
2022-01-22 05:14:48 +00:00
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
SafeReader reader=SafeReader(buf,len);
|
|
|
|
|
|
|
|
unsigned char magic[16];
|
|
|
|
bool isFurnaceInstr=false;
|
|
|
|
try {
|
|
|
|
reader.read(magic,16);
|
|
|
|
if (memcmp("-Furnace instr.-",magic,16)==0) {
|
|
|
|
isFurnaceInstr=true;
|
|
|
|
}
|
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
reader.seek(0,SEEK_SET);
|
|
|
|
}
|
|
|
|
|
|
|
|
DivInstrument* ins=new DivInstrument;
|
|
|
|
if (isFurnaceInstr) {
|
|
|
|
try {
|
|
|
|
short version=reader.readS();
|
|
|
|
reader.readS(); // reserved
|
|
|
|
|
2022-01-29 06:22:32 +00:00
|
|
|
if (version>DIV_ENGINE_VERSION) {
|
|
|
|
warnings="this instrument is made with a more recent version of Furnace!";
|
|
|
|
}
|
|
|
|
|
2022-01-22 05:14:48 +00:00
|
|
|
unsigned int dataPtr=reader.readI();
|
|
|
|
reader.seek(dataPtr,SEEK_SET);
|
|
|
|
|
|
|
|
if (ins->readInsData(reader,version)!=DIV_DATA_SUCCESS) {
|
2022-01-29 08:21:47 +00:00
|
|
|
lastError="invalid instrument header/data!";
|
2022-01-22 05:14:48 +00:00
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
2022-01-29 08:21:47 +00:00
|
|
|
delete ins;
|
2022-01-22 05:14:48 +00:00
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
} else { // read as a different format
|
|
|
|
const char* ext=strrchr(path,'.');
|
|
|
|
DivInsFormats format=DIV_INSFORMAT_DMP;
|
|
|
|
if (ext!=NULL) {
|
|
|
|
String extS;
|
|
|
|
for (; *ext; ext++) {
|
|
|
|
char i=*ext;
|
|
|
|
if (i>='A' && i<='Z') {
|
|
|
|
i+='a'-'A';
|
|
|
|
}
|
|
|
|
extS+=i;
|
|
|
|
}
|
|
|
|
if (extS==String(".dmp")) {
|
|
|
|
format=DIV_INSFORMAT_DMP;
|
|
|
|
} else if (extS==String(".tfi")) {
|
|
|
|
format=DIV_INSFORMAT_TFI;
|
|
|
|
} else if (extS==String(".vgi")) {
|
|
|
|
format=DIV_INSFORMAT_VGI;
|
|
|
|
} else if (extS==String(".fti")) {
|
|
|
|
format=DIV_INSFORMAT_FTI;
|
|
|
|
} else if (extS==String(".bti")) {
|
|
|
|
format=DIV_INSFORMAT_BTI;
|
|
|
|
}
|
2022-01-31 17:55:51 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
switch (format) {
|
|
|
|
case DIV_INSFORMAT_DMP: {
|
|
|
|
// this is a ridiculous mess
|
|
|
|
unsigned char version=0;
|
|
|
|
unsigned char sys=0;
|
|
|
|
try {
|
|
|
|
reader.seek(0,SEEK_SET);
|
|
|
|
version=reader.readC();
|
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
2022-01-31 17:55:51 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
if (version>11) {
|
|
|
|
lastError="unknown instrument version!";
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->name=pathRedux;
|
|
|
|
|
|
|
|
if (version>=11) { // 1.0
|
|
|
|
try {
|
|
|
|
sys=reader.readC();
|
|
|
|
|
|
|
|
switch (sys) {
|
|
|
|
case 1: // YMU759
|
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
break;
|
|
|
|
case 2: // Genesis
|
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
break;
|
|
|
|
case 3: // SMS
|
|
|
|
ins->type=DIV_INS_STD;
|
|
|
|
break;
|
|
|
|
case 4: // Game Boy
|
|
|
|
ins->type=DIV_INS_GB;
|
|
|
|
break;
|
|
|
|
case 5: // PC Engine
|
|
|
|
ins->type=DIV_INS_PCE;
|
|
|
|
break;
|
|
|
|
case 6: // NES
|
|
|
|
ins->type=DIV_INS_STD;
|
|
|
|
break;
|
|
|
|
case 7: case 0x17: // C64
|
|
|
|
ins->type=DIV_INS_C64;
|
|
|
|
break;
|
|
|
|
case 8: // Arcade
|
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
lastError="unknown instrument type!";
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
2022-01-31 17:55:51 +00:00
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
2022-02-01 05:58:00 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
try {
|
|
|
|
bool mode=true;
|
2022-02-01 06:21:51 +00:00
|
|
|
if (version>1) {
|
2022-02-21 06:44:51 +00:00
|
|
|
mode=reader.readC();
|
|
|
|
if (mode==0) {
|
|
|
|
if (version<11) {
|
|
|
|
ins->type=DIV_INS_STD;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
}
|
2022-01-29 08:21:47 +00:00
|
|
|
} else {
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->type=DIV_INS_FM;
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
|
|
|
|
if (mode) { // FM
|
|
|
|
if (version<10) {
|
|
|
|
if (version>1) {
|
|
|
|
ins->fm.ops=reader.readC()?4:2;
|
|
|
|
} else {
|
|
|
|
ins->fm.ops=reader.readC()?2:4;
|
|
|
|
}
|
2022-02-01 05:58:00 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
if (version>1) { // HELP! in which version of the format did we start storing FMS!
|
|
|
|
ins->fm.fms=reader.readC();
|
|
|
|
}
|
|
|
|
ins->fm.fb=reader.readC();
|
|
|
|
ins->fm.alg=reader.readC();
|
|
|
|
// DITTO
|
|
|
|
if (sys!=1) ins->fm.ams=reader.readC();
|
|
|
|
|
|
|
|
for (int j=0; j<ins->fm.ops; j++) {
|
|
|
|
ins->fm.op[j].mult=reader.readC();
|
|
|
|
ins->fm.op[j].tl=reader.readC();
|
|
|
|
ins->fm.op[j].ar=reader.readC();
|
|
|
|
ins->fm.op[j].dr=reader.readC();
|
|
|
|
ins->fm.op[j].sl=reader.readC();
|
|
|
|
ins->fm.op[j].rr=reader.readC();
|
|
|
|
ins->fm.op[j].am=reader.readC();
|
|
|
|
// what the hell how do I tell!
|
|
|
|
if (sys==1) { // YMU759
|
|
|
|
ins->fm.op[j].ws=reader.readC();
|
|
|
|
ins->fm.op[j].ksl=reader.readC();
|
|
|
|
ins->fm.op[j].vib=reader.readC();
|
|
|
|
ins->fm.op[j].egt=reader.readC();
|
|
|
|
ins->fm.op[j].sus=reader.readC();
|
|
|
|
ins->fm.op[j].ksr=reader.readC();
|
|
|
|
ins->fm.op[j].dvb=reader.readC();
|
|
|
|
ins->fm.op[j].dam=reader.readC();
|
|
|
|
} else {
|
|
|
|
ins->fm.op[j].rs=reader.readC();
|
|
|
|
ins->fm.op[j].dt=reader.readC();
|
|
|
|
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
|
|
|
|
ins->fm.op[j].dt&=15;
|
|
|
|
ins->fm.op[j].d2r=reader.readC();
|
|
|
|
ins->fm.op[j].ssgEnv=reader.readC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { // STD
|
|
|
|
if (ins->type!=DIV_INS_GB) {
|
|
|
|
ins->std.volMacroLen=reader.readC();
|
|
|
|
if (version>5) {
|
|
|
|
for (int i=0; i<ins->std.volMacroLen; i++) {
|
|
|
|
ins->std.volMacro[i]=reader.readI();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i=0; i<ins->std.volMacroLen; i++) {
|
|
|
|
ins->std.volMacro[i]=reader.readC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (version<11) for (int i=0; i<ins->std.volMacroLen; i++) {
|
|
|
|
if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE;
|
|
|
|
}
|
|
|
|
if (ins->std.volMacroLen>0) {
|
|
|
|
ins->std.volMacroOpen=true;
|
|
|
|
ins->std.volMacroLoop=reader.readC();
|
|
|
|
} else {
|
|
|
|
ins->std.volMacroOpen=false;
|
|
|
|
}
|
2022-02-01 05:58:00 +00:00
|
|
|
}
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->std.arpMacroLen=reader.readC();
|
|
|
|
if (version>5) {
|
|
|
|
for (int i=0; i<ins->std.arpMacroLen; i++) {
|
|
|
|
ins->std.arpMacro[i]=reader.readI();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i=0; i<ins->std.arpMacroLen; i++) {
|
|
|
|
ins->std.arpMacro[i]=reader.readC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ins->std.arpMacroLen>0) {
|
|
|
|
ins->std.arpMacroOpen=true;
|
|
|
|
ins->std.arpMacroLoop=reader.readC();
|
|
|
|
} else {
|
|
|
|
ins->std.arpMacroOpen=false;
|
|
|
|
}
|
|
|
|
if (version>8) { // TODO: when?
|
|
|
|
ins->std.arpMacroMode=reader.readC();
|
|
|
|
}
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->std.dutyMacroLen=reader.readC();
|
|
|
|
if (version>5) {
|
|
|
|
for (int i=0; i<ins->std.dutyMacroLen; i++) {
|
|
|
|
ins->std.dutyMacro[i]=reader.readI();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i=0; i<ins->std.dutyMacroLen; i++) {
|
|
|
|
ins->std.dutyMacro[i]=reader.readC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ins->std.dutyMacroLen>0) {
|
|
|
|
ins->std.dutyMacroOpen=true;
|
|
|
|
ins->std.dutyMacroLoop=reader.readC();
|
|
|
|
} else {
|
|
|
|
ins->std.dutyMacroOpen=false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ins->std.waveMacroLen=reader.readC();
|
|
|
|
if (version>5) {
|
|
|
|
for (int i=0; i<ins->std.waveMacroLen; i++) {
|
|
|
|
ins->std.waveMacro[i]=reader.readI();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i=0; i<ins->std.waveMacroLen; i++) {
|
|
|
|
ins->std.waveMacro[i]=reader.readC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ins->std.waveMacroLen>0) {
|
|
|
|
ins->std.waveMacroOpen=true;
|
|
|
|
ins->std.waveMacroLoop=reader.readC();
|
|
|
|
} else {
|
|
|
|
ins->std.waveMacroOpen=false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ins->type==DIV_INS_C64) {
|
|
|
|
ins->c64.triOn=reader.readC();
|
|
|
|
ins->c64.sawOn=reader.readC();
|
|
|
|
ins->c64.pulseOn=reader.readC();
|
|
|
|
ins->c64.noiseOn=reader.readC();
|
|
|
|
|
|
|
|
ins->c64.a=reader.readC();
|
|
|
|
ins->c64.d=reader.readC();
|
|
|
|
ins->c64.s=reader.readC();
|
|
|
|
ins->c64.r=reader.readC();
|
|
|
|
|
|
|
|
ins->c64.duty=(reader.readC()*4095)/100;
|
|
|
|
|
|
|
|
ins->c64.ringMod=reader.readC();
|
|
|
|
ins->c64.oscSync=reader.readC();
|
|
|
|
ins->c64.toFilter=reader.readC();
|
|
|
|
if (version<0x07) { // TODO: UNSURE
|
|
|
|
ins->c64.volIsCutoff=reader.readI();
|
|
|
|
} else {
|
|
|
|
ins->c64.volIsCutoff=reader.readC();
|
|
|
|
}
|
|
|
|
ins->c64.initFilter=reader.readC();
|
|
|
|
|
|
|
|
ins->c64.res=reader.readC();
|
|
|
|
ins->c64.cut=(reader.readC()*2047)/100;
|
|
|
|
ins->c64.hp=reader.readC();
|
|
|
|
ins->c64.bp=reader.readC();
|
|
|
|
ins->c64.lp=reader.readC();
|
|
|
|
ins->c64.ch3off=reader.readC();
|
|
|
|
}
|
|
|
|
if (ins->type==DIV_INS_GB) {
|
|
|
|
ins->gb.envVol=reader.readC();
|
|
|
|
ins->gb.envDir=reader.readC();
|
|
|
|
ins->gb.envLen=reader.readC();
|
|
|
|
ins->gb.soundLen=reader.readC();
|
|
|
|
}
|
2022-02-01 05:58:00 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DIV_INSFORMAT_TFI:
|
|
|
|
try {
|
|
|
|
reader.seek(0,SEEK_SET);
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
ins->name=pathRedux;
|
|
|
|
|
|
|
|
ins->fm.alg=reader.readC();
|
|
|
|
ins->fm.fb=reader.readC();
|
|
|
|
|
|
|
|
for (int i=0; i<4; i++) {
|
|
|
|
DivInstrumentFM::Operator& op=ins->fm.op[i];
|
|
|
|
|
|
|
|
op.mult=reader.readC();
|
|
|
|
op.dt=reader.readC();
|
|
|
|
op.tl=reader.readC();
|
|
|
|
op.rs=reader.readC();
|
|
|
|
op.ar=reader.readC();
|
|
|
|
op.dr=reader.readC();
|
|
|
|
op.d2r=reader.readC();
|
|
|
|
op.rr=reader.readC();
|
|
|
|
op.sl=reader.readC();
|
|
|
|
op.ssgEnv=reader.readC();
|
2022-02-01 05:58:00 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
break;
|
|
|
|
case DIV_INSFORMAT_VGI:
|
|
|
|
try {
|
|
|
|
reader.seek(0,SEEK_SET);
|
2022-01-29 08:21:47 +00:00
|
|
|
|
2022-02-21 06:44:51 +00:00
|
|
|
ins->type=DIV_INS_FM;
|
|
|
|
ins->name=pathRedux;
|
|
|
|
|
|
|
|
ins->fm.alg=reader.readC();
|
|
|
|
ins->fm.fb=reader.readC();
|
|
|
|
unsigned char fmsams=reader.readC();
|
|
|
|
ins->fm.fms=fmsams&7;
|
|
|
|
ins->fm.ams=fmsams>>4;
|
|
|
|
|
|
|
|
for (int i=0; i<4; i++) {
|
|
|
|
DivInstrumentFM::Operator& op=ins->fm.op[i];
|
|
|
|
|
|
|
|
op.mult=reader.readC();
|
|
|
|
op.dt=reader.readC();
|
|
|
|
op.tl=reader.readC();
|
|
|
|
op.rs=reader.readC();
|
|
|
|
op.ar=reader.readC();
|
|
|
|
op.dr=reader.readC();
|
|
|
|
if (op.dr&0x80) {
|
|
|
|
op.am=1;
|
|
|
|
op.dr&=0x7f;
|
|
|
|
}
|
|
|
|
op.d2r=reader.readC();
|
|
|
|
op.rr=reader.readC();
|
|
|
|
op.sl=reader.readC();
|
|
|
|
op.ssgEnv=reader.readC();
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
} catch (EndOfFileException e) {
|
|
|
|
lastError="premature end of file";
|
|
|
|
logE("premature end of file!\n");
|
|
|
|
delete ins;
|
|
|
|
delete[] buf;
|
|
|
|
return false;
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-21 06:44:51 +00:00
|
|
|
break;
|
|
|
|
case DIV_INSFORMAT_FTI:
|
|
|
|
break;
|
|
|
|
case DIV_INSFORMAT_BTI:
|
|
|
|
break;
|
2022-01-29 08:21:47 +00:00
|
|
|
}
|
2022-02-01 06:21:51 +00:00
|
|
|
|
|
|
|
if (reader.tell()<reader.size()) {
|
|
|
|
addWarning("https://github.com/tildearrow/furnace/issues/84");
|
|
|
|
addWarning("there is more data at the end of the file! what happened here!");
|
|
|
|
addWarning(fmt::sprintf("exactly %d bytes, if you are curious",reader.size()-reader.tell()));
|
|
|
|
}
|
2022-01-22 05:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
isBusy.lock();
|
|
|
|
int insCount=(int)song.ins.size();
|
|
|
|
song.ins.push_back(ins);
|
|
|
|
song.insLen=insCount+1;
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-12-17 08:33:12 +00:00
|
|
|
void DivEngine::delInstrument(int index) {
|
|
|
|
isBusy.lock();
|
|
|
|
if (index>=0 && index<(int)song.ins.size()) {
|
2022-01-13 22:40:29 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].dispatch->notifyInsDeletion(song.ins[index]);
|
|
|
|
}
|
2021-12-17 08:33:12 +00:00
|
|
|
delete song.ins[index];
|
|
|
|
song.ins.erase(song.ins.begin()+index);
|
|
|
|
song.insLen=song.ins.size();
|
2022-02-08 04:42:54 +00:00
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
for (int j=0; j<128; j++) {
|
|
|
|
if (song.pat[i].data[j]==NULL) continue;
|
|
|
|
for (int k=0; k<song.patLen; k++) {
|
|
|
|
if (song.pat[i].data[j]->data[k][2]>index) {
|
|
|
|
song.pat[i].data[j]->data[k][2]--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-17 08:33:12 +00:00
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
int DivEngine::addWave() {
|
|
|
|
isBusy.lock();
|
|
|
|
DivWavetable* wave=new DivWavetable;
|
|
|
|
int waveCount=(int)song.wave.size();
|
|
|
|
song.wave.push_back(wave);
|
|
|
|
song.waveLen=waveCount+1;
|
|
|
|
isBusy.unlock();
|
|
|
|
return waveCount;
|
|
|
|
}
|
|
|
|
|
2022-01-11 21:25:55 +00:00
|
|
|
bool DivEngine::addWaveFromFile(const char* path) {
|
2022-01-21 22:59:48 +00:00
|
|
|
FILE* f=ps_fopen(path,"rb");
|
|
|
|
if (f==NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
unsigned char* buf;
|
|
|
|
ssize_t len;
|
|
|
|
if (fseek(f,0,SEEK_END)!=0) {
|
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
len=ftell(f);
|
|
|
|
if (len<0) {
|
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (len==0) {
|
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (fseek(f,0,SEEK_SET)!=0) {
|
|
|
|
fclose(f);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
buf=new unsigned char[len];
|
|
|
|
if (fread(buf,1,len,f)!=(size_t)len) {
|
|
|
|
logW("did not read entire wavetable file buffer!\n");
|
|
|
|
delete[] buf;
|
|
|
|
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
|
2022-01-21 23:17:05 +00:00
|
|
|
reader.seek(20,SEEK_SET);
|
|
|
|
if (wave->readWaveData(reader,version)!=DIV_DATA_SUCCESS) {
|
|
|
|
lastError="invalid wavetable header/data!";
|
|
|
|
delete wave;
|
2022-01-21 22:59:48 +00:00
|
|
|
delete[] buf;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
// read as .dmw
|
|
|
|
reader.seek(0,SEEK_SET);
|
|
|
|
int len=reader.readI();
|
|
|
|
wave->max=(unsigned char)reader.readC();
|
2022-02-21 03:16:43 +00:00
|
|
|
if (wave->max==255) { // new wavetable format
|
|
|
|
unsigned char waveVersion=reader.readC();
|
|
|
|
logI("reading modern .dmw...\n");
|
|
|
|
logD("wave version %d\n",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)) {
|
2022-01-21 22:59:48 +00:00
|
|
|
// read as .dmw
|
|
|
|
logI("reading .dmw...\n");
|
|
|
|
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...\n");
|
|
|
|
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...\n");
|
|
|
|
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;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-11 21:25:55 +00:00
|
|
|
isBusy.lock();
|
2022-01-21 22:59:48 +00:00
|
|
|
int waveCount=(int)song.wave.size();
|
|
|
|
song.wave.push_back(wave);
|
|
|
|
song.waveLen=waveCount+1;
|
2022-01-11 21:25:55 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-12-17 08:33:12 +00:00
|
|
|
void DivEngine::delWave(int index) {
|
|
|
|
isBusy.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();
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
int DivEngine::addSample() {
|
|
|
|
isBusy.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;
|
|
|
|
renderSamples();
|
|
|
|
isBusy.unlock();
|
|
|
|
return sampleCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::addSampleFromFile(const char* path) {
|
|
|
|
isBusy.lock();
|
|
|
|
SF_INFO si;
|
|
|
|
SNDFILE* f=sf_open(path,SFM_READ,&si);
|
|
|
|
if (f==NULL) {
|
|
|
|
isBusy.unlock();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (si.frames>1000000) {
|
|
|
|
sf_close(f);
|
|
|
|
isBusy.unlock();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
short* buf=new short[si.channels*si.frames];
|
|
|
|
if (sf_readf_short(f,buf,si.frames)!=si.frames) {
|
|
|
|
logW("sample read size mismatch!\n");
|
|
|
|
}
|
|
|
|
sf_close(f);
|
|
|
|
DivSample* sample=new DivSample;
|
|
|
|
int sampleCount=(int)song.sample.size();
|
2022-02-01 08:20:15 +00:00
|
|
|
const char* sName=strrchr(path,DIR_SEPARATOR);
|
2021-12-17 08:33:12 +00:00
|
|
|
if (sName==NULL) {
|
|
|
|
sName=path;
|
|
|
|
} else {
|
|
|
|
sName++;
|
|
|
|
}
|
|
|
|
sample->name=sName;
|
|
|
|
|
|
|
|
int index=0;
|
|
|
|
sample->length=si.frames;
|
|
|
|
sample->data=new short[si.frames];
|
|
|
|
sample->depth=16;
|
|
|
|
sample->vol=50;
|
|
|
|
sample->pitch=5;
|
|
|
|
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
|
|
|
|
int averaged=0;
|
|
|
|
for (int j=0; j<si.channels; j++) {
|
2022-01-15 22:28:33 +00:00
|
|
|
if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) {
|
2022-02-04 21:46:56 +00:00
|
|
|
averaged+=buf[i+j];
|
2022-01-15 22:28:33 +00:00
|
|
|
} else {
|
|
|
|
averaged+=buf[i+j];
|
|
|
|
}
|
2021-12-17 08:33:12 +00:00
|
|
|
}
|
|
|
|
averaged/=si.channels;
|
2022-01-15 22:28:33 +00:00
|
|
|
sample->data[index++]=averaged;
|
2021-12-17 08:33:12 +00:00
|
|
|
}
|
|
|
|
delete[] buf;
|
2021-12-23 23:04:44 +00:00
|
|
|
sample->rate=si.samplerate;
|
|
|
|
if (sample->rate<4000) sample->rate=4000;
|
|
|
|
if (sample->rate>32000) sample->rate=32000;
|
2021-12-17 08:33:12 +00:00
|
|
|
|
|
|
|
song.sample.push_back(sample);
|
|
|
|
song.sampleLen=sampleCount+1;
|
|
|
|
renderSamples();
|
|
|
|
isBusy.unlock();
|
|
|
|
return sampleCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::delSample(int index) {
|
|
|
|
isBusy.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();
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-22 22:39:16 +00:00
|
|
|
void DivEngine::addOrder(bool duplicate, bool where) {
|
2022-01-10 03:17:03 +00:00
|
|
|
unsigned char order[DIV_MAX_CHANS];
|
2021-12-22 22:39:16 +00:00
|
|
|
if (song.ordersLen>=0x7e) return;
|
|
|
|
isBusy.lock();
|
|
|
|
if (duplicate) {
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
order[i]=song.orders.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<song.ordersLen; j++) {
|
|
|
|
used[song.orders.ord[i][j]]=true;
|
|
|
|
}
|
|
|
|
order[i]=0x7e;
|
|
|
|
for (int j=0; j<256; j++) {
|
|
|
|
if (!used[j]) {
|
|
|
|
order[i]=j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (where) { // at the end
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
song.orders.ord[i][song.ordersLen]=order[i];
|
|
|
|
}
|
|
|
|
song.ordersLen++;
|
|
|
|
} else { // after current order
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
for (int j=song.ordersLen; j>curOrder; j--) {
|
|
|
|
song.orders.ord[i][j]=song.orders.ord[i][j-1];
|
|
|
|
}
|
|
|
|
song.orders.ord[i][curOrder+1]=order[i];
|
|
|
|
}
|
|
|
|
song.ordersLen++;
|
|
|
|
curOrder++;
|
2021-12-28 23:23:57 +00:00
|
|
|
if (playing && !freelance) {
|
2021-12-22 22:39:16 +00:00
|
|
|
playSub(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-12 08:59:05 +00:00
|
|
|
void DivEngine::deepCloneOrder(bool where) {
|
|
|
|
unsigned char order[DIV_MAX_CHANS];
|
|
|
|
if (song.ordersLen>=0x7e) return;
|
2022-02-12 23:02:33 +00:00
|
|
|
warnings="";
|
2022-02-12 08:59:05 +00:00
|
|
|
isBusy.lock();
|
|
|
|
for (int i=0; i<chans; i++) {
|
2022-02-21 04:07:46 +00:00
|
|
|
bool didNotFind=true;
|
|
|
|
logD("channel %d\n",i);
|
2022-02-12 08:59:05 +00:00
|
|
|
order[i]=song.orders.ord[i][curOrder];
|
|
|
|
// find free slot
|
|
|
|
for (int j=0; j<128; j++) {
|
2022-02-21 04:07:46 +00:00
|
|
|
logD("finding free slot in %d...\n",j);
|
2022-02-12 08:59:05 +00:00
|
|
|
if (song.pat[i].data[j]==NULL) {
|
|
|
|
int origOrd=order[i];
|
|
|
|
order[i]=j;
|
|
|
|
DivPattern* oldPat=song.pat[i].getPattern(origOrd,false);
|
|
|
|
DivPattern* pat=song.pat[i].getPattern(j,true);
|
|
|
|
memcpy(pat->data,oldPat->data,256*32*sizeof(short));
|
2022-02-21 04:07:46 +00:00
|
|
|
logD("found at %d\n",j);
|
|
|
|
didNotFind=false;
|
2022-02-12 08:59:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-02-21 04:07:46 +00:00
|
|
|
if (didNotFind) {
|
2022-02-12 23:02:33 +00:00
|
|
|
addWarning(fmt::sprintf("no free patterns in channel %d!",i));
|
|
|
|
}
|
2022-02-12 08:59:05 +00:00
|
|
|
}
|
|
|
|
if (where) { // at the end
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
song.orders.ord[i][song.ordersLen]=order[i];
|
|
|
|
}
|
|
|
|
song.ordersLen++;
|
|
|
|
} else { // after current order
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
for (int j=song.ordersLen; j>curOrder; j--) {
|
|
|
|
song.orders.ord[i][j]=song.orders.ord[i][j-1];
|
|
|
|
}
|
|
|
|
song.orders.ord[i][curOrder+1]=order[i];
|
|
|
|
}
|
|
|
|
song.ordersLen++;
|
|
|
|
curOrder++;
|
|
|
|
if (playing && !freelance) {
|
|
|
|
playSub(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-22 22:39:16 +00:00
|
|
|
void DivEngine::deleteOrder() {
|
|
|
|
if (song.ordersLen<=1) return;
|
|
|
|
isBusy.lock();
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
for (int j=curOrder; j<song.ordersLen; j++) {
|
|
|
|
song.orders.ord[i][j]=song.orders.ord[i][j+1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
song.ordersLen--;
|
|
|
|
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1;
|
2021-12-28 23:23:57 +00:00
|
|
|
if (playing && !freelance) {
|
2021-12-22 22:39:16 +00:00
|
|
|
playSub(false);
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::moveOrderUp() {
|
|
|
|
isBusy.lock();
|
|
|
|
if (curOrder<1) {
|
|
|
|
isBusy.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
|
|
|
|
song.orders.ord[i][curOrder-1]^=song.orders.ord[i][curOrder];
|
|
|
|
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
|
|
|
|
}
|
|
|
|
curOrder--;
|
2021-12-28 23:23:57 +00:00
|
|
|
if (playing && !freelance) {
|
2021-12-22 22:39:16 +00:00
|
|
|
playSub(false);
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::moveOrderDown() {
|
|
|
|
isBusy.lock();
|
|
|
|
if (curOrder>=song.ordersLen-1) {
|
|
|
|
isBusy.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-01-08 22:15:12 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-22 22:39:16 +00:00
|
|
|
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
|
|
|
|
song.orders.ord[i][curOrder+1]^=song.orders.ord[i][curOrder];
|
|
|
|
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
|
|
|
|
}
|
|
|
|
curOrder++;
|
2021-12-28 23:23:57 +00:00
|
|
|
if (playing && !freelance) {
|
2021-12-22 22:39:16 +00:00
|
|
|
playSub(false);
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2022-02-08 04:42:54 +00:00
|
|
|
void DivEngine::exchangeIns(int one, int two) {
|
|
|
|
for (int i=0; i<chans; i++) {
|
|
|
|
for (int j=0; j<128; j++) {
|
|
|
|
if (song.pat[i].data[j]==NULL) continue;
|
|
|
|
for (int k=0; k<song.patLen; k++) {
|
|
|
|
if (song.pat[i].data[j]->data[k][2]==one) {
|
|
|
|
song.pat[i].data[j]->data[k][2]=two;
|
|
|
|
} else if (song.pat[i].data[j]->data[k][2]==two) {
|
|
|
|
song.pat[i].data[j]->data[k][2]=one;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-11 08:52:11 +00:00
|
|
|
bool DivEngine::moveInsUp(int which) {
|
|
|
|
if (which<1 || which>=(int)song.ins.size()) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivInstrument* prev=song.ins[which];
|
|
|
|
song.ins[which]=song.ins[which-1];
|
|
|
|
song.ins[which-1]=prev;
|
2022-02-08 04:42:54 +00:00
|
|
|
exchangeIns(which,which-1);
|
2022-01-11 08:52:11 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::moveWaveUp(int which) {
|
|
|
|
if (which<1 || which>=(int)song.wave.size()) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivWavetable* prev=song.wave[which];
|
|
|
|
song.wave[which]=song.wave[which-1];
|
|
|
|
song.wave[which-1]=prev;
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::moveSampleUp(int which) {
|
|
|
|
if (which<1 || which>=(int)song.sample.size()) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivSample* prev=song.sample[which];
|
|
|
|
song.sample[which]=song.sample[which-1];
|
|
|
|
song.sample[which-1]=prev;
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::moveInsDown(int which) {
|
|
|
|
if (which<0 || which>=((int)song.ins.size())-1) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivInstrument* prev=song.ins[which];
|
|
|
|
song.ins[which]=song.ins[which+1];
|
|
|
|
song.ins[which+1]=prev;
|
2022-02-08 04:42:54 +00:00
|
|
|
exchangeIns(which,which+1);
|
2022-01-11 08:52:11 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::moveWaveDown(int which) {
|
|
|
|
if (which<0 || which>=((int)song.wave.size())-1) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivWavetable* prev=song.wave[which];
|
|
|
|
song.wave[which]=song.wave[which+1];
|
|
|
|
song.wave[which+1]=prev;
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::moveSampleDown(int which) {
|
|
|
|
if (which<0 || which>=((int)song.sample.size())-1) return false;
|
|
|
|
isBusy.lock();
|
|
|
|
DivSample* prev=song.sample[which];
|
|
|
|
song.sample[which]=song.sample[which+1];
|
|
|
|
song.sample[which+1]=prev;
|
|
|
|
isBusy.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-12-28 23:23:57 +00:00
|
|
|
void DivEngine::noteOn(int chan, int ins, int note, int vol) {
|
2022-01-24 22:13:47 +00:00
|
|
|
if (chan<0 || chan>=chans) return;
|
2021-12-28 23:23:57 +00:00
|
|
|
isBusy.lock();
|
|
|
|
pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true));
|
|
|
|
if (!playing) {
|
|
|
|
reset();
|
|
|
|
freelance=true;
|
|
|
|
playing=true;
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::noteOff(int chan) {
|
2022-01-24 22:13:47 +00:00
|
|
|
if (chan<0 || chan>=chans) return;
|
2021-12-28 23:23:57 +00:00
|
|
|
isBusy.lock();
|
|
|
|
pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false));
|
|
|
|
if (!playing) {
|
|
|
|
reset();
|
|
|
|
freelance=true;
|
|
|
|
playing=true;
|
|
|
|
}
|
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-11 08:34:43 +00:00
|
|
|
void DivEngine::setOrder(unsigned char order) {
|
|
|
|
isBusy.lock();
|
|
|
|
curOrder=order;
|
|
|
|
if (order>=song.ordersLen) curOrder=0;
|
2021-12-28 23:23:57 +00:00
|
|
|
if (playing && !freelance) {
|
2021-12-21 07:02:25 +00:00
|
|
|
playSub(false);
|
2021-12-11 08:34:43 +00:00
|
|
|
}
|
|
|
|
isBusy.unlock();
|
2021-05-11 20:08:08 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 22:24:26 +00:00
|
|
|
void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
|
2022-01-28 23:12:56 +00:00
|
|
|
isBusy.lock();
|
|
|
|
song.systemFlags[system]=flags;
|
|
|
|
disCont[system].dispatch->setFlags(song.systemFlags[system]);
|
|
|
|
disCont[system].setRates(got.rate);
|
2022-02-07 22:24:26 +00:00
|
|
|
if (restart) {
|
|
|
|
playSub(false);
|
|
|
|
}
|
2022-01-28 23:12:56 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-12-15 22:32:08 +00:00
|
|
|
void DivEngine::setSongRate(int hz, bool pal) {
|
|
|
|
isBusy.lock();
|
|
|
|
song.pal=!pal;
|
|
|
|
song.hz=hz;
|
|
|
|
song.customTempo=(song.hz!=50 && song.hz!=60);
|
2022-01-12 07:45:26 +00:00
|
|
|
divider=60;
|
|
|
|
if (song.customTempo) {
|
|
|
|
divider=song.hz;
|
|
|
|
} else {
|
|
|
|
if (song.pal) {
|
|
|
|
divider=60;
|
|
|
|
} else {
|
|
|
|
divider=50;
|
|
|
|
}
|
2022-01-08 21:03:32 +00:00
|
|
|
}
|
2021-12-15 22:32:08 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
2021-06-09 08:33:03 +00:00
|
|
|
void DivEngine::setAudio(DivAudioEngines which) {
|
|
|
|
audioEngine=which;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::setView(DivStatusView which) {
|
|
|
|
view=which;
|
|
|
|
}
|
|
|
|
|
2022-01-04 05:02:41 +00:00
|
|
|
bool DivEngine::getMetronome() {
|
|
|
|
return metronome;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::setMetronome(bool enable) {
|
|
|
|
metronome=enable;
|
|
|
|
metroAmp=0;
|
|
|
|
}
|
|
|
|
|
2021-12-18 09:26:17 +00:00
|
|
|
void DivEngine::setConsoleMode(bool enable) {
|
|
|
|
consoleMode=enable;
|
|
|
|
}
|
|
|
|
|
2022-02-06 04:48:56 +00:00
|
|
|
bool DivEngine::switchMaster() {
|
2022-01-17 06:42:26 +00:00
|
|
|
deinitAudioBackend();
|
2022-02-03 04:08:45 +00:00
|
|
|
quitDispatch();
|
|
|
|
initDispatch();
|
2022-01-17 06:42:26 +00:00
|
|
|
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!\n");
|
2022-02-06 04:48:56 +00:00
|
|
|
return false;
|
2022-01-17 06:42:26 +00:00
|
|
|
}
|
2022-02-06 04:48:56 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2022-01-17 06:42:26 +00:00
|
|
|
}
|
2022-02-06 04:48:56 +00:00
|
|
|
return true;
|
2022-01-17 06:42:26 +00:00
|
|
|
}
|
|
|
|
|
2022-02-06 02:26:24 +00:00
|
|
|
TAAudioDesc& DivEngine::getAudioDescWant() {
|
|
|
|
return want;
|
|
|
|
}
|
|
|
|
|
|
|
|
TAAudioDesc& DivEngine::getAudioDescGot() {
|
|
|
|
return got;
|
|
|
|
}
|
|
|
|
|
2022-02-14 02:42:57 +00:00
|
|
|
std::vector<String>& DivEngine::getAudioDevices() {
|
|
|
|
return audioDevs;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::rescanAudioDevices() {
|
|
|
|
audioDevs.clear();
|
|
|
|
if (output!=NULL) {
|
|
|
|
audioDevs=output->listAudioDevices();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 19:40:03 +00:00
|
|
|
void DivEngine::initDispatch() {
|
|
|
|
isBusy.lock();
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
2022-01-28 23:12:56 +00:00
|
|
|
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlags[i]);
|
2022-01-08 21:03:32 +00:00
|
|
|
disCont[i].setRates(got.rate);
|
2022-01-17 06:42:26 +00:00
|
|
|
disCont[i].setQuality(lowQuality);
|
2021-12-13 19:40:03 +00:00
|
|
|
}
|
2022-01-08 21:03:32 +00:00
|
|
|
recalcChans();
|
2021-12-13 19:40:03 +00:00
|
|
|
isBusy.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DivEngine::quitDispatch() {
|
2021-12-15 05:37:27 +00:00
|
|
|
isBusy.lock();
|
2022-01-08 21:03:32 +00:00
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].quit();
|
|
|
|
}
|
2022-01-12 22:45:07 +00:00
|
|
|
cycles=0;
|
|
|
|
clockDrift=0;
|
2021-12-15 05:37:27 +00:00
|
|
|
chans=0;
|
|
|
|
playing=false;
|
|
|
|
speedAB=false;
|
|
|
|
endOfSong=false;
|
|
|
|
ticks=0;
|
|
|
|
curRow=0;
|
|
|
|
curOrder=0;
|
|
|
|
nextSpeed=3;
|
|
|
|
changeOrd=-1;
|
|
|
|
changePos=0;
|
|
|
|
totalTicks=0;
|
2022-01-12 07:45:26 +00:00
|
|
|
totalSeconds=0;
|
|
|
|
totalTicksR=0;
|
2021-12-15 05:37:27 +00:00
|
|
|
totalCmds=0;
|
|
|
|
lastCmds=0;
|
|
|
|
cmdsPerSecond=0;
|
2022-01-08 06:57:37 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-18 08:25:42 +00:00
|
|
|
isMuted[i]=0;
|
|
|
|
}
|
2021-12-15 05:37:27 +00:00
|
|
|
isBusy.unlock();
|
2021-12-13 19:40:03 +00:00
|
|
|
}
|
|
|
|
|
2022-01-20 21:27:11 +00:00
|
|
|
#define CHECK_CONFIG_DIR_MAC() \
|
|
|
|
configPath+="/Library/Application Support/Furnace"; \
|
|
|
|
if (stat(configPath.c_str(),&st)<0) { \
|
|
|
|
logI("creating config dir...\n"); \
|
|
|
|
if (mkdir(configPath.c_str(),0755)<0) { \
|
|
|
|
logW("could not make config dir! (%s)\n",strerror(errno)); \
|
|
|
|
configPath="."; \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2021-12-19 08:16:24 +00:00
|
|
|
#define CHECK_CONFIG_DIR() \
|
|
|
|
configPath+="/.config"; \
|
|
|
|
if (stat(configPath.c_str(),&st)<0) { \
|
|
|
|
logI("creating user config dir...\n"); \
|
|
|
|
if (mkdir(configPath.c_str(),0755)<0) { \
|
|
|
|
logW("could not make user config dir! (%s)\n",strerror(errno)); \
|
|
|
|
configPath="."; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
if (configPath!=".") { \
|
|
|
|
configPath+="/furnace"; \
|
|
|
|
if (stat(configPath.c_str(),&st)<0) { \
|
|
|
|
logI("creating config dir...\n"); \
|
|
|
|
if (mkdir(configPath.c_str(),0755)<0) { \
|
|
|
|
logW("could not make config dir! (%s)\n",strerror(errno)); \
|
|
|
|
configPath="."; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2022-01-17 06:20:02 +00:00
|
|
|
bool DivEngine::initAudioBackend() {
|
2022-01-17 06:42:26 +00:00
|
|
|
// load values
|
2022-01-23 04:50:49 +00:00
|
|
|
if (audioEngine==DIV_AUDIO_NULL) {
|
|
|
|
if (getConfString("audioEngine","SDL")=="JACK") {
|
|
|
|
audioEngine=DIV_AUDIO_JACK;
|
|
|
|
} else {
|
|
|
|
audioEngine=DIV_AUDIO_SDL;
|
|
|
|
}
|
2022-01-17 06:42:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lowQuality=getConfInt("audioQuality",0);
|
2022-02-04 22:04:36 +00:00
|
|
|
forceMono=getConfInt("forceMono",0);
|
2022-01-17 06:42:26 +00:00
|
|
|
|
2022-01-17 06:20:02 +00:00
|
|
|
switch (audioEngine) {
|
|
|
|
case DIV_AUDIO_JACK:
|
|
|
|
#ifndef HAVE_JACK
|
|
|
|
logE("Furnace was not compiled with JACK support!\n");
|
|
|
|
setConf("audioEngine","SDL");
|
|
|
|
saveConf();
|
|
|
|
output=new TAAudioSDL;
|
|
|
|
#else
|
|
|
|
output=new TAAudioJACK;
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DIV_AUDIO_SDL:
|
|
|
|
output=new TAAudioSDL;
|
|
|
|
break;
|
2022-01-23 04:50:49 +00:00
|
|
|
case DIV_AUDIO_DUMMY:
|
|
|
|
output=new TAAudio;
|
|
|
|
break;
|
2022-01-17 06:20:02 +00:00
|
|
|
default:
|
|
|
|
logE("invalid audio engine!\n");
|
|
|
|
return false;
|
|
|
|
}
|
2022-02-14 02:42:57 +00:00
|
|
|
|
|
|
|
audioDevs=output->listAudioDevices();
|
|
|
|
|
|
|
|
want.deviceName=getConfString("audioDevice","");
|
2022-01-17 06:20:02 +00:00
|
|
|
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!\n");
|
2022-01-17 06:42:26 +00:00
|
|
|
delete output;
|
|
|
|
output=NULL;
|
2022-02-06 22:00:01 +00:00
|
|
|
audioEngine=DIV_AUDIO_NULL;
|
2022-01-17 06:20:02 +00:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-27 22:49:00 +00:00
|
|
|
|
2022-01-17 06:20:02 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DivEngine::deinitAudioBackend() {
|
|
|
|
if (output!=NULL) {
|
|
|
|
output->quit();
|
|
|
|
delete output;
|
|
|
|
output=NULL;
|
2022-02-06 22:00:01 +00:00
|
|
|
audioEngine=DIV_AUDIO_NULL;
|
2022-01-17 06:20:02 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-12-19 08:16:24 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include "winStuff.h"
|
|
|
|
#endif
|
|
|
|
|
2022-01-17 04:32:13 +00:00
|
|
|
bool DivEngine::init() {
|
2021-12-19 08:16:24 +00:00
|
|
|
// init config
|
|
|
|
#ifdef _WIN32
|
|
|
|
configPath=getWinConfigPath();
|
|
|
|
#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)\n",strerror(errno));
|
|
|
|
configPath=".";
|
|
|
|
} else {
|
|
|
|
configPath=entry->pw_dir;
|
2022-01-20 21:27:11 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
CHECK_CONFIG_DIR_MAC();
|
|
|
|
#else
|
2021-12-19 08:16:24 +00:00
|
|
|
CHECK_CONFIG_DIR();
|
2022-01-20 21:27:11 +00:00
|
|
|
#endif
|
2021-12-19 08:16:24 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
configPath=home;
|
2022-01-20 21:27:11 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
CHECK_CONFIG_DIR_MAC();
|
|
|
|
#else
|
2021-12-19 08:16:24 +00:00
|
|
|
CHECK_CONFIG_DIR();
|
2022-01-20 21:27:11 +00:00
|
|
|
#endif
|
2021-12-19 08:16:24 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
logD("config path: %s\n",configPath.c_str());
|
|
|
|
|
2021-12-19 21:52:04 +00:00
|
|
|
loadConf();
|
|
|
|
|
2021-12-19 08:16:24 +00:00
|
|
|
// init the rest of engine
|
2022-02-06 04:48:56 +00:00
|
|
|
bool haveAudio=false;
|
|
|
|
if (!initAudioBackend()) {
|
|
|
|
logE("no audio output available!\n");
|
|
|
|
} else {
|
|
|
|
haveAudio=true;
|
|
|
|
}
|
2021-05-11 20:08:08 +00:00
|
|
|
|
2022-01-08 21:03:32 +00:00
|
|
|
samp_bb=blip_new(32768);
|
|
|
|
if (samp_bb==NULL) {
|
2021-05-12 08:58:55 +00:00
|
|
|
logE("not enough memory!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-02-06 04:48:56 +00:00
|
|
|
samp_bbOut=new short[32768];
|
2021-12-21 18:06:14 +00:00
|
|
|
|
2022-01-08 21:03:32 +00:00
|
|
|
samp_bbIn=new short[32768];
|
|
|
|
samp_bbInLen=32768;
|
2021-12-21 18:06:14 +00:00
|
|
|
|
2022-01-08 21:03:32 +00:00
|
|
|
blip_set_rates(samp_bb,44100,got.rate);
|
2021-12-06 10:21:42 +00:00
|
|
|
|
2021-05-16 08:03:23 +00:00
|
|
|
for (int i=0; i<64; i++) {
|
|
|
|
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
|
2021-05-14 19:16:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-08 06:57:37 +00:00
|
|
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
2021-12-18 08:25:42 +00:00
|
|
|
isMuted[i]=0;
|
2022-02-10 08:15:39 +00:00
|
|
|
keyHit[i]=false;
|
2021-12-18 08:25:42 +00:00
|
|
|
}
|
|
|
|
|
2022-01-27 22:49:00 +00:00
|
|
|
oscBuf[0]=new float[32768];
|
|
|
|
oscBuf[1]=new float[32768];
|
|
|
|
|
2021-12-13 19:40:03 +00:00
|
|
|
initDispatch();
|
2021-12-11 21:51:34 +00:00
|
|
|
reset();
|
2021-12-20 19:20:05 +00:00
|
|
|
active=true;
|
2021-05-17 20:06:11 +00:00
|
|
|
|
2022-02-06 04:48:56 +00:00
|
|
|
if (!haveAudio) {
|
2022-01-17 04:32:13 +00:00
|
|
|
return false;
|
2022-02-06 04:48:56 +00:00
|
|
|
} else {
|
|
|
|
if (!output->setRun(true)) {
|
|
|
|
logE("error while activating!\n");
|
|
|
|
return false;
|
|
|
|
}
|
2021-05-11 20:08:08 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2021-12-13 22:09:46 +00:00
|
|
|
|
|
|
|
bool DivEngine::quit() {
|
2022-01-17 06:20:02 +00:00
|
|
|
deinitAudioBackend();
|
2021-12-13 22:09:46 +00:00
|
|
|
quitDispatch();
|
2021-12-19 21:52:04 +00:00
|
|
|
logI("saving config.\n");
|
|
|
|
saveConf();
|
2021-12-20 19:20:05 +00:00
|
|
|
active=false;
|
2022-01-27 22:49:00 +00:00
|
|
|
delete[] oscBuf[0];
|
|
|
|
delete[] oscBuf[1];
|
2021-12-13 22:09:46 +00:00
|
|
|
return true;
|
2021-12-14 17:33:26 +00:00
|
|
|
}
|