Example #1
0
// 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);
   }
}
Example #2
0
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;
}