Beispiel #1
0
/**
 * Reads program change commands.
 * +--+--+--+--+--+
 * |Bn|00|xx|Cn|yy| where n=channel, xx=bank, yy=preset
 * +--+--+--+--+--+
 * @param info an Info value object.
 * @param val the data.
 * @param bank the selected bank, initialised on exit.
 * @param preset the selected preset, initialised on exit.
 * @return true if frame complete, otherwise false.
 */
bool GSysPC::read(Info& info, int* bank, int* preset) {

    bool ret = false;
    if(info.serial->available() == 0)  return ret;         // Available?

    int val = info.serial->read();                         // The data

    if(isControlChange(info.channel, val)) info.index = 0; // Start frame on CC

    if(info.index >= BUFSIZE ||                            // Frame started?
            info.index == UNDEFINED) return ret;

    info.buffer[info.index++] = val;                       // Store

    if(info.index == CMDSIZE) {                            // Frame complete?
        info.index = UNDEFINED;                            // Reset index
        volatile byte* b = &info.buffer[0];                // Buffer pointer
        if(b[1] == BANK_SELECT &&
            isProgramChange(info.channel, b[3])) {         // CC#0 and PC?
              *bank   = b[2];                              // Assign bank
              *preset = b[4];                              // Assign preset
               ret    = true;                              // Finished
        }
    }

    return ret;

}   // read()
String MidiMessage::getDescription() const
{
    if (isNoteOn())           return "Note on "  + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel());
    if (isNoteOff())          return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel());
    if (isProgramChange())    return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel());
    if (isPitchWheel())       return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel());
    if (isAftertouch())       return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) +  ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel());
    if (isChannelPressure())  return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel());
    if (isAllNotesOff())      return "All notes off Channel " + String (getChannel());
    if (isAllSoundOff())      return "All sound off Channel " + String (getChannel());
    if (isMetaEvent())        return "Meta event";

    if (isController())
    {
        String name (MidiMessage::getControllerName (getControllerNumber()));

        if (name.isEmpty())
            name = String (getControllerNumber());

        return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel());
    }

    return String::toHexString (getRawData(), getRawDataSize());
}
int MidiMessage::getProgramChangeNumber() const noexcept
{
    jassert (isProgramChange());
    return getRawData()[1];
}
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;
        }
    }
}