diff --git a/src/engine/engine.h b/src/engine/engine.h index 07a4d99a5..18fdcf1f8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -103,27 +103,43 @@ enum DivMIDIModes { enum DivAudioExportFormats { DIV_EXPORT_FORMAT_S16=0, - DIV_EXPORT_FORMAT_F32 + DIV_EXPORT_FORMAT_F32, + DIV_EXPORT_FORMAT_OPUS, + DIV_EXPORT_FORMAT_FLAC, + DIV_EXPORT_FORMAT_VORBIS, + DIV_EXPORT_FORMAT_MPEG_L3 +}; + +enum DivAudioExportBitrateModes { + DIV_EXPORT_BITRATE_CONSTANT=0, + DIV_EXPORT_BITRATE_VARIABLE, + DIV_EXPORT_BITRATE_AVERAGE, }; struct DivAudioExportOptions { DivAudioExportModes mode; DivAudioExportFormats format; + DivAudioExportBitrateModes bitRateMode; int sampleRate; int chans; int loops; double fadeOut; int orderBegin, orderEnd; bool channelMask[DIV_MAX_CHANS]; + int bitRate; + float vbrQuality; DivAudioExportOptions(): mode(DIV_EXPORT_MODE_ONE), format(DIV_EXPORT_FORMAT_S16), + bitRateMode(DIV_EXPORT_BITRATE_CONSTANT), sampleRate(44100), chans(2), loops(0), fadeOut(0.0), orderBegin(-1), - orderEnd(-1) { + orderEnd(-1), + bitRate(128000), + vbrQuality(6.0f) { for (int i=0; i pendingNotes; @@ -1488,9 +1507,12 @@ class DivEngine { audioEngine(DIV_AUDIO_NULL), exportMode(DIV_EXPORT_MODE_ONE), exportFormat(DIV_EXPORT_FORMAT_S16), + exportBitRateMode(DIV_EXPORT_BITRATE_CONSTANT), exportFadeOut(0.0), isFadingOut(false), exportOutputs(2), + exportBitRate(128000), + exportVBRQuality(6.0f), cmdStreamInt(NULL), midiBaseChan(0), midiPoly(true), diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index 0a659ac34..244510423 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -123,10 +123,25 @@ void DivEngine::runExportThread() { SFWrapper sfWrap; si.samplerate=got.rate; si.channels=exportOutputs; - if (exportFormat==DIV_EXPORT_FORMAT_S16) { - si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - } else { - si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT; + switch (exportFormat) { + case DIV_EXPORT_FORMAT_S16: + si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + break; + case DIV_EXPORT_FORMAT_F32: + si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT; + break; + case DIV_EXPORT_FORMAT_OPUS: + si.format=SF_FORMAT_OGG|SF_FORMAT_OPUS; + break; + case DIV_EXPORT_FORMAT_FLAC: + si.format=SF_FORMAT_FLAC; + break; + case DIV_EXPORT_FORMAT_VORBIS: + si.format=SF_FORMAT_OGG|SF_FORMAT_VORBIS; + break; + case DIV_EXPORT_FORMAT_MPEG_L3: + si.format=SF_FORMAT_MPEG|SF_FORMAT_MPEG_LAYER_III; + break; } sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si); @@ -136,6 +151,59 @@ void DivEngine::runExportThread() { return; } + if (exportFormat!=DIV_EXPORT_FORMAT_S16 && exportFormat!=DIV_EXPORT_FORMAT_F32) { + float mappedLevel=0.0f; + + switch (exportFormat) { + case DIV_EXPORT_FORMAT_OPUS: + mappedLevel=(float)(256000-exportBitRate)/250000.0; + break; + case DIV_EXPORT_FORMAT_FLAC: + mappedLevel=exportVBRQuality*0.125; + break; + case DIV_EXPORT_FORMAT_VORBIS: + mappedLevel=10.0-exportVBRQuality*0.1; + break; + case DIV_EXPORT_FORMAT_MPEG_L3: { + int mappedBitRateMode=SF_BITRATE_MODE_CONSTANT; + switch (exportBitRateMode) { + case DIV_EXPORT_BITRATE_CONSTANT: + mappedBitRateMode=SF_BITRATE_MODE_CONSTANT; + break; + case DIV_EXPORT_BITRATE_VARIABLE: + mappedBitRateMode=SF_BITRATE_MODE_VARIABLE; + break; + case DIV_EXPORT_BITRATE_AVERAGE: + mappedBitRateMode=SF_BITRATE_MODE_AVERAGE; + break; + } + if (exportBitRateMode==DIV_EXPORT_BITRATE_VARIABLE) { + mappedLevel=exportVBRQuality*0.1; + } else { + // a bit complicated + if (got.rate>=32000) { + mappedLevel=(320000.0f-(float)exportBitRate)/288000.0f; + } else if (got.rate>=16000) { + mappedLevel=(160000.0f-(float)exportBitRate)/152000.0f; + } else { + mappedLevel=(64000.0f-(float)exportBitRate)/56000.0f; + } + } + + if (sf_command(sf,SFC_SET_BITRATE_MODE,&mappedBitRateMode,sizeof(mappedBitRateMode))!=SF_TRUE) { + logE("could not set bit rate mode! (%s)",sf_strerror(sf)); + } + break; + } + default: + break; + } + + if (sf_command(sf,SFC_SET_COMPRESSION_LEVEL,&mappedLevel,sizeof(mappedLevel))!=SF_TRUE) { + logE("could not set compression level! (%s)",sf_strerror(sf)); + } + } + float* outBuf[DIV_MAX_OUTPUTS]; float* outBufFinal; for (int i=0; i384000) audioExportOptions.sampleRate=384000; } + if (rateCheck) { + ImGui::SetItemTooltip(_("Opus only supports the following sample rates: 8000, 12000, 16000, 24000 and 48000.")); + } + popWarningColor(); if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) { if (ImGui::InputInt(_("Channels in file"),&audioExportOptions.chans,1,1)) { @@ -64,6 +95,56 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { } } + if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) { + ImGui::Text(_("Bit rate mode:")); + ImGui::Indent(); + if (ImGui::RadioButton(_("Constant"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_CONSTANT)) { + audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_CONSTANT; + } + if (ImGui::RadioButton(_("Variable"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) { + audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_VARIABLE; + } + if (ImGui::RadioButton(_("Average"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_AVERAGE)) { + audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_AVERAGE; + } + ImGui::Unindent(); + } + + int minBitRate=6000; + int maxBitRate=256000; + + if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) { + if (audioExportOptions.sampleRate>=32000) { + minBitRate=32000; + maxBitRate=320000; + } else if (audioExportOptions.sampleRate>=16000) { + minBitRate=8000; + maxBitRate=160000; + } else { + minBitRate=8000; + maxBitRate=64000; + } + } + + if (audioExportOptions.format!=DIV_EXPORT_FORMAT_S16 && audioExportOptions.format!=DIV_EXPORT_FORMAT_F32) { + if (audioExportOptions.format==DIV_EXPORT_FORMAT_FLAC) { + if (ImGui::SliderFloat(_("Compression level"),&audioExportOptions.vbrQuality,0,8)) { + if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0; + if (audioExportOptions.vbrQuality>8) audioExportOptions.vbrQuality=8; + } + } else if (audioExportOptions.format==DIV_EXPORT_FORMAT_VORBIS || (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3 && audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) { + if (ImGui::SliderFloat(_("Quality"),&audioExportOptions.vbrQuality,0,10)) { + if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0; + if (audioExportOptions.vbrQuality>10) audioExportOptions.vbrQuality=10; + } + } else { + if (ImGui::InputInt(_("Bit rate"),&audioExportOptions.bitRate,1000,10000)) { + } + if (audioExportOptions.bitRatemaxBitRate) audioExportOptions.bitRate=maxBitRate; + } + } + if (ImGui::InputInt(_("Loops"),&audioExportOptions.loops,1,2)) { if (audioExportOptions.loops<0) audioExportOptions.loops=0; } @@ -123,8 +204,33 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { ImGui::SameLine(); } - if (isOneOn) { + if (isOneOn && !rateCheck) { if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) { + switch (audioExportOptions.format) { + case DIV_EXPORT_FORMAT_S16: + case DIV_EXPORT_FORMAT_F32: + audioExportFilterName=_("Wave file"); + audioExportFilterExt=".wav"; + break; + case DIV_EXPORT_FORMAT_OPUS: + case DIV_EXPORT_FORMAT_VORBIS: + audioExportFilterName=_("Ogg files"); + audioExportFilterExt=".ogg"; + break; + case DIV_EXPORT_FORMAT_FLAC: + audioExportFilterName=_("FLAC files"); + audioExportFilterExt=".flac"; + break; + case DIV_EXPORT_FORMAT_MPEG_L3: + audioExportFilterName=_("MPEG Layer 3 files"); + audioExportFilterExt=".mp3"; + break; + default: + audioExportFilterName=_("all files"); + audioExportFilterExt="*"; + break; + } + switch (audioExportOptions.mode) { case DIV_EXPORT_MODE_ONE: openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); @@ -139,7 +245,11 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { ImGui::CloseCurrentPopup(); } } else { - ImGui::Text(_("select at least one channel")); + if (rateCheck) { + ImGui::Text(_("check sample rate")); + } else { + ImGui::Text(_("select at least one channel")); + } } } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 17469e32a..0c927f10c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2097,7 +2097,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), - {_("Wave file"), "*.wav"}, + {audioExportFilterName, "*"+audioExportFilterExt}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" @@ -2107,7 +2107,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), - {_("Wave file"), "*.wav"}, + {audioExportFilterName, "*"+audioExportFilterExt}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" @@ -2117,7 +2117,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), - {_("Wave file"), "*.wav"}, + {audioExportFilterName, "*"+audioExportFilterExt}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" @@ -5267,7 +5267,9 @@ bool FurnaceGUI::loop() { curFileDialog==GUI_FILE_EXPORT_AUDIO_ONE || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_SYS || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_CHANNEL) { - checkExtension(".wav"); + if (audioExportFilterExt!="*") { + checkExtension(audioExportFilterExt.c_str()); + } } if (curFileDialog==GUI_FILE_INS_SAVE) { checkExtension(".fui"); @@ -9090,6 +9092,8 @@ FurnaceGUI::FurnaceGUI(): csExportResult(NULL), csExportTarget(false), csExportDone(false), + audioExportFilterName("???"), + audioExportFilterExt("*"), dmfExportVersion(0), curExportType(GUI_EXPORT_NONE), romTarget(DIV_ROM_ABSTRACT), diff --git a/src/gui/gui.h b/src/gui/gui.h index c4d6784fe..fc1a2f280 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2811,6 +2811,7 @@ class FurnaceGUI { // export options DivAudioExportOptions audioExportOptions; + String audioExportFilterName, audioExportFilterExt; int dmfExportVersion; FurnaceGUIExportTypes curExportType; DivCSOptions csExportOptions;