diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp new file mode 100644 index 00000000..20470260 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/Mod2wave.cpp @@ -0,0 +1,1401 @@ +/* + * 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 <variant> +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" + + +OPENMPT_NAMESPACE_BEGIN + + +extern const TCHAR *gszChnCfgNames[3]; + + +template <typename Tsample> +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<mpt::audio_span_interleaved<Tsample>, DithersOpenMPT> target(mpt::audio_span_interleaved<Tsample>(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<EncoderFactoryBase*> &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("<empty slot>")), 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<int>(m_CbnSampleRate.GetItemData(m_CbnSampleRate.GetCurSel())); + int channels = static_cast<int>(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<int>(encSettings.Quality*100.0f) == quality) + { + sel = ndx; + } + } + } + if(encTraits->modes & Encoder::ModeVBR) + { + for(int bitrate = static_cast<int>(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<int>(encSettings.Bitrate) == encTraits->bitrates[bitrate]) + { + sel = ndx; + } + } + } + if(encTraits->modes & Encoder::ModeABR) + { + for(int bitrate = static_cast<int>(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<int>(encSettings.Bitrate) == encTraits->bitrates[bitrate]) + { + sel = ndx; + } + } + } + if(encTraits->modes & Encoder::ModeCBR) + { + for(int bitrate = static_cast<int>(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<int>(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<ORDERINDEX>(GetDlgItemInt(IDC_EDIT3, NULL, FALSE)); + m_Settings.maxOrder = static_cast<ORDERINDEX>(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<SEQUENCEINDEX>(std::clamp(GetDlgItemInt(IDC_EDIT12, NULL, FALSE), 1u, maxSequence) - 1u); + m_Settings.maxSequence = static_cast<SEQUENCEINDEX>(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<uint16>(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<SAMPLEINDEX>(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<uint32>(m_CbnSampleRate.GetItemData(m_CbnSampleRate.GetCurSel())); + encSettings.Channels = static_cast<uint16>(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<int>(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<int>(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<float>(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<Encoder::Settings>(*(EncoderSettings[EncoderIndex])); + settings.Details = static_cast<Encoder::StreamSettings>(TrackerSettings::Instance().ExportStreamEncoderSettings); + return settings; +} + + +CWaveConvertSettings::CWaveConvertSettings(SettingsContainer &conf, const std::vector<EncoderFactoryBase*> &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<EncoderSettingsConf>( + 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<float> normalizeBufferData; + float *normalizeBuffer = nullptr; + float normalizePeak = 0.0f; + const mpt::PathString normalizeFileName = mpt::CreateTempFileName(P_("OpenMPT")); + std::optional<mpt::fstream> 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<IAudioStreamEncoder> fileEnc = m_Settings.GetEncoderFactory()->ConstructStreamEncoder(fileStream, encSettings, m_Settings.Tags); + + std::variant< + std::unique_ptr<std::array<double, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<float, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<int32, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<int24, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<int16, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<int8, MIXBUFFERSIZE * 4>>, + std::unique_ptr<std::array<uint8, MIXBUFFERSIZE * 4>>> 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<std::array<double, MIXBUFFERSIZE * 4>>(); + buffer.float64 = std::get<0>(bufferData)->data(); + break; + case SampleFormat::Float32: + bufferData = std::make_unique<std::array<float, MIXBUFFERSIZE * 4>>(); + buffer.float32 = std::get<1>(bufferData)->data(); + break; + case SampleFormat::Int32: + bufferData = std::make_unique<std::array<int32, MIXBUFFERSIZE * 4>>(); + buffer.int32 = std::get<2>(bufferData)->data(); + break; + case SampleFormat::Int24: + bufferData = std::make_unique<std::array<int24, MIXBUFFERSIZE * 4>>(); + buffer.int24 = std::get<3>(bufferData)->data(); + break; + case SampleFormat::Int16: + bufferData = std::make_unique<std::array<int16, MIXBUFFERSIZE * 4>>(); + buffer.int16 = std::get<4>(bufferData)->data(); + break; + case SampleFormat::Int8: + bufferData = std::make_unique<std::array<int8, MIXBUFFERSIZE * 4>>(); + buffer.int8 = std::get<5>(bufferData)->data(); + break; + case SampleFormat::Unsigned8: + bufferData = std::make_unique<std::array<uint8, MIXBUFFERSIZE * 4>>(); + 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<uint64>(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<PatternCuePoint> 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<const std::byte*>(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<uint64>(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<uint32>((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<std::size_t>(framesToProcess), std::size_t(MIXBUFFERSIZE)); + const uint32 samplesChunk = static_cast<uint32>(framesChunk * channels); + + const std::size_t bytes = samplesChunk * sizeof(float); + if(mpt::IO::ReadRaw(*normalizeFile, mpt::as_span(reinterpret_cast<std::byte*>(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<false>(mpt::audio_span_interleaved<uint8>(buffer.uint8, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Int8: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<int8>(buffer.int8, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Int16: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<int16>(buffer.int16, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Int24: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<int24>(buffer.int24, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Int32: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<int32>(buffer.int32, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Float32: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<float>(buffer.float32, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(normalizeBuffer, channels, framesChunk), ditherInstance, channels, framesChunk); + break; + case SampleFormat::Float64: + ConvertBufferMixInternalToBuffer<false>(mpt::audio_span_interleaved<double>(buffer.float64, channels, framesChunk), mpt::audio_span_interleaved<const MixSampleFloat>(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<std::size_t>(newPos - oldPos); + + auto currentTime = timeGetTime(); + if((currentTime - prevTime) >= 16) + { + prevTime = currentTime; + + int percent = static_cast<int>(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<uint64> cues; + cues.reserve(patternCuePoints.size()); + for(const auto &cue : patternCuePoints) + { + cues.push_back(static_cast<uint32>(cue.offset)); + } + fileEnc->WriteCues(cues); + } + } + m_SndFile.m_PatternCuePoints = nullptr; + + fileEnc->WriteFinalize(); + + fileEnc = nullptr; + + CMainFrame::UpdateAudioParameters(m_SndFile, TRUE); + EndDialog(ok); +} + + +OPENMPT_NAMESPACE_END |