Chris@301: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@301: Chris@301: /* Chris@301: Sonic Visualiser Chris@301: An audio file viewer and annotation editor. Chris@301: Centre for Digital Music, Queen Mary, University of London. Chris@301: Chris@301: This program is free software; you can redistribute it and/or Chris@301: modify it under the terms of the GNU General Public License as Chris@301: published by the Free Software Foundation; either version 2 of the Chris@301: License, or (at your option) any later version. See the file Chris@301: COPYING included with this distribution for more information. Chris@301: */ Chris@301: Chris@301: Chris@301: /* Chris@301: This is a modified version of a source file from the Chris@301: Rosegarden MIDI and audio sequencer and notation editor. Chris@301: This file copyright 2000-2007 Richard Bown and Chris Cannam Chris@301: and copyright 2007 QMUL. Chris@301: */ Chris@301: Chris@301: #include "MIDIFileWriter.h" Chris@301: Chris@301: #include "MIDIEvent.h" Chris@301: Chris@301: #include "model/NoteModel.h" Chris@301: Chris@301: #include "base/Pitch.h" Chris@301: Chris@301: #include Chris@301: #include Chris@301: Chris@301: using std::ofstream; Chris@301: using std::string; Chris@301: using std::ios; Chris@301: Chris@301: using namespace MIDIConstants; Chris@301: Chris@301: MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) : Chris@301: m_path(path), Chris@301: m_model(model), Chris@301: m_modelUsesHz(false), Chris@301: m_tempo(tempo) Chris@301: { Chris@301: if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true; Chris@301: Chris@301: if (!convert()) { Chris@301: m_error = "Conversion from model to internal MIDI format failed"; Chris@301: } Chris@301: } Chris@301: Chris@301: MIDIFileWriter::~MIDIFileWriter() Chris@301: { Chris@301: for (MIDIComposition::iterator i = m_midiComposition.begin(); Chris@301: i != m_midiComposition.end(); ++i) { Chris@301: Chris@301: for (MIDITrack::iterator j = i->second.begin(); Chris@301: j != i->second.end(); ++j) { Chris@301: delete *j; Chris@301: } Chris@301: Chris@301: i->second.clear(); Chris@301: } Chris@301: Chris@301: m_midiComposition.clear(); Chris@301: } Chris@301: Chris@301: bool Chris@301: MIDIFileWriter::isOK() const Chris@301: { Chris@301: return m_error == ""; Chris@301: } Chris@301: Chris@301: QString Chris@301: MIDIFileWriter::getError() const Chris@301: { Chris@301: return m_error; Chris@301: } Chris@301: Chris@301: void Chris@301: MIDIFileWriter::write() Chris@301: { Chris@301: writeComposition(); Chris@301: } Chris@301: Chris@301: string Chris@301: MIDIFileWriter::intToMIDIBytes(int number) const Chris@301: { Chris@301: MIDIByte upper; Chris@301: MIDIByte lower; Chris@301: Chris@301: upper = (number & 0xFF00) >> 8; Chris@301: lower = (number & 0x00FF); Chris@301: Chris@301: string rv; Chris@301: rv += upper; Chris@301: rv += lower; Chris@301: return rv; Chris@301: } Chris@301: Chris@301: string Chris@301: MIDIFileWriter::longToMIDIBytes(unsigned long number) const Chris@301: { Chris@301: MIDIByte upper1; Chris@301: MIDIByte lower1; Chris@301: MIDIByte upper2; Chris@301: MIDIByte lower2; Chris@301: Chris@301: upper1 = (number & 0xff000000) >> 24; Chris@301: lower1 = (number & 0x00ff0000) >> 16; Chris@301: upper2 = (number & 0x0000ff00) >> 8; Chris@301: lower2 = (number & 0x000000ff); Chris@301: Chris@301: string rv; Chris@301: rv += upper1; Chris@301: rv += lower1; Chris@301: rv += upper2; Chris@301: rv += lower2; Chris@301: return rv; Chris@301: } Chris@301: Chris@301: // Turn a delta time into a MIDI time - overlapping into Chris@301: // a maximum of four bytes using the MSB as the carry on Chris@301: // flag. Chris@301: // Chris@301: string Chris@301: MIDIFileWriter::longToVarBuffer(unsigned long number) const Chris@301: { Chris@301: string rv; Chris@301: Chris@301: long inNumber = number; Chris@301: long outNumber; Chris@301: Chris@301: // get the lowest 7 bits of the number Chris@301: outNumber = number & 0x7f; Chris@301: Chris@301: // Shift and test and move the numbers Chris@301: // on if we need them - setting the MSB Chris@301: // as we go. Chris@301: // Chris@301: while ((inNumber >>= 7 ) > 0) { Chris@301: outNumber <<= 8; Chris@301: outNumber |= 0x80; Chris@301: outNumber += (inNumber & 0x7f); Chris@301: } Chris@301: Chris@301: // Now move the converted number out onto the buffer Chris@301: // Chris@301: while (true) { Chris@301: rv += (MIDIByte)(outNumber & 0xff); Chris@301: if (outNumber & 0x80) Chris@301: outNumber >>= 8; Chris@301: else Chris@301: break; Chris@301: } Chris@301: Chris@301: return rv; Chris@301: } Chris@301: Chris@301: bool Chris@301: MIDIFileWriter::writeHeader() Chris@301: { Chris@301: *m_midiFile << MIDI_FILE_HEADER; Chris@301: Chris@301: // Number of bytes in header Chris@301: *m_midiFile << (MIDIByte) 0x00; Chris@301: *m_midiFile << (MIDIByte) 0x00; Chris@301: *m_midiFile << (MIDIByte) 0x00; Chris@301: *m_midiFile << (MIDIByte) 0x06; Chris@301: Chris@301: // File format Chris@301: *m_midiFile << (MIDIByte) 0x00; Chris@301: *m_midiFile << (MIDIByte) m_format; Chris@301: Chris@301: *m_midiFile << intToMIDIBytes(m_numberOfTracks); Chris@301: Chris@301: *m_midiFile << intToMIDIBytes(m_timingDivision); Chris@301: Chris@301: return true; Chris@301: } Chris@301: Chris@301: bool Chris@301: MIDIFileWriter::writeTrack(int trackNumber) Chris@301: { Chris@301: bool retOK = true; Chris@301: MIDIByte eventCode = 0; Chris@301: MIDITrack::iterator midiEvent; Chris@301: Chris@301: // First we write into the trackBuffer, then write it out to the Chris@301: // file with its accompanying length. Chris@301: // Chris@301: string trackBuffer; Chris@301: Chris@301: for (midiEvent = m_midiComposition[trackNumber].begin(); Chris@301: midiEvent != m_midiComposition[trackNumber].end(); Chris@301: midiEvent++) { Chris@301: Chris@301: // Write the time to the buffer in MIDI format Chris@301: trackBuffer += longToVarBuffer((*midiEvent)->getTime()); Chris@301: Chris@301: if ((*midiEvent)->isMeta()) { Chris@301: trackBuffer += MIDI_FILE_META_EVENT; Chris@301: trackBuffer += (*midiEvent)->getMetaEventCode(); Chris@301: Chris@301: // Variable length number field Chris@301: trackBuffer += longToVarBuffer((*midiEvent)-> Chris@301: getMetaMessage().length()); Chris@301: Chris@301: trackBuffer += (*midiEvent)->getMetaMessage(); Chris@301: } else { Chris@301: // Send the normal event code (with encoded channel information) Chris@301: if (((*midiEvent)->getEventCode() != eventCode) || Chris@301: ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) { Chris@301: trackBuffer += (*midiEvent)->getEventCode(); Chris@301: eventCode = (*midiEvent)->getEventCode(); Chris@301: } Chris@301: Chris@301: // Send the relevant data Chris@301: // Chris@301: switch ((*midiEvent)->getMessageType()) { Chris@301: case MIDI_NOTE_ON: Chris@301: case MIDI_NOTE_OFF: Chris@301: case MIDI_POLY_AFTERTOUCH: Chris@301: trackBuffer += (*midiEvent)->getData1(); Chris@301: trackBuffer += (*midiEvent)->getData2(); Chris@301: break; Chris@301: Chris@301: case MIDI_CTRL_CHANGE: Chris@301: trackBuffer += (*midiEvent)->getData1(); Chris@301: trackBuffer += (*midiEvent)->getData2(); Chris@301: break; Chris@301: Chris@301: case MIDI_PROG_CHANGE: Chris@301: trackBuffer += (*midiEvent)->getData1(); Chris@301: break; Chris@301: Chris@301: case MIDI_CHNL_AFTERTOUCH: Chris@301: trackBuffer += (*midiEvent)->getData1(); Chris@301: break; Chris@301: Chris@301: case MIDI_PITCH_BEND: Chris@301: trackBuffer += (*midiEvent)->getData1(); Chris@301: trackBuffer += (*midiEvent)->getData2(); Chris@301: break; Chris@301: Chris@301: case MIDI_SYSTEM_EXCLUSIVE: Chris@301: // write out message length Chris@301: trackBuffer += Chris@301: longToVarBuffer((*midiEvent)->getMetaMessage().length()); Chris@301: Chris@301: // now the message Chris@301: trackBuffer += (*midiEvent)->getMetaMessage(); Chris@301: break; Chris@301: Chris@301: default: Chris@301: break; Chris@301: } Chris@301: } Chris@301: } Chris@301: Chris@301: // Now we write the track - First the standard header.. Chris@301: // Chris@301: *m_midiFile << MIDI_TRACK_HEADER; Chris@301: Chris@301: // ..now the length of the buffer.. Chris@301: // Chris@301: *m_midiFile << longToMIDIBytes((long)trackBuffer.length()); Chris@301: Chris@301: // ..then the buffer itself.. Chris@301: // Chris@301: *m_midiFile << trackBuffer; Chris@301: Chris@301: return retOK; Chris@301: } Chris@301: Chris@301: bool Chris@301: MIDIFileWriter::writeComposition() Chris@301: { Chris@301: bool retOK = true; Chris@301: Chris@301: m_midiFile = Chris@301: new ofstream(m_path.toLocal8Bit().data(), ios::out | ios::binary); Chris@301: Chris@301: if (!(*m_midiFile)) { Chris@301: m_error = "Can't open file for writing."; Chris@301: delete m_midiFile; Chris@301: m_midiFile = 0; Chris@301: return false; Chris@301: } Chris@301: Chris@301: if (!writeHeader()) { Chris@301: retOK = false; Chris@301: } Chris@301: Chris@301: for (unsigned int i = 0; i < m_numberOfTracks; i++) { Chris@301: if (!writeTrack(i)) { Chris@301: retOK = false; Chris@301: } Chris@301: } Chris@301: Chris@301: m_midiFile->close(); Chris@301: delete m_midiFile; Chris@301: m_midiFile = 0; Chris@301: Chris@301: if (!retOK) { Chris@301: m_error = "MIDI file write failed"; Chris@301: } Chris@301: Chris@301: return retOK; Chris@301: } Chris@301: Chris@301: bool Chris@301: MIDIFileWriter::convert() Chris@301: { Chris@301: m_timingDivision = 480; Chris@301: m_format = MIDI_SINGLE_TRACK_FILE; Chris@301: m_numberOfTracks = 1; Chris@301: Chris@301: int track = 0; Chris@301: int midiChannel = 0; Chris@301: Chris@301: MIDIEvent *event; Chris@301: Chris@301: event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, Chris@301: "Exported from Sonic Visualiser"); Chris@301: m_midiComposition[track].push_back(event); Chris@301: Chris@301: event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, Chris@301: "http://www.sonicvisualiser.org/"); Chris@301: m_midiComposition[track].push_back(event); Chris@301: Chris@301: long tempoValue = long(60000000.0 / m_tempo + 0.01); Chris@301: string tempoString; Chris@301: tempoString += (MIDIByte)(tempoValue >> 16 & 0xFF); Chris@301: tempoString += (MIDIByte)(tempoValue >> 8 & 0xFF); Chris@301: tempoString += (MIDIByte)(tempoValue & 0xFF); Chris@301: Chris@301: event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_SET_TEMPO, Chris@301: tempoString); Chris@301: m_midiComposition[track].push_back(event); Chris@301: Chris@301: // Omit time signature Chris@301: Chris@301: const NoteModel::PointList ¬es = Chris@301: static_cast *>(m_model)->getPoints(); Chris@301: Chris@301: for (NoteModel::PointList::const_iterator i = notes.begin(); Chris@301: i != notes.end(); ++i) { Chris@301: Chris@301: long frame = i->frame; Chris@301: float value = i->value; Chris@301: size_t duration = i->duration; Chris@301: Chris@301: int pitch; Chris@301: Chris@301: if (m_modelUsesHz) { Chris@301: pitch = Pitch::getPitchForFrequency(value); Chris@301: } else { Chris@301: pitch = lrintf(value); Chris@301: } Chris@301: Chris@301: if (pitch < 0) pitch = 0; Chris@301: if (pitch > 127) pitch = 127; Chris@301: Chris@301: // Convert frame to MIDI time Chris@301: Chris@301: double seconds = double(frame) / double(m_model->getSampleRate()); Chris@301: double quarters = (seconds * m_tempo) / 60.0; Chris@301: unsigned long midiTime = lrint(quarters * m_timingDivision); Chris@301: Chris@301: // We don't support velocity in note models yet Chris@301: int velocity = 100; Chris@301: Chris@301: // Get the sounding time for the matching NOTE_OFF Chris@301: seconds = double(frame + duration) / double(m_model->getSampleRate()); Chris@301: quarters = (seconds * m_tempo) / 60.0; Chris@301: unsigned long endTime = lrint(quarters * m_timingDivision); Chris@301: Chris@301: // At this point all the notes we insert have absolute times Chris@301: // in the delta time fields. We resolve these into delta Chris@301: // times further down (can't do it until all the note offs are Chris@301: // in place). Chris@301: Chris@301: event = new MIDIEvent(midiTime, Chris@301: MIDI_NOTE_ON | midiChannel, Chris@301: pitch, Chris@301: velocity); Chris@301: m_midiComposition[track].push_back(event); Chris@301: Chris@301: event = new MIDIEvent(endTime, Chris@301: MIDI_NOTE_OFF | midiChannel, Chris@301: pitch, Chris@301: 127); // loudest silence you can muster Chris@301: Chris@301: m_midiComposition[track].push_back(event); Chris@301: } Chris@301: Chris@301: // Now gnash through the MIDI events and turn the absolute times Chris@301: // into delta times. Chris@301: // Chris@301: for (unsigned int i = 0; i < m_numberOfTracks; i++) { Chris@301: Chris@301: unsigned long lastMidiTime = 0; Chris@301: Chris@301: // First sort the track with the MIDIEvent comparator. Use Chris@301: // stable_sort so that events with equal times are maintained Chris@301: // in their current order. Chris@301: // Chris@301: std::stable_sort(m_midiComposition[i].begin(), Chris@301: m_midiComposition[i].end(), Chris@301: MIDIEventCmp()); Chris@301: Chris@301: for (MIDITrack::iterator it = m_midiComposition[i].begin(); Chris@301: it != m_midiComposition[i].end(); it++) { Chris@301: unsigned long deltaTime = (*it)->getTime() - lastMidiTime; Chris@301: lastMidiTime = (*it)->getTime(); Chris@301: (*it)->setTime(deltaTime); Chris@301: } Chris@301: Chris@301: // Insert end of track event (delta time = 0) Chris@301: // Chris@301: event = new MIDIEvent(0, MIDI_FILE_META_EVENT, Chris@301: MIDI_END_OF_TRACK, ""); Chris@301: Chris@301: m_midiComposition[i].push_back(event); Chris@301: } Chris@301: Chris@301: return true; Chris@301: } Chris@301: