/* * mod2wave.cpp * ------------ * Purpose: Module to WAV conversion (dialog + conversion code). * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Mptrack.h" #include "Sndfile.h" #include "Dlsbank.h" #include "Mainfrm.h" #include "Mpdlgs.h" #include "mod2wave.h" #include "WAVTools.h" #include "../common/mptString.h" #include "../common/version.h" #include "../soundlib/MixerLoops.h" #include "openmpt/soundbase/Dither.hpp" #include "../common/Dither.h" #include "../soundlib/AudioReadTarget.h" #include "../soundlib/plugins/PlugInterface.h" #include "../common/mptFileIO.h" #include "mpt/audio/span.hpp" #include #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" OPENMPT_NAMESPACE_BEGIN extern const TCHAR *gszChnCfgNames[3]; template static CSoundFile::samplecount_t ReadInterleaved(CSoundFile &sndFile, Tsample *outputBuffer, std::size_t channels, CSoundFile::samplecount_t count, DithersOpenMPT &dithers) { sndFile.ResetMixStat(); MPT_ASSERT(sndFile.m_MixerSettings.gnChannels == channels); AudioTargetBuffer, DithersOpenMPT> target(mpt::audio_span_interleaved(outputBuffer, channels, count), dithers); return sndFile.Read(count, target); } /////////////////////////////////////////////////// // CWaveConvert - setup for converting a wave file BEGIN_MESSAGE_MAP(CWaveConvert, CDialog) ON_COMMAND(IDC_CHECK2, &CWaveConvert::OnCheckTimeLimit) ON_COMMAND(IDC_CHECK4, &CWaveConvert::OnCheckChannelMode) ON_COMMAND(IDC_CHECK6, &CWaveConvert::OnCheckInstrMode) ON_COMMAND(IDC_RADIO1, &CWaveConvert::UpdateDialog) ON_COMMAND(IDC_RADIO2, &CWaveConvert::UpdateDialog) ON_COMMAND(IDC_RADIO3, &CWaveConvert::UpdateDialog) ON_COMMAND(IDC_RADIO4, &CWaveConvert::OnExportModeChanged) ON_COMMAND(IDC_RADIO5, &CWaveConvert::OnExportModeChanged) ON_COMMAND(IDC_PLAYEROPTIONS, &CWaveConvert::OnPlayerOptions) ON_CBN_SELCHANGE(IDC_COMBO5, &CWaveConvert::OnFileTypeChanged) ON_CBN_SELCHANGE(IDC_COMBO1, &CWaveConvert::OnSamplerateChanged) ON_CBN_SELCHANGE(IDC_COMBO4, &CWaveConvert::OnChannelsChanged) ON_CBN_SELCHANGE(IDC_COMBO6, &CWaveConvert::OnDitherChanged) ON_CBN_SELCHANGE(IDC_COMBO2, &CWaveConvert::OnFormatChanged) ON_CBN_SELCHANGE(IDC_COMBO9, &CWaveConvert::OnSampleSlotChanged) END_MESSAGE_MAP() CWaveConvert::CWaveConvert(CWnd *parent, ORDERINDEX minOrder, ORDERINDEX maxOrder, ORDERINDEX numOrders, CSoundFile &sndFile, const std::vector &encFactories) : CDialog(IDD_WAVECONVERT, parent) , m_Settings(theApp.GetSettings(), encFactories) , m_SndFile(sndFile) { ASSERT(!encFactories.empty()); encTraits = m_Settings.GetTraits(); m_bGivePlugsIdleTime = false; if(minOrder != ORDERINDEX_INVALID && maxOrder != ORDERINDEX_INVALID) { // render selection m_Settings.minOrder = minOrder; m_Settings.maxOrder = maxOrder; } m_Settings.repeatCount = 1; m_Settings.minSequence = m_Settings.maxSequence = m_SndFile.Order.GetCurrentSequenceIndex(); m_nNumOrders = numOrders; m_dwSongLimit = 0; } void CWaveConvert::DoDataExchange(CDataExchange *pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_COMBO5, m_CbnFileType); DDX_Control(pDX, IDC_COMBO1, m_CbnSampleRate); DDX_Control(pDX, IDC_COMBO4, m_CbnChannels); DDX_Control(pDX, IDC_COMBO6, m_CbnDither); DDX_Control(pDX, IDC_COMBO2, m_CbnSampleFormat); DDX_Control(pDX, IDC_SPIN3, m_SpinMinOrder); DDX_Control(pDX, IDC_SPIN4, m_SpinMaxOrder); DDX_Control(pDX, IDC_SPIN5, m_SpinLoopCount); DDX_Control(pDX, IDC_SPIN6, m_SpinMinSequence); DDX_Control(pDX, IDC_SPIN7, m_SpinMaxSequence); DDX_Control(pDX, IDC_COMBO9, m_CbnSampleSlot); DDX_Control(pDX, IDC_COMBO3, m_CbnGenre); DDX_Control(pDX, IDC_EDIT10, m_EditGenre); DDX_Control(pDX, IDC_EDIT11, m_EditTitle); DDX_Control(pDX, IDC_EDIT6, m_EditAuthor); DDX_Control(pDX, IDC_EDIT7, m_EditAlbum); DDX_Control(pDX, IDC_EDIT8, m_EditURL); DDX_Control(pDX, IDC_EDIT9, m_EditYear); } BOOL CWaveConvert::OnInitDialog() { CDialog::OnInitDialog(); CheckDlgButton(IDC_CHECK5, BST_UNCHECKED); // Normalize CheckDlgButton(IDC_CHECK3, BST_CHECKED); // Cue points CheckDlgButton(IDC_CHECK4, BST_UNCHECKED); CheckDlgButton(IDC_CHECK6, BST_UNCHECKED); const bool selection = (m_Settings.minOrder != ORDERINDEX_INVALID && m_Settings.maxOrder != ORDERINDEX_INVALID); CheckRadioButton(IDC_RADIO1, IDC_RADIO3, selection ? IDC_RADIO2 : IDC_RADIO1); if(selection) { SetDlgItemInt(IDC_EDIT3, m_Settings.minOrder); SetDlgItemInt(IDC_EDIT4, m_Settings.maxOrder); } m_SpinMinOrder.SetRange32(0, m_nNumOrders); m_SpinMaxOrder.SetRange32(0, m_nNumOrders); const SEQUENCEINDEX numSequences = m_SndFile.Order.GetNumSequences(); const BOOL enableSeq = numSequences > 1 ? TRUE : FALSE; GetDlgItem(IDC_RADIO3)->EnableWindow(enableSeq); m_SpinMinSequence.SetRange32(1, numSequences); m_SpinMaxSequence.SetRange32(1, numSequences); SetDlgItemInt(IDC_EDIT12, m_Settings.minSequence + 1); SetDlgItemInt(IDC_EDIT13, m_Settings.maxSequence + 1); SetDlgItemInt(IDC_EDIT5, m_Settings.repeatCount, FALSE); m_SpinLoopCount.SetRange32(1, int16_max); FillFileTypes(); FillSamplerates(); FillChannels(); FillFormats(); FillDither(); LoadTags(); m_EditYear.SetLimitText(4); m_EditTitle.SetWindowText(mpt::ToCString(m_Settings.Tags.title)); m_EditAuthor.SetWindowText(mpt::ToCString(m_Settings.Tags.artist)); m_EditURL.SetWindowText(mpt::ToCString(m_Settings.Tags.url)); m_EditAlbum.SetWindowText(mpt::ToCString(m_Settings.Tags.album)); m_EditYear.SetWindowText(mpt::ToCString(m_Settings.Tags.year)); m_EditGenre.SetWindowText(mpt::ToCString(m_Settings.Tags.genre)); FillTags(); // Plugin quirk options are only available if there are any plugins loaded. GetDlgItem(IDC_GIVEPLUGSIDLETIME)->EnableWindow(FALSE); GetDlgItem(IDC_RENDERSILENCE)->EnableWindow(FALSE); #ifndef NO_PLUGINS for(const auto &plug : m_SndFile.m_MixPlugins) { if(plug.pMixPlugin != nullptr) { GetDlgItem(IDC_GIVEPLUGSIDLETIME)->EnableWindow(TRUE); GetDlgItem(IDC_RENDERSILENCE)->EnableWindow(TRUE); break; } } #endif // NO_PLUGINS // Fill list of sample slots to render into if(m_SndFile.GetNextFreeSample() != SAMPLEINDEX_INVALID) { m_CbnSampleSlot.SetItemData(m_CbnSampleSlot.AddString(_T("")), 0); } CString s; for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++) { s.Format(_T("%02u: %s%s"), smp, m_SndFile.GetSample(smp).HasSampleData() ? _T("*") : _T(""), mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.GetSampleName(smp)).GetString()); m_CbnSampleSlot.SetItemData(m_CbnSampleSlot.AddString(s), smp); } if(m_Settings.sampleSlot > m_SndFile.GetNumSamples()) m_Settings.sampleSlot = 0; m_CbnSampleSlot.SetCurSel(m_Settings.sampleSlot); CheckRadioButton(IDC_RADIO4, IDC_RADIO5, m_Settings.outputToSample ? IDC_RADIO5 : IDC_RADIO4); UpdateDialog(); return TRUE; } void CWaveConvert::LoadTags() { m_Settings.Tags.title = mpt::ToUnicode(mpt::Charset::Locale, m_SndFile.GetTitle()); m_Settings.Tags.comments = mpt::ToUnicode(mpt::Charset::Locale, m_SndFile.m_songMessage.GetFormatted(SongMessage::leLF)); m_Settings.Tags.artist = m_SndFile.m_songArtist; m_Settings.Tags.album = m_Settings.storedTags.album; m_Settings.Tags.trackno = m_Settings.storedTags.trackno; m_Settings.Tags.year = m_Settings.storedTags.year; m_Settings.Tags.url = m_Settings.storedTags.url; m_Settings.Tags.genre = m_Settings.storedTags.genre; } void CWaveConvert::SaveTags() { m_Settings.storedTags.artist = m_Settings.Tags.artist; m_Settings.storedTags.album = m_Settings.Tags.album; m_Settings.storedTags.trackno = m_Settings.Tags.trackno; m_Settings.storedTags.year = m_Settings.Tags.year; m_Settings.storedTags.url = m_Settings.Tags.url; m_Settings.storedTags.genre = m_Settings.Tags.genre; } void CWaveConvert::FillTags() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); DWORD_PTR dwFormat = m_CbnSampleFormat.GetItemData(m_CbnSampleFormat.GetCurSel()); Encoder::Mode mode = (Encoder::Mode)((dwFormat >> 24) & 0xff); CheckDlgButton(IDC_CHECK3, encTraits->canCues?encSettings.Cues?TRUE:FALSE:FALSE); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_CHECK3), encTraits->canCues?TRUE:FALSE); const BOOL canTags = encTraits->canTags ? TRUE : FALSE; CheckDlgButton(IDC_CHECK7, encSettings.Tags ? canTags : FALSE); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_CHECK7), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_COMBO3), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT11), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT6), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT7), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT8), canTags); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT9), canTags); m_CbnGenre.EnableWindow(canTags?TRUE:FALSE); m_EditGenre.EnableWindow(canTags?TRUE:FALSE); if((encTraits->modesWithFixedGenres & mode) && !encTraits->genres.empty()) { m_EditGenre.ShowWindow(SW_HIDE); m_CbnGenre.ShowWindow(SW_SHOW); m_EditGenre.Clear(); m_CbnGenre.ResetContent(); m_CbnGenre.AddString(_T("")); for(const auto &genre : encTraits->genres) { m_CbnGenre.AddString(mpt::ToCString(genre)); } } else { m_CbnGenre.ShowWindow(SW_HIDE); m_EditGenre.ShowWindow(SW_SHOW); m_CbnGenre.ResetContent(); m_EditGenre.Clear(); } } void CWaveConvert::FillFileTypes() { m_CbnFileType.ResetContent(); int sel = 0; for(std::size_t i = 0; i < m_Settings.EncoderFactories.size(); ++i) { int ndx = m_CbnFileType.AddString(MPT_CFORMAT("{} ({})")(mpt::ToCString(m_Settings.EncoderFactories[i]->GetTraits().fileShortDescription), mpt::ToCString(m_Settings.EncoderFactories[i]->GetTraits().fileDescription))); m_CbnFileType.SetItemData(ndx, i); if(m_Settings.EncoderIndex == i) { sel = ndx; } } m_CbnFileType.SetCurSel(sel); } void CWaveConvert::FillSamplerates() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); m_CbnSampleRate.CComboBox::ResetContent(); int sel = -1; if(TrackerSettings::Instance().ExportDefaultToSoundcardSamplerate) { for(auto samplerate : encTraits->samplerates) { if(samplerate == TrackerSettings::Instance().MixerSamplerate) { encSettings.Samplerate = samplerate; } } } for(auto samplerate : encTraits->samplerates) { int ndx = m_CbnSampleRate.AddString(MPT_CFORMAT("{} Hz")(samplerate)); m_CbnSampleRate.SetItemData(ndx, samplerate); if(samplerate == encSettings.Samplerate) { sel = ndx; } } if(sel == -1) { sel = 0; } m_CbnSampleRate.SetCurSel(sel); } void CWaveConvert::FillChannels() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); m_CbnChannels.CComboBox::ResetContent(); int sel = 0; for(int channels = 4; channels >= 1; channels /= 2) { if(channels > encTraits->maxChannels) { continue; } if(IsDlgButtonChecked(IDC_RADIO5) != BST_UNCHECKED) { if(channels > 2) { // sample export only supports 2 channels max continue; } } int ndx = m_CbnChannels.AddString(gszChnCfgNames[(channels+2)/2-1]); m_CbnChannels.SetItemData(ndx, channels); if(channels == encSettings.Channels) { sel = ndx; } } m_CbnChannels.SetCurSel(sel); } void CWaveConvert::FillFormats() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); m_CbnSampleFormat.CComboBox::ResetContent(); int sel = -1; int samplerate = static_cast(m_CbnSampleRate.GetItemData(m_CbnSampleRate.GetCurSel())); int channels = static_cast(m_CbnChannels.GetItemData(m_CbnChannels.GetCurSel())); if(encTraits->modes & Encoder::ModeQuality) { for(int quality = 100; quality >= 0; quality -= 10) { int ndx = m_CbnSampleFormat.AddString(mpt::ToCString(m_Settings.GetEncoderFactory()->DescribeQuality(quality * 0.01f))); m_CbnSampleFormat.SetItemData(ndx, (Encoder::ModeQuality<<24) | (quality<<0)); if(encSettings.Mode == Encoder::ModeQuality && mpt::saturate_round(encSettings.Quality*100.0f) == quality) { sel = ndx; } } } if(encTraits->modes & Encoder::ModeVBR) { for(int bitrate = static_cast(encTraits->bitrates.size()-1); bitrate >= 0; --bitrate) { if(!m_Settings.GetEncoderFactory()->IsBitrateSupported(samplerate, channels, encTraits->bitrates[bitrate])) { continue; } int ndx = m_CbnSampleFormat.AddString(mpt::ToCString(m_Settings.GetEncoderFactory()->DescribeBitrateVBR(encTraits->bitrates[bitrate]))); m_CbnSampleFormat.SetItemData(ndx, (Encoder::ModeVBR<<24) | (encTraits->bitrates[bitrate]<<0)); if(encSettings.Mode == Encoder::ModeVBR && static_cast(encSettings.Bitrate) == encTraits->bitrates[bitrate]) { sel = ndx; } } } if(encTraits->modes & Encoder::ModeABR) { for(int bitrate = static_cast(encTraits->bitrates.size()-1); bitrate >= 0; --bitrate) { if(!m_Settings.GetEncoderFactory()->IsBitrateSupported(samplerate, channels, encTraits->bitrates[bitrate])) { continue; } int ndx = m_CbnSampleFormat.AddString(mpt::ToCString(m_Settings.GetEncoderFactory()->DescribeBitrateABR(encTraits->bitrates[bitrate]))); m_CbnSampleFormat.SetItemData(ndx, (Encoder::ModeABR<<24) | (encTraits->bitrates[bitrate]<<0)); if(encSettings.Mode == Encoder::ModeABR && static_cast(encSettings.Bitrate) == encTraits->bitrates[bitrate]) { sel = ndx; } } } if(encTraits->modes & Encoder::ModeCBR) { for(int bitrate = static_cast(encTraits->bitrates.size()-1); bitrate >= 0; --bitrate) { if(!m_Settings.GetEncoderFactory()->IsBitrateSupported(samplerate, channels, encTraits->bitrates[bitrate])) { continue; } int ndx = m_CbnSampleFormat.AddString(mpt::ToCString(m_Settings.GetEncoderFactory()->DescribeBitrateCBR(encTraits->bitrates[bitrate]))); m_CbnSampleFormat.SetItemData(ndx, (Encoder::ModeCBR<<24) | (encTraits->bitrates[bitrate]<<0)); if(encSettings.Mode == Encoder::ModeCBR && static_cast(encSettings.Bitrate) == encTraits->bitrates[bitrate]) { sel = ndx; } } } if(encTraits->modes & Encoder::ModeLossless) { bool allBig = true; bool allLittle = true; for(const auto &format : encTraits->formats) { if(format.endian != mpt::endian::little) { allLittle = false; } if(format.endian != mpt::endian::big) { allBig = false; } } bool showEndian = !(allBig || allLittle); for(std::size_t i = 0; i < encTraits->formats.size(); ++i) { const Encoder::Format &format = encTraits->formats[i]; mpt::ustring description; switch(format.encoding) { case Encoder::Format::Encoding::Float: description = MPT_UFORMAT("{} Bit Floating Point")(format.bits); break; case Encoder::Format::Encoding::Integer: description = MPT_UFORMAT("{} Bit")(format.bits); break; case Encoder::Format::Encoding::Alaw: description = U_("A-law"); break; case Encoder::Format::Encoding::ulaw: description = MPT_UTF8("\xce\xbc-law"); break; case Encoder::Format::Encoding::Unsigned: description = MPT_UFORMAT("{} Bit (unsigned)")(format.bits); break; } if(showEndian && format.bits != 8 && format.encoding != Encoder::Format::Encoding::Alaw && format.encoding != Encoder::Format::Encoding::ulaw) { switch(format.endian) { case mpt::endian::big: description += U_(" Big-Endian"); break; case mpt::endian::little: description += U_(" Little-Endian"); break; } } int ndx = m_CbnSampleFormat.AddString(mpt::ToCString(description)); m_CbnSampleFormat.SetItemData(ndx, format.AsInt()); if(encSettings.Mode & Encoder::ModeLossless && format == encSettings.Format2) { sel = ndx; } } } if(sel == -1) { sel = 0; } m_CbnSampleFormat.SetCurSel(sel); } void CWaveConvert::FillDither() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); m_CbnDither.CComboBox::ResetContent(); int format = m_CbnSampleFormat.GetItemData(m_CbnSampleFormat.GetCurSel()) & 0xffffff; if((encTraits->modes & Encoder::ModeLossless) && Encoder::Format::FromInt(format).GetSampleFormat() != SampleFormat::Invalid && !Encoder::Format::FromInt(format).GetSampleFormat().IsFloat()) { m_CbnDither.EnableWindow(TRUE); for(std::size_t dither = 0; dither < DithersOpenMPT::GetNumDithers(); ++dither) { int ndx = m_CbnDither.AddString(mpt::ToCString(DithersOpenMPT::GetModeName(dither) + U_(" dither"))); m_CbnDither.SetItemData(ndx, dither); } } else { m_CbnDither.EnableWindow(FALSE); for(std::size_t dither = 0; dither < DithersOpenMPT::GetNumDithers(); ++dither) { int ndx = m_CbnDither.AddString(mpt::ToCString(DithersOpenMPT::GetModeName(DithersOpenMPT::GetNoDither()) + U_(" dither"))); m_CbnDither.SetItemData(ndx, dither); } } m_CbnDither.SetCurSel(encSettings.Dither); } void CWaveConvert::OnFileTypeChanged() { SaveEncoderSettings(); DWORD_PTR dwFileType = m_CbnFileType.GetItemData(m_CbnFileType.GetCurSel()); m_Settings.SelectEncoder(dwFileType); encTraits = m_Settings.GetTraits(); FillSamplerates(); FillChannels(); FillFormats(); FillDither(); FillTags(); } void CWaveConvert::OnSamplerateChanged() { SaveEncoderSettings(); FillFormats(); FillDither(); } void CWaveConvert::OnChannelsChanged() { SaveEncoderSettings(); FillFormats(); FillDither(); } void CWaveConvert::OnDitherChanged() { SaveEncoderSettings(); } void CWaveConvert::OnFormatChanged() { SaveEncoderSettings(); FillDither(); FillTags(); } void CWaveConvert::UpdateDialog() { CheckDlgButton(IDC_CHECK2, (m_dwSongLimit) ? BST_CHECKED : 0); GetDlgItem(IDC_EDIT2)->EnableWindow(m_dwSongLimit ? TRUE : FALSE); // Repeat / selection play int sel = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3); GetDlgItem(IDC_EDIT3)->EnableWindow(sel == IDC_RADIO2); GetDlgItem(IDC_EDIT4)->EnableWindow(sel == IDC_RADIO2); m_SpinMinOrder.EnableWindow(sel == IDC_RADIO2); m_SpinMaxOrder.EnableWindow(sel == IDC_RADIO2); GetDlgItem(IDC_EDIT5)->EnableWindow(sel == IDC_RADIO1); m_SpinLoopCount.EnableWindow(sel == IDC_RADIO1); const SEQUENCEINDEX numSequences = m_SndFile.Order.GetNumSequences(); const BOOL enableSeq = (numSequences > 1 && sel == IDC_RADIO3) ? TRUE : FALSE; GetDlgItem(IDC_EDIT12)->EnableWindow(enableSeq); GetDlgItem(IDC_EDIT13)->EnableWindow(enableSeq); m_SpinMinSequence.EnableWindow(enableSeq); m_SpinMaxSequence.EnableWindow(enableSeq); // No free slots => Cannot do instrument- or channel-based export to sample BOOL canDoMultiExport = (IsDlgButtonChecked(IDC_RADIO4) != BST_UNCHECKED /* normal export */ || m_CbnSampleSlot.GetItemData(0) == 0 /* "free slot" is in list */) ? TRUE : FALSE; GetDlgItem(IDC_CHECK4)->EnableWindow(canDoMultiExport); GetDlgItem(IDC_CHECK6)->EnableWindow(canDoMultiExport); } void CWaveConvert::OnExportModeChanged() { SaveEncoderSettings(); bool sampleExport = (IsDlgButtonChecked(IDC_RADIO5) != BST_UNCHECKED); m_CbnFileType.EnableWindow(sampleExport ? FALSE : TRUE); m_CbnSampleSlot.EnableWindow(sampleExport && !IsDlgButtonChecked(IDC_CHECK4) && !IsDlgButtonChecked(IDC_CHECK6)); if(sampleExport) { // Render to sample: Always use WAV if(m_CbnFileType.GetCurSel() != 0) { m_CbnFileType.SetCurSel(0); OnFileTypeChanged(); } } FillChannels(); FillFormats(); FillDither(); FillTags(); } void CWaveConvert::OnSampleSlotChanged() { CheckRadioButton(IDC_RADIO4, IDC_RADIO5, IDC_RADIO5); // When choosing a specific sample slot, we cannot use per-channel or per-instrument export int sel = m_CbnSampleSlot.GetCurSel(); if(sel >= 0 && m_CbnSampleSlot.GetItemData(sel) > 0) { CheckDlgButton(IDC_CHECK4, BST_UNCHECKED); CheckDlgButton(IDC_CHECK6, BST_UNCHECKED); } UpdateDialog(); } void CWaveConvert::OnPlayerOptions() { CPropertySheet dlg(_T("Mixer Settings"), this); COptionsMixer mixerpage; dlg.AddPage(&mixerpage); #if !defined(NO_REVERB) || !defined(NO_DSP) || !defined(NO_EQ) || !defined(NO_AGC) COptionsPlayer dsppage; dlg.AddPage(&dsppage); #endif dlg.DoModal(); } void CWaveConvert::OnCheckTimeLimit() { if (IsDlgButtonChecked(IDC_CHECK2)) { m_dwSongLimit = GetDlgItemInt(IDC_EDIT2, NULL, FALSE); if (!m_dwSongLimit) { m_dwSongLimit = 600; SetDlgItemText(IDC_EDIT2, _T("600")); } } else m_dwSongLimit = 0; UpdateDialog(); } // Channel render is mutually exclusive with instrument render void CWaveConvert::OnCheckChannelMode() { if(IsDlgButtonChecked(IDC_CHECK4) != BST_UNCHECKED) { CheckDlgButton(IDC_CHECK6, BST_UNCHECKED); m_CbnSampleSlot.SetCurSel(0); } UpdateDialog(); } // Channel render is mutually exclusive with instrument render void CWaveConvert::OnCheckInstrMode() { if(IsDlgButtonChecked(IDC_CHECK6) != BST_UNCHECKED) { CheckDlgButton(IDC_CHECK4, BST_UNCHECKED); m_CbnSampleSlot.SetCurSel(0); } UpdateDialog(); } void CWaveConvert::OnOK() { if (m_dwSongLimit) m_dwSongLimit = GetDlgItemInt(IDC_EDIT2, NULL, FALSE); const bool selection = IsDlgButtonChecked(IDC_RADIO2) != BST_UNCHECKED; if(selection) { // Play selection m_Settings.minOrder = static_cast(GetDlgItemInt(IDC_EDIT3, NULL, FALSE)); m_Settings.maxOrder = static_cast(GetDlgItemInt(IDC_EDIT4, NULL, FALSE)); if(m_Settings.minOrder > m_Settings.maxOrder) std::swap(m_Settings.minOrder, m_Settings.maxOrder); } else { m_Settings.minOrder = m_Settings.maxOrder = ORDERINDEX_INVALID; } if(IsDlgButtonChecked(IDC_RADIO3)) { const UINT maxSequence = m_SndFile.Order.GetNumSequences(); m_Settings.minSequence = static_cast(std::clamp(GetDlgItemInt(IDC_EDIT12, NULL, FALSE), 1u, maxSequence) - 1u); m_Settings.maxSequence = static_cast(std::clamp(GetDlgItemInt(IDC_EDIT13, NULL, FALSE), 1u, maxSequence) - 1u); if(m_Settings.minSequence > m_Settings.maxSequence) std::swap(m_Settings.minSequence, m_Settings.maxSequence); } else { m_Settings.minSequence = m_Settings.maxSequence = m_SndFile.Order.GetCurrentSequenceIndex(); } m_Settings.repeatCount = static_cast(GetDlgItemInt(IDC_EDIT5, NULL, FALSE)); m_Settings.normalize = IsDlgButtonChecked(IDC_CHECK5) != BST_UNCHECKED; m_Settings.silencePlugBuffers = IsDlgButtonChecked(IDC_RENDERSILENCE) != BST_UNCHECKED; m_Settings.outputToSample = IsDlgButtonChecked(IDC_RADIO5) != BST_UNCHECKED; m_bGivePlugsIdleTime = IsDlgButtonChecked(IDC_GIVEPLUGSIDLETIME) != BST_UNCHECKED; if (m_bGivePlugsIdleTime) { static bool showWarning = true; if(showWarning && Reporting::Confirm("You only need slow render if you are experiencing dropped notes with a Kontakt based sampler with Direct-From-Disk enabled, or buggy plugins that use the system time for parameter automation.\nIt will make rendering *very* slow.\n\nAre you sure you want to enable slow render?", "Really enable slow render?") == cnfNo) { m_bGivePlugsIdleTime = false; } else { showWarning = false; } } m_bChannelMode = IsDlgButtonChecked(IDC_CHECK4) != BST_UNCHECKED; m_bInstrumentMode= IsDlgButtonChecked(IDC_CHECK6) != BST_UNCHECKED; m_Settings.sampleSlot = static_cast(m_CbnSampleSlot.GetItemData(m_CbnSampleSlot.GetCurSel())); SaveEncoderSettings(); EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); m_Settings.Tags = FileTags(); m_Settings.Tags.SetEncoder(); if(encSettings.Tags) { CString tmp; m_EditTitle.GetWindowText(tmp); m_Settings.Tags.title = mpt::ToUnicode(tmp); m_EditAuthor.GetWindowText(tmp); m_Settings.Tags.artist = mpt::ToUnicode(tmp); m_EditAlbum.GetWindowText(tmp); m_Settings.Tags.album = mpt::ToUnicode(tmp); m_EditURL.GetWindowText(tmp); m_Settings.Tags.url = mpt::ToUnicode(tmp); if((encTraits->modesWithFixedGenres & encSettings.Mode) && !encTraits->genres.empty()) { m_CbnGenre.GetWindowText(tmp); m_Settings.Tags.genre = mpt::ToUnicode(tmp); } else { m_EditGenre.GetWindowText(tmp); m_Settings.Tags.genre = mpt::ToUnicode(tmp); } m_EditYear.GetWindowText(tmp); m_Settings.Tags.year = mpt::ToUnicode(tmp); if(m_Settings.Tags.year == U_("0")) { m_Settings.Tags.year = mpt::ustring(); } if(!m_SndFile.m_songMessage.empty()) { m_Settings.Tags.comments = mpt::ToUnicode(mpt::Charset::Locale, m_SndFile.m_songMessage.GetFormatted(SongMessage::leLF)); } m_Settings.Tags.bpm = mpt::ufmt::val(m_SndFile.GetCurrentBPM()); SaveTags(); } CDialog::OnOK(); } void CWaveConvert::SaveEncoderSettings() { EncoderSettingsConf &encSettings = m_Settings.GetEncoderSettings(); encSettings.Samplerate = static_cast(m_CbnSampleRate.GetItemData(m_CbnSampleRate.GetCurSel())); encSettings.Channels = static_cast(m_CbnChannels.GetItemData(m_CbnChannels.GetCurSel())); DWORD_PTR dwFormat = m_CbnSampleFormat.GetItemData(m_CbnSampleFormat.GetCurSel()); if(encTraits->modes & Encoder::ModeLossless) { int format = (int)((dwFormat >> 0) & 0xffffff); encSettings.Dither = static_cast(m_CbnDither.GetItemData(m_CbnDither.GetCurSel())); encSettings.Format2 = Encoder::Format::FromInt(format); encSettings.Mode = Encoder::ModeLossless; encSettings.Bitrate = 0; encSettings.Quality = encTraits->defaultQuality; } else { encSettings.Dither = static_cast(m_CbnDither.GetItemData(m_CbnDither.GetCurSel())); Encoder::Mode mode = (Encoder::Mode)((dwFormat >> 24) & 0xff); int quality = (int)((dwFormat >> 0) & 0xff); int bitrate = (int)((dwFormat >> 0) & 0xffff); encSettings.Mode = mode; encSettings.Bitrate = bitrate; encSettings.Quality = static_cast(quality) * 0.01f; encSettings.Format2 = { Encoder::Format::Encoding::Float, 32, mpt::get_endian() }; } encSettings.Cues = IsDlgButtonChecked(IDC_CHECK3) ? true : false; encSettings.Tags = IsDlgButtonChecked(IDC_CHECK7) ? true : false; } std::size_t CWaveConvertSettings::FindEncoder(const mpt::ustring &name) const { for(std::size_t i = 0; i < EncoderFactories.size(); ++i) { if(EncoderFactories[i]->GetTraits().encoderSettingsName == name) { return i; } } return 0; } void CWaveConvertSettings::SelectEncoder(std::size_t index) { MPT_ASSERT(!EncoderFactories.empty()); MPT_ASSERT(index < EncoderFactories.size()); EncoderIndex = index; EncoderName = EncoderFactories[EncoderIndex]->GetTraits().encoderSettingsName; } EncoderFactoryBase *CWaveConvertSettings::GetEncoderFactory() const { MPT_ASSERT(!EncoderFactories.empty()); return EncoderFactories[EncoderIndex]; } const Encoder::Traits *CWaveConvertSettings::GetTraits() const { MPT_ASSERT(!EncoderFactories.empty()); return &EncoderFactories[EncoderIndex]->GetTraits(); } EncoderSettingsConf &CWaveConvertSettings::GetEncoderSettings() const { MPT_ASSERT(!EncoderSettings.empty()); return *(EncoderSettings[EncoderIndex]); } Encoder::Settings CWaveConvertSettings::GetEncoderSettingsWithDetails() const { MPT_ASSERT(!EncoderSettings.empty()); Encoder::Settings settings = static_cast(*(EncoderSettings[EncoderIndex])); settings.Details = static_cast(TrackerSettings::Instance().ExportStreamEncoderSettings); return settings; } CWaveConvertSettings::CWaveConvertSettings(SettingsContainer &conf, const std::vector &encFactories) : EncoderFactories(encFactories) , EncoderName(conf, U_("Export"), U_("Encoder"), U_("")) , EncoderIndex(FindEncoder(EncoderName)) , storedTags(conf) , repeatCount(0) , minOrder(ORDERINDEX_INVALID), maxOrder(ORDERINDEX_INVALID) , sampleSlot(0) , normalize(false) , silencePlugBuffers(false) , outputToSample(false) { Tags.SetEncoder(); for(const auto & factory : EncoderFactories) { const Encoder::Traits &encTraits = factory->GetTraits(); EncoderSettings.push_back( std::make_unique( conf, encTraits.encoderSettingsName, encTraits.canCues, encTraits.canTags, encTraits.defaultSamplerate, encTraits.defaultChannels, encTraits.defaultMode, encTraits.defaultBitrate, encTraits.defaultQuality, encTraits.defaultFormat, encTraits.defaultDitherType ) ); } SelectEncoder(EncoderIndex); } ///////////////////////////////////////////////////////////////////////////////////////// // CDoWaveConvert: save a mod as a wave file void CDoWaveConvert::Run() { UINT ok = IDOK; uint64 ullSamples = 0; std::vector normalizeBufferData; float *normalizeBuffer = nullptr; float normalizePeak = 0.0f; const mpt::PathString normalizeFileName = mpt::CreateTempFileName(P_("OpenMPT")); std::optional normalizeFile; if(m_Settings.normalize) { normalizeBufferData.resize(MIXBUFFERSIZE * 4); normalizeBuffer = normalizeBufferData.data(); // Ensure this temporary file is marked as temporary in the file system, to increase the chance it will never be written to disk if(HANDLE hFile = ::CreateFile(normalizeFileName.AsNative().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); hFile != INVALID_HANDLE_VALUE) { ::CloseHandle(hFile); } normalizeFile.emplace(normalizeFileName, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); } const Encoder::Settings encSettings = m_Settings.GetEncoderSettingsWithDetails(); const uint32 samplerate = encSettings.Samplerate; const uint16 channels = encSettings.Channels; ASSERT(m_Settings.GetEncoderFactory() && m_Settings.GetEncoderFactory()->IsAvailable()); // Silence mix buffer of plugins, for plugins that don't clear their reverb buffers and similar stuff when they are reset #ifndef NO_PLUGINS if(m_Settings.silencePlugBuffers) { SetText(_T("Clearing plugin buffers")); for(auto &plug : m_SndFile.m_MixPlugins) { if(plug.pMixPlugin != nullptr) { // Render up to 20 seconds per plugin for(int j = 0; j < 20; j++) { const float maxVal = plug.pMixPlugin->RenderSilence(samplerate); if(maxVal <= FLT_EPSILON) { break; } } ProcessMessages(); if(m_abort) { m_abort = false; break; } } } } #endif // NO_PLUGINS MixerSettings oldmixersettings = m_SndFile.m_MixerSettings; MixerSettings mixersettings = TrackerSettings::Instance().GetMixerSettings(); mixersettings.m_nMaxMixChannels = MAX_CHANNELS; // always use max mixing channels when rendering mixersettings.gdwMixingFreq = samplerate; mixersettings.gnChannels = channels; m_SndFile.m_SongFlags.reset(SONG_PAUSED | SONG_STEP); if(m_Settings.normalize) { #ifndef NO_AGC mixersettings.DSPMask &= ~SNDDSP_AGC; #endif } DithersOpenMPT dithers(theApp.PRNG(), encSettings.Dither, encSettings.Channels); m_SndFile.ResetChannels(); m_SndFile.SetMixerSettings(mixersettings); m_SndFile.SetResamplerSettings(TrackerSettings::Instance().GetResamplerSettings()); m_SndFile.InitPlayer(true); // Tags must be known at the stream start, // so that the encoder class could write them before audio data if mandated by the format, // otherwise they should just be cached by the encoder. std::unique_ptr fileEnc = m_Settings.GetEncoderFactory()->ConstructStreamEncoder(fileStream, encSettings, m_Settings.Tags); std::variant< std::unique_ptr>, std::unique_ptr>, std::unique_ptr>, std::unique_ptr>, std::unique_ptr>, std::unique_ptr>, std::unique_ptr>> bufferData; union AnyBufferSamplePointer { double *float64; float *float32; int32 *int32; int24 *int24; int16 *int16; int8 *int8; uint8 *uint8; void *any; }; AnyBufferSamplePointer buffer; buffer.any = nullptr; switch(fileEnc->GetSampleFormat()) { case SampleFormat::Float64: bufferData = std::make_unique>(); buffer.float64 = std::get<0>(bufferData)->data(); break; case SampleFormat::Float32: bufferData = std::make_unique>(); buffer.float32 = std::get<1>(bufferData)->data(); break; case SampleFormat::Int32: bufferData = std::make_unique>(); buffer.int32 = std::get<2>(bufferData)->data(); break; case SampleFormat::Int24: bufferData = std::make_unique>(); buffer.int24 = std::get<3>(bufferData)->data(); break; case SampleFormat::Int16: bufferData = std::make_unique>(); buffer.int16 = std::get<4>(bufferData)->data(); break; case SampleFormat::Int8: bufferData = std::make_unique>(); buffer.int8 = std::get<5>(bufferData)->data(); break; case SampleFormat::Unsigned8: bufferData = std::make_unique>(); buffer.uint8 = std::get<6>(bufferData)->data(); break; } uint64 ullMaxSamples = uint64_max / (channels * ((fileEnc->GetSampleFormat().GetBitsPerSample()+7) / 8)); if (m_dwSongLimit) { LimitMax(ullMaxSamples, m_dwSongLimit * samplerate); } // Calculate maximum samples uint64 max = m_dwSongLimit ? ullMaxSamples : uint64_max; // Reset song position tracking m_SndFile.ResetPlayPos(); m_SndFile.m_SongFlags.reset(SONG_PATTERNLOOP); ORDERINDEX startOrder = 0; GetLengthTarget target; if(m_Settings.minOrder != ORDERINDEX_INVALID && m_Settings.maxOrder != ORDERINDEX_INVALID) { m_SndFile.SetRepeatCount(0); startOrder = m_Settings.minOrder; ORDERINDEX endOrder = m_Settings.maxOrder; while(!m_SndFile.Order().IsValidPat(endOrder) && endOrder > startOrder) { endOrder--; } if(m_SndFile.Order().IsValidPat(endOrder)) { target = GetLengthTarget(endOrder, m_SndFile.Patterns[m_SndFile.Order()[endOrder]].GetNumRows() - 1); } target.StartPos(m_SndFile.Order.GetCurrentSequenceIndex(), startOrder, 0); m_SndFile.m_nMaxOrderPosition = endOrder + 1; } else { m_SndFile.SetRepeatCount(std::max(0, m_Settings.repeatCount - 1)); } uint64 l = mpt::saturate_round(m_SndFile.GetLength(eNoAdjust, target).front().duration * samplerate * (1 + m_SndFile.GetRepeatCount())); m_SndFile.SetCurrentOrder(startOrder); m_SndFile.GetLength(eAdjust, GetLengthTarget(startOrder, 0)); // adjust playback variables / visited rows vector m_SndFile.m_PlayState.m_nCurrentOrder = startOrder; if (l < max) max = l; SetRange(0, max); EnableTaskbarProgress(); // No pattern cue points yet std::vector patternCuePoints; patternCuePoints.reserve(m_SndFile.Order().size()); m_SndFile.m_PatternCuePoints = &patternCuePoints; // Process the conversion // For calculating the remaining time auto dwStartTime = timeGetTime(), prevTime = dwStartTime; uint32 timeRemaining = 0; uint64 bytesWritten = 0; auto mainFrame = CMainFrame::GetMainFrame(); mainFrame->PauseMod(); m_SndFile.m_SongFlags.reset(SONG_STEP | SONG_PATTERNLOOP); mainFrame->InitRenderer(&m_SndFile); for (UINT n = 0; ; n++) { UINT lRead = 0; if(m_Settings.normalize) { lRead = ReadInterleaved(m_SndFile, normalizeBuffer, channels, MIXBUFFERSIZE, dithers); } else { switch(fileEnc->GetSampleFormat()) { case SampleFormat::Float64: lRead = ReadInterleaved(m_SndFile, buffer.float64, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Float32: lRead = ReadInterleaved(m_SndFile, buffer.float32, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Int32: lRead = ReadInterleaved(m_SndFile, buffer.int32, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Int24: lRead = ReadInterleaved(m_SndFile, buffer.int24, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Int16: lRead = ReadInterleaved(m_SndFile, buffer.int16, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Int8: lRead = ReadInterleaved(m_SndFile, buffer.int8, channels, MIXBUFFERSIZE, dithers); break; case SampleFormat::Unsigned8: lRead = ReadInterleaved(m_SndFile, buffer.uint8, channels, MIXBUFFERSIZE, dithers); break; } } // Process cue points (add base offset), if there are any to process. for(auto iter = patternCuePoints.rbegin(); iter != patternCuePoints.rend(); ++iter) { if(iter->processed) { // From this point, all cues have already been processed. break; } iter->offset += ullSamples; iter->processed = true; } if (m_bGivePlugsIdleTime) { Sleep(20); } if (!lRead) break; ullSamples += lRead; if(m_Settings.normalize) { std::size_t countSamples = lRead * m_SndFile.m_MixerSettings.gnChannels; const float *src = normalizeBuffer; while(countSamples--) { const float val = *src; if(val > normalizePeak) normalizePeak = val; else if(0.0f - val >= normalizePeak) normalizePeak = 0.0f - val; src++; } if(!mpt::IO::WriteRaw(*normalizeFile, mpt::as_span(reinterpret_cast(normalizeBuffer), lRead * m_SndFile.m_MixerSettings.gnChannels * sizeof(float)))) { break; } } else { const std::streampos oldPos = fileStream.tellp(); switch(fileEnc->GetSampleFormat()) { case SampleFormat::Float64: fileEnc->WriteInterleaved(lRead, buffer.float64); break; case SampleFormat::Float32: fileEnc->WriteInterleaved(lRead, buffer.float32); break; case SampleFormat::Int32: fileEnc->WriteInterleaved(lRead, buffer.int32); break; case SampleFormat::Int24: fileEnc->WriteInterleaved(lRead, buffer.int24); break; case SampleFormat::Int16: fileEnc->WriteInterleaved(lRead, buffer.int16); break; case SampleFormat::Int8: fileEnc->WriteInterleaved(lRead, buffer.int8); break; case SampleFormat::Unsigned8: fileEnc->WriteInterleaved(lRead, buffer.uint8); break; } const std::streampos newPos = fileStream.tellp(); bytesWritten += static_cast(newPos - oldPos); if(!fileStream) { break; } } if(m_dwSongLimit && (ullSamples >= ullMaxSamples)) { break; } auto currentTime = timeGetTime(); if((currentTime - prevTime) >= 16) { prevTime = currentTime; DWORD seconds = (DWORD)(ullSamples / m_SndFile.m_MixerSettings.gdwMixingFreq); if((ullSamples > 0) && (ullSamples < max)) { timeRemaining = static_cast((timeRemaining + ((currentTime - dwStartTime) * (max - ullSamples) / ullSamples) / 1000) / 2); } if(m_Settings.normalize) { SetText(MPT_CFORMAT("Rendering {}... ({}mn{}s, {}mn{}s remaining)")(caption, seconds / 60, mpt::ufmt::dec0<2>(seconds % 60u), timeRemaining / 60, mpt::ufmt::dec0<2>(timeRemaining % 60u))); } else { SetText(MPT_CFORMAT("Writing {}... ({}kB, {}mn{}s, {}mn{}s remaining)")(caption, bytesWritten >> 10, seconds / 60, mpt::ufmt::dec0<2>(seconds % 60u), timeRemaining / 60, mpt::ufmt::dec0<2>(timeRemaining % 60u))); } SetProgress(ullSamples); } ProcessMessages(); if (m_abort) { ok = IDCANCEL; break; } } m_SndFile.m_nMaxOrderPosition = 0; mainFrame->StopRenderer(&m_SndFile); if(m_Settings.normalize) { const float normalizeFactor = (normalizePeak != 0.0f) ? (1.0f / normalizePeak) : 1.0f; const uint64 framesTotal = ullSamples; int lastPercent = -1; mpt::IO::SeekAbsolute(*normalizeFile, 0); uint64 framesProcessed = 0; uint64 framesToProcess = framesTotal; SetRange(0, framesTotal); while(framesToProcess) { const std::size_t framesChunk = std::min(mpt::saturate_cast(framesToProcess), std::size_t(MIXBUFFERSIZE)); const uint32 samplesChunk = static_cast(framesChunk * channels); const std::size_t bytes = samplesChunk * sizeof(float); if(mpt::IO::ReadRaw(*normalizeFile, mpt::as_span(reinterpret_cast(normalizeBuffer), bytes)).size() != bytes) { break; } for(std::size_t i = 0; i < samplesChunk; ++i) { normalizeBuffer[i] *= normalizeFactor; } const std::streampos oldPos = fileStream.tellp(); std::visit( [&](auto& ditherInstance) { switch(fileEnc->GetSampleFormat()) { case SampleFormat::Unsigned8: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.uint8, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Int8: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.int8, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Int16: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.int16, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Int24: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.int24, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Int32: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.int32, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Float32: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.float32, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; case SampleFormat::Float64: ConvertBufferMixInternalToBuffer(mpt::audio_span_interleaved(buffer.float64, channels, framesChunk), mpt::audio_span_interleaved(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); break; default: MPT_ASSERT_NOTREACHED(); break; } }, dithers.Variant() ); switch(fileEnc->GetSampleFormat()) { case SampleFormat::Float64: fileEnc->WriteInterleaved(framesChunk, buffer.float64); break; case SampleFormat::Float32: fileEnc->WriteInterleaved(framesChunk, buffer.float32); break; case SampleFormat::Int32: fileEnc->WriteInterleaved(framesChunk, buffer.int32); break; case SampleFormat::Int24: fileEnc->WriteInterleaved(framesChunk, buffer.int24); break; case SampleFormat::Int16: fileEnc->WriteInterleaved(framesChunk, buffer.int16); break; case SampleFormat::Int8: fileEnc->WriteInterleaved(framesChunk, buffer.int8); break; case SampleFormat::Unsigned8: fileEnc->WriteInterleaved(framesChunk, buffer.uint8); break; } const std::streampos newPos = fileStream.tellp(); bytesWritten += static_cast(newPos - oldPos); auto currentTime = timeGetTime(); if((currentTime - prevTime) >= 16) { prevTime = currentTime; int percent = static_cast(100 * framesProcessed / framesTotal); if(percent != lastPercent) { SetText(MPT_CFORMAT("Normalizing... ({}%)")(percent)); SetProgress(framesProcessed); lastPercent = percent; } ProcessMessages(); } framesProcessed += framesChunk; framesToProcess -= framesChunk; } mpt::IO::Flush(*normalizeFile); normalizeFile.reset(); for(int retry=0; retry<10; retry++) { // stupid virus scanners if(DeleteFile(normalizeFileName.AsNative().c_str()) != EACCES) { break; } Sleep(10); } } if(!patternCuePoints.empty()) { if(encSettings.Cues) { std::vector cues; cues.reserve(patternCuePoints.size()); for(const auto &cue : patternCuePoints) { cues.push_back(static_cast(cue.offset)); } fileEnc->WriteCues(cues); } } m_SndFile.m_PatternCuePoints = nullptr; fileEnc->WriteFinalize(); fileEnc = nullptr; CMainFrame::UpdateAudioParameters(m_SndFile, TRUE); EndDialog(ok); } OPENMPT_NAMESPACE_END