// NOTE: This is required for much of the other functionality provided // by this class, however, this causes a destructive change in the way // the MIDI is represented internally which means we can never save the // file back out to disk exactly as we loaded it. // // This adds an extra track dedicated to tempo change events. Tempo events // are extracted from every other track and placed in the new one. // // This allows quick(er) calculation of wall-clock event times void Midi::BuildTempoTrack() { // This map will help us get rid of duplicate events if // the tempo is specified in every track (as is common). // // It also does sorting for us so we can just copy the // events right over to the new track. std::map<unsigned long, MidiEvent> tempo_events; // Run through each track looking for tempo events for (MidiTrackList::iterator t = m_tracks.begin(); t != m_tracks.end(); ++t) { for (size_t i = 0; i < t->Events().size(); ++i) { MidiEvent ev = t->Events()[i]; unsigned long ev_pulses = t->EventPulses()[i]; if (ev.Type() == MidiEventType_Meta && ev.MetaType() == MidiMetaEvent_TempoChange) { // Pull tempo event out of both lists // // Vector is kind of a hassle this way -- we have to // walk an iterator to that point in the list because // erase MUST take an iterator... but erasing from a // list invalidates iterators. bleah. MidiEventList::iterator event_to_erase = t->Events().begin(); MidiEventPulsesList::iterator event_pulse_to_erase = t->EventPulses().begin(); for (size_t j = 0; j < i; ++j) { ++event_to_erase; ++event_pulse_to_erase; } t->Events().erase(event_to_erase); t->EventPulses().erase(event_pulse_to_erase); // Adjust next event's delta time if (t->Events().size() > i) { // (We just erased the element at i, so // now i is pointing to the next element) unsigned long next_dt = t->Events()[i].GetDeltaPulses(); t->Events()[i].SetDeltaPulses(ev.GetDeltaPulses() + next_dt); } // We have to roll i back for the next loop around --i; // Insert our newly stolen event into the auto-sorting map tempo_events[ev_pulses] = ev; } } } // Create a new track (always the last track in the track list) m_tracks.push_back(MidiTrack::CreateBlankTrack()); MidiEventList &tempo_track_events = m_tracks[m_tracks.size()-1].Events(); MidiEventPulsesList &tempo_track_event_pulses = m_tracks[m_tracks.size()-1].EventPulses(); // Copy over all our tempo events unsigned long previous_absolute_pulses = 0; for (std::map<unsigned long, MidiEvent>::const_iterator i = tempo_events.begin(); i != tempo_events.end(); ++i) { unsigned long absolute_pulses = i->first; MidiEvent ev = i->second; // Reset each of their delta times while we go ev.SetDeltaPulses(absolute_pulses - previous_absolute_pulses); previous_absolute_pulses = absolute_pulses; // Add them to the track tempo_track_event_pulses.push_back(absolute_pulses); tempo_track_events.push_back(ev); } }
MidiTrack MidiTrack::ReadFromStream(std::istream &stream) { // Verify the track header const static string MidiTrackHeader = "MTrk"; // I could use (MidiTrackHeader.length() + 1), but then this has to be // dynamically allocated. More hassle than it's worth. MIDI is well // defined and will always have a 4-byte header. We use 5 so we get // free null termination. char header_id[5] = { 0, 0, 0, 0, 0 }; unsigned long track_length; stream.read(header_id, static_cast<streamsize>(MidiTrackHeader.length())); stream.read(reinterpret_cast<char*>(&track_length), sizeof(unsigned long)); if (stream.fail()) throw MidiError(MidiError_TrackHeaderTooShort); string header(header_id); if (header != MidiTrackHeader) throw MidiError_BadTrackHeaderType; // Pull the full track out of the file all at once -- there is an // End-Of-Track event, but this allows us handle malformed MIDI a // little more gracefully. track_length = BigToSystem32(track_length); char *buffer = new char[track_length + 1]; buffer[track_length] = 0; stream.read(buffer, track_length); if (stream.fail()) { delete[] buffer; throw MidiError(MidiError_TrackTooShort); } // We have to jump through a couple hoops because istringstream // can't handle binary data unless constructed through an std::string. string buffer_string(buffer, track_length); istringstream event_stream(buffer_string, ios::binary); delete[] buffer; MidiTrack t; // Read events until we run out of track char last_status = 0; unsigned long current_pulse_count = 0; while (event_stream.peek() != char_traits<char>::eof()) { MidiEvent ev = MidiEvent::ReadFromStream(event_stream, last_status); last_status = ev.StatusCode(); t.m_events.push_back(ev); current_pulse_count += ev.GetDeltaPulses(); t.m_event_pulses.push_back(current_pulse_count); } t.BuildNoteSet(); t.DiscoverInstrument(); return t; }