Mercurial > hg > svcore
view data/fileio/MIDIFileWriter.cpp @ 618:b1dc68507e46 sv-v1.7.1
* Layer data editor window: fix sorting for columns in region model,
add Find feature
* RDF import: assign names to layers based on event types, if no suitable
labels are found in the RDF
* Add label to status bar showing the last text that was passed in current
layer (so e.g. counting 1, 2, 3, 4 if that's what beats are labelled)
* Better layout of text labels for region layers in segmentation mode when
they are close together
* Give text layer the same method for finding "nearest point" as region and
note layers, should improve its editability
author | Chris Cannam |
---|---|
date | Thu, 22 Oct 2009 15:54:21 +0000 |
parents | 2e50d95cf621 |
children | d6bd5751b8f6 |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. 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 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ /* This is a modified version of a source file from the Rosegarden MIDI and audio sequencer and notation editor. This file copyright 2000-2007 Richard Bown and Chris Cannam and copyright 2007 QMUL. */ #include "MIDIFileWriter.h" #include "data/midi/MIDIEvent.h" #include "model/NoteModel.h" #include "base/Pitch.h" #include <algorithm> #include <fstream> using std::ofstream; using std::string; using std::ios; using namespace MIDIConstants; MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) : m_path(path), m_model(model), m_modelUsesHz(false), m_tempo(tempo) { if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true; if (!convert()) { m_error = "Conversion from model to internal MIDI format failed"; } } MIDIFileWriter::~MIDIFileWriter() { for (MIDIComposition::iterator i = m_midiComposition.begin(); i != m_midiComposition.end(); ++i) { for (MIDITrack::iterator j = i->second.begin(); j != i->second.end(); ++j) { delete *j; } i->second.clear(); } m_midiComposition.clear(); } bool MIDIFileWriter::isOK() const { return m_error == ""; } QString MIDIFileWriter::getError() const { return m_error; } void MIDIFileWriter::write() { writeComposition(); } string MIDIFileWriter::intToMIDIBytes(int number) const { MIDIByte upper; MIDIByte lower; upper = (number & 0xFF00) >> 8; lower = (number & 0x00FF); string rv; rv += upper; rv += lower; return rv; } string MIDIFileWriter::longToMIDIBytes(unsigned long number) const { MIDIByte upper1; MIDIByte lower1; MIDIByte upper2; MIDIByte lower2; upper1 = (number & 0xff000000) >> 24; lower1 = (number & 0x00ff0000) >> 16; upper2 = (number & 0x0000ff00) >> 8; lower2 = (number & 0x000000ff); string rv; rv += upper1; rv += lower1; rv += upper2; rv += lower2; return rv; } // Turn a delta time into a MIDI time - overlapping into // a maximum of four bytes using the MSB as the carry on // flag. // string MIDIFileWriter::longToVarBuffer(unsigned long number) const { string rv; long inNumber = number; long outNumber; // get the lowest 7 bits of the number outNumber = number & 0x7f; // Shift and test and move the numbers // on if we need them - setting the MSB // as we go. // while ((inNumber >>= 7 ) > 0) { outNumber <<= 8; outNumber |= 0x80; outNumber += (inNumber & 0x7f); } // Now move the converted number out onto the buffer // while (true) { rv += (MIDIByte)(outNumber & 0xff); if (outNumber & 0x80) outNumber >>= 8; else break; } return rv; } bool MIDIFileWriter::writeHeader() { *m_midiFile << MIDI_FILE_HEADER; // Number of bytes in header *m_midiFile << (MIDIByte) 0x00; *m_midiFile << (MIDIByte) 0x00; *m_midiFile << (MIDIByte) 0x00; *m_midiFile << (MIDIByte) 0x06; // File format *m_midiFile << (MIDIByte) 0x00; *m_midiFile << (MIDIByte) m_format; *m_midiFile << intToMIDIBytes(m_numberOfTracks); *m_midiFile << intToMIDIBytes(m_timingDivision); return true; } bool MIDIFileWriter::writeTrack(int trackNumber) { bool retOK = true; MIDIByte eventCode = 0; MIDITrack::iterator midiEvent; // First we write into the trackBuffer, then write it out to the // file with its accompanying length. // string trackBuffer; for (midiEvent = m_midiComposition[trackNumber].begin(); midiEvent != m_midiComposition[trackNumber].end(); midiEvent++) { // Write the time to the buffer in MIDI format trackBuffer += longToVarBuffer((*midiEvent)->getTime()); if ((*midiEvent)->isMeta()) { trackBuffer += MIDI_FILE_META_EVENT; trackBuffer += (*midiEvent)->getMetaEventCode(); // Variable length number field trackBuffer += longToVarBuffer((*midiEvent)-> getMetaMessage().length()); trackBuffer += (*midiEvent)->getMetaMessage(); } else { // Send the normal event code (with encoded channel information) if (((*midiEvent)->getEventCode() != eventCode) || ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) { trackBuffer += (*midiEvent)->getEventCode(); eventCode = (*midiEvent)->getEventCode(); } // Send the relevant data // switch ((*midiEvent)->getMessageType()) { case MIDI_NOTE_ON: case MIDI_NOTE_OFF: case MIDI_POLY_AFTERTOUCH: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_CTRL_CHANGE: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_PROG_CHANGE: trackBuffer += (*midiEvent)->getData1(); break; case MIDI_CHNL_AFTERTOUCH: trackBuffer += (*midiEvent)->getData1(); break; case MIDI_PITCH_BEND: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_SYSTEM_EXCLUSIVE: // write out message length trackBuffer += longToVarBuffer((*midiEvent)->getMetaMessage().length()); // now the message trackBuffer += (*midiEvent)->getMetaMessage(); break; default: break; } } } // Now we write the track - First the standard header.. // *m_midiFile << MIDI_TRACK_HEADER; // ..now the length of the buffer.. // *m_midiFile << longToMIDIBytes((long)trackBuffer.length()); // ..then the buffer itself.. // *m_midiFile << trackBuffer; return retOK; } bool MIDIFileWriter::writeComposition() { bool retOK = true; m_midiFile = new ofstream(m_path.toLocal8Bit().data(), ios::out | ios::binary); if (!(*m_midiFile)) { m_error = "Can't open file for writing."; delete m_midiFile; m_midiFile = 0; return false; } if (!writeHeader()) { retOK = false; } for (unsigned int i = 0; i < m_numberOfTracks; i++) { if (!writeTrack(i)) { retOK = false; } } m_midiFile->close(); delete m_midiFile; m_midiFile = 0; if (!retOK) { m_error = "MIDI file write failed"; } return retOK; } bool MIDIFileWriter::convert() { m_timingDivision = 480; m_format = MIDI_SINGLE_TRACK_FILE; m_numberOfTracks = 1; int track = 0; int midiChannel = 0; MIDIEvent *event; event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, "Exported from Sonic Visualiser"); m_midiComposition[track].push_back(event); event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, "http://www.sonicvisualiser.org/"); m_midiComposition[track].push_back(event); long tempoValue = long(60000000.0 / m_tempo + 0.01); string tempoString; tempoString += (MIDIByte)(tempoValue >> 16 & 0xFF); tempoString += (MIDIByte)(tempoValue >> 8 & 0xFF); tempoString += (MIDIByte)(tempoValue & 0xFF); event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_SET_TEMPO, tempoString); m_midiComposition[track].push_back(event); // Omit time signature const NoteModel::PointList ¬es = static_cast<SparseModel<Note> *>(m_model)->getPoints(); for (NoteModel::PointList::const_iterator i = notes.begin(); i != notes.end(); ++i) { long frame = i->frame; float value = i->value; size_t duration = i->duration; int pitch; if (m_modelUsesHz) { pitch = Pitch::getPitchForFrequency(value); } else { pitch = lrintf(value); } if (pitch < 0) pitch = 0; if (pitch > 127) pitch = 127; // Convert frame to MIDI time double seconds = double(frame) / double(m_model->getSampleRate()); double quarters = (seconds * m_tempo) / 60.0; unsigned long midiTime = lrint(quarters * m_timingDivision); int velocity = 100; if (i->level > 0.f && i->level <= 1.f) { velocity = lrintf(i->level * 127.f); } // Get the sounding time for the matching NOTE_OFF seconds = double(frame + duration) / double(m_model->getSampleRate()); quarters = (seconds * m_tempo) / 60.0; unsigned long endTime = lrint(quarters * m_timingDivision); // At this point all the notes we insert have absolute times // in the delta time fields. We resolve these into delta // times further down (can't do it until all the note offs are // in place). event = new MIDIEvent(midiTime, MIDI_NOTE_ON | midiChannel, pitch, velocity); m_midiComposition[track].push_back(event); event = new MIDIEvent(endTime, MIDI_NOTE_OFF | midiChannel, pitch, 127); // loudest silence you can muster m_midiComposition[track].push_back(event); } // Now gnash through the MIDI events and turn the absolute times // into delta times. // for (unsigned int i = 0; i < m_numberOfTracks; i++) { unsigned long lastMidiTime = 0; // First sort the track with the MIDIEvent comparator. Use // stable_sort so that events with equal times are maintained // in their current order. // std::stable_sort(m_midiComposition[i].begin(), m_midiComposition[i].end(), MIDIEventCmp()); for (MIDITrack::iterator it = m_midiComposition[i].begin(); it != m_midiComposition[i].end(); it++) { unsigned long deltaTime = (*it)->getTime() - lastMidiTime; lastMidiTime = (*it)->getTime(); (*it)->setTime(deltaTime); } // Insert end of track event (delta time = 0) // event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_END_OF_TRACK, ""); m_midiComposition[i].push_back(event); } return true; }