123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 |
- /*
- * OPLExport.cpp
- * -------------
- * Purpose: Export of OPL register dumps as VGM/VGZ or DRO files
- * 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 "FileDialog.h"
- #include "InputHandler.h"
- #include "Mainfrm.h"
- #include "Moddoc.h"
- #include "ProgressDialog.h"
- #include "../soundlib/OPL.h"
- #include "../soundlib/Tagging.h"
- #include <zlib/zlib.h>
- OPENMPT_NAMESPACE_BEGIN
- // DRO file header
- struct DROHeaderV1
- {
- static constexpr char droMagic[] = "DBRAWOPL";
- char magic[8];
- uint16le verHi;
- uint16le verLo;
- uint32le lengthMs;
- uint32le lengthBytes;
- uint32le hardwareType;
- };
- MPT_BINARY_STRUCT(DROHeaderV1, 24);
- // VGM file header
- struct VGMHeader
- {
- static constexpr char VgmMagic[] = "Vgm ";
- char magic[4];
- uint32le eofOffset;
- uint32le version;
- uint32le sn76489clock;
- uint32le ym2413clock;
- uint32le gd3Offset;
- uint32le totalNumSamples;
- uint32le loopOffset;
- uint32le loopNumSamples;
- uint32le rate;
- uint32le someChipClocks[3];
- uint32le vgmDataOffset;
- uint32le variousChipClocks[9];
- uint32le ymf262clock; // 14318180
- uint32le evenMoreChipClocks[7];
- uint8 volumeModifier;
- uint8 reserved[131]; // Various other fields we're not interested in
- };
- MPT_BINARY_STRUCT(VGMHeader, 256);
- // VGM metadata header
- struct Gd3Header
- {
- static constexpr char Gd3Magic[] = "Gd3 ";
- char magic[4];
- uint32le version;
- uint32le size;
- };
- MPT_BINARY_STRUCT(Gd3Header, 12);
- // The OPL register logger and serializer for VGM/VGZ/DRO files
- class OPLCapture final : public OPL::IRegisterLogger
- {
- struct RegisterDump
- {
- CSoundFile::samplecount_t sampleOffset;
- uint8 regLo;
- uint8 regHi;
- uint8 value;
- };
- public:
- OPLCapture(CSoundFile &sndFile) : m_sndFile{sndFile} {}
- void Reset()
- {
- m_registerDump.clear();
- m_prevRegisters.clear();
- }
- void CaptureAllVoiceRegisters()
- {
- for(const auto reg : OPL::AllVoiceRegisters())
- {
- uint8 value = 0;
- if(const auto prevValue = m_prevRegisters.find(reg); prevValue != m_prevRegisters.end())
- value = prevValue->second;
- m_registerDumpAtLoopStart[reg] = value;
- }
- }
- void WriteDRO(std::ostream &f) const
- {
- DROHeaderV1 header{};
- memcpy(header.magic, DROHeaderV1::droMagic, 8);
- header.verHi = 0;
- header.verLo = 1;
- header.lengthMs = Util::muldivr_unsigned(m_sndFile.GetTotalSampleCount(), 1000, m_sndFile.GetSampleRate());
- header.lengthBytes = 0;
- header.hardwareType = 1; // OPL3
- mpt::IO::Write(f, header);
- CSoundFile::samplecount_t prevOffset = 0, prevOffsetMs = 0;
- bool prevHigh = false;
- for(const auto ® : m_registerDump)
- {
- if(reg.sampleOffset > prevOffset)
- {
- uint32 offsetMs = Util::muldivr_unsigned(reg.sampleOffset, 1000, m_sndFile.GetSampleRate());
- header.lengthBytes += WriteDRODelay(f, offsetMs - prevOffsetMs);
- prevOffset = reg.sampleOffset;
- prevOffsetMs = offsetMs;
- }
- if(const bool isHigh = (reg.regHi == 1); isHigh != prevHigh)
- {
- prevHigh = isHigh;
- mpt::IO::Write(f, mpt::as_byte(2 + reg.regHi));
- header.lengthBytes++;
- }
- if(reg.regLo <= 4)
- {
- mpt::IO::Write(f, mpt::as_byte(4));
- header.lengthBytes++;
- }
- const uint8 regValue[] = {reg.regLo, reg.value};
- mpt::IO::Write(f, regValue);
- header.lengthBytes += 2;
- }
- if(header.lengthMs > prevOffsetMs)
- header.lengthBytes += WriteDRODelay(f, header.lengthMs - prevOffsetMs);
-
- MPT_ASSERT(mpt::IO::TellWrite(f) == static_cast<mpt::IO::Offset>(header.lengthBytes + sizeof(header)));
- // AdPlug can read some metadata following the register dump, but DroTrimmer panics if it see that data.
- // As the metadata is very limited (40 characters per field, unknown 8-bit encoding) we'll leave that feature to the VGM export.
- #if 0
- mpt::IO::Write(f, mpt::as_byte(0xFF));
- mpt::IO::Write(f, mpt::as_byte(0xFF));
- char name[40];
- mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = m_sndFile.m_songName;
- mpt::IO::Write(f, mpt::as_byte(0x1A));
- mpt::IO::Write(f, name);
- if(!m_sndFile.m_songArtist.empty())
- {
- mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = mpt::ToCharset(mpt::Charset::ISO8859_1, m_sndFile.m_songArtist);
- mpt::IO::Write(f, mpt::as_byte(0x1B));
- mpt::IO::Write(f, name);
- }
- #endif
- mpt::IO::SeekAbsolute(f, 0);
- mpt::IO::Write(f, header);
- }
- void WriteVGZ(std::ostream &f, const CSoundFile::samplecount_t loopStart, const FileTags &fileTags, const mpt::ustring &filename) const
- {
- std::ostringstream outStream;
- WriteVGM(outStream, loopStart, fileTags);
- std::string outData = std::move(outStream).str();
- z_stream strm{};
- strm.avail_in = static_cast<uInt>(outData.size());
- strm.next_in = reinterpret_cast<Bytef *>(outData.data());
- if(deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 | 16, 9, Z_DEFAULT_STRATEGY) != Z_OK)
- throw std::runtime_error{"zlib init failed"};
- gz_header gzHeader{};
- gzHeader.time = static_cast<uLong>(time(nullptr));
- std::string filenameISO = mpt::ToCharset(mpt::Charset::ISO8859_1, filename);
- gzHeader.name = reinterpret_cast<Bytef *>(filenameISO.data());
- deflateSetHeader(&strm, &gzHeader);
- do
- {
- std::array<Bytef, mpt::IO::BUFFERSIZE_TINY> buffer;
- strm.avail_out = static_cast<uInt>(buffer.size());
- strm.next_out = buffer.data();
- deflate(&strm, Z_FINISH);
- mpt::IO::WritePartial(f, buffer, buffer.size() - strm.avail_out);
- } while(strm.avail_out == 0);
- deflateEnd(&strm);
- }
-
- void WriteVGM(std::ostream &f, const CSoundFile::samplecount_t loopStart, const FileTags &fileTags) const
- {
- VGMHeader header{};
- memcpy(header.magic, VGMHeader::VgmMagic, 4);
- header.version = 0x160;
- header.vgmDataOffset = sizeof(header) - offsetof(VGMHeader, vgmDataOffset);
- header.ymf262clock = 14318180;
- header.totalNumSamples = static_cast<uint32>(m_sndFile.GetTotalSampleCount());
- if(loopStart != Util::MaxValueOfType(loopStart))
- header.loopNumSamples = static_cast<uint32>(m_sndFile.GetTotalSampleCount() - loopStart);
- mpt::IO::Write(f, header);
- bool wroteLoopStart = (header.loopNumSamples == 0);
- CSoundFile::samplecount_t prevOffset = 0;
- for(const auto ® : m_registerDump)
- {
- if(reg.sampleOffset >= loopStart && !wroteLoopStart)
- {
- WriteVGMDelay(f, loopStart - prevOffset);
- prevOffset = loopStart;
- header.loopOffset = static_cast<uint32>(mpt::IO::TellWrite(f) - 0x1C);
- wroteLoopStart = true;
- for(const auto & [loopReg, value] : m_registerDumpAtLoopStart)
- {
- if(m_prevRegisters.count(loopReg))
- {
- const uint8 data[] = {static_cast<uint8>(0x5E + (loopReg >> 8)), static_cast<uint8>(loopReg & 0xFF), value};
- mpt::IO::Write(f, data);
- }
- }
- }
- WriteVGMDelay(f, reg.sampleOffset - prevOffset);
- prevOffset = reg.sampleOffset;
- const uint8 data[] = {static_cast<uint8>(0x5E + reg.regHi), reg.regLo, reg.value};
- mpt::IO::Write(f, data);
- }
- WriteVGMDelay(f, m_sndFile.GetTotalSampleCount() - prevOffset);
- mpt::IO::Write(f, mpt::as_byte(0x66));
- header.gd3Offset = static_cast<uint32>(mpt::IO::TellWrite(f) - offsetof(VGMHeader, gd3Offset));
- const mpt::ustring tags[] =
- {
- fileTags.title,
- {}, // Song name JP
- {}, // Game name EN
- {}, // Game name JP
- Version::Current().GetOpenMPTVersionString(),
- {}, // System name JP
- fileTags.artist,
- {}, // Author name JP
- fileTags.year,
- {}, // Person who created the VGM file
- mpt::String::Replace(fileTags.comments, U_("\r\n"), U_("\n")),
- };
- std::ostringstream tagStream;
- for(const auto &tag : tags)
- {
- WriteVGMString(tagStream, mpt::ToWide(tag));
- }
- const auto tagsData = std::move(tagStream).str();
- Gd3Header gd3Header{};
- memcpy(gd3Header.magic, Gd3Header::Gd3Magic, 4);
- gd3Header.version = 0x100;
- gd3Header.size = static_cast<uint32>(tagsData.size());
- mpt::IO::Write(f, gd3Header);
- mpt::IO::WriteRaw(f, mpt::as_span(tagsData));
- header.eofOffset = static_cast<uint32>(mpt::IO::TellWrite(f) - offsetof(VGMHeader, eofOffset));
- mpt::IO::SeekAbsolute(f, 0);
- mpt::IO::Write(f, header);
- }
- private:
- static uint32 WriteDRODelay(std::ostream &f, uint32 delay)
- {
- uint32 bytesWritten = 0;
- while(delay > 256)
- {
- uint32 subDelay = std::min(delay, 65536u);
- mpt::IO::Write(f, mpt::as_byte(1));
- mpt::IO::WriteIntLE(f, static_cast<uint16>(subDelay - 1));
- bytesWritten += 3;
- delay -= subDelay;
- }
- if(delay)
- {
- mpt::IO::Write(f, mpt::as_byte(0));
- mpt::IO::WriteIntLE(f, static_cast<uint8>(delay - 1));
- bytesWritten += 2;
- }
- return bytesWritten;
- }
- static void WriteVGMDelay(std::ostream &f, CSoundFile::samplecount_t delay)
- {
- while(delay)
- {
- uint16 subDelay = mpt::saturate_cast<uint16>(delay);
- if(subDelay <= 16)
- {
- mpt::IO::Write(f, mpt::as_byte(0x6F + subDelay));
- } else if(subDelay == 735)
- {
- mpt::IO::Write(f, mpt::as_byte(0x62)); // 1/60th of a second
- } else if(subDelay == 882)
- {
- mpt::IO::Write(f, mpt::as_byte(0x63)); // 1/50th of a second
- } else
- {
- mpt::IO::Write(f, mpt::as_byte(0x61));
- mpt::IO::WriteIntLE(f, subDelay);
- }
- delay -= subDelay;
- }
- }
- static void WriteVGMString(std::ostream &f, const std::wstring &s)
- {
- std::vector<uint16le> s16le(s.length() + 1);
- for(size_t i = 0; i < s.length(); i++)
- {
- s16le[i] = s[i] ? s[i] : L' ';
- }
- mpt::IO::Write(f, s16le);
- }
- void Port(CHANNELINDEX, uint16 reg, uint8 value) override
- {
- if(const auto prevValue = m_prevRegisters.find(reg); prevValue != m_prevRegisters.end() && prevValue->second == value)
- return;
- m_registerDump.push_back({m_sndFile.GetTotalSampleCount(), static_cast<uint8>(reg & 0xFF), static_cast<uint8>(reg >> 8), value});
- m_prevRegisters[reg] = value;
- }
- std::vector<RegisterDump> m_registerDump;
- std::map<uint16, uint8> m_prevRegisters, m_registerDumpAtLoopStart;
- CSoundFile &m_sndFile;
- };
- class OPLExportDlg : public CProgressDialog
- {
- private:
- enum class ExportFormat
- {
- VGZ = IDC_RADIO1,
- VGM = IDC_RADIO2,
- DRO = IDC_RADIO3,
- };
- static ExportFormat s_format;
- OPLCapture m_oplLogger;
- CSoundFile &m_sndFile;
- CModDoc &m_modDoc;
- std::vector<SubSong> m_subSongs;
- size_t m_selectedSong = 0;
- bool m_conversionRunning = false;
- bool m_locked = true;
- public:
- OPLExportDlg(CModDoc &modDoc, CWnd *parent = nullptr)
- : CProgressDialog{parent, IDD_OPLEXPORT}
- , m_oplLogger{modDoc.GetSoundFile()}
- , m_sndFile{modDoc.GetSoundFile()}
- , m_modDoc{modDoc}
- , m_subSongs{modDoc.GetSoundFile().GetAllSubSongs()}
- {
- }
- BOOL OnInitDialog() override
- {
- CProgressDialog::OnInitDialog();
- CheckRadioButton(IDC_RADIO1, IDC_RADIO3, static_cast<int>(s_format));
- CheckRadioButton(IDC_RADIO4, IDC_RADIO5, IDC_RADIO4);
- static_cast<CSpinButtonCtrl *>(GetDlgItem(IDC_SPIN1))->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_SPIN1};
- for(int control : controls)
- GetDlgItem(control)->EnableWindow(FALSE);
- }
- UpdateSubsongName();
- OnFormatChanged();
- SetDlgItemText(IDC_EDIT2, mpt::ToWin(m_sndFile.GetCharsetFile(), m_sndFile.GetTitle()).c_str());
- SetDlgItemText(IDC_EDIT3, mpt::ToWin(m_sndFile.m_songArtist).c_str());
- if(!m_sndFile.GetFileHistory().empty())
- SetDlgItemText(IDC_EDIT4, mpt::ToWin(mpt::String::Replace(m_sndFile.GetFileHistory().back().AsISO8601().substr(0, 10), U_("-"), U_("/"))).c_str());
- SetDlgItemText(IDC_EDIT5, mpt::ToWin(m_sndFile.GetCharsetFile(), m_sndFile.m_songMessage.GetFormatted(SongMessage::leCRLF)).c_str());
- m_locked = false;
- return TRUE;
- }
- void OnOK() override
- {
- mpt::PathString extension = P_("vgz");
- s_format = static_cast<ExportFormat>(GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3));
- if(s_format == ExportFormat::DRO)
- extension = P_("dro");
- else if(s_format == ExportFormat::VGM)
- extension = P_("vgm");
- FileDialog dlg = SaveFileDialog()
- .DefaultExtension(extension)
- .DefaultFilename(m_modDoc.GetPathNameMpt().GetFileName().ReplaceExt(P_(".") + extension))
- .ExtensionFilter(MPT_UFORMAT("{} Files|*.{}||")(mpt::ToUpperCase(extension.ToUnicode()), extension))
- .WorkingDirectory(TrackerSettings::Instance().PathExport.GetWorkingDir());
- if(!dlg.Show())
- {
- OnCancel();
- return;
- }
- TrackerSettings::Instance().PathExport.SetWorkingDir(dlg.GetWorkingDirectory());
- DoConversion(dlg.GetFirstFile());
- CProgressDialog::OnOK();
- }
- void OnCancel() override
- {
- if(m_conversionRunning)
- CProgressDialog::OnCancel();
- else
- CDialog::OnCancel();
- }
- void Run() override {}
- afx_msg void OnFormatChanged()
- {
- const int controls[] = {IDC_EDIT2, IDC_EDIT3, IDC_EDIT4, IDC_EDIT5};
- for(int control : controls)
- GetDlgItem(control)->EnableWindow(GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3) == static_cast<int>(ExportFormat::DRO) ? FALSE : TRUE);
- }
- afx_msg void OnSubsongChanged()
- {
- 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();
- }
- void UpdateSubsongName()
- {
- 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());
- }
- void DoConversion(const mpt::PathString &fileName)
- {
- const int controls[] = {IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4, IDC_RADIO5, IDC_EDIT1, IDC_EDIT2, IDC_EDIT3, IDC_EDIT4, IDC_EDIT5, IDC_SPIN1, IDOK};
- for(int control : controls)
- GetDlgItem(control)->EnableWindow(FALSE);
- BypassInputHandler bih;
- CMainFrame::GetMainFrame()->StopMod(&m_modDoc);
- FileTags fileTags;
- {
- CString title, artist, date, notes;
- GetDlgItemText(IDC_EDIT2, title);
- GetDlgItemText(IDC_EDIT3, artist);
- GetDlgItemText(IDC_EDIT4, date);
- GetDlgItemText(IDC_EDIT5, notes);
- fileTags.title = mpt::ToUnicode(title);
- fileTags.artist = mpt::ToUnicode(artist);
- fileTags.year = mpt::ToUnicode(date);
- fileTags.comments = mpt::ToUnicode(notes);
- }
- if(IsDlgButtonChecked(IDC_RADIO5))
- m_subSongs = {m_subSongs[m_selectedSong]};
- 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; }) * m_sndFile.GetSampleRate()));
- GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_SHOW);
- m_sndFile.m_bIsRendering = true;
- const auto origSettings = m_sndFile.m_MixerSettings;
- auto newSettings = m_sndFile.m_MixerSettings;
- if(s_format != ExportFormat::DRO)
- newSettings.gdwMixingFreq = 44100; // required for VGM, DRO doesn't care
- m_sndFile.SetMixerSettings(newSettings);
- const auto origSequence = m_sndFile.Order.GetCurrentSequenceIndex();
- const auto origRepeatCount = m_sndFile.GetRepeatCount();
- m_sndFile.SetRepeatCount(0);
- auto opl = std::move(m_sndFile.m_opl);
- const auto songIndexFmt = mpt::FormatSpec{}.Dec().FillNul().Width(1 + static_cast<int>(std::log10(m_subSongs.size())));
- size_t totalSamples = 0;
- for(size_t i = 0; i < m_subSongs.size() && !m_abort; i++)
- {
- const auto &song = m_subSongs[i];
- m_sndFile.ResetPlayPos();
- m_sndFile.GetLength(eAdjust, GetLengthTarget(song.startOrder, song.startRow).StartPos(song.sequence, 0, 0));
- m_sndFile.m_SongFlags.reset(SONG_PLAY_FLAGS);
- m_oplLogger.Reset();
- m_sndFile.m_opl = std::make_unique<OPL>(m_oplLogger);
- auto prevTime = timeGetTime();
- CSoundFile::samplecount_t loopStart = std::numeric_limits<CSoundFile::samplecount_t>::max(), subsongSamples = 0;
- while(!m_abort)
- {
- auto count = m_sndFile.ReadOneTick();
- if(count == 0)
- break;
- if(loopStart == Util::MaxValueOfType(loopStart)
- && m_sndFile.m_PlayState.m_nCurrentOrder == song.loopStartOrder && m_sndFile.m_PlayState.m_nRow == song.loopStartRow
- && (song.loopStartOrder != song.startOrder || song.loopStartRow != song.startRow))
- {
- loopStart = subsongSamples;
- m_oplLogger.CaptureAllVoiceRegisters(); // Make sure all registers are in the correct state when looping back
- }
- totalSamples += count;
- subsongSamples += count;
- auto currentTime = timeGetTime();
- if(currentTime - prevTime >= 16)
- {
- prevTime = currentTime;
- auto timeSec = subsongSamples / m_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();
- }
- }
- if(m_sndFile.m_SongFlags[SONG_BREAKTOROW] && loopStart == Util::MaxValueOfType(loopStart) && song.loopStartOrder == song.startOrder && song.loopStartRow == song.startRow)
- loopStart = 0;
- mpt::PathString currentFileName = fileName;
- if(m_subSongs.size() > 1)
- currentFileName = fileName.ReplaceExt(mpt::PathString::FromNative(MPT_TFORMAT(" ({})")(mpt::ufmt::fmt(i + 1, songIndexFmt))) + fileName.GetFileExt());
- mpt::SafeOutputFile sf(currentFileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
- mpt::ofstream &f = sf;
- try
- {
- if(!f)
- throw std::exception{};
- f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
- if(s_format == ExportFormat::DRO)
- m_oplLogger.WriteDRO(f);
- else if(s_format == ExportFormat::VGM)
- m_oplLogger.WriteVGM(f, loopStart, fileTags);
- else
- m_oplLogger.WriteVGZ(f, loopStart, fileTags, currentFileName.ReplaceExt(P_(".vgm")).GetFullFileName().ToUnicode());
- } catch(const std::exception &)
- {
- Reporting::Error(MPT_UFORMAT("Unable to write to file {}!")(currentFileName));
- break;
- }
- }
- // Reset globals to previous values
- m_sndFile.m_opl = std::move(opl);
- m_sndFile.SetRepeatCount(origRepeatCount);
- m_sndFile.Order.SetSequence(origSequence);
- m_sndFile.ResetPlayPos();
- m_sndFile.SetMixerSettings(origSettings);
- m_sndFile.m_bIsRendering = false;
- }
- DECLARE_MESSAGE_MAP()
- };
- OPLExportDlg::ExportFormat OPLExportDlg::s_format = OPLExportDlg::ExportFormat::VGZ;
- BEGIN_MESSAGE_MAP(OPLExportDlg, CDialog)
- //{{AFX_MSG_MAP(OPLExportDlg)
- ON_COMMAND(IDC_RADIO1, &OPLExportDlg::OnFormatChanged)
- ON_COMMAND(IDC_RADIO2, &OPLExportDlg::OnFormatChanged)
- ON_COMMAND(IDC_RADIO3, &OPLExportDlg::OnFormatChanged)
- ON_EN_CHANGE(IDC_EDIT1, &OPLExportDlg::OnSubsongChanged)
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
- void CModDoc::OnFileOPLExport()
- {
- bool anyOPL = false;
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- if(m_SndFile.GetSample(smp).uFlags[CHN_ADLIB])
- {
- anyOPL = true;
- break;
- }
- }
- if(!anyOPL)
- {
- Reporting::Information(_T("This module does not use any OPL instruments."), _T("No OPL Instruments Found"));
- return;
- }
- OPLExportDlg dlg{*this, CMainFrame::GetMainFrame()};
- dlg.DoModal();
- }
- OPENMPT_NAMESPACE_END
|