void MidiPlayer::run() { // Workaround to fix errors with the Microsoft GS Wavetable Synth on // Windows 10 - see http://stackoverflow.com/a/32553208/586978 #ifdef _WIN32 CoInitializeEx(nullptr, COINIT_MULTITHREADED); BOOST_SCOPE_EXIT(this_) { CoUninitialize(); } BOOST_SCOPE_EXIT_END #endif boost::signals2::scoped_connection connection( mySettingsManager.subscribeToChanges([&]() { auto settings = mySettingsManager.getReadHandle(); myMetronomeEnabled = settings->get(Settings::MetronomeEnabled); })); setIsPlaying(true); MidiFile::LoadOptions options; options.myEnableMetronome = true; options.myRecordPositionChanges = true; // Load MIDI settings. int api; int port; { auto settings = mySettingsManager.getReadHandle(); myMetronomeEnabled = settings->get(Settings::MetronomeEnabled); api = settings->get(Settings::MidiApi); port = settings->get(Settings::MidiPort); options.myMetronomePreset = settings->get(Settings::MetronomePreset) + Midi::MIDI_PERCUSSION_PRESET_OFFSET; options.myStrongAccentVel = settings->get(Settings::MetronomeStrongAccent); options.myWeakAccentVel = settings->get(Settings::MetronomeWeakAccent); options.myVibratoStrength = settings->get(Settings::MidiVibratoLevel); options.myWideVibratoStrength = settings->get(Settings::MidiWideVibratoLevel); } MidiFile file; file.load(myScore, options); const int ticks_per_beat = file.getTicksPerBeat(); // Merge the MIDI evvents for each track. MidiEventList events; for (MidiEventList &track : file.getTracks()) { track.convertToAbsoluteTicks(); events.concat(track); } // TODO - since each track is already sorted, an n-way merge should be faster. std::stable_sort(events.begin(), events.end()); events.convertToDeltaTicks(); // Initialize RtMidi and set the port. MidiOutputDevice device; if (!device.initialize(api, port)) { emit error(tr("Error initializing MIDI output device.")); return; } bool started = false; int beat_duration = Midi::BEAT_DURATION_120_BPM; const SystemLocation start_location(myStartLocation.getSystemIndex(), myStartLocation.getPositionIndex()); SystemLocation current_location = start_location; for (auto event = events.begin(); event != events.end(); ++event) { if (!isPlaying()) break; if (event->isTempoChange()) beat_duration = event->getTempo(); // Skip events before the start location, except for events such as // instrument changes. Tempo changes are tracked above. if (!started) { if (event->getLocation() < start_location) { if (event->isProgramChange()) device.sendMessage(event->getData()); continue; } else { performCountIn(device, event->getLocation(), beat_duration); started = true; } } const int delta = event->getTicks(); assert(delta >= 0); const int duration_us = boost::rational_cast<int>( boost::rational<int>(delta, ticks_per_beat) * beat_duration); usleep(duration_us * (100.0 / myPlaybackSpeed)); // Don't play metronome events if the metronome is disabled. if (event->isNoteOnOff() && event->getChannel() == METRONOME_CHANNEL && !myMetronomeEnabled) { continue; } device.sendMessage(event->getData()); // Notify listeners of the current playback position. if (event->getLocation() != current_location) { const SystemLocation &new_location = event->getLocation(); // Don't move backwards unless a repeat occurred. if (new_location < current_location && !event->isPositionChange()) continue; if (new_location.getSystem() != current_location.getSystem()) emit playbackSystemChanged(new_location.getSystem()); emit playbackPositionChanged(new_location.getPosition()); current_location = new_location; } } }