mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-02 17:27:25 +00:00
rewrite audio export
now it is possible to export audio from the GUI! multiple export coming soon
This commit is contained in:
parent
a377ba1f96
commit
8d4d47950c
5 changed files with 187 additions and 6 deletions
|
@ -2122,6 +2122,90 @@ SafeWriter* DivEngine::saveVGM() {
|
||||||
return NULL;
|
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
|
#ifdef _WIN32
|
||||||
#define CONFIG_FILE "\\furnace.cfg"
|
#define CONFIG_FILE "\\furnace.cfg"
|
||||||
#else
|
#else
|
||||||
|
@ -2577,6 +2661,7 @@ void DivEngine::stop() {
|
||||||
freelance=false;
|
freelance=false;
|
||||||
playing=false;
|
playing=false;
|
||||||
extValuePresent=false;
|
extValuePresent=false;
|
||||||
|
remainingLoops=-1;
|
||||||
isBusy.unlock();
|
isBusy.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "safeWriter.h"
|
#include "safeWriter.h"
|
||||||
#include "../audio/taAudio.h"
|
#include "../audio/taAudio.h"
|
||||||
#include "blip_buf.h"
|
#include "blip_buf.h"
|
||||||
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -23,6 +24,12 @@ enum DivAudioEngines {
|
||||||
DIV_AUDIO_SDL=1
|
DIV_AUDIO_SDL=1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DivAudioExportModes {
|
||||||
|
DIV_EXPORT_MODE_ONE=0,
|
||||||
|
DIV_EXPORT_MODE_MANY_SYS,
|
||||||
|
DIV_EXPORT_MODE_MANY_CHAN
|
||||||
|
};
|
||||||
|
|
||||||
struct DivChannelState {
|
struct DivChannelState {
|
||||||
std::vector<DivDelayedCommand> delayed;
|
std::vector<DivDelayedCommand> delayed;
|
||||||
int note, oldNote, pitch, portaSpeed, portaNote;
|
int note, oldNote, pitch, portaSpeed, portaNote;
|
||||||
|
@ -110,6 +117,8 @@ class DivEngine {
|
||||||
DivDispatchContainer disCont[32];
|
DivDispatchContainer disCont[32];
|
||||||
TAAudio* output;
|
TAAudio* output;
|
||||||
TAAudioDesc want, got;
|
TAAudioDesc want, got;
|
||||||
|
String exportPath;
|
||||||
|
std::thread* exportThread;
|
||||||
int chans;
|
int chans;
|
||||||
bool active;
|
bool active;
|
||||||
bool lowQuality;
|
bool lowQuality;
|
||||||
|
@ -121,6 +130,7 @@ class DivEngine {
|
||||||
bool extValuePresent;
|
bool extValuePresent;
|
||||||
bool repeatPattern;
|
bool repeatPattern;
|
||||||
bool metronome;
|
bool metronome;
|
||||||
|
bool exporting;
|
||||||
int ticks, curRow, curOrder, remainingLoops, nextSpeed, divider;
|
int ticks, curRow, curOrder, remainingLoops, nextSpeed, divider;
|
||||||
int cycles, clockDrift;
|
int cycles, clockDrift;
|
||||||
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
|
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
|
||||||
|
@ -129,6 +139,7 @@ class DivEngine {
|
||||||
DivStatusView view;
|
DivStatusView view;
|
||||||
DivChannelState chan[DIV_MAX_CHANS];
|
DivChannelState chan[DIV_MAX_CHANS];
|
||||||
DivAudioEngines audioEngine;
|
DivAudioEngines audioEngine;
|
||||||
|
DivAudioExportModes exportMode;
|
||||||
std::map<String,String> conf;
|
std::map<String,String> conf;
|
||||||
std::queue<DivNoteEvent> pendingNotes;
|
std::queue<DivNoteEvent> pendingNotes;
|
||||||
bool isMuted[DIV_MAX_CHANS];
|
bool isMuted[DIV_MAX_CHANS];
|
||||||
|
@ -185,6 +196,7 @@ class DivEngine {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DivSong song;
|
DivSong song;
|
||||||
|
void runExportThread();
|
||||||
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
|
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
|
||||||
DivInstrument* getIns(int index);
|
DivInstrument* getIns(int index);
|
||||||
DivWavetable* getWave(int index);
|
DivWavetable* getWave(int index);
|
||||||
|
@ -202,7 +214,7 @@ class DivEngine {
|
||||||
// dump to VGM (TODO).
|
// dump to VGM (TODO).
|
||||||
SafeWriter* saveVGM();
|
SafeWriter* saveVGM();
|
||||||
// export to an audio file
|
// 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
|
// wait for audio export to finish
|
||||||
void waitAudioFile();
|
void waitAudioFile();
|
||||||
// stop audio file export
|
// stop audio file export
|
||||||
|
@ -338,6 +350,9 @@ class DivEngine {
|
||||||
// is playing
|
// is playing
|
||||||
bool isPlaying();
|
bool isPlaying();
|
||||||
|
|
||||||
|
// is exporting
|
||||||
|
bool isExporting();
|
||||||
|
|
||||||
// add instrument
|
// add instrument
|
||||||
int addInstrument(int refChan=0);
|
int addInstrument(int refChan=0);
|
||||||
|
|
||||||
|
@ -448,6 +463,7 @@ class DivEngine {
|
||||||
|
|
||||||
DivEngine():
|
DivEngine():
|
||||||
output(NULL),
|
output(NULL),
|
||||||
|
exportThread(NULL),
|
||||||
chans(0),
|
chans(0),
|
||||||
active(false),
|
active(false),
|
||||||
lowQuality(false),
|
lowQuality(false),
|
||||||
|
@ -459,6 +475,7 @@ class DivEngine {
|
||||||
extValuePresent(false),
|
extValuePresent(false),
|
||||||
repeatPattern(false),
|
repeatPattern(false),
|
||||||
metronome(false),
|
metronome(false),
|
||||||
|
exporting(false),
|
||||||
ticks(0),
|
ticks(0),
|
||||||
curRow(0),
|
curRow(0),
|
||||||
curOrder(0),
|
curOrder(0),
|
||||||
|
|
|
@ -962,7 +962,14 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
||||||
if (nextTick()) {
|
if (nextTick()) {
|
||||||
if (remainingLoops>0) {
|
if (remainingLoops>0) {
|
||||||
remainingLoops--;
|
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 {
|
} else {
|
||||||
|
@ -993,7 +1000,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
||||||
playing=false;
|
playing=false;
|
||||||
extValuePresent=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++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
disCont[i].fillBuf(runtotal[i],size);
|
disCont[i].fillBuf(runtotal[i],size);
|
||||||
|
|
|
@ -2787,6 +2787,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
case GUI_FILE_SAMPLE_SAVE:
|
case GUI_FILE_SAMPLE_SAVE:
|
||||||
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDir);
|
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDir);
|
||||||
break;
|
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;
|
curFileDialog=type;
|
||||||
}
|
}
|
||||||
|
@ -2949,6 +2961,11 @@ int FurnaceGUI::load(String path) {
|
||||||
return 0;
|
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) {
|
void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) {
|
||||||
warnString=what;
|
warnString=what;
|
||||||
warnAction=type;
|
warnAction=type;
|
||||||
|
@ -3118,6 +3135,22 @@ bool FurnaceGUI::loop() {
|
||||||
openFileDialog(GUI_FILE_SAVE);
|
openFileDialog(GUI_FILE_SAVE);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
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...")) {
|
if (ImGui::BeginMenu("add system...")) {
|
||||||
sysAddOption(DIV_SYSTEM_GENESIS);
|
sysAddOption(DIV_SYSTEM_GENESIS);
|
||||||
sysAddOption(DIV_SYSTEM_GENESIS_EXT);
|
sysAddOption(DIV_SYSTEM_GENESIS_EXT);
|
||||||
|
@ -3302,6 +3335,19 @@ bool FurnaceGUI::loop() {
|
||||||
e->song.sample[curSample]->save(copyOfName.c_str());
|
e->song.sample[curSample]->save(copyOfName.c_str());
|
||||||
}
|
}
|
||||||
break;
|
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;
|
curFileDialog=GUI_FILE_OPEN;
|
||||||
}
|
}
|
||||||
|
@ -3325,8 +3371,26 @@ bool FurnaceGUI::loop() {
|
||||||
ImGui::OpenPopup("Error");
|
ImGui::OpenPopup("Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (displayExporting) {
|
||||||
|
displayExporting=false;
|
||||||
|
ImGui::OpenPopup("Rendering...");
|
||||||
|
}
|
||||||
|
|
||||||
if (aboutOpen) drawAbout();
|
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)) {
|
if (ImGui::BeginPopupModal("Error",NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
ImGui::Text("%s",errorString.c_str());
|
ImGui::Text("%s",errorString.c_str());
|
||||||
if (ImGui::Button("OK")) {
|
if (ImGui::Button("OK")) {
|
||||||
|
@ -3544,6 +3608,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
edit(false),
|
edit(false),
|
||||||
modified(false),
|
modified(false),
|
||||||
displayError(false),
|
displayError(false),
|
||||||
|
displayExporting(false),
|
||||||
curFileDialog(GUI_FILE_OPEN),
|
curFileDialog(GUI_FILE_OPEN),
|
||||||
warnAction(GUI_WARN_OPEN),
|
warnAction(GUI_WARN_OPEN),
|
||||||
scrW(1280),
|
scrW(1280),
|
||||||
|
@ -3560,6 +3625,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
oldOrder(0),
|
oldOrder(0),
|
||||||
oldOrder1(0),
|
oldOrder1(0),
|
||||||
editStep(1),
|
editStep(1),
|
||||||
|
exportLoops(0),
|
||||||
editControlsOpen(true),
|
editControlsOpen(true),
|
||||||
ordersOpen(true),
|
ordersOpen(true),
|
||||||
insListOpen(true),
|
insListOpen(true),
|
||||||
|
|
|
@ -80,7 +80,12 @@ enum FurnaceGUIFileDialogs {
|
||||||
GUI_FILE_OPEN,
|
GUI_FILE_OPEN,
|
||||||
GUI_FILE_SAVE,
|
GUI_FILE_SAVE,
|
||||||
GUI_FILE_SAMPLE_OPEN,
|
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 {
|
enum FurnaceGUIWarnings {
|
||||||
|
@ -147,7 +152,7 @@ class FurnaceGUI {
|
||||||
|
|
||||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName;
|
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;
|
FurnaceGUIFileDialogs curFileDialog;
|
||||||
FurnaceGUIWarnings warnAction;
|
FurnaceGUIWarnings warnAction;
|
||||||
|
@ -197,7 +202,7 @@ class FurnaceGUI {
|
||||||
|
|
||||||
char finalLayoutPath[4096];
|
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 editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen;
|
||||||
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
|
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
|
||||||
bool mixerOpen;
|
bool mixerOpen;
|
||||||
|
@ -291,6 +296,7 @@ class FurnaceGUI {
|
||||||
void openFileDialog(FurnaceGUIFileDialogs type);
|
void openFileDialog(FurnaceGUIFileDialogs type);
|
||||||
int save(String path);
|
int save(String path);
|
||||||
int load(String path);
|
int load(String path);
|
||||||
|
void exportAudio(String path, DivAudioExportModes mode);
|
||||||
|
|
||||||
void showWarning(String what, FurnaceGUIWarnings type);
|
void showWarning(String what, FurnaceGUIWarnings type);
|
||||||
void showError(String what);
|
void showError(String what);
|
||||||
|
|
Loading…
Reference in a new issue