|
From: Lasse Kärkkäi. <tr...@us...> - 2011-02-25 18:19:51
|
Author: Lasse Kärkkäinen <tronic+ndrm at trn.iki.fi> Date: Fri Feb 25 19:19:24 2011 +0100 Midifile cleanup and songwriter WIP --- midifile.cc | 46 ++++++++++++++++++++++++++++++++++++++++++---- midifile.hh | 48 +++++++++++++++++++++--------------------------- songparser-ini.cc | 8 ++++---- songwriter-ini.cc | 22 ++++++++++------------ 4 files changed, 77 insertions(+), 47 deletions(-) diff --git a/midifile.cc b/midifile.cc index 9386910..afee1ca 100644 --- a/midifile.cc +++ b/midifile.cc @@ -5,9 +5,10 @@ #include <iterator> #include <sstream> -using namespace mid; +using namespace midifile; Reader::Reader(char const* filename) { + // Read the entire file into a buffer { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) throw std::runtime_error("Unable to open " + std::string(filename)); @@ -21,11 +22,10 @@ Reader::Reader(char const* filename) { m_fileEnd = m_pos + size; } parseMThd(); - parseMTrk(); } -bool Reader::nextTrack() { - m_pos = m_riffEnd; +bool Reader::startTrack() { + m_pos = m_riffEnd; // Jump to the end of the current riff if (m_pos == m_fileEnd) return false; parseMTrk(); return true; @@ -99,6 +99,27 @@ bool Reader::parseEvent(Event& ev) { return true; } +Writer::Writer(unsigned fmt, unsigned tracks, unsigned division) { + beginRiff("MThd"); + if (fmt == 0 && tracks != 1) throw std::logic_error("Format 0 MIDI must have exactly one track"); + if (fmt == 1 && tracks < 2) throw std::logic_error("Format 1 MIDI must have a separate timing track"); + if (division == 0) throw std::logic_error("Division must be set to a positive value"); + write<2>(fmt); + write<2>(tracks); + write<2>(division); +} + +void Writer::save(char const* filename) { + endRiff(); + std::ofstream f(filename, std::ios::binary); + f.write(reinterpret_cast<char const*>(&m_data[0]), m_data.size()); +} + +void Writer::startTrack() { + endRiff(); + beginRiff("MTrk"); +} + void Writer::writeEvent(Event const& ev) { write_varlen(ev.timecode); if (ev.type & ~0xF0 || ev.type < 0x80) throw std::logic_error("Invalid MIDI event type"); @@ -124,6 +145,23 @@ void Writer::writeEvent(Event const& ev) { } } +void Writer::beginRiff(char const* name) { + m_riffBegin = m_data.size(); + m_data.resize(m_riffBegin + 8); // Add space for header + std::copy(name, name + 4, m_data.begin() + m_riffBegin); // Set RIFF name +} + +void Writer::endRiff() { + unsigned size = m_data.size() - m_riffBegin; + if (size == 0) return; // Nothing to close + size -= 8; + m_data[m_riffBegin + 4] = size >> 24; + m_data[m_riffBegin + 5] = size >> 16; + m_data[m_riffBegin + 6] = size >> 8; + m_data[m_riffBegin + 7] = size; + m_riffBegin = m_data.size(); +} + // Debugging facilities follow namespace { diff --git a/midifile.hh b/midifile.hh index 4983d4b..6abeba6 100644 --- a/midifile.hh +++ b/midifile.hh @@ -5,7 +5,7 @@ #include <string> #include <vector> -namespace mid { +namespace midifile { typedef uint8_t value_type; typedef uint8_t* iterator; typedef uint8_t const* const_iterator; @@ -54,13 +54,16 @@ namespace mid { class Reader { public: static const unsigned margin = 20; ///< The minimum number of extra bytes required after the end of the buffer for more efficient processing + /// MIDI reader, read header. Reader(char const* filename); + /// Get the number of tracks, including the timing track if used in the current format unsigned numTracks() const { return m_tracks; } - unsigned Tracks() const { return m_tracks; } - /// Switch to the next track, returns false if end of file reached (all track processed) - bool nextTrack(); + /// Start reading a track (or jump to the next track), must be called before parsing any events + /// @returns false if end of file reached (all tracks processed) + bool startTrack(); /// Parse the next event of the current track, returns false if at the end of track bool parseEvent(Event& ev); + /// Get the number of timecode units in a beat (1/4 note) unsigned getDivision() const { return m_division; } private: void parseMThd(); @@ -94,30 +97,21 @@ namespace mid { class Writer { public: - private: - void writeMThd(unsigned tracks, unsigned division) { - beginRiff("MThd"); - write<2>(1); // fmt 1 (multitrack) - write<2>(tracks); - write<2>(division); - endRiff(); - } - void writeMTrk() { - beginRiff("MTrk"); - } + /// MIDI file writer (constructs the output in a memory buffer) + /// @param fmt MIDI format (use 1 if in doubt) + /// @param tracks The number of tracks (with fmt 1 this is one more than the actual tracks) + /// @param division How many timecode units fit into a beat (1/4 note) + Writer(unsigned fmt, unsigned tracks, unsigned division); + /// Flush the output to a file (after writing everything) + void save(char const* filename); + /// Start a new track, must be called before each track. + /// Ends any previous track but won't automatically add end of track event. + void startTrack(); + /// Writes an event to current track void writeEvent(Event const& ev); - void beginRiff(char const* name) { - m_riffBegin = m_data.size(); - m_data.resize(m_riffBegin + 8); // Add space for header - std::copy(name, name + 4, m_data.begin() + m_riffBegin); // Set RIFF name - } - void endRiff() { - unsigned size = m_data.size() - 8 - m_riffBegin; - m_data[m_riffBegin + 4] = size >> 24; - m_data[m_riffBegin + 5] = size >> 16; - m_data[m_riffBegin + 6] = size >> 8; - m_data[m_riffBegin + 7] = size; - } + private: + void beginRiff(char const* name); + void endRiff(); template <unsigned N> void write(unsigned value) { for (unsigned i = N - 1; i < N; --i) m_data.push_back(value >> (8 * i)); } diff --git a/songparser-ini.cc b/songparser-ini.cc index 52e9c54..745864e 100644 --- a/songparser-ini.cc +++ b/songparser-ini.cc @@ -61,13 +61,13 @@ namespace { void SongParser::midParse() { QByteArray name = (m_song.path + "notes.mid").toLocal8Bit(); - using namespace mid; - Reader reader(std::string(name.data(), name.size()).c_str()); + midifile::Reader reader(std::string(name.data(), name.size()).c_str()); + using midifile::Event; double tempo = 120.0; double division = reader.getDivision(); addBPM(0, tempo, division); unsigned track = 0; - do { + while (++track, reader.startTrack()) { VocalTrack vt(""); unsigned timecode = 0; std::string trackName, lyric; @@ -129,7 +129,7 @@ void SongParser::midParse() { } } if (trackName == "PART VOCALS") m_song.insertVocalTrack("vocals", vt); - } while (++track, reader.nextTrack()); + }; } #if 0 diff --git a/songwriter-ini.cc b/songwriter-ini.cc index 0ca0ffd..526b135 100644 --- a/songwriter-ini.cc +++ b/songwriter-ini.cc @@ -1,21 +1,19 @@ #include "songwriter.hh" + +#include "midifile.hh" #include <QTextStream> void FoFMIDIWriter::writeMIDI() const { - throw std::runtime_error("MIDI export is not implemented."); - /*std::ofstream f((path + "notes.mid").c_str(), std::ios::binary); - // FIXME: The following is just an example and doesn't actually output MID format - char buf[1024] = {}; Notes const& notes = s.getVocalTrack().notes; - std::cout << notes.size() << std::endl; - for (unsigned int i = 0; i < notes.size(); ++i) { - Note const& n = notes[i]; - buf[0] = 0xFF; - buf[1] = n.note; // MIDI note value - // Others are n.begin, n.end, n.type etc. (see notes.hh) - f.write(buf, 1024); - }*/ + unsigned division = 16; + midifile::Writer writer(1, 2, division); + writer.startTrack(); + // TODO: write timing info + writer.startTrack(); + // TODO: write notes + QByteArray name = (path + "/notes.mid").toLocal8Bit(); + writer.save(std::string(name.data(), name.size()).c_str()); } void FoFMIDIWriter::writeINI() const { |