diff --git a/source/app/powertabeditor.cpp b/source/app/powertabeditor.cpp index 7d3e48c71..e8c00c3aa 100644 --- a/source/app/powertabeditor.cpp +++ b/source/app/powertabeditor.cpp @@ -349,7 +349,7 @@ bool PowerTabEditor::saveFile(QString path) { QFileInfo info(path); QString extension = info.suffix(); - Q_ASSERT(extension == "pt2"); + Q_ASSERT(!extension.isEmpty()); boost::optional format = myFileFormatManager->findFormat(extension.toStdString()); @@ -363,23 +363,29 @@ bool PowerTabEditor::saveFile(QString path) const std::string path_str = path.toStdString(); Document &doc = myDocumentManager->getCurrentDocument(); - if (!myFileFormatManager->exportFile(doc.getScore(), path_str, *format)) + if (!myFileFormatManager->exportFile(doc.getScore(), path_str, *format, + this)) + { return false; + } - doc.setFilename(path_str); + if (extension == "pt2") + { + doc.setFilename(path_str); - // Update window title and tab bar. - updateWindowTitle(); - const QString filename = info.fileName(); - myTabWidget->setTabText(myTabWidget->currentIndex(), filename); - myTabWidget->setTabToolTip(myTabWidget->currentIndex(), filename); + // Update window title and tab bar. + updateWindowTitle(); + const QString filename = info.fileName(); + myTabWidget->setTabText(myTabWidget->currentIndex(), filename); + myTabWidget->setTabToolTip(myTabWidget->currentIndex(), filename); - // Add to the recent files list and update the last used directory. - myRecentFiles->add(path); - setPreviousDirectory(path); + // Add to the recent files list and update the last used directory. + myRecentFiles->add(path); + setPreviousDirectory(path); - // Mark the file as being in an unmodified state. - myUndoManager->setClean(); + // Mark the file as being in an unmodified state. + myUndoManager->setClean(); + } return true; } @@ -394,12 +400,6 @@ bool PowerTabEditor::saveFileAs() if (path.isEmpty()) return false; - // If the user didn't type the extension, add it in. - QFileInfo info(path); - QString extension = info.suffix(); - if (extension.isEmpty()) - path += "." + extension; - return saveFile(path); } diff --git a/source/formats/CMakeLists.txt b/source/formats/CMakeLists.txt index c6369c709..982b8c7b2 100644 --- a/source/formats/CMakeLists.txt +++ b/source/formats/CMakeLists.txt @@ -27,6 +27,9 @@ add_library(pteformats guitar_pro/guitarproimporter.h guitar_pro/inputstream.h + midi/midiexporter.cpp + midi/midiexporter.h + powertab/powertabexporter.cpp powertab/powertabimporter.cpp diff --git a/source/formats/fileformat.cpp b/source/formats/fileformat.cpp index f583b297d..230a00b35 100644 --- a/source/formats/fileformat.cpp +++ b/source/formats/fileformat.cpp @@ -26,9 +26,10 @@ FileFormat::FileFormat(const std::string &name, { } -bool FileFormat::operator<(const FileFormat &format) const +bool FileFormat::operator==(const FileFormat &format) const { - return myName < format.myName; + return myName == format.myName && + myFileExtensions == format.myFileExtensions; } std::string FileFormat::fileFilter() const diff --git a/source/formats/fileformat.h b/source/formats/fileformat.h index 817f1073b..853b399ee 100644 --- a/source/formats/fileformat.h +++ b/source/formats/fileformat.h @@ -30,7 +30,7 @@ class FileFormat FileFormat(const std::string &myName, const std::vector &myFileExtensions); - bool operator<(const FileFormat &format) const; + bool operator==(const FileFormat &format) const; /// Returns a correctly formatted file filter for a Qt file dialog. /// e.g. "FileType (*.ext1 *.ext2)". diff --git a/source/formats/fileformatmanager.cpp b/source/formats/fileformatmanager.cpp index b1b15de16..308961cec 100644 --- a/source/formats/fileformatmanager.cpp +++ b/source/formats/fileformatmanager.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -26,21 +27,28 @@ FileFormatManager::FileFormatManager() { - registerImporter(); - registerImporter(); - registerExporter(); - registerImporter(); - registerImporter(); + myImporters.emplace_back(new PowerTabImporter()); + myImporters.emplace_back(new PowerTabOldImporter()); + myImporters.emplace_back(new GuitarProImporter()); + myImporters.emplace_back(new GpxImporter()); + + myExporters.emplace_back(new PowerTabExporter()); + myExporters.emplace_back(new MidiExporter()); } boost::optional FileFormatManager::findFormat( const std::string &extension) const { - for (auto importer = myImporters.begin(); importer != myImporters.end(); - ++importer) + for (auto &importer : myImporters) + { + if (importer->fileFormat().contains(extension)) + return importer->fileFormat(); + } + + for (auto &exporter : myExporters) { - if (importer->first.contains(extension)) - return importer->first; + if (exporter->fileFormat().contains(extension)) + return exporter->fileFormat(); } return boost::none; @@ -51,14 +59,14 @@ std::string FileFormatManager::importFileFilter() const std::string filterAll = "All Supported Formats ("; std::string filterOther; - for (auto importer = myImporters.begin(); importer != myImporters.end(); - ++importer) + for (auto it = myImporters.begin(); it != myImporters.end(); ++it) { - if (importer != myImporters.begin()) + if (it != myImporters.begin()) filterAll += " "; - filterAll += importer->first.allExtensions(); - filterOther += ";;" + importer->first.fileFilter(); + const FileFormat &format = (*it)->fileFormat(); + filterAll += format.allExtensions(); + filterOther += ";;" + format.fileFilter(); } filterAll += ")"; @@ -67,21 +75,26 @@ std::string FileFormatManager::importFileFilter() const } bool FileFormatManager::importFile(Score &score, const std::string &filename, - const FileFormat &format, QWidget *parentWindow) + const FileFormat &format, + QWidget *parent_window) { - if (myImporters.find(format) != myImporters.end()) + for (auto &importer : myImporters) { - try + if (importer->fileFormat() == format) { - myImporters.at(format)->load(filename, score); - return true; - } - catch (const std::exception &e) - { - QMessageBox msgBox(parentWindow); - msgBox.setText(QObject::tr("Error importing file - ") + QString(e.what())); - msgBox.exec(); - return false; + try + { + importer->load(filename, score); + return true; + } + catch (const std::exception &e) + { + QMessageBox msg(parent_window); + msg.setText(QObject::tr("Error importing file - ") + + QString(e.what())); + msg.exec(); + return false; + } } } @@ -92,33 +105,39 @@ std::string FileFormatManager::exportFileFilter() const { std::string filter; - for (auto exporter = myExporters.begin(); exporter != myExporters.end(); - ++exporter) + for (auto it = myExporters.begin(); it != myExporters.end(); ++it) { - if (exporter != myExporters.begin()) + if (it != myExporters.begin()) filter += ";;"; - filter += exporter->first.fileFilter(); + filter += (*it)->fileFormat().fileFilter(); } return filter; } -bool FileFormatManager::exportFile(const Score &score, const std::string &filename, - const FileFormat &format) +bool FileFormatManager::exportFile(const Score &score, + const std::string &filename, + const FileFormat &format, + QWidget *parent_window) { - if (myExporters.find(format) != myExporters.end()) + for (auto &exporter : myExporters) { - try - { - myExporters.at(format)->save(filename, score); - return true; - } - catch (const std::exception &e) + if (exporter->fileFormat() == format) { - QMessageBox msgBox; - msgBox.setText(QObject::tr("Error saving file - ") + QString(e.what())); - msgBox.exec(); + try + { + exporter->save(filename, score); + return true; + } + catch (const std::exception &e) + { + QMessageBox msg(parent_window); + msg.setText(QObject::tr("Error saving file - ") + + QString(e.what())); + msg.exec(); + return false; + } } } diff --git a/source/formats/fileformatmanager.h b/source/formats/fileformatmanager.h index bf85e239f..f73e2427e 100644 --- a/source/formats/fileformatmanager.h +++ b/source/formats/fileformatmanager.h @@ -19,8 +19,8 @@ #define FORMATS_FILEFORMATMANAGER_H #include -#include #include +#include #include "fileformat.h" class FileFormatImporter; @@ -43,14 +43,14 @@ class FileFormatManager /// Imports a file into the given score. bool importFile(Score &score, const std::string &filename, - const FileFormat &format, QWidget *parentWindow); + const FileFormat &format, QWidget *parent_window); /// Returns a correctly formatted file filter for a Qt file dialog. std::string exportFileFilter() const; /// Exports the given score to a file. bool exportFile(const Score &score, const std::string &filename, - const FileFormat &format); + const FileFormat &format, QWidget *parent_window); private: template @@ -59,22 +59,8 @@ class FileFormatManager template void registerExporter(); - std::map> myImporters; - std::map> myExporters; + std::vector> myImporters; + std::vector> myExporters; }; -template -void FileFormatManager::registerImporter() -{ - FileFormat format = Importer().fileFormat(); - myImporters[format].reset(new Importer()); -} - -template -void FileFormatManager::registerExporter() -{ - FileFormat format = Exporter().fileFormat(); - myExporters[format].reset(new Exporter()); -} - #endif diff --git a/source/formats/midi/midiexporter.cpp b/source/formats/midi/midiexporter.cpp new file mode 100644 index 000000000..9e020a02c --- /dev/null +++ b/source/formats/midi/midiexporter.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 Cameron White + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +#include "midiexporter.h" + +#include + +#include +#include +#include +#include + +static uint8_t bswap(uint8_t val) +{ + return val; +} + +static uint16_t bswap(uint16_t val) +{ + return _byteswap_ushort(val); +} + +static uint32_t bswap(uint32_t val) +{ + return _byteswap_ulong(val); +} + +template +static void write(std::ostream &os, T val) +{ + val = bswap(val); + os.write(reinterpret_cast(&val), sizeof(T)); +} + +static void writeVariableLength(std::ostream &os, uint32_t val) +{ + std::array bytes; + for (int i = 0; i < 5; ++i) + bytes[i] = (val >> ((4 - i) * 7)) & 0x7f; + + // Find the first byte that needs to be written. + int i = 0; + for (i = 0; i < 4; ++i) + { + if (bytes[i] != 0) + break; + } + + for (; i < 5; ++i) + { + // Set the top bit to indicate that more bytes will follow it. + if (i < 4) + bytes[i] |= 0x80; + + write(os, bytes[i]); + } +} + +MidiExporter::MidiExporter() + : FileFormatExporter(FileFormat("MIDI File", { "mid" })) +{ +} + +void MidiExporter::save(const std::string &filename, const Score &score) +{ + std::ofstream os(filename, std::ios::out | std::ios::binary); + os.exceptions(std::ios::failbit | std::ios::badbit | std::ios::eofbit); + + MidiFile file(score); + writeHeader(os, file); + + for (const MidiEventList &track : file.getTracks()) + writeTrack(os, track); +} + +void MidiExporter::writeHeader(std::ostream &os, const MidiFile &file) +{ + // Chunk ID for the header chunk. + os << "MThd"; + // 6 bytes will follow the chunk size. + write(os, static_cast(6)); + + // A format type of 1 indicates that we'll have multiple tracks. + write(os, static_cast(1)); + write(os, static_cast(file.getTracks().size())); + + // Time division. + write(os, static_cast(file.getTicksPerBeat())); +} + +void MidiExporter::writeTrack(std::ostream & os, const MidiEventList & events) +{ + // Chunk ID for a track chunk. + os << "MTrk"; + + // Size in bytes of the track chunk. This will be updated after writing out + // all of the data. + const std::iostream::pos_type chunk_len_pos = os.tellp(); + write(os, static_cast(0)); + + // Write out the MIDI events. + const std::iostream::pos_type chunk_start_pos = os.tellp(); + for (const MidiEvent &event : events) + { + writeVariableLength(os, event.getTicks()); + write(os, static_cast(event.getStatusByte())); + os.write(reinterpret_cast(event.getData().data()), + event.getData().size()); + } + + const std::iostream::pos_type chunk_end_pos = os.tellp(); + + // Record the size in bytes of the track chunk. + os.seekp(chunk_len_pos); + write(os, static_cast(chunk_end_pos - chunk_start_pos)); + os.seekp(chunk_end_pos); +} diff --git a/source/formats/midi/midiexporter.h b/source/formats/midi/midiexporter.h new file mode 100644 index 000000000..c3de6df59 --- /dev/null +++ b/source/formats/midi/midiexporter.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Cameron White + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +#ifndef FORMATS_MIDIEXPORTER_H +#define FORMATS_MIDIEXPORTER_H + +#include + +class MidiEventList; +class MidiFile; + +class MidiExporter : public FileFormatExporter +{ +public: + MidiExporter(); + + virtual void save(const std::string &filename, const Score &score) override; + +private: + static void writeHeader(std::ostream &os, const MidiFile &file); + static void writeTrack(std::ostream &os, const MidiEventList &events); +}; + +#endif