Example #1
0
MidiEvent MidiEvent::Build(const MidiEventSimple &simple) {
  MidiEvent ev;

  ev.m_delta_pulses = 0;
  ev.m_status = simple.status;
  ev.m_data1 = simple.byte1;
  ev.m_data2 = simple.byte2;

  if (ev.Type() == MidiEventType_Meta)
    throw MidiError(MidiError_MetaEventOnInput);

  return ev;
}
Example #2
0
MidiEvent MidiEvent::ReadFromStream(istream &stream, 
				    unsigned char last_status, 
				    bool contains_delta_pulses) {
  MidiEvent ev;

  if (contains_delta_pulses)
    ev.m_delta_pulses = parse_variable_length(stream);
  else
    ev.m_delta_pulses = 0;

  // MIDI uses a compression mechanism called "running status".
  // Anytime you read a status byte that doesn't have the highest-
  // order bit set, what you actually read is the 1st data byte
  // of a message with the status of the previous message.
  ev.m_status = static_cast<unsigned char>(stream.peek());

  if ((ev.m_status & 0x80) == 0)
    ev.m_status = last_status;

  else
    // It was a status byte after all, just read past it
    // in the stream
    stream.read(reinterpret_cast<char*>(&ev.m_status), sizeof(unsigned char));

  switch (ev.Type()) {
  case MidiEventType_Meta:
    ev.ReadMeta(stream);
    break;

  case MidiEventType_SysEx:
    ev.ReadSysEx(stream);
    break;

  default:
    ev.ReadStandard(stream);
    break;
  }

  return ev;
}
Example #3
0
void TitleState::Update() {

  MouseInfo mouse = Mouse();

  if (m_skip_next_mouse_up) {
    mouse.released.left = false;
    m_skip_next_mouse_up = false;
  }

  m_continue_button.Update(mouse);
  m_back_button.Update(mouse);

  MouseInfo output_mouse(mouse);
  output_mouse.x -= m_output_tile->GetX();
  output_mouse.y -= m_output_tile->GetY();
  m_output_tile->Update(output_mouse);

  MouseInfo input_mouse(mouse);
  input_mouse.x -= m_input_tile->GetX();
  input_mouse.y -= m_input_tile->GetY();
  m_input_tile->Update(input_mouse);

  MouseInfo file_mouse(mouse);
  file_mouse.x -= m_file_tile->GetX();
  file_mouse.y -= m_file_tile->GetY();
  m_file_tile->Update(file_mouse);

  // Check to see for clicks on the file box
  if (m_file_tile->Hit()) {
    m_skip_next_mouse_up = true;

    if (m_state.midi_out) {
      m_state.midi_out->Reset();
      m_output_tile->TurnOffPreview();
    }

    Midi *new_midi = 0;

    string filename;
    string file_title;
    FileSelector::RequestMidiFilename(&filename, &file_title);

    if (filename != "") {
      try {
        new_midi = new Midi(Midi::ReadFromFile(filename));
      }
      catch (const MidiError &e) {
        string description = STRING("Problem while loading file: " <<
                                    file_title << "\n") + e.GetErrorDescription();
        Compatible::ShowError(description);
        new_midi = 0;
      }

      if (new_midi) {

        SharedState new_state;
        new_state.midi = new_midi;
        new_state.midi_in = m_state.midi_in;
        new_state.midi_out = m_state.midi_out;
        new_state.song_title = FileSelector::TrimFilename(filename);

        delete m_state.midi;
        m_state = new_state;

        m_file_tile->SetString(m_state.song_title);
      }
    }
  }

  // Check to see if we need to switch to a newly selected output device
  int output_id = m_output_tile->GetDeviceId();
  if (!m_state.midi_out ||
      output_id != static_cast<int>(m_state.midi_out->GetDeviceDescription().id)) {

    if (m_state.midi_out)
      m_state.midi_out->Reset();

    delete m_state.midi_out;
    m_state.midi_out = 0;

    // save to user preferences
    if (output_id >= 0) {

      m_state.midi_out = new MidiCommOut(output_id);
      m_state.midi->Reset(0,0);

      UserSetting::Set(OutputDeviceKey, m_state.midi_out->GetDeviceDescription().name);
    }
    else
      UserSetting::Set(OutputDeviceKey, OutputKeySpecialDisabled);

  }

  if (m_state.midi_out) {
    if (m_output_tile->HitPreviewButton()) {
      m_state.midi_out->Reset();

      if (m_output_tile->IsPreviewOn()) {

        const microseconds_t PreviewLeadIn  = 250000;
        const microseconds_t PreviewLeadOut = 250000;
        m_state.midi->Reset(PreviewLeadIn, PreviewLeadOut);

        PlayDevicePreview(0);
      }
    }

    else
      PlayDevicePreview(static_cast<microseconds_t>(GetDeltaMilliseconds()) * 1000);

  }

  int input_id = m_input_tile->GetDeviceId();
   if (!m_state.midi_in ||
       input_id != static_cast<int>(m_state.midi_in->GetDeviceDescription().id)) {

     if (m_state.midi_in)
       m_state.midi_in->Reset();

     m_last_input_note_name = "";

     delete m_state.midi_in;
     m_state.midi_in = 0;

     if (input_id >= 0) {

       try {
         m_state.midi_in = new MidiCommIn(input_id);
         UserSetting::Set(InputDeviceKey, m_state.midi_in->GetDeviceDescription().name);
       }

       catch (MidiErrorCode) {
         m_state.midi_in = 0;
       }
     }

     else
       UserSetting::Set(InputDeviceKey, InputKeySpecialDisabled);

   }

   if (m_state.midi_in && m_input_tile->IsPreviewOn()) {

     // Read note events to display on screen
     while (m_state.midi_in->KeepReading()) {
       MidiEvent ev = m_state.midi_in->Read();

       // send to output (for a possible audio preview)
       if (m_state.midi_out)
         m_state.midi_out->Write(ev);

       if (ev.Type() == MidiEventType_NoteOff || ev.Type() == MidiEventType_NoteOn) {
         string note = MidiEvent::NoteName(ev.NoteNumber());

         if (ev.Type() == MidiEventType_NoteOn && ev.NoteVelocity() > 0)
           m_last_input_note_name = note;

         else
           if (note == m_last_input_note_name)
             m_last_input_note_name = "";

       }
     }
   }

   else
     m_last_input_note_name = "";

   if (IsKeyPressed(KeyEscape) || m_back_button.hit) {
     delete m_state.midi_out;
     m_state.midi_out = 0;

     delete m_state.midi_in;
     m_state.midi_in = 0;

     delete m_state.midi;
     m_state.midi = 0;

     Compatible::GracefulShutdown();
     return;
   }

   if (IsKeyPressed(KeyEnter) || m_continue_button.hit) {

     if (m_state.midi_out)
       m_state.midi_out->Reset();

     if (m_state.midi_in)
       m_state.midi_in->Reset();

     ChangeState(new TrackSelectionState(m_state));
     return;
   }

   m_tooltip = "";

   if (m_back_button.hovering)
     m_tooltip = "Click to exit Linthesia.";

   if (m_continue_button.hovering)
     m_tooltip = "Click to continue on to the track selection screen.";

   if (m_file_tile->WholeTile().hovering)
     m_tooltip = "Click to choose a different MIDI file.";

   if (m_input_tile->ButtonLeft().hovering)
     m_tooltip = "Cycle through available input devices.";

   if (m_input_tile->ButtonRight().hovering)
     m_tooltip = "Cycle through available input devices.";

   if (m_input_tile->ButtonPreview().hovering) {
     if (m_input_tile->IsPreviewOn())
       m_tooltip = "Turn off test MIDI input for this device.";
     else
       m_tooltip = "Click to test your MIDI input device by playing notes.";
   }

   if (m_output_tile->ButtonLeft().hovering)
     m_tooltip = "Cycle through available output devices.";

   if (m_output_tile->ButtonRight().hovering)
     m_tooltip = "Cycle through available output devices.";

   if (m_output_tile->ButtonPreview().hovering) {
     if (m_output_tile->IsPreviewOn())
       m_tooltip = "Turn off output test for this device.";
      else
        m_tooltip = "Click to test MIDI output on this device.";
   }
}
Example #4
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 #5
0
void MidiCommOut::Write(const MidiEvent &out)
{
   MidiEventSimple simple;
   if (!out.GetSimpleEvent(&simple)) return;
   
   if (m_description.id == 0)
   {
      
      MusicDeviceMIDIEvent(m_device, simple.status, simple.byte1, simple.byte2, 0);
      
      if (out.Type() == MidiEventType_Controller)
      {
         // If we just set the data byte for some previous controller event,
         // "close off" changes to it. That way, if the output device doesn't
         // accept this (N)RPN event, it won't accidentally overwrite the last
         // one that it did.
         
         // NOTE: Hopefully there aren't any (N)RPN types that rely on sequentially
         // changing these values smoothly.  That seems like a pretty special
         // case though.  I'll cross that bridge when I come to it.
         //
         // I tried "closing" controller changes just *before* a data (N)RPN
         // event (in order to cut off some hypothetical previous (N)RPN event
         // at the last possible second), but it didn't appear to work.
         
         // NOTE: This appears to only be necessary for the DLS Synth.  I suppose
         // I've only got a VERY limited pool of MIDI devices to work with though,
         // and I'm sure there are a handful of devices out there that have the
         // same problem.  Again, I'll cross that bridge when I come to it.

         // Detect coarse data byte changes
         if (simple.byte1 == 0x06)
         {
            MusicDeviceMIDIEvent(m_device, simple.status, 0x64, 0x7F, 0); // RPN (coarse) reset
            MusicDeviceMIDIEvent(m_device, simple.status, 0x62, 0x7F, 0); // NRPN (coarse) reset
         }
         
         // Detect fine data byte changes
         if (simple.byte1 == 0x26)
         {
            MusicDeviceMIDIEvent(m_device, simple.status, 0x65, 0x7F, 0); // RPN (fine) reset
            MusicDeviceMIDIEvent(m_device, simple.status, 0x63, 0x7F, 0); // NRPN (fine) reset
         }
      }
      
   }
   else
   {
      const static int PacketBufferSize = 128;
      Byte packet_buffer[PacketBufferSize];
      MIDIPacketList *packets = reinterpret_cast<MIDIPacketList*>(packet_buffer);
      
      MIDIPacket *packet = MIDIPacketListInit(packets);
      
      int messageSize = 3;
      if (out.Type() == MidiEventType_ProgramChange || out.Type() == MidiEventType_ChannelPressure) messageSize = 2;
      
      const static int MaxMessageSize = 3;
      const Byte message[MaxMessageSize] = { simple.status, simple.byte1, simple.byte2 };
      packet = MIDIPacketListAdd(packets, PacketBufferSize, packet, 0, messageSize, message);
      
      MIDISend(m_port, m_endpoint, packets);
   }
   
}
Example #6
0
void PlayingState::Listen() {
  if (!m_state.midi_in)
    return;

  while (m_state.midi_in->KeepReading()) {

    microseconds_t cur_time = m_state.midi->GetSongPositionInMicroseconds();
    MidiEvent ev = m_state.midi_in->Read();
    if (m_state.midi_in->ShouldReconnect())
    {
        m_state.midi_in->Reconnect();
        m_state.midi_out->Reconnect();
        continue;
    }


    // Just eat input if we're paused
    if (m_paused)
      continue;

    // We're only interested in NoteOn and NoteOff
    if (ev.Type() != MidiEventType_NoteOn && ev.Type() != MidiEventType_NoteOff)
      continue;

    // Octave Sliding
    ev.ShiftNote(m_note_offset);

    int note_number = ev.NoteNumber();
    string note_name = MidiEvent::NoteName(note_number);

    // On key release we have to look for existing "active" notes and turn them off.
    if (ev.Type() == MidiEventType_NoteOff || ev.NoteVelocity() == 0) {

      // NOTE: This assumes mono-channel input.  If they're piping an entire MIDI file
      //       (or even the *same* MIDI file) through another source, we could get the
      //       same NoteId on different channels -- and this code would start behaving
      //       incorrectly.
      for (ActiveNoteSet::iterator i = m_active_notes.begin(); i != m_active_notes.end(); ++i) {
        if (ev.NoteNumber() != i->note_id)
          continue;

        // Play it on the correct channel to turn the note we started
        // previously, off.
        ev.SetChannel(i->channel);
        if (m_state.midi_out)
          m_state.midi_out->Write(ev);

        m_active_notes.erase(i);
        break;
      }

      // User releases the key
      // If we delete this line, than all pressed keys will be gray until
      // it is unpressed automatically
      m_keyboard->SetKeyActive(note_name, false, Track::FlatGray);
      userPressedKey(note_number, false);
      continue;
    }

    TranslatedNoteSet::iterator closest_match = m_notes.end();
    for (TranslatedNoteSet::iterator i = m_notes.begin(); i != m_notes.end(); ++i) {

      const microseconds_t window_start = i->start - (KeyboardDisplay::NoteWindowLength / 2);
      const microseconds_t window_end = i->start + (KeyboardDisplay::NoteWindowLength / 2);

      // As soon as we start processing notes that couldn't possibly
      // have been played yet, we're done.
      if (window_start > cur_time)
        break;

      if (i->state != UserPlayable)
        continue;

      if (window_end > cur_time && i->note_id == ev.NoteNumber()) {

        if (closest_match == m_notes.end()) {
          closest_match = i;
          continue;
        }

        microseconds_t this_distance = cur_time - i->start;
        if (i->start > cur_time)
          this_distance = i->start - cur_time;

        microseconds_t known_best = cur_time - closest_match->start;
        if (closest_match->start > cur_time)
          known_best = closest_match->start - cur_time;

        if (this_distance < known_best)
          closest_match = i;
      }
    }

    Track::TrackColor note_color = Track::FlatGray;

    if (closest_match != m_notes.end()) {
      note_color = m_state.track_properties[closest_match->track_id].color;

      // "Open" this note so we can catch the close later and turn off
      // the note.
      ActiveNote n;
      n.channel = closest_match->channel;
      n.note_id = closest_match->note_id;
      n.velocity = closest_match->velocity;
      m_active_notes.insert(n);

      // Play it
      ev.SetChannel(n.channel);
      ev.SetVelocity(n.velocity);

      bool silently =
          m_state.track_properties[closest_match->track_id].mode == Track::ModeYouPlaySilently ||
          m_state.track_properties[closest_match->track_id].mode == Track::ModeLearningSilently;
      if (m_state.midi_out && !silently)
        m_state.midi_out->Write(ev);

      // Adjust our statistics
      const static double NoteValue = 100.0;
      m_state.stats.score += NoteValue * CalculateScoreMultiplier() * (m_state.song_speed / 100.0);

      m_state.stats.notes_user_could_have_played++;
      m_state.stats.speed_integral += m_state.song_speed;

      m_state.stats.notes_user_actually_played++;
      m_current_combo++;
      m_state.stats.longest_combo = max(m_current_combo, m_state.stats.longest_combo);

      TranslatedNote replacement = *closest_match;
      replacement.state = UserHit;

      m_notes.erase(closest_match);
      m_notes.insert(replacement);
    }

    else
      m_state.stats.stray_notes++;

    m_state.stats.total_notes_user_pressed++;
    // Display a pressed key by an user
    // Display a colored key, if it is pressed correctly
    // Otherwise display a grey key
    // 
    // If we comment this code, than a missed user pressed key will not shown.
    // But correct presed key will be shown as usual.
    m_keyboard->SetKeyActive(note_name, true, note_color);
    userPressedKey(note_number, true);
  }
}