From: <sv...@op...> - 2024-10-26 20:21:54
|
Author: sagamusix Date: Sat Oct 26 22:21:46 2024 New Revision: 21994 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=21994 Log: [Imp] MIDI Export: Allow sub-songs to be exported to individual files (https://bugs.openmpt.org/view.php?id=1515). Modified: trunk/OpenMPT/mptrack/Moddoc.cpp trunk/OpenMPT/mptrack/Moddoc.h trunk/OpenMPT/mptrack/OPLExport.cpp trunk/OpenMPT/mptrack/mod2midi.cpp trunk/OpenMPT/mptrack/mod2midi.h trunk/OpenMPT/mptrack/mptrack.rc Modified: trunk/OpenMPT/mptrack/Moddoc.cpp ============================================================================== --- trunk/OpenMPT/mptrack/Moddoc.cpp Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/Moddoc.cpp Sat Oct 26 22:21:46 2024 (r21994) @@ -2001,41 +2001,9 @@ void CModDoc::OnFileMidiConvert() { #ifndef NO_PLUGINS - CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); - - if ((!pMainFrm) || (!m_SndFile.GetType())) return; - - mpt::PathString filename = GetPathNameMpt().ReplaceExtension(P_(".mid")); - - FileDialog dlg = SaveFileDialog() - .DefaultExtension(U_("mid")) - .DefaultFilename(filename) - .ExtensionFilter(U_("MIDI Files (*.mid)|*.mid||")); - if(!dlg.Show()) return; - - CModToMidi mididlg(m_SndFile, pMainFrm); + CModToMidi mididlg(*this, CMainFrame::GetMainFrame()); BypassInputHandler bih; - if(mididlg.DoModal() == IDOK) - { - try - { - mpt::IO::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); - mpt::IO::ofstream &f = sf; - f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit); - - if(!f.good()) - { - Reporting::Error("Could not open file for writing. Is it open in another application?"); - return; - } - - CDoMidiConvert doconv(m_SndFile, f, mididlg.m_instrMap); - doconv.DoModal(); - } catch(const std::exception &) - { - Reporting::Error(_T("Error while writing file!")); - } - } + mididlg.DoModal(); #else Reporting::Error("In order to use MIDI export, OpenMPT must be built with plugin support."); #endif // NO_PLUGINS @@ -3076,6 +3044,20 @@ } +mpt::tstring CModDoc::FormatSubsongName(const SubSong &song) +{ + const auto sequenceName = m_SndFile.Order(song.sequence).GetName(); + const auto startPattern = m_SndFile.Order(song.sequence).PatternAt(song.startOrder); + const auto orderName = startPattern ? startPattern->GetName() : std::string{}; + return MPT_TFORMAT("Sequence {}{}\nOrder {} to {}{}")( + song.sequence + 1, + sequenceName.empty() ? mpt::tstring{} : MPT_TFORMAT(" ({})")(sequenceName), + song.startOrder, + song.endOrder, + orderName.empty() ? mpt::tstring{} : MPT_TFORMAT(" ({})")(mpt::ToWin(m_SndFile.GetCharsetInternal(), orderName))); +} + + void CModDoc::SafeFileClose() { // Verify that the main window has the focus. This saves us a lot of trouble because active modal dialogs cannot know if their pSndFile pointers are still valid. Modified: trunk/OpenMPT/mptrack/Moddoc.h ============================================================================== --- trunk/OpenMPT/mptrack/Moddoc.h Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/Moddoc.h Sat Oct 26 22:21:46 2024 (r21994) @@ -336,6 +336,8 @@ // [in] bIncludeIndex: True to include instrument index in front of the instrument name, false otherwise. CString GetPatternViewInstrumentName(INSTRUMENTINDEX nInstr, bool bEmptyInsteadOfNoName = false, bool bIncludeIndex = true) const; + mpt::tstring FormatSubsongName(const SubSong &song); + // Check if a given channel contains data. bool IsChannelUnused(CHANNELINDEX nChn) const; // Check whether a sample is used. Modified: trunk/OpenMPT/mptrack/OPLExport.cpp ============================================================================== --- trunk/OpenMPT/mptrack/OPLExport.cpp Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/OPLExport.cpp Sat Oct 26 22:21:46 2024 (r21994) @@ -454,17 +454,7 @@ const auto subsongText = GetDlgItem(IDC_SUBSONG); if(subsongText == nullptr || m_selectedSong >= m_subSongs.size()) return; - const auto &song = m_subSongs[m_selectedSong]; - const auto sequenceName = m_sndFile.Order(song.sequence).GetName(); - const auto startPattern = m_sndFile.Order(song.sequence).PatternAt(song.startOrder); - const auto orderName = startPattern ? startPattern->GetName() : std::string{}; - subsongText->SetWindowText(MPT_TFORMAT("Sequence {}{}\nOrder {} to {}{}")( - song.sequence + 1, - sequenceName.empty() ? mpt::tstring{} : MPT_TFORMAT(" ({})")(sequenceName), - song.startOrder, - song.endOrder, - orderName.empty() ? mpt::tstring{} : MPT_TFORMAT(" ({})")(mpt::ToWin(m_sndFile.GetCharsetInternal(), orderName))) - .c_str()); + subsongText->SetWindowText(m_modDoc.FormatSubsongName(m_subSongs[m_selectedSong]).c_str()); } void DoConversion(const mpt::PathString &fileName) @@ -512,11 +502,14 @@ const auto songIndexFmt = mpt::format_simple_spec<mpt::ustring>{}.Dec().FillNul().Width(1 + static_cast<int>(std::log10(m_subSongs.size()))); - size_t totalSamples = 0; + uint64 totalSamples = 0; for(size_t i = 0; i < m_subSongs.size() && !m_abort; i++) { const auto &song = m_subSongs[i]; + m_selectedSong = i; + UpdateSubsongName(); + m_sndFile.ResetPlayPos(); m_sndFile.GetLength(eAdjust, GetLengthTarget(song.startOrder, song.startRow).StartPos(song.sequence, 0, 0)); m_sndFile.m_PlayState.m_flags.reset(); Modified: trunk/OpenMPT/mptrack/mod2midi.cpp ============================================================================== --- trunk/OpenMPT/mptrack/mod2midi.cpp Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/mod2midi.cpp Sat Oct 26 22:21:46 2024 (r21994) @@ -10,17 +10,21 @@ #include "stdafx.h" #include "mod2midi.h" +#include "FileDialog.h" +#include "InputHandler.h" #include "Mainfrm.h" #include "Moddoc.h" #include "Mptrack.h" #include "Reporting.h" #include "resource.h" +#include "TrackerSettings.h" #include "../common/mptFileIO.h" #include "../common/mptStringBuffer.h" #include "../soundlib/plugins/PlugInterface.h" #include "../soundlib/plugins/PluginManager.h" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#include "mpt/io_file/outputfile.hpp" #include <sstream> @@ -61,7 +65,7 @@ ModInstrument m_instr; const ModInstrument *const m_oldInstr; const CSoundFile &m_sndFile; - const GetLengthType &m_songLength; + const SubSong &m_subsongInfo; MidiTrack *const m_tempoTrack; // Pointer to tempo track, nullptr if this is the tempo track decltype(m_MidiCh) *m_lastMidiCh = nullptr; std::shared_ptr<MidiChannelState> m_channelState; @@ -119,11 +123,11 @@ operator ModInstrument& () { return m_instr; } - MidiTrack(VSTPluginLib &factory, CSoundFile &sndFile, const GetLengthType &songLength, SNDMIXPLUGIN &mixStruct, MidiTrack *tempoTrack, const mpt::ustring &name, const ModInstrument *oldInstr, bool overlappingInstruments) + MidiTrack(VSTPluginLib &factory, CSoundFile &sndFile, const SubSong &subsongInfo, SNDMIXPLUGIN &mixStruct, MidiTrack *tempoTrack, const mpt::ustring &name, const ModInstrument *oldInstr, bool overlappingInstruments) : IMidiPlugin(factory, sndFile, mixStruct) , m_oldInstr(oldInstr) , m_sndFile(sndFile) - , m_songLength(songLength) + , m_subsongInfo(subsongInfo) , m_tempoTrack(tempoTrack) , m_sampleRate(sndFile.GetSampleRate()) , m_overlappingInstruments(overlappingInstruments) @@ -208,7 +212,7 @@ mpt::IO::WriteRaw(f, msg, 9); } - if(!m_tempoTrack && !m_wroteLoopStart && m_sndFile.m_PlayState.m_nRow == m_songLength.restartRow && m_sndFile.m_PlayState.m_nCurrentOrder == m_songLength.restartOrder) + if(!m_tempoTrack && !m_wroteLoopStart && m_sndFile.m_PlayState.m_nRow == m_subsongInfo.loopStartRow && m_sndFile.m_PlayState.m_nCurrentOrder == m_subsongInfo.loopStartOrder) { WriteString(kCue, U_("loopStart")); m_wroteLoopStart = true; @@ -434,16 +438,16 @@ VSTPluginLib m_plugFactory; CSoundFile &m_sndFile; std::ostream &m_file; - const GetLengthType m_songLength; + const SubSong &m_subsongInfo; const bool m_wasInstrumentMode; public: - Conversion(CSoundFile &sndFile, const InstrMap &instrMap, std::ostream &file, bool overlappingInstruments, const GetLengthType &songLength) + Conversion(CSoundFile &sndFile, const InstrMap &instrMap, std::ostream &file, bool overlappingInstruments, const SubSong &subsongInfo) : m_oldInstruments(sndFile.GetNumInstruments()) , m_plugFactory(nullptr, true, {}, {}) , m_sndFile(sndFile) , m_file(file) - , m_songLength(songLength) + , m_subsongInfo(subsongInfo) , m_wasInstrumentMode(sndFile.GetNumInstruments() > 0) { m_oldPlugins.assign(std::begin(m_sndFile.m_MixPlugins), std::end(m_sndFile.m_MixPlugins)); @@ -458,7 +462,7 @@ } m_tracks.reserve(m_sndFile.GetNumInstruments() + 1); - MidiTrack &tempoTrack = *(new MidiTrack(m_plugFactory, m_sndFile, m_songLength, tempoTrackPlugin, nullptr, mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.m_songName), nullptr, overlappingInstruments)); + MidiTrack &tempoTrack = *(new MidiTrack(m_plugFactory, m_sndFile, m_subsongInfo, tempoTrackPlugin, nullptr, mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.m_songName), nullptr, overlappingInstruments)); tempoTrackPlugin.pMixPlugin = &tempoTrack; tempoTrack.WriteString(kText, mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.m_songMessage.GetString())); tempoTrack.WriteString(kCopyright, m_sndFile.m_songArtist); @@ -475,7 +479,7 @@ SNDMIXPLUGIN &mixPlugin = m_sndFile.m_MixPlugins[nextPlug++]; ModInstrument *oldInstr = m_wasInstrumentMode ? m_oldInstruments[i - 1] : nullptr; - MidiTrack &midiInstr = *(new MidiTrack(m_plugFactory, m_sndFile, m_songLength, mixPlugin, &tempoTrack, m_wasInstrumentMode ? mpt::ToUnicode(m_sndFile.GetCharsetInternal(), oldInstr->name) : mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(i)), oldInstr, overlappingInstruments)); + MidiTrack &midiInstr = *(new MidiTrack(m_plugFactory, m_sndFile, m_subsongInfo, mixPlugin, &tempoTrack, m_wasInstrumentMode ? mpt::ToUnicode(m_sndFile.GetCharsetInternal(), oldInstr->name) : mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(i)), oldInstr, overlappingInstruments)); ModInstrument &instr = midiInstr; mixPlugin.pMixPlugin = &midiInstr; @@ -581,10 +585,11 @@ bool CModToMidi::s_overlappingInstruments = false; BEGIN_MESSAGE_MAP(CModToMidi, DialogBase) - ON_CBN_SELCHANGE(IDC_COMBO1, &CModToMidi::UpdateDialog) - ON_CBN_SELCHANGE(IDC_COMBO2, &CModToMidi::OnChannelChanged) - ON_CBN_SELCHANGE(IDC_COMBO3, &CModToMidi::OnProgramChanged) - ON_COMMAND(IDC_CHECK1, &CModToMidi::OnOverlapChanged) + ON_CBN_SELCHANGE(IDC_COMBO1, &CModToMidi::UpdateDialog) + ON_CBN_SELCHANGE(IDC_COMBO2, &CModToMidi::OnChannelChanged) + ON_CBN_SELCHANGE(IDC_COMBO3, &CModToMidi::OnProgramChanged) + ON_COMMAND(IDC_CHECK1, &CModToMidi::OnOverlapChanged) + ON_EN_CHANGE(IDC_EDIT1, &CModToMidi::OnSubsongChanged) ON_WM_VSCROLL() END_MESSAGE_MAP() @@ -599,14 +604,15 @@ } -CModToMidi::CModToMidi(CSoundFile &sndFile, CWnd *pWndParent) - : DialogBase(IDD_MOD2MIDI, pWndParent) - , m_sndFile(sndFile) - , m_instrMap((sndFile.GetNumInstruments() ? sndFile.GetNumInstruments() : sndFile.GetNumSamples()) + 1) +CModToMidi::CModToMidi(CModDoc &modDoc, CWnd *parent) + : CProgressDialog{parent, IDD_MOD2MIDI} + , m_modDoc{modDoc} + , m_instrMap((modDoc.GetNumInstruments() ? modDoc.GetNumInstruments() : modDoc.GetNumSamples()) + 1) + , m_subSongs{modDoc.GetSoundFile().GetAllSubSongs()} { - for (INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++) + for (INSTRUMENTINDEX i = 1; i <= modDoc.GetNumInstruments(); i++) { - ModInstrument *pIns = m_sndFile.Instruments[i]; + ModInstrument *pIns = modDoc.GetSoundFile().Instruments[i]; if(pIns != nullptr) { m_instrMap[i].channel = pIns->nMidiChannel; @@ -621,6 +627,9 @@ } +CModToMidi::~CModToMidi() {} + + BOOL CModToMidi::OnInitDialog() { CString s; @@ -632,25 +641,26 @@ m_SpinInstrument.SetPos(0); m_currentInstr = 1; m_CbnInstrument.SetRedraw(FALSE); - if(m_sndFile.GetNumInstruments()) + const CSoundFile &sndFile = m_modDoc.GetSoundFile(); + if(m_modDoc.GetNumInstruments()) { - for(INSTRUMENTINDEX nIns = 1; nIns <= m_sndFile.GetNumInstruments(); nIns++) + for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++) { - ModInstrument *pIns = m_sndFile.Instruments[nIns]; - if(pIns && m_sndFile.GetpModDoc()->IsInstrumentUsed(nIns, false)) + ModInstrument *pIns = sndFile.Instruments[nIns]; + if(pIns && m_modDoc.IsInstrumentUsed(nIns, false)) { - const CString name = m_sndFile.GetpModDoc()->GetPatternViewInstrumentName(nIns); + const CString name = m_modDoc.GetPatternViewInstrumentName(nIns); m_CbnInstrument.SetItemData(m_CbnInstrument.AddString(name), nIns); } } } else { - for(SAMPLEINDEX nSmp = 1; nSmp <= m_sndFile.GetNumSamples(); nSmp++) + for(SAMPLEINDEX nSmp = 1; nSmp <= sndFile.GetNumSamples(); nSmp++) { - if(m_sndFile.GetpModDoc()->IsSampleUsed(nSmp, false)) + if(m_modDoc.IsSampleUsed(nSmp, false)) { s.Format(_T("%02d: "), nSmp); - s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.m_szNames[nSmp]); + s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.m_szNames[nSmp]); m_CbnInstrument.SetItemData(m_CbnInstrument.AddString(s), nSmp); } } @@ -679,6 +689,21 @@ m_CbnProgram.SetCurSel(0); UpdateDialog(); CheckDlgButton(IDC_CHECK1, s_overlappingInstruments ? BST_CHECKED : BST_UNCHECKED); + + // Subsongs + CheckRadioButton(IDC_RADIO4, IDC_RADIO5, IDC_RADIO4); + + static_cast<CSpinButtonCtrl *>(GetDlgItem(IDC_SPIN2))->SetRange32(1, static_cast<int>(m_subSongs.size())); + SetDlgItemInt(IDC_EDIT1, static_cast<UINT>(m_selectedSong + 1), FALSE); + if(m_subSongs.size() <= 1) + { + const int controls[] = {IDC_RADIO4, IDC_RADIO5, IDC_EDIT1, IDC_SPIN2}; + for(int control : controls) + GetDlgItem(control)->EnableWindow(FALSE); + } + UpdateSubsongName(); + m_locked = false; + return TRUE; } @@ -687,6 +712,7 @@ { if(m_percussion == percussion) return; + const CSoundFile &sndFile = m_modDoc.GetSoundFile(); m_CbnProgram.SetRedraw(FALSE); m_CbnProgram.ResetContent(); if(percussion) @@ -697,7 +723,7 @@ ModCommand::NOTE note = i + 24; auto s = MPT_CFORMAT("{} ({}): {}")( note, - mpt::ToCString(m_sndFile.GetNoteName(note + NOTE_MIN)), + mpt::ToCString(sndFile.GetNoteName(note + NOTE_MIN)), mpt::ToCString(mpt::Charset::ASCII, szMidiPercussionNames[i])); m_CbnProgram.SetItemData(m_CbnProgram.AddString(s), note); } @@ -825,80 +851,167 @@ void CModToMidi::OnOK() { + bool ok = false; for(size_t i = 1; i < m_instrMap.size(); i++) { if(m_instrMap[i].channel != MidiNoChannel) { - DialogBase::OnOK(); + ok = true; + break; + } + } + + if (!ok) + { + auto choice = Reporting::Confirm(_T("No instruments have been selected for export. Would you still like to export the file?"), true, true); + if(choice != cnfYes) + { + if(choice == cnfNo) + OnCancel(); return; } } + + mpt::PathString filename = m_modDoc.GetPathNameMpt().ReplaceExtension(P_(".mid")); + FileDialog dlg = SaveFileDialog() + .DefaultExtension(U_("mid")) + .DefaultFilename(filename) + .ExtensionFilter(U_("MIDI Files (*.mid)|*.mid||")); + if(!dlg.Show()) + { + OnCancel(); + return; + } + m_conversionRunning = true; + DoConversion(dlg.GetFirstFile()); + + CProgressDialog::OnOK(); +} + - auto choice = Reporting::Confirm(_T("No instruments have been selected for export. Would you still like to export the file?"), true, true); - if(choice == cnfYes) - DialogBase::OnOK(); - else if(choice == cnfNo) +void CModToMidi::OnCancel() +{ + if(m_conversionRunning) + CProgressDialog::OnCancel(); + else DialogBase::OnCancel(); } -void CDoMidiConvert::Run() +void CModToMidi::OnSubsongChanged() { - CMainFrame::GetMainFrame()->PauseMod(m_sndFile.GetpModDoc()); + if(m_locked) + return; + CheckRadioButton(IDC_RADIO4, IDC_RADIO5, IDC_RADIO5); + BOOL ok = FALSE; + const auto newSubSong = std::clamp(static_cast<size_t>(GetDlgItemInt(IDC_EDIT1, &ok, FALSE)), size_t(1), m_subSongs.size()) - 1; + if(m_selectedSong == newSubSong || !ok) + return; + m_selectedSong = newSubSong; + UpdateSubsongName(); +} - const auto songLength = m_sndFile.GetLength(eNoAdjust).front(); - const double duration = songLength.duration; - const uint64 totalSamples = mpt::saturate_round<uint64>(duration * m_sndFile.m_MixerSettings.gdwMixingFreq); - SetRange(0, totalSamples); - auto conv = std::make_unique<MidiExport::Conversion>(m_sndFile, m_instrMap, m_file, CModToMidi::s_overlappingInstruments, songLength); +void CModToMidi::UpdateSubsongName() +{ + const auto subsongText = GetDlgItem(IDC_SUBSONG); + if(subsongText == nullptr || m_selectedSong >= m_subSongs.size()) + return; + subsongText->SetWindowText(m_modDoc.FormatSubsongName(m_subSongs[m_selectedSong]).c_str()); +} - auto startTime = timeGetTime(), prevTime = startTime; - m_sndFile.SetCurrentOrder(0); - m_sndFile.GetLength(eAdjust, GetLengthTarget(0, 0)); - m_sndFile.m_PlayState.m_flags.reset(SONG_PATTERNLOOP); - int oldRepCount = m_sndFile.GetRepeatCount(); - m_sndFile.SetRepeatCount(0); - m_sndFile.m_bIsRendering = true; +void CModToMidi::DoConversion(const mpt::PathString &fileName) +{ + if(IsDlgButtonChecked(IDC_RADIO5)) + m_subSongs = {m_subSongs[m_selectedSong]}; + + const int controls[] = {IDC_COMBO1, IDC_COMBO2, IDC_COMBO3, IDC_RADIO4, IDC_RADIO5, IDC_EDIT1, IDC_CHECK1, IDC_SPIN1, IDC_SPIN2, IDOK}; + for(int control : controls) + GetDlgItem(control)->EnableWindow(FALSE); + GetDlgItem(IDCANCEL)->SetFocus(); + + CSoundFile &sndFile = m_modDoc.GetSoundFile(); + SetRange(0, mpt::saturate_round<uint64>(std::accumulate(m_subSongs.begin(), m_subSongs.end(), 0.0, [](double acc, const auto &song) { return acc + song.duration; }) * sndFile.GetSampleRate())); + GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_SHOW); + + BypassInputHandler bih; + CMainFrame::GetMainFrame()->PauseMod(&m_modDoc); + + sndFile.m_bIsRendering = true; + + const auto origSequence = sndFile.Order.GetCurrentSequenceIndex(); + const auto origRepeatCount = sndFile.GetRepeatCount(); + sndFile.SetRepeatCount(0); EnableTaskbarProgress(); - MidiExport::DummyAudioTarget target; - UINT ok = IDOK; - const auto fmt = MPT_TFORMAT("Rendering file... ({}mn{}s, {}mn{}s remaining)"); - while(m_sndFile.Read(MIXBUFFERSIZE, target) > 0) + const auto songIndexFmt = mpt::format_simple_spec<mpt::ustring>{}.Dec().FillNul().Width(1 + static_cast<int>(std::log10(m_subSongs.size()))); + + uint64 totalSamples = 0; + for(size_t i = 0; i < m_subSongs.size() && !m_abort; i++) { - m_sndFile.ResetMixStat(); - auto currentTime = timeGetTime(); - if(currentTime - prevTime >= 16) + const auto &song = m_subSongs[i]; + + m_selectedSong = i; + UpdateSubsongName(); + + sndFile.ResetPlayPos(); + sndFile.GetLength(eAdjust, GetLengthTarget(song.startOrder, song.startRow).StartPos(song.sequence, 0, 0)); + sndFile.m_PlayState.m_flags.reset(); + + mpt::PathString currentFileName = fileName; + if(m_subSongs.size() > 1) + currentFileName = fileName.ReplaceExtension(mpt::PathString::FromNative(MPT_TFORMAT(" ({})")(mpt::ufmt::fmt(i + 1, songIndexFmt))) + fileName.GetFilenameExtension()); + mpt::IO::SafeOutputFile sf(currentFileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); + mpt::IO::ofstream &f = sf; + + try { - prevTime = currentTime; - uint64 curSamples = m_sndFile.GetTotalSampleCount(); - uint32 curTime = static_cast<uint32>(curSamples / m_sndFile.m_MixerSettings.gdwMixingFreq); - uint32 timeRemaining = 0; - if(curSamples > 0 && curSamples < totalSamples) - { - timeRemaining = static_cast<uint32>(((currentTime - startTime) * (totalSamples - curSamples) / curSamples) / 1000u); - } - SetText(fmt(curTime / 60u, mpt::tfmt::dec0<2>(curTime % 60u), timeRemaining / 60u, mpt::tfmt::dec0<2>(timeRemaining % 60u)).c_str()); - SetProgress(curSamples); - ProcessMessages(); + if(!f) + throw std::exception{}; + f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit); + + auto conv = std::make_unique<MidiExport::Conversion>(sndFile, m_instrMap, f, CModToMidi::s_overlappingInstruments, m_subSongs[i]); - if(m_abort) + MidiExport::DummyAudioTarget target; + const auto fmt = MPT_TFORMAT("Rendering file... ({}mn{}s, {}mn{}s remaining)"); + auto prevTime = timeGetTime(); + uint64 subsongSamples = 0; + while(!m_abort) { - ok = IDCANCEL; - break; + auto count = sndFile.Read(MIXBUFFERSIZE, target); + if(count == 0) + break; + + totalSamples += count; + subsongSamples += count; + sndFile.ResetMixStat(); + + auto currentTime = timeGetTime(); + if(currentTime - prevTime >= 16) + { + prevTime = currentTime; + auto timeSec = subsongSamples / sndFile.GetSampleRate(); + SetWindowText(MPT_TFORMAT("Exporting Song {} / {}... {}:{}:{}")(i + 1, m_subSongs.size(), timeSec / 3600, mpt::cfmt::dec0<2>((timeSec / 60) % 60), mpt::cfmt::dec0<2>(timeSec % 60)).c_str()); + + SetProgress(totalSamples); + ProcessMessages(); + } } + + conv->Finalise(); + } catch(const std::exception &) + { + Reporting::Error(MPT_UFORMAT("Unable to write to file {}!")(currentFileName)); + break; } } - conv->Finalise(); - - m_sndFile.m_bIsRendering = false; - m_sndFile.SetRepeatCount(oldRepCount); - - EndDialog(ok); + sndFile.SetRepeatCount(origRepeatCount); + sndFile.Order.SetSequence(origSequence); + sndFile.ResetPlayPos(); + sndFile.m_bIsRendering = false; } OPENMPT_NAMESPACE_END Modified: trunk/OpenMPT/mptrack/mod2midi.h ============================================================================== --- trunk/OpenMPT/mptrack/mod2midi.h Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/mod2midi.h Sat Oct 26 22:21:46 2024 (r21994) @@ -19,7 +19,8 @@ OPENMPT_NAMESPACE_BEGIN -class CSoundFile; +class CModDoc; +struct SubSong; namespace MidiExport { @@ -32,23 +33,34 @@ } -class CModToMidi : public DialogBase +class CModToMidi : public CProgressDialog { protected: CComboBox m_CbnInstrument, m_CbnChannel, m_CbnProgram; CSpinButtonCtrl m_SpinInstrument; - CSoundFile &m_sndFile; + CModDoc &m_modDoc; + MidiExport::InstrMap m_instrMap; + std::vector<SubSong> m_subSongs; + size_t m_selectedSong = 0; size_t m_currentInstr = 1; bool m_percussion = false; + bool m_conversionRunning = false; + bool m_locked = true; public: - MidiExport::InstrMap m_instrMap; static bool s_overlappingInstruments; public: - CModToMidi(CSoundFile &sndFile, CWnd *pWndParent = nullptr); + CModToMidi(CModDoc &modDoc, CWnd *parent = nullptr); + ~CModToMidi(); protected: + void Run() override {}; + + void UpdateSubsongName(); + void DoConversion(const mpt::PathString &fileName); + void OnOK() override; + void OnCancel() override; BOOL OnInitDialog() override; void DoDataExchange(CDataExchange *pDX) override; void FillProgramBox(bool percussion); @@ -57,25 +69,9 @@ afx_msg void OnChannelChanged(); afx_msg void OnProgramChanged(); afx_msg void OnOverlapChanged(); - DECLARE_MESSAGE_MAP(); -}; - + afx_msg void OnSubsongChanged(); -class CDoMidiConvert: public CProgressDialog -{ -public: - CSoundFile &m_sndFile; - std::ostream &m_file; - const MidiExport::InstrMap &m_instrMap; - -public: - CDoMidiConvert(CSoundFile &sndFile, std::ostream &f, const MidiExport::InstrMap &instrMap, CWnd *parent = nullptr) - : CProgressDialog(parent) - , m_sndFile(sndFile) - , m_file(f) - , m_instrMap(instrMap) - { } - void Run() override; + DECLARE_MESSAGE_MAP(); }; Modified: trunk/OpenMPT/mptrack/mptrack.rc ============================================================================== --- trunk/OpenMPT/mptrack/mptrack.rc Sat Oct 26 21:58:29 2024 (r21993) +++ trunk/OpenMPT/mptrack/mptrack.rc Sat Oct 26 22:21:46 2024 (r21994) @@ -2055,23 +2055,30 @@ CONTROL "Tree1",IDC_TREEDATA,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | WS_BORDER,0,135,85,131 END -IDD_MOD2MIDI DIALOGEX 0, 0, 257, 98 +IDD_MOD2MIDI DIALOGEX 0, 0, 267, 194 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "MIDI Conversion Setup" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN - DEFPUSHBUTTON "OK",IDOK,198,6,50,14 - PUSHBUTTON "Cancel",IDCANCEL,198,24,50,14 - GROUPBOX "",IDC_STATIC,6,6,184,72 - LTEXT "Instrument:",IDC_STATIC,12,18,67,8 + DEFPUSHBUTTON "&OK",IDOK,156,174,50,14 + PUSHBUTTON "&Cancel",IDCANCEL,210,174,50,14 + GROUPBOX "Instruments",IDC_STATIC,6,6,252,90 + LTEXT "&Instrument:",IDC_STATIC,12,18,67,8 CONTROL "Spin1",IDC_SPIN1,"msctls_updown32",0x0,12,28,11,12 - COMBOBOX IDC_COMBO1,25,28,158,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "MIDI Channel:",IDC_STATIC,12,48,46,8 - COMBOBOX IDC_COMBO2,12,58,66,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "MIDI Program / Percussion:",IDC_STATIC,83,48,97,8 - COMBOBOX IDC_COMBO3,83,58,100,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Allow instruments to overlap on same MIDI channel",IDC_CHECK1, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,84,246,10 + COMBOBOX IDC_COMBO1,25,28,221,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "MIDI &Channel:",IDC_STATIC,12,48,46,8 + COMBOBOX IDC_COMBO2,12,58,84,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "MIDI &Program / Percussion:",IDC_STATIC,102,48,97,8 + COMBOBOX IDC_COMBO3,102,58,144,114,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Allow instruments to overlap on same &MIDI channel",IDC_CHECK1, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,78,234,10 + GROUPBOX "Sub Songs",IDC_STATIC,6,102,252,66 + CONTROL "&All Sub Songs (Separate Files)",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,12,116,132,10 + CONTROL "&Sub Song:",IDC_RADIO5,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,12,130,54,10 + EDITTEXT IDC_EDIT1,66,129,54,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_SPIN2,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,126,126,11,14 + CONTROL "",IDC_SUBSONG,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | WS_GROUP,23,145,223,18 + CONTROL "",IDC_PROGRESS1,"msctls_progress32",NOT WS_VISIBLE | WS_BORDER,6,174,144,14 END IDD_SAVEPRESET DIALOGEX 0, 0, 154, 58 @@ -2483,9 +2490,9 @@ IDD_MOD2MIDI, DIALOG BEGIN LEFTMARGIN, 4 - RIGHTMARGIN, 253 + RIGHTMARGIN, 263 TOPMARGIN, 4 - BOTTOMMARGIN, 94 + BOTTOMMARGIN, 190 END IDD_SAVEPRESET, DIALOG @@ -2704,6 +2711,11 @@ BEGIN 0 END + +IDD_MOD2MIDI AFX_DIALOG_LAYOUT +BEGIN + 0 +END #ifdef APSTUDIO_INVOKED |