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; }
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; }
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."; } }
// 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); } }
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); } }
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); } }