receive(uart)

e/serial-midi/receive(uart)

No description
receive(uart)
@/receive(uart)
MIDIxod/uart/uart
RCVpulse
receive(uart)
MSG
DONE
MIDI
RCV
DONEpulse
MSGe/midi/message
To use the node in your project you should have the e/serial-midi library installed. Use the “File → Add Library” menu item in XOD IDE if you don’t have it yet. See Using libraries for more info.

C++ implementation

// Most of the code below is taken from Arduino MIDI Library (https://github.com/FortySevenEffects/arduino_midi_library)
// and modified to work as a XOD node.

enum MidiType {
    InvalidType           = 0x00,    ///< For notifying errors
    NoteOff               = 0x80,    ///< Note Off
    NoteOn                = 0x90,    ///< Note On
    AfterTouchPoly        = 0xA0,    ///< Polyphonic AfterTouch
    ControlChange         = 0xB0,    ///< Control Change / Channel Mode
    ProgramChange         = 0xC0,    ///< Program Change
    AfterTouchChannel     = 0xD0,    ///< Channel (monophonic) AfterTouch
    PitchBend             = 0xE0,    ///< Pitch Bend
    SystemExclusive       = 0xF0,    ///< System Exclusive
    TimeCodeQuarterFrame  = 0xF1,    ///< System Common - MIDI Time Code Quarter Frame
    SongPosition          = 0xF2,    ///< System Common - Song Position Pointer
    SongSelect            = 0xF3,    ///< System Common - Song Select
    TuneRequest           = 0xF6,    ///< System Common - Tune Request
    Clock                 = 0xF8,    ///< System Real Time - Timing Clock
    Start                 = 0xFA,    ///< System Real Time - Start
    Continue              = 0xFB,    ///< System Real Time - Continue
    Stop                  = 0xFC,    ///< System Real Time - Stop
    ActiveSensing         = 0xFE,    ///< System Real Time - Active Sensing
    SystemReset           = 0xFF,    ///< System Real Time - System Reset
};

struct State {
    MidiType runningStatus = InvalidType;
    uint8_t pendingMessage[3] = {0, 0, 0};
    uint8_t pendingMessageExpectedLength = 0;
    uint8_t pendingMessageIndex = 0;
};

// clang-format off
{{ GENERATED_CODE }}
// clang-format on

MidiType getTypeFromStatusByte(byte inStatus) {
    if ((inStatus  < 0x80) ||
        (inStatus == 0xf4) ||
        (inStatus == 0xf5) ||
        (inStatus == 0xf9) ||
        (inStatus == 0xfD)) {
        // Data bytes and undefined.
        return InvalidType;
    }

    if (inStatus < 0xf0) {
        // Channel message, remove channel nibble.
        return MidiType(inStatus & 0xf0);
    }

    return MidiType(inStatus);
}

inline uint8_t getChannelFromStatusByte(byte inStatus) {
    return (inStatus & 0x0f) + 1;
}

bool isChannelMessage(MidiType inType) {
    return (inType == NoteOff           ||
            inType == NoteOn            ||
            inType == ControlChange     ||
            inType == AfterTouchPoly    ||
            inType == AfterTouchChannel ||
            inType == PitchBend         ||
            inType == ProgramChange);
}

inline void resetInput(State* state) {
    state->pendingMessageIndex = 0;
    state->pendingMessageExpectedLength = 0;
    state->runningStatus = InvalidType;
}

void evaluate(Context ctx) {
    auto uart = getValue<input_MIDI>(ctx);

    // TODO: is this really necessary?
    if (uart->getBaudRate() != 31250) {
        uart->changeBaudRate(31250);
    }

    if (!isInputDirty<input_RCV>(ctx))
        return;

    if (!uart->available())
        return;

    auto state = getState(ctx);

    // Parsing algorithm:
    // Get a byte from the serial buffer.
    // If there is no pending message to be recomposed, start a new one.
    //  - Find type and channel (if pertinent)
    //  - Look for other bytes in buffer, call parser recursively,
    //    until the message is assembled or the buffer is empty.
    // Else, add the extracted byte to the pending message, and check validity.
    // When the message is done, emit it along with the "DONE" pulse.

    uint8_t extracted = 0x00;
    if (!uart->readByte(&extracted))
        return;

    // Ignore Undefined
    if (extracted == 0xf9 || extracted == 0xfd)
        return;

    if (state->pendingMessageIndex == 0) {
        // Start a new pending message
        state->pendingMessage[0] = extracted;

        // Check for running status first
        if (isChannelMessage(getTypeFromStatusByte(state->runningStatus))) {
            // Only these types allow Running Status

            // If the status byte is not received, prepend it
            // to the pending message
            if (extracted < 0x80) {
                state->pendingMessage[0] = state->runningStatus;
                state->pendingMessage[1] = extracted;
                state->pendingMessageIndex = 1;
            }
            // Else: well, we received another status byte,
            // so the running status does not apply here.
            // It will be updated upon completion of this message.
        }

        switch (getTypeFromStatusByte(state->pendingMessage[0])) {
            // 1 byte messages
            case Start:
            case Continue:
            case Stop:
            case Clock:
            case ActiveSensing:
            case SystemReset:
            case TuneRequest: {
                // Handle the message type directly here.
                ValueType<output_MSG>::T msg = {};
                msg.type = getTypeFromStatusByte(state->pendingMessage[0]);
                msg.channel = 0;
                msg.data1 = 0;
                msg.data2 = 0;

                emitValue<output_MSG>(ctx, msg);
                emitValue<output_DONE>(ctx, 1);

                // Do not reset all input attributes, Running Status must remain unchanged.
                // We still need to reset these
                state->pendingMessageIndex = 0;
                state->pendingMessageExpectedLength = 0;

                return;
            }

            // 2 byte messages
            case ProgramChange:
            case AfterTouchChannel:
            case TimeCodeQuarterFrame:
            case SongSelect: {
                state->pendingMessageExpectedLength = 2;
                break;
            }

            // 3 byte messages
            case NoteOn:
            case NoteOff:
            case ControlChange:
            case PitchBend:
            case AfterTouchPoly:
            case SongPosition: {
                state->pendingMessageExpectedLength = 3;
                break;
            }

            case SystemExclusive: {
                // The message can be any length
                // between 3 and MidiMessage::sSysExMaxSize bytes
                state->pendingMessageExpectedLength = 3;
                state->runningStatus = InvalidType;
                // TODO: properly handle SysEx messages
                break;
            }

            case InvalidType:
            default: {
                resetInput(state);
                return;
            }
        }

        if (state->pendingMessageIndex >= (state->pendingMessageExpectedLength - 1)) {
            // Reception complete
            ValueType<output_MSG>::T msg = {};
            msg.type = getTypeFromStatusByte(state->pendingMessage[0]);
            msg.channel = getChannelFromStatusByte(state->pendingMessage[0]);
            msg.data1 = state->pendingMessage[1];
            msg.data2 = 0; // Completed new message has 1 data byte

            emitValue<output_MSG>(ctx, msg);
            emitValue<output_DONE>(ctx, 1);

            state->pendingMessageIndex = 0;
            state->pendingMessageExpectedLength = 0;
            return;
        } else {
            // Waiting for more data
            state->pendingMessageIndex++;
        }

        // Message is not complete, wait for another call
        return;
    } else {
        // First, test if this is a status byte
        if (extracted >= 0x80) {
            // Reception of status bytes in the middle of an uncompleted message
            // are allowed only for interleaved Real Time message or EOX
            switch (extracted) {
                case Clock:
                case Start:
                case Continue:
                case Stop:
                case ActiveSensing:
                case SystemReset:
                    // Here we will have to extract the one-byte message,
                    // emit it, and recompose the message it was
                    // interleaved into. Oh, and without killing the running status..
                    // This is done by leaving the pending message as is,
                    // it will be completed on next calls.
                    ValueType<output_MSG>::T msg = {};
                    msg.type = extracted;
                    msg.channel = 0;
                    msg.data1= 0;
                    msg.data2 = 0;

                    emitValue<output_MSG>(ctx, msg);
                    emitValue<output_DONE>(ctx, 1);

                    return;

                // End of Exclusive
                case 0xf7:
                    // TODO: properly handle SysEx messages
                    resetInput(state);
                    return;

                default:
                    break; // LCOV_EXCL_LINE - Coverage blind spot
            }
        }

        // Add extracted data byte to pending message
        if (state->pendingMessage[0] == SystemExclusive) {
            // TODO: properly handle SysEx messages
        } else {
            state->pendingMessage[state->pendingMessageIndex] = extracted;
        }

        // Now we are going to check if we have reached the end of the message
        if (state->pendingMessageIndex >= (state->pendingMessageExpectedLength - 1)) {
            // "FML" case: fall down here with an overflown SysEx..
            // This means we received the last possible data byte that can fit the buffer.
            if (state->pendingMessage[0] == SystemExclusive) {
                resetInput(state);
                return;
            }

            ValueType<output_MSG>::T msg = {};
            msg.type = getTypeFromStatusByte(state->pendingMessage[0]);

            if (isChannelMessage(msg.type))
                msg.channel = getChannelFromStatusByte(state->pendingMessage[0]);
            else
                msg.channel = 0;

            msg.data1 = state->pendingMessage[1];

            // Save data2 only if applicable
            msg.data2 = state->pendingMessageExpectedLength == 3 ? state->pendingMessage[2] : 0;

            // Reset local variables
            state->pendingMessageIndex = 0;
            state->pendingMessageExpectedLength = 0;

            // Handle NoteOn meaasges with 0 velocity as NoteOff
            if (msg.type == NoteOn && msg.data2 == 0) {
                msg.type = NoteOff;
            }

            emitValue<output_MSG>(ctx, msg);
            emitValue<output_DONE>(ctx, 1);

            // Activate running status (if enabled for the received type)
            switch (msg.type) {
                case NoteOff:
                case NoteOn:
                case AfterTouchPoly:
                case ControlChange:
                case ProgramChange:
                case AfterTouchChannel:
                case PitchBend:
                    // Running status enabled: store it from received message
                    state->runningStatus = state->pendingMessage[0];
                    break;

                default:
                    // No running status
                    state->runningStatus = InvalidType;
                    break;
            }
        } else {
            // Message is not complete.
            // Update the index of the pending message.
            state->pendingMessageIndex++;
        }
    }
}