POORLY WORKING Opus export

This commit is contained in:
tildearrow 2025-10-22 20:57:38 -05:00
parent e485c05a80
commit 1bf5e8baaa
5 changed files with 243 additions and 20 deletions

View file

@ -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<DIV_MAX_CHANS; i++) {
channelMask[i]=true;
}
@ -498,9 +514,12 @@ class DivEngine {
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
DivAudioExportFormats exportFormat;
DivAudioExportBitrateModes exportBitRateMode;
double exportFadeOut;
bool isFadingOut;
int exportOutputs;
int exportBitRate;
float exportVBRQuality;
bool exportChannelMask[DIV_MAX_CHANS];
DivConfig conf;
FixedQueue<DivNoteEvent,8192> 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),

View file

@ -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; i<exportOutputs; i++) {
@ -349,10 +417,25 @@ void DivEngine::runExportThread() {
logI("- %s",fname.c_str());
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(fname.c_str(),SFM_WRITE,&si);
@ -490,6 +573,9 @@ bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
exportPath=path;
exportMode=options.mode;
exportFormat=options.format;
exportBitRate=options.bitRate;
exportBitRateMode=options.bitRateMode;
exportVBRQuality=options.vbrQuality;
exportFadeOut=options.fadeOut;
memcpy(exportChannelMask,options.channelMask,DIV_MAX_CHANS*sizeof(bool));
if (exportMode!=DIV_EXPORT_MODE_ONE) {

View file

@ -34,28 +34,59 @@ void FurnaceGUI::drawExportAudio(bool onWindow) {
}
if (ImGui::RadioButton(_("multiple files (one per chip)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_SYS)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_SYS;
}
audioExportOptions.format=DIV_EXPORT_FORMAT_S16;
}
if (ImGui::RadioButton(_("multiple files (one per channel)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN;
}
ImGui::Unindent();
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) {
ImGui::Text(_("Bit depth:"));
ImGui::Text(_("File format:"));
ImGui::Indent();
if (ImGui::RadioButton(_("16-bit integer"),audioExportOptions.format==DIV_EXPORT_FORMAT_S16)) {
if (ImGui::RadioButton(_("Wave (16-bit integer)"),audioExportOptions.format==DIV_EXPORT_FORMAT_S16)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_S16;
}
if (ImGui::RadioButton(_("32-bit float"),audioExportOptions.format==DIV_EXPORT_FORMAT_F32)) {
if (ImGui::RadioButton(_("Wave (32-bit float)"),audioExportOptions.format==DIV_EXPORT_FORMAT_F32)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_F32;
}
#ifdef HAVE_OGG
if (ImGui::RadioButton(_("Opus (lossy compression)"),audioExportOptions.format==DIV_EXPORT_FORMAT_OPUS)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_OPUS;
}
if (ImGui::RadioButton(_("FLAC (Free Lossless Audio Codec)"),audioExportOptions.format==DIV_EXPORT_FORMAT_FLAC)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_FLAC;
}
if (ImGui::RadioButton(_("Vorbis (lossy compression)"),audioExportOptions.format==DIV_EXPORT_FORMAT_VORBIS)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_VORBIS;
}
#endif
#ifdef HAVE_MP3_EXPORT
if (ImGui::RadioButton(_("MP3 (lossy compression)"),audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_MPEG_L3;
}
#endif
ImGui::Unindent();
}
bool rateCheck=(
audioExportOptions.format==DIV_EXPORT_FORMAT_OPUS && (
audioExportOptions.sampleRate!=8000 &&
audioExportOptions.sampleRate!=12000 &&
audioExportOptions.sampleRate!=16000 &&
audioExportOptions.sampleRate!=24000 &&
audioExportOptions.sampleRate!=48000
)
);
pushWarningColor(false,rateCheck);
if (ImGui::InputInt(_("Sample rate"),&audioExportOptions.sampleRate,100,10000)) {
if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000;
if (audioExportOptions.sampleRate>384000) 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.bitRate<minBitRate) audioExportOptions.bitRate=minBitRate;
if (audioExportOptions.bitRate>maxBitRate) 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"));
}
}
}

View file

@ -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),

View file

@ -2811,6 +2811,7 @@ class FurnaceGUI {
// export options
DivAudioExportOptions audioExportOptions;
String audioExportFilterName, audioExportFilterExt;
int dmfExportVersion;
FurnaceGUIExportTypes curExportType;
DivCSOptions csExportOptions;