From 4776eaed6896612263d797ae309a2b2b6cfd0c42 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Aug 2023 14:49:31 -0500 Subject: [PATCH] split audio/command stream export functions into two other source files --- CMakeLists.txt | 2 + src/engine/cmdStreamOps.cpp | 540 ++++++++++++++++++++ src/engine/engine.cpp | 947 ------------------------------------ src/engine/wavOps.cpp | 455 +++++++++++++++++ 4 files changed, 997 insertions(+), 947 deletions(-) create mode 100644 src/engine/cmdStreamOps.cpp create mode 100644 src/engine/wavOps.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cb5586a97..30e2c123e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,6 +518,7 @@ src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp src/engine/cmdStream.cpp +src/engine/cmdStreamOps.cpp src/engine/config.cpp src/engine/configEngine.cpp src/engine/dispatchContainer.cpp @@ -535,6 +536,7 @@ src/engine/song.cpp src/engine/sysDef.cpp src/engine/wavetable.cpp src/engine/waveSynth.cpp +src/engine/wavOps.cpp src/engine/vgmOps.cpp src/engine/zsmOps.cpp src/engine/zsm.cpp diff --git a/src/engine/cmdStreamOps.cpp b/src/engine/cmdStreamOps.cpp new file mode 100644 index 000000000..bff6bc66a --- /dev/null +++ b/src/engine/cmdStreamOps.cpp @@ -0,0 +1,540 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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. + */ + +#include "engine.h" +#include "../ta-log.h" + +#define WRITE_TICK(x) \ + if (binary) { \ + if (!wroteTick[x]) { \ + wroteTick[x]=true; \ + if (tick-lastTick[x]>255) { \ + chanStream[x]->writeC(0xfc); \ + chanStream[x]->writeS(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>1) { \ + delayPopularity[tick-lastTick[x]]++; \ + chanStream[x]->writeC(0xfd); \ + chanStream[x]->writeC(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>0) { \ + chanStream[x]->writeC(0xfe); \ + } \ + lastTick[x]=tick; \ + } \ + } else { \ + if (!wroteTickGlobal) { \ + wroteTickGlobal=true; \ + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ + } \ + } + +void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xb4); + } else { + w->writeC(CLAMP(c.value+60,0,0xb3)); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + case DIV_CMD_INSTRUMENT: + case DIV_CMD_PANNING: + case DIV_CMD_PRE_PORTA: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_LEGATO: + w->writeC((unsigned char)c.cmd+0xb4); + break; + default: + w->writeC(0xf0); // unoptimized extended command + w->writeC(c.cmd); + break; + } + switch (c.cmd) { + case DIV_CMD_HINT_LEGATO: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xff); + } else { + w->writeC(c.value+60); + } + break; + case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + break; + case DIV_CMD_INSTRUMENT: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_VOLUME: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + w->writeS(c.value); + break; + case DIV_CMD_SAMPLE_MODE: + case DIV_CMD_SAMPLE_FREQ: + case DIV_CMD_SAMPLE_BANK: + case DIV_CMD_SAMPLE_POS: + case DIV_CMD_SAMPLE_DIR: + case DIV_CMD_FM_HARD_RESET: + case DIV_CMD_FM_LFO: + case DIV_CMD_FM_LFO_WAVE: + case DIV_CMD_FM_FB: + case DIV_CMD_FM_EXTCH: + case DIV_CMD_FM_AM_DEPTH: + case DIV_CMD_FM_PM_DEPTH: + case DIV_CMD_STD_NOISE_FREQ: + case DIV_CMD_STD_NOISE_MODE: + case DIV_CMD_WAVE: + case DIV_CMD_GB_SWEEP_TIME: + case DIV_CMD_GB_SWEEP_DIR: + case DIV_CMD_PCE_LFO_MODE: + case DIV_CMD_PCE_LFO_SPEED: + case DIV_CMD_NES_DMC: + case DIV_CMD_C64_CUTOFF: + case DIV_CMD_C64_RESONANCE: + case DIV_CMD_C64_FILTER_MODE: + case DIV_CMD_C64_RESET_TIME: + case DIV_CMD_C64_RESET_MASK: + case DIV_CMD_C64_FILTER_RESET: + case DIV_CMD_C64_DUTY_RESET: + case DIV_CMD_C64_EXTENDED: + case DIV_CMD_AY_ENVELOPE_SET: + case DIV_CMD_AY_ENVELOPE_LOW: + case DIV_CMD_AY_ENVELOPE_HIGH: + case DIV_CMD_AY_ENVELOPE_SLIDE: + case DIV_CMD_AY_NOISE_MASK_AND: + case DIV_CMD_AY_NOISE_MASK_OR: + case DIV_CMD_AY_AUTO_ENVELOPE: + case DIV_CMD_FDS_MOD_DEPTH: + case DIV_CMD_FDS_MOD_HIGH: + case DIV_CMD_FDS_MOD_LOW: + case DIV_CMD_FDS_MOD_POS: + case DIV_CMD_FDS_MOD_WAVE: + case DIV_CMD_SAA_ENVELOPE: + case DIV_CMD_AMIGA_FILTER: + case DIV_CMD_AMIGA_AM: + case DIV_CMD_AMIGA_PM: + case DIV_CMD_MACRO_OFF: + case DIV_CMD_MACRO_ON: + case DIV_CMD_HINT_ARP_TIME: + w->writeC(1); // length + w->writeC(c.value); + break; + case DIV_CMD_FM_TL: + case DIV_CMD_FM_AM: + case DIV_CMD_FM_AR: + case DIV_CMD_FM_DR: + case DIV_CMD_FM_SL: + case DIV_CMD_FM_D2R: + case DIV_CMD_FM_RR: + case DIV_CMD_FM_DT: + case DIV_CMD_FM_DT2: + case DIV_CMD_FM_RS: + case DIV_CMD_FM_KSR: + case DIV_CMD_FM_VIB: + case DIV_CMD_FM_SUS: + case DIV_CMD_FM_WS: + case DIV_CMD_FM_SSG: + case DIV_CMD_FM_REV: + case DIV_CMD_FM_EG_SHIFT: + case DIV_CMD_FM_MULT: + case DIV_CMD_FM_FINE: + case DIV_CMD_AY_IO_WRITE: + case DIV_CMD_AY_AUTO_PWM: + case DIV_CMD_SURROUND_PANNING: + w->writeC(2); // length + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_C64_FINE_DUTY: + case DIV_CMD_C64_FINE_CUTOFF: + case DIV_CMD_LYNX_LFSR_LOAD: + w->writeC(2); // length + w->writeS(c.value); + break; + case DIV_CMD_FM_FIXFREQ: + w->writeC(2); // length + w->writeS((c.value<<12)|(c.value2&0x7ff)); + break; + case DIV_CMD_NES_SWEEP: + w->writeC(1); // length + w->writeC((c.value?8:0)|(c.value2&0x77)); + break; + default: + logW("unimplemented command %s!",cmdName[c.cmd]); + w->writeC(0); // length + break; + } +} + +SafeWriter* DivEngine::saveCommand(bool binary) { + stop(); + repeatPattern=false; + shallStop=false; + setOrder(0); + BUSY_BEGIN_SOFT; + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + + int cmdPopularity[256]; + int delayPopularity[256]; + + int sortedCmdPopularity[16]; + int sortedDelayPopularity[16]; + unsigned char sortedCmd[16]; + unsigned char sortedDelay[16]; + + SafeWriter* chanStream[DIV_MAX_CHANS]; + unsigned int chanStreamOff[DIV_MAX_CHANS]; + bool wroteTick[DIV_MAX_CHANS]; + + memset(cmdPopularity,0,256*sizeof(int)); + memset(delayPopularity,0,256*sizeof(int)); + memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); + memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); + memset(sortedCmdPopularity,0,16*sizeof(int)); + memset(sortedDelayPopularity,0,16*sizeof(int)); + memset(sortedCmd,0,16); + memset(sortedDelay,0,16); + + SafeWriter* w=new SafeWriter; + w->init(); + + // write header + if (binary) { + w->write("FCS",4); + w->writeI(chans); + // offsets + for (int i=0; iinit(); + w->writeI(0); + } + // preset delays and speed dial + for (int i=0; i<32; i++) { + w->writeC(0); + } + } else { + w->writeText("# Furnace Command Stream\n\n"); + + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + + w->writeText("\n"); + + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + + w->writeText("\n"); + + w->writeText("[SysDefinition]\n"); + // TODO + + w->writeText("\n"); + } + + // play the song ourselves + bool done=false; + playSub(false); + + if (!binary) { + w->writeText("[Stream]\n"); + } + int tick=0; + bool oldCmdStreamEnabled=cmdStreamEnabled; + cmdStreamEnabled=true; + double curDivider=divider; + int lastTick[DIV_MAX_CHANS]; + + memset(lastTick,0,DIV_MAX_CHANS*sizeof(int)); + while (!done) { + if (nextTick(false,true) || !playing) { + done=true; + } + // get command stream + bool wroteTickGlobal=false; + memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); + if (curDivider!=divider) { + curDivider=divider; + WRITE_TICK(0); + if (binary) { + chanStream[0]->writeC(0xfb); + chanStream[0]->writeI((int)(curDivider*65536)); + } else { + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); + } + } + for (DivCommand& i: cmdStream) { + switch (i.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + case DIV_CMD_PRE_NOTE: + break; + default: + WRITE_TICK(i.chan); + if (binary) { + cmdPopularity[i.cmd]++; + writePackedCommandValues(chanStream[i.chan],i); + } else { + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); + } + break; + } + } + cmdStream.clear(); + tick++; + } + cmdStreamEnabled=oldCmdStreamEnabled; + + if (binary) { + int sortCand=-1; + int sortPos=0; + while (sortPos<16) { + sortCand=-1; + for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { + if (cmdPopularity[i]) { + if (sortCand==-1) { + sortCand=i; + } else if (cmdPopularity[sortCand]writeC(0xff); + // optimize stream + SafeWriter* oldStream=chanStream[i]; + SafeReader* reader=oldStream->toReader(); + chanStream[i]=new SafeWriter; + chanStream[i]->init(); + + while (1) { + try { + unsigned char next=reader->readC(); + switch (next) { + case 0xb8: // instrument + case 0xc0: // pre porta + case 0xc3: // vibrato range + case 0xc4: // vibrato shape + case 0xc5: // pitch + case 0xc7: // volume + case 0xca: // legato + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xbe: // panning + case 0xc2: // vibrato + case 0xc6: // arpeggio + case 0xc8: // vol slide + case 0xc9: // porta + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xf0: { // full command (pre) + unsigned char cmd=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedCmd[j]==cmd) { + chanStream[i]->writeC(0xd0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(0xf7); // full command + chanStream[i]->writeC(cmd); + } + + unsigned char cmdLen=reader->readC(); + logD("cmdLen: %d",cmdLen); + for (unsigned char j=0; jreadC(); + chanStream[i]->writeC(next); + } + break; + } + case 0xfb: // tick rate + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xfc: { // 16-bit wait + unsigned short delay=reader->readS(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeS(delay); + } + break; + } + case 0xfd: { // 8-bit wait + unsigned char delay=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeC(delay); + } + break; + } + default: + chanStream[i]->writeC(next); + break; + } + } catch (EndOfFileException& e) { + break; + } + } + + oldStream->finish(); + delete oldStream; + } + + for (int i=0; itell(); + logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); + w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); + chanStream[i]->finish(); + delete chanStream[i]; + } + + w->seek(8,SEEK_SET); + for (int i=0; iwriteI(chanStreamOff[i]); + } + + logD("delay popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedDelay[i]); + if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); + } + + logD("command popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedCmd[i]); + if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); + } + } else { + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } + } + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + BUSY_END; + + return w; +} diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index de13e7750..b490880ed 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -289,953 +289,6 @@ double DivEngine::benchmarkSeek() { return tAvg; } -#define WRITE_TICK(x) \ - if (binary) { \ - if (!wroteTick[x]) { \ - wroteTick[x]=true; \ - if (tick-lastTick[x]>255) { \ - chanStream[x]->writeC(0xfc); \ - chanStream[x]->writeS(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>1) { \ - delayPopularity[tick-lastTick[x]]++; \ - chanStream[x]->writeC(0xfd); \ - chanStream[x]->writeC(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>0) { \ - chanStream[x]->writeC(0xfe); \ - } \ - lastTick[x]=tick; \ - } \ - } else { \ - if (!wroteTickGlobal) { \ - wroteTickGlobal=true; \ - w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ - } \ - } - -void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { - switch (c.cmd) { - case DIV_CMD_NOTE_ON: - if (c.value==DIV_NOTE_NULL) { - w->writeC(0xb4); - } else { - w->writeC(CLAMP(c.value+60,0,0xb3)); - } - break; - case DIV_CMD_NOTE_OFF: - case DIV_CMD_NOTE_OFF_ENV: - case DIV_CMD_ENV_RELEASE: - case DIV_CMD_INSTRUMENT: - case DIV_CMD_PANNING: - case DIV_CMD_PRE_PORTA: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_VIBRATO_RANGE: - case DIV_CMD_HINT_VIBRATO_SHAPE: - case DIV_CMD_HINT_PITCH: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_VOLUME: - case DIV_CMD_HINT_PORTA: - case DIV_CMD_HINT_VOL_SLIDE: - case DIV_CMD_HINT_LEGATO: - w->writeC((unsigned char)c.cmd+0xb4); - break; - default: - w->writeC(0xf0); // unoptimized extended command - w->writeC(c.cmd); - break; - } - switch (c.cmd) { - case DIV_CMD_HINT_LEGATO: - if (c.value==DIV_NOTE_NULL) { - w->writeC(0xff); - } else { - w->writeC(c.value+60); - } - break; - case DIV_CMD_NOTE_ON: - case DIV_CMD_NOTE_OFF: - case DIV_CMD_NOTE_OFF_ENV: - case DIV_CMD_ENV_RELEASE: - break; - case DIV_CMD_INSTRUMENT: - case DIV_CMD_HINT_VIBRATO_RANGE: - case DIV_CMD_HINT_VIBRATO_SHAPE: - case DIV_CMD_HINT_PITCH: - case DIV_CMD_HINT_VOLUME: - w->writeC(c.value); - break; - case DIV_CMD_PANNING: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_PORTA: - w->writeC(c.value); - w->writeC(c.value2); - break; - case DIV_CMD_PRE_PORTA: - w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); - break; - case DIV_CMD_HINT_VOL_SLIDE: - w->writeS(c.value); - break; - case DIV_CMD_SAMPLE_MODE: - case DIV_CMD_SAMPLE_FREQ: - case DIV_CMD_SAMPLE_BANK: - case DIV_CMD_SAMPLE_POS: - case DIV_CMD_SAMPLE_DIR: - case DIV_CMD_FM_HARD_RESET: - case DIV_CMD_FM_LFO: - case DIV_CMD_FM_LFO_WAVE: - case DIV_CMD_FM_FB: - case DIV_CMD_FM_EXTCH: - case DIV_CMD_FM_AM_DEPTH: - case DIV_CMD_FM_PM_DEPTH: - case DIV_CMD_STD_NOISE_FREQ: - case DIV_CMD_STD_NOISE_MODE: - case DIV_CMD_WAVE: - case DIV_CMD_GB_SWEEP_TIME: - case DIV_CMD_GB_SWEEP_DIR: - case DIV_CMD_PCE_LFO_MODE: - case DIV_CMD_PCE_LFO_SPEED: - case DIV_CMD_NES_DMC: - case DIV_CMD_C64_CUTOFF: - case DIV_CMD_C64_RESONANCE: - case DIV_CMD_C64_FILTER_MODE: - case DIV_CMD_C64_RESET_TIME: - case DIV_CMD_C64_RESET_MASK: - case DIV_CMD_C64_FILTER_RESET: - case DIV_CMD_C64_DUTY_RESET: - case DIV_CMD_C64_EXTENDED: - case DIV_CMD_AY_ENVELOPE_SET: - case DIV_CMD_AY_ENVELOPE_LOW: - case DIV_CMD_AY_ENVELOPE_HIGH: - case DIV_CMD_AY_ENVELOPE_SLIDE: - case DIV_CMD_AY_NOISE_MASK_AND: - case DIV_CMD_AY_NOISE_MASK_OR: - case DIV_CMD_AY_AUTO_ENVELOPE: - case DIV_CMD_FDS_MOD_DEPTH: - case DIV_CMD_FDS_MOD_HIGH: - case DIV_CMD_FDS_MOD_LOW: - case DIV_CMD_FDS_MOD_POS: - case DIV_CMD_FDS_MOD_WAVE: - case DIV_CMD_SAA_ENVELOPE: - case DIV_CMD_AMIGA_FILTER: - case DIV_CMD_AMIGA_AM: - case DIV_CMD_AMIGA_PM: - case DIV_CMD_MACRO_OFF: - case DIV_CMD_MACRO_ON: - case DIV_CMD_HINT_ARP_TIME: - w->writeC(1); // length - w->writeC(c.value); - break; - case DIV_CMD_FM_TL: - case DIV_CMD_FM_AM: - case DIV_CMD_FM_AR: - case DIV_CMD_FM_DR: - case DIV_CMD_FM_SL: - case DIV_CMD_FM_D2R: - case DIV_CMD_FM_RR: - case DIV_CMD_FM_DT: - case DIV_CMD_FM_DT2: - case DIV_CMD_FM_RS: - case DIV_CMD_FM_KSR: - case DIV_CMD_FM_VIB: - case DIV_CMD_FM_SUS: - case DIV_CMD_FM_WS: - case DIV_CMD_FM_SSG: - case DIV_CMD_FM_REV: - case DIV_CMD_FM_EG_SHIFT: - case DIV_CMD_FM_MULT: - case DIV_CMD_FM_FINE: - case DIV_CMD_AY_IO_WRITE: - case DIV_CMD_AY_AUTO_PWM: - case DIV_CMD_SURROUND_PANNING: - w->writeC(2); // length - w->writeC(c.value); - w->writeC(c.value2); - break; - case DIV_CMD_C64_FINE_DUTY: - case DIV_CMD_C64_FINE_CUTOFF: - case DIV_CMD_LYNX_LFSR_LOAD: - w->writeC(2); // length - w->writeS(c.value); - break; - case DIV_CMD_FM_FIXFREQ: - w->writeC(2); // length - w->writeS((c.value<<12)|(c.value2&0x7ff)); - break; - case DIV_CMD_NES_SWEEP: - w->writeC(1); // length - w->writeC((c.value?8:0)|(c.value2&0x77)); - break; - default: - logW("unimplemented command %s!",cmdName[c.cmd]); - w->writeC(0); // length - break; - } -} - -SafeWriter* DivEngine::saveCommand(bool binary) { - stop(); - repeatPattern=false; - shallStop=false; - setOrder(0); - BUSY_BEGIN_SOFT; - // determine loop point - int loopOrder=0; - int loopRow=0; - int loopEnd=0; - walkSong(loopOrder,loopRow,loopEnd); - logI("loop point: %d %d",loopOrder,loopRow); - - int cmdPopularity[256]; - int delayPopularity[256]; - - int sortedCmdPopularity[16]; - int sortedDelayPopularity[16]; - unsigned char sortedCmd[16]; - unsigned char sortedDelay[16]; - - SafeWriter* chanStream[DIV_MAX_CHANS]; - unsigned int chanStreamOff[DIV_MAX_CHANS]; - bool wroteTick[DIV_MAX_CHANS]; - - memset(cmdPopularity,0,256*sizeof(int)); - memset(delayPopularity,0,256*sizeof(int)); - memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); - memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); - memset(sortedCmdPopularity,0,16*sizeof(int)); - memset(sortedDelayPopularity,0,16*sizeof(int)); - memset(sortedCmd,0,16); - memset(sortedDelay,0,16); - - SafeWriter* w=new SafeWriter; - w->init(); - - // write header - if (binary) { - w->write("FCS",4); - w->writeI(chans); - // offsets - for (int i=0; iinit(); - w->writeI(0); - } - // preset delays and speed dial - for (int i=0; i<32; i++) { - w->writeC(0); - } - } else { - w->writeText("# Furnace Command Stream\n\n"); - - w->writeText("[Information]\n"); - w->writeText(fmt::sprintf("name: %s\n",song.name)); - w->writeText(fmt::sprintf("author: %s\n",song.author)); - w->writeText(fmt::sprintf("category: %s\n",song.category)); - w->writeText(fmt::sprintf("system: %s\n",song.systemName)); - - w->writeText("\n"); - - w->writeText("[SubSongInformation]\n"); - w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); - w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); - - w->writeText("\n"); - - w->writeText("[SysDefinition]\n"); - // TODO - - w->writeText("\n"); - } - - // play the song ourselves - bool done=false; - playSub(false); - - if (!binary) { - w->writeText("[Stream]\n"); - } - int tick=0; - bool oldCmdStreamEnabled=cmdStreamEnabled; - cmdStreamEnabled=true; - double curDivider=divider; - int lastTick[DIV_MAX_CHANS]; - - memset(lastTick,0,DIV_MAX_CHANS*sizeof(int)); - while (!done) { - if (nextTick(false,true) || !playing) { - done=true; - } - // get command stream - bool wroteTickGlobal=false; - memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); - if (curDivider!=divider) { - curDivider=divider; - WRITE_TICK(0); - if (binary) { - chanStream[0]->writeC(0xfb); - chanStream[0]->writeI((int)(curDivider*65536)); - } else { - w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); - } - } - for (DivCommand& i: cmdStream) { - switch (i.cmd) { - // strip away hinted/useless commands - case DIV_ALWAYS_SET_VOLUME: - break; - case DIV_CMD_GET_VOLUME: - break; - case DIV_CMD_VOLUME: - break; - case DIV_CMD_NOTE_PORTA: - break; - case DIV_CMD_LEGATO: - break; - case DIV_CMD_PITCH: - break; - case DIV_CMD_PRE_NOTE: - break; - default: - WRITE_TICK(i.chan); - if (binary) { - cmdPopularity[i.cmd]++; - writePackedCommandValues(chanStream[i.chan],i); - } else { - w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); - } - break; - } - } - cmdStream.clear(); - tick++; - } - cmdStreamEnabled=oldCmdStreamEnabled; - - if (binary) { - int sortCand=-1; - int sortPos=0; - while (sortPos<16) { - sortCand=-1; - for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { - if (cmdPopularity[i]) { - if (sortCand==-1) { - sortCand=i; - } else if (cmdPopularity[sortCand]writeC(0xff); - // optimize stream - SafeWriter* oldStream=chanStream[i]; - SafeReader* reader=oldStream->toReader(); - chanStream[i]=new SafeWriter; - chanStream[i]->init(); - - while (1) { - try { - unsigned char next=reader->readC(); - switch (next) { - case 0xb8: // instrument - case 0xc0: // pre porta - case 0xc3: // vibrato range - case 0xc4: // vibrato shape - case 0xc5: // pitch - case 0xc7: // volume - case 0xca: // legato - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xbe: // panning - case 0xc2: // vibrato - case 0xc6: // arpeggio - case 0xc8: // vol slide - case 0xc9: // porta - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xf0: { // full command (pre) - unsigned char cmd=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedCmd[j]==cmd) { - chanStream[i]->writeC(0xd0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(0xf7); // full command - chanStream[i]->writeC(cmd); - } - - unsigned char cmdLen=reader->readC(); - logD("cmdLen: %d",cmdLen); - for (unsigned char j=0; jreadC(); - chanStream[i]->writeC(next); - } - break; - } - case 0xfb: // tick rate - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xfc: { // 16-bit wait - unsigned short delay=reader->readS(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeS(delay); - } - break; - } - case 0xfd: { // 8-bit wait - unsigned char delay=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeC(delay); - } - break; - } - default: - chanStream[i]->writeC(next); - break; - } - } catch (EndOfFileException& e) { - break; - } - } - - oldStream->finish(); - delete oldStream; - } - - for (int i=0; itell(); - logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); - w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); - chanStream[i]->finish(); - delete chanStream[i]; - } - - w->seek(8,SEEK_SET); - for (int i=0; iwriteI(chanStreamOff[i]); - } - - logD("delay popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedDelay[i]); - if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); - } - - logD("command popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedCmd[i]); - if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); - } - } else { - if (!playing) { - w->writeText(">> END\n"); - } else { - w->writeText(">> LOOP 0\n"); - } - } - - remainingLoops=-1; - playing=false; - freelance=false; - extValuePresent=false; - BUSY_END; - - return w; -} - -void _runExportThread(DivEngine* caller) { - caller->runExportThread(); -} - -bool DivEngine::isExporting() { - return exporting; -} - -#ifdef HAVE_SNDFILE -void DivEngine::runExportThread() { - size_t fadeOutSamples=got.rate*exportFadeOut; - size_t curFadeOutSample=0; - bool isFadingOut=false; - - switch (exportMode) { - case DIV_EXPORT_MODE_ONE: { - SNDFILE* sf; - SF_INFO si; - SFWrapper sfWrap; - si.samplerate=got.rate; - si.channels=2; - si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - - sf=sfWrap.doOpen(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) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int i=0; i<(int)totalProcessed; i++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; - outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; - if (++curFadeOutSample>=fadeOutSamples) { - playing=false; - break; - } - } else { - 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 (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - - if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { - logE("error: failed to write entire buffer!"); - break; - } - } - - delete[] outBuf[0]; - delete[] outBuf[1]; - delete[] outBuf[2]; - - if (sfWrap.doClose()!=0) { - logE("could not close audio file!"); - } - exporting=false; - - if (initAudioBackend()) { - for (int i=0; isetRun(true)) { - logE("error while activating audio!"); - } - } - logI("done!"); - break; - } - case DIV_EXPORT_MODE_MANY_SYS: { - SNDFILE* sf[DIV_MAX_CHIPS]; - SF_INFO si[DIV_MAX_CHIPS]; - String fname[DIV_MAX_CHIPS]; - SFWrapper sfWrap[DIV_MAX_CHIPS]; - for (int i=0; igetOutputCount(); - si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - } - - for (int i=0; igetOutputCount()]; - } - - // take control of audio output - deinitAudioBackend(); - playSub(false); - - logI("rendering to files..."); - - while (playing) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int j=0; j<(int)totalProcessed; j++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - for (int i=0; i=fadeOutSamples) { - playing=false; - break; - } - } else { - for (int i=0; i-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - for (int i=0; isetRun(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; imuteChannel(dispatchChanOfChan[j],isMuted[j]); - } - } - - curOrder=0; - prevOrder=0; - curFadeOutSample=0; - lastLoopPos=-1; - totalLoops=0; - isFadingOut=false; - if (exportFadeOut<=0.01) { - remainingLoops=loopCount; - } else { - remainingLoops=-1; - } - playSub(false); - - while (playing) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int j=0; j<(int)totalProcessed; j++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul; - outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul; - if (++curFadeOutSample>=fadeOutSamples) { - playing=false; - break; - } - } else { - 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 (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { - logE("error: failed to write entire buffer!"); - break; - } - } - - if (sfWrap.doClose()!=0) { - logE("could not close audio file!"); - } - - if (getChannelType(i)==5) { - i++; - while (true) { - if (i>=chans) break; - if (getChannelType(i)!=5) break; - i++; - } - i--; - } - - if (stopExport) break; - } - exporting=false; - - delete[] outBuf[0]; - delete[] outBuf[1]; - delete[] outBuf[2]; - - for (int i=0; imuteChannel(dispatchChanOfChan[i],false); - } - } - - if (initAudioBackend()) { - for (int i=0; isetRun(true)) { - logE("error while activating audio!"); - } - } - logI("done!"); - break; - } - } - - stopExport=false; -} -#else -void DivEngine::runExportThread() { -} -#endif - -bool DivEngine::shallSwitchCores() { - // TODO: detect whether we should - return true; -} - -bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { -#ifndef HAVE_SNDFILE - logE("Furnace was not compiled with libsndfile. cannot export!"); - return false; -#else - exportPath=path; - exportMode=mode; - exportFadeOut=fadeOutTime; - 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); - if (exportFadeOut<=0.01) { - remainingLoops=loops; - } else { - remainingLoops=-1; - } - - if (shallSwitchCores()) { - bool isMutedBefore[DIV_MAX_CHANS]; - memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); - quitDispatch(); - initDispatch(true); - renderSamplesP(); - for (int i=0; ijoin(); - } -} - -bool DivEngine::haltAudioFile() { - stopExport=true; - stop(); - waitAudioFile(); - finishAudioFile(); - return true; -} - -void DivEngine::finishAudioFile() { - if (shallSwitchCores()) { - bool isMutedBefore[DIV_MAX_CHANS]; - memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); - quitDispatch(); - initDispatch(false); - renderSamplesP(); - for (int i=0; irunExportThread(); +} + +bool DivEngine::isExporting() { + return exporting; +} + +#ifdef HAVE_SNDFILE +void DivEngine::runExportThread() { + size_t fadeOutSamples=got.rate*exportFadeOut; + size_t curFadeOutSample=0; + bool isFadingOut=false; + + switch (exportMode) { + case DIV_EXPORT_MODE_ONE: { + SNDFILE* sf; + SF_INFO si; + SFWrapper sfWrap; + si.samplerate=got.rate; + si.channels=2; + si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + + sf=sfWrap.doOpen(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) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int i=0; i<(int)totalProcessed; i++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; + outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + 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 (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { + logE("error: failed to write entire buffer!"); + break; + } + } + + delete[] outBuf[0]; + delete[] outBuf[1]; + delete[] outBuf[2]; + + if (sfWrap.doClose()!=0) { + logE("could not close audio file!"); + } + exporting=false; + + if (initAudioBackend()) { + for (int i=0; isetRun(true)) { + logE("error while activating audio!"); + } + } + logI("done!"); + break; + } + case DIV_EXPORT_MODE_MANY_SYS: { + SNDFILE* sf[DIV_MAX_CHIPS]; + SF_INFO si[DIV_MAX_CHIPS]; + String fname[DIV_MAX_CHIPS]; + SFWrapper sfWrap[DIV_MAX_CHIPS]; + for (int i=0; igetOutputCount(); + si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + } + + for (int i=0; igetOutputCount()]; + } + + // take control of audio output + deinitAudioBackend(); + playSub(false); + + logI("rendering to files..."); + + while (playing) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + for (int i=0; i=fadeOutSamples) { + playing=false; + break; + } + } else { + for (int i=0; i-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + for (int i=0; isetRun(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; imuteChannel(dispatchChanOfChan[j],isMuted[j]); + } + } + + curOrder=0; + prevOrder=0; + curFadeOutSample=0; + lastLoopPos=-1; + totalLoops=0; + isFadingOut=false; + if (exportFadeOut<=0.01) { + remainingLoops=loopCount; + } else { + remainingLoops=-1; + } + playSub(false); + + while (playing) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul; + outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + 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 (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { + logE("error: failed to write entire buffer!"); + break; + } + } + + if (sfWrap.doClose()!=0) { + logE("could not close audio file!"); + } + + if (getChannelType(i)==5) { + i++; + while (true) { + if (i>=chans) break; + if (getChannelType(i)!=5) break; + i++; + } + i--; + } + + if (stopExport) break; + } + exporting=false; + + delete[] outBuf[0]; + delete[] outBuf[1]; + delete[] outBuf[2]; + + for (int i=0; imuteChannel(dispatchChanOfChan[i],false); + } + } + + if (initAudioBackend()) { + for (int i=0; isetRun(true)) { + logE("error while activating audio!"); + } + } + logI("done!"); + break; + } + } + + stopExport=false; +} +#else +void DivEngine::runExportThread() { +} +#endif + +bool DivEngine::shallSwitchCores() { + // TODO: detect whether we should + return true; +} + +bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { +#ifndef HAVE_SNDFILE + logE("Furnace was not compiled with libsndfile. cannot export!"); + return false; +#else + exportPath=path; + exportMode=mode; + exportFadeOut=fadeOutTime; + 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); + if (exportFadeOut<=0.01) { + remainingLoops=loops; + } else { + remainingLoops=-1; + } + + if (shallSwitchCores()) { + bool isMutedBefore[DIV_MAX_CHANS]; + memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); + quitDispatch(); + initDispatch(true); + renderSamplesP(); + for (int i=0; ijoin(); + } +} + +bool DivEngine::haltAudioFile() { + stopExport=true; + stop(); + waitAudioFile(); + finishAudioFile(); + return true; +} + +void DivEngine::finishAudioFile() { + if (shallSwitchCores()) { + bool isMutedBefore[DIV_MAX_CHANS]; + memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); + quitDispatch(); + initDispatch(false); + renderSamplesP(); + for (int i=0; i