int SMFProcessor::seek(SynthRoute *synthRoute, const MidiEventList &midiEvents, int currentEventIx, MasterClockNanos seekNanos, MasterClockNanos currentEventNanos) { MasterClockNanos nanosNow = MasterClock::getClockNanos(); while (!stopProcessing && currentEventNanos < seekNanos && currentEventIx < midiEvents.size()) { const MidiEvent &e = midiEvents.at(currentEventIx); while (!stopProcessing && synthRoute->getState() == SynthRouteState_OPEN) { bool res = true; switch (e.getType()) { case SHORT_MESSAGE: { quint32 msg = e.getShortMessage(); if ((msg & 0xE0) != 0x80) res = synthRoute->pushMIDIShortMessage(msg, nanosNow); break; } case SYSEX: res = synthRoute->pushMIDISysex(e.getSysexData(), e.getSysexLen(), nanosNow); break; case SET_TEMPO: { uint tempo = e.getShortMessage(); midiTick = parser.getMidiTick(tempo); emit driver->tempoUpdated(MidiParser::MICROSECONDS_PER_MINUTE / tempo); break; } default: break; } if (res) break; qDebug() << "SMFProcessor: MIDI buffer became full while seeking, taking a nap"; usleep(MAX_SLEEP_TIME / MasterClock::NANOS_PER_MICROSECOND); nanosNow = MasterClock::getClockNanos(); } currentEventIx++; currentEventNanos += e.getTimestamp() * midiTick; } return currentEventIx; }
quint32 SMFProcessor::estimateRemainingTime(const MidiEventList &midiEvents, int currentEventIx) { MasterClockNanos tick = midiTick; MasterClockNanos totalNanos = 0; for (int i = currentEventIx; i < midiEvents.count(); i++) { const MidiEvent &e = midiEvents.at(i); totalNanos += e.getTimestamp() * tick; if (e.getType() == SET_TEMPO) tick = parser.getMidiTick(e.getShortMessage()); } return quint32(totalNanos / MasterClock::NANOS_PER_SECOND); }
MidiEventList MidiIn::readAllNotes() { MidiEventList allNotes; //TODO: should be possible to transform it into // a do while MidiEvent event = read(); while( event.get_type() == MidiEventType_NoteOff || event.get_type() == MidiEventType_NoteOn ) { allNotes.push_back(event); event = read(); } return allNotes; }
MidiEventList MidiTrack::Update(microseconds_t delta_microseconds) { m_running_microseconds += delta_microseconds; MidiEventList evs; for (size_t i = m_last_event + 1; i < m_events.size(); ++i) { if (m_event_usecs[i] <= m_running_microseconds) { evs.push_back(m_events[i]); m_last_event = static_cast<long>(i); if (m_events[i].Type() == MidiEventType_NoteOn && m_events[i].NoteVelocity() > 0) m_notes_remaining--; } else break; } return evs; }
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; } } }
bool MidiParser::parseTrack(MidiEventList &midiEventList) { char header[8]; forever { if (!readFile(header, 8)) return false; if (memcmp(header, trackID, 4) == 0) { break; } else { qDebug() << "MidiParser: Wrong MIDI track signature, skipping unknown data chunk"; quint32 dataLen = qFromBigEndian<quint32>((uchar *)&header[4]); if (file.seek(file.pos() + dataLen)) { qDebug() << "MidiParser: Error in data chunk"; return false; } } } quint32 trackLen = qFromBigEndian<quint32>((uchar *)&header[4]); char *trackData = new char[trackLen]; if (!readFile(trackData, trackLen)) { delete trackData; return false; } // Reserve memory for MIDI events, approx. 3 bytes per event midiEventList.reserve(trackLen / 3); qDebug() << "MidiParser: Memory reservation" << trackLen / 3; // Parsing actual MIDI events unsigned int runningStatus = 0; uchar *data = (uchar *)trackData; uchar sysexBuffer[MAX_SYSEX_LENGTH]; while(data < (uchar *)trackData + trackLen) { SynthTimestamp time = parseVarLenInt(data); quint32 message = 0; if (*data & 0x80) { // It's normal status byte if ((*data & 0xF0) == 0xF0) { // It's a special event if (*data == 0xF0) { // It's a sysex event sysexBuffer[0] = *(data++); quint32 sysexLength = parseVarLenInt(data); if (MAX_SYSEX_LENGTH <= sysexLength) { qDebug() << "MidiParser: too long sysex encountered:" << sysexLength; data += sysexLength; continue; } memcpy(&sysexBuffer[1], data, sysexLength); data += sysexLength; midiEventList.newMidiEvent().assignSysex(time, sysexBuffer, sysexLength + 1); continue; } else if (*data == 0xF7) { qDebug() << "MidiParser: Fragmented sysex, unsupported"; data++; quint32 len = parseVarLenInt(data); data += len; } else if (*(data++) == 0xFF) { uint metaType = *(data++); quint32 len = parseVarLenInt(data); if (metaType == 0x2F) { qDebug() << "MidiParser: End-of-track Meta-event"; runningStatus = 0x2F; break; } else if (metaType == 0x51) { uint newTempo = qFromBigEndian<quint32>(data) >> 8; midiEventList.newMidiEvent().assignSetTempoMessage(time, newTempo); qDebug() << "MidiParser: Meta-event: Set tempo:" << newTempo; data += len; continue; } else { qDebug() << "MidiParser: Meta-event code" << metaType << "unsupported"; } data += len; } else { qDebug() << "MidiParser: Unsupported event" << *(data++); } if (time > 0) { // The event is unsupported. Nevertheless, assign a special marker event to retain timing information qDebug() << "MidiParser: Adding sync event for" << time << "divisions"; midiEventList.newMidiEvent().assignSyncMessage(time); } continue; } else if ((*data & 0xE0) == 0xC0) { // It's a short message with one data byte message = qFromLittleEndian<quint16>(data); data += 2; } else { // It's a short message with two data bytes message = qFromLittleEndian<quint32>(data) & 0xFFFFFF; data += 3; } runningStatus = message & 0xFF; } else { // Handle running status if ((runningStatus & 0x80) == 0) { qDebug() << "MidiParser: First MIDI event must has status byte"; data++; continue; } if ((runningStatus & 0xE0) == 0xC0) { // It's a short message with one data byte message = runningStatus | ((quint32)*data << 8); data++; } else { // It's a short message with two data bytes message = runningStatus | ((quint32)qFromLittleEndian<quint16>(data) << 8); data += 2; } } midiEventList.newMidiEvent().assignShortMessage(time, message); } if (runningStatus != 0x2F) { qDebug() << "MidiParser: End-of-track Meta-event isn't the last event, file is probably corrupted."; } delete trackData; qDebug() << "MidiParser: Parsed" << midiEventList.count() << "MIDI events"; return true; }
void AudioFileWriter::run() { QFile file(outFileName); bool waveMode = false; if (outFileName.endsWith(".wav")) waveMode = true; if (!file.open(QIODevice::WriteOnly)) { qDebug() << "AudioFileWriter: Can't open file for writing:" << outFileName; synth->close(); return; } if (waveMode) file.seek(44); MasterClockNanos startNanos = MasterClock::getClockNanos(); MasterClockNanos firstSampleNanos = 0; MasterClockNanos midiTick = 0; MasterClockNanos midiNanos = 0; MidiEventList midiEvents; int midiEventIx = 0; uint parserIx = 0; if (realtimeMode) { firstSampleNanos = startNanos; } else { midiEvents = parsers[parserIx].getMIDIEvents(); midiTick = parsers[parserIx].getMidiTick(); } qDebug() << "AudioFileWriter: Rendering started"; while (!stopProcessing) { unsigned int frameCount = 0; if (realtimeMode) { frameCount = sampleRate * (MasterClock::getClockNanos() - firstSampleNanos) / MasterClock::NANOS_PER_SECOND; if (frameCount < bufferSize) { usleep(1000000 * (bufferSize - frameCount) / sampleRate); continue; } else { frameCount = bufferSize; } } else { while (midiEventIx < midiEvents.count()) { const MidiEvent &e = midiEvents.at(midiEventIx); bool eventPushed = true; MasterClockNanos nextEventNanos = midiNanos + e.getTimestamp() * midiTick; frameCount = (uint)(sampleRate * (nextEventNanos - firstSampleNanos) / MasterClock::NANOS_PER_SECOND); if (bufferSize < frameCount) { frameCount = bufferSize; break; } switch (e.getType()) { case SHORT_MESSAGE: eventPushed = synth->pushMIDIShortMessage(e.getShortMessage(), nextEventNanos); break; case SYSEX: eventPushed = synth->pushMIDISysex(e.getSysexData(), e.getSysexLen(), nextEventNanos); break; case SET_TEMPO: midiTick = parsers[parserIx].getMidiTick(e.getShortMessage()); break; default: break; } if (!eventPushed) { qDebug() << "AudioFileWriter: MIDI buffer overflow, midiNanos:" << midiNanos; break; } midiNanos = nextEventNanos; midiEventIx++; emit midiEventProcessed(midiEventIx, midiEvents.count()); } if (midiEvents.count() <= midiEventIx) { if (parserIx < parsersCount - 1) { ++parserIx; midiEventIx = 0; midiEvents = parsers[parserIx].getMIDIEvents(); midiTick = parsers[parserIx].getMidiTick(); continue; } if (!synth->isActive()) break; frameCount += bufferSize; qDebug() << "AudioFileWriter: Rendering after the end of MIDI file, time:" << (double)midiNanos / MasterClock::NANOS_PER_SECOND; } } while (frameCount > 0) { unsigned int framesToRender = qMin(bufferSize, frameCount); unsigned int framesRendered = synth->render(buffer, framesToRender, firstSampleNanos - latency, sampleRate); qint64 bytesToWrite = framesRendered * FRAME_SIZE; char *bufferPos = (char *)buffer; while (bytesToWrite > 0) { qint64 bytesWritten = file.write(bufferPos, bytesToWrite); if (bytesWritten == -1) { qDebug() << "AudioFileWriter: error writing into the audio file:" << file.errorString(); file.close(); return; } bytesToWrite -= bytesWritten; bufferPos += bytesWritten; } firstSampleNanos += MasterClock::NANOS_PER_SECOND * framesRendered / sampleRate; frameCount -= framesRendered; if (!realtimeMode) qDebug() << "AudioFileWriter: Rendering time:" << (double)firstSampleNanos / MasterClock::NANOS_PER_SECOND; } } qDebug() << "AudioFileWriter: Rendering finished"; if (!realtimeMode) qDebug() << "AudioFileWriter: Elapsed seconds: " << 1e-9 * (MasterClock::getClockNanos() - startNanos); if (waveMode) { unsigned char *charBuffer = (unsigned char *)buffer; memcpy(charBuffer, WAVE_HEADER, 44); quint32 fileSize = (quint32)file.size(); qToLittleEndian(fileSize - 8, charBuffer + 4); qToLittleEndian(fileSize - 44, charBuffer + 40); qToLittleEndian(sampleRate, charBuffer + 24); qToLittleEndian(sampleRate * FRAME_SIZE, charBuffer + 28); file.seek(0); file.write((char *)charBuffer, 44); } file.close(); synth->close(); if (!stopProcessing) emit conversionFinished(); }