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); } }