rewrite audio export

now it is possible to export audio from the GUI!
multiple export coming soon
This commit is contained in:
tildearrow 2022-01-17 23:34:29 -05:00
parent a377ba1f96
commit 8d4d47950c
5 changed files with 187 additions and 6 deletions

View file

@ -2122,6 +2122,90 @@ SafeWriter* DivEngine::saveVGM() {
return NULL;
}
void _runExportThread(DivEngine* caller) {
caller->runExportThread();
}
bool DivEngine::isExporting() {
return exporting;
}
#define EXPORT_BUFSIZE 2048
void DivEngine::runExportThread() {
SNDFILE* sf;
SF_INFO si;
si.samplerate=got.rate;
si.channels=2;
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
sf=sf_open(exportPath.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
logE("could not open file for writing!\n");
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);
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;
}
}
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");
}
}
}
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) {
exportPath=path;
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;
}
#ifdef _WIN32
#define CONFIG_FILE "\\furnace.cfg"
#else
@ -2577,6 +2661,7 @@ void DivEngine::stop() {
freelance=false;
playing=false;
extValuePresent=false;
remainingLoops=-1;
isBusy.unlock();
}

View file

@ -5,6 +5,7 @@
#include "safeWriter.h"
#include "../audio/taAudio.h"
#include "blip_buf.h"
#include <thread>
#include <mutex>
#include <map>
#include <queue>
@ -23,6 +24,12 @@ enum DivAudioEngines {
DIV_AUDIO_SDL=1
};
enum DivAudioExportModes {
DIV_EXPORT_MODE_ONE=0,
DIV_EXPORT_MODE_MANY_SYS,
DIV_EXPORT_MODE_MANY_CHAN
};
struct DivChannelState {
std::vector<DivDelayedCommand> delayed;
int note, oldNote, pitch, portaSpeed, portaNote;
@ -110,6 +117,8 @@ class DivEngine {
DivDispatchContainer disCont[32];
TAAudio* output;
TAAudioDesc want, got;
String exportPath;
std::thread* exportThread;
int chans;
bool active;
bool lowQuality;
@ -121,6 +130,7 @@ class DivEngine {
bool extValuePresent;
bool repeatPattern;
bool metronome;
bool exporting;
int ticks, curRow, curOrder, remainingLoops, nextSpeed, divider;
int cycles, clockDrift;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
@ -129,6 +139,7 @@ class DivEngine {
DivStatusView view;
DivChannelState chan[DIV_MAX_CHANS];
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
std::map<String,String> conf;
std::queue<DivNoteEvent> pendingNotes;
bool isMuted[DIV_MAX_CHANS];
@ -185,6 +196,7 @@ class DivEngine {
public:
DivSong song;
void runExportThread();
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
DivInstrument* getIns(int index);
DivWavetable* getWave(int index);
@ -202,7 +214,7 @@ class DivEngine {
// dump to VGM (TODO).
SafeWriter* saveVGM();
// export to an audio file
bool saveAudio(const char* path);
bool saveAudio(const char* path, int loops, DivAudioExportModes mode);
// wait for audio export to finish
void waitAudioFile();
// stop audio file export
@ -338,6 +350,9 @@ class DivEngine {
// is playing
bool isPlaying();
// is exporting
bool isExporting();
// add instrument
int addInstrument(int refChan=0);
@ -448,6 +463,7 @@ class DivEngine {
DivEngine():
output(NULL),
exportThread(NULL),
chans(0),
active(false),
lowQuality(false),
@ -459,6 +475,7 @@ class DivEngine {
extValuePresent(false),
repeatPattern(false),
metronome(false),
exporting(false),
ticks(0),
curRow(0),
curOrder(0),

View file

@ -962,7 +962,14 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
if (nextTick()) {
if (remainingLoops>0) {
remainingLoops--;
if (!remainingLoops) logI("end of song!\n");
if (!remainingLoops) {
logI("end of song!\n");
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
break;
}
}
}
} else {
@ -993,7 +1000,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
playing=false;
extValuePresent=false;
}
totalProcessed=(1+runPos[0])*got.rate/disCont[0].dispatch->rate;
totalProcessed=size-(runLeftG>>MASTER_CLOCK_PREC);
for (int i=0; i<song.systemLen; i++) {
disCont[i].fillBuf(runtotal[i],size);

View file

@ -2787,6 +2787,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
case GUI_FILE_SAMPLE_SAVE:
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDir);
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir);
break;
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir);
break;
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir);
break;
case GUI_FILE_EXPORT_VGM: case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
}
curFileDialog=type;
}
@ -2949,6 +2961,11 @@ int FurnaceGUI::load(String path) {
return 0;
}
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
e->saveAudio(path.c_str(),exportLoops+1,mode);
displayExporting=true;
}
void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) {
warnString=what;
warnAction=type;
@ -3118,6 +3135,22 @@ bool FurnaceGUI::loop() {
openFileDialog(GUI_FILE_SAVE);
}
ImGui::Separator();
if (ImGui::BeginMenu("export audio...")) {
if (ImGui::MenuItem("one file")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
if (ImGui::MenuItem("multiple files (one per system)")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS);
}
if (ImGui::MenuItem("multiple files (one per channel)")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL);
}
if (ImGui::InputInt("Loops",&exportLoops,1,2)) {
if (exportLoops<0) exportLoops=0;
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::BeginMenu("add system...")) {
sysAddOption(DIV_SYSTEM_GENESIS);
sysAddOption(DIV_SYSTEM_GENESIS_EXT);
@ -3302,6 +3335,19 @@ bool FurnaceGUI::loop() {
e->song.sample[curSample]->save(copyOfName.c_str());
}
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
exportAudio(copyOfName,DIV_EXPORT_MODE_ONE);
break;
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
exportAudio(copyOfName,DIV_EXPORT_MODE_MANY_SYS);
break;
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
exportAudio(copyOfName,DIV_EXPORT_MODE_MANY_CHAN);
break;
case GUI_FILE_EXPORT_VGM:
case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
}
curFileDialog=GUI_FILE_OPEN;
}
@ -3325,8 +3371,26 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup("Error");
}
if (displayExporting) {
displayExporting=false;
ImGui::OpenPopup("Rendering...");
}
if (aboutOpen) drawAbout();
if (ImGui::BeginPopupModal("Rendering...",NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Please wait...\n");
if (ImGui::Button("Abort")) {
if (e->haltAudioFile()) {
ImGui::CloseCurrentPopup();
}
}
if (!e->isExporting()) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal("Error",NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("%s",errorString.c_str());
if (ImGui::Button("OK")) {
@ -3544,6 +3608,7 @@ FurnaceGUI::FurnaceGUI():
edit(false),
modified(false),
displayError(false),
displayExporting(false),
curFileDialog(GUI_FILE_OPEN),
warnAction(GUI_WARN_OPEN),
scrW(1280),
@ -3560,6 +3625,7 @@ FurnaceGUI::FurnaceGUI():
oldOrder(0),
oldOrder1(0),
editStep(1),
exportLoops(0),
editControlsOpen(true),
ordersOpen(true),
insListOpen(true),

View file

@ -80,7 +80,12 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_OPEN,
GUI_FILE_SAVE,
GUI_FILE_SAMPLE_OPEN,
GUI_FILE_SAMPLE_SAVE
GUI_FILE_SAMPLE_SAVE,
GUI_FILE_EXPORT_AUDIO_ONE,
GUI_FILE_EXPORT_AUDIO_PER_SYS,
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
GUI_FILE_EXPORT_VGM,
GUI_FILE_EXPORT_ROM
};
enum FurnaceGUIWarnings {
@ -147,7 +152,7 @@ class FurnaceGUI {
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName;
bool quit, warnQuit, willCommit, edit, modified, displayError;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting;
FurnaceGUIFileDialogs curFileDialog;
FurnaceGUIWarnings warnAction;
@ -197,7 +202,7 @@ class FurnaceGUI {
char finalLayoutPath[4096];
int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep;
int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops;
bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen;
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
bool mixerOpen;
@ -291,6 +296,7 @@ class FurnaceGUI {
void openFileDialog(FurnaceGUIFileDialogs type);
int save(String path);
int load(String path);
void exportAudio(String path, DivAudioExportModes mode);
void showWarning(String what, FurnaceGUIWarnings type);
void showError(String what);