mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-27 23:13:01 +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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue