1051 lines
35 KiB
C++
1051 lines
35 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Project : VST SDK
|
|
// Flags : clang-format SMTGSequencer
|
|
// Category : Helpers
|
|
// Filename : public.sdk/source/vst/utility/ump.h
|
|
// Created by : Steinberg, 12/2023
|
|
// Description : a single c++17 header UMP parser implementation without other dependencies than
|
|
// to std::array
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// This file is part of a Steinberg SDK. It is subject to the license terms
|
|
// in the LICENSE file found in the top-level directory of this distribution
|
|
// and at www.steinberg.net/sdklicenses.
|
|
// No part of the SDK, including this file, may be copied, modified, propagated,
|
|
// or distributed except according to the terms contained in the LICENSE file.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#pragma once
|
|
|
|
#ifndef SMTG_ALWAYS_INLINE
|
|
#if _MSVC
|
|
#define SMTG_ALWAYS_INLINE __forceinline
|
|
#else
|
|
#define SMTG_ALWAYS_INLINE __inline__ __attribute__ ((__always_inline__))
|
|
#endif
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
|
|
//------------------------------------------------------------------------
|
|
namespace Steinberg::Vst::UMP {
|
|
|
|
struct IUniversalMidiPacketHandler;
|
|
enum class ParsingAction : uint32_t;
|
|
|
|
//------------------------------------------------------------------------
|
|
/** the parse sections control which messages to process of UMP
|
|
*
|
|
* the generated code is smaller and runtime is faster if only the needed sections are enabled.
|
|
*/
|
|
enum ParseSections : uint8_t
|
|
{
|
|
Utility = 1 << 0,
|
|
System = 1 << 1,
|
|
ChannelVoice1 = 1 << 2,
|
|
SysEx = 1 << 3,
|
|
ChannelVoice2 = 1 << 3,
|
|
Data128 = 1 << 4,
|
|
All = 0xff
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
/** stateless parsing universal MIDI packets
|
|
*
|
|
* @tparam sections which sections to parse
|
|
* @param numWords number of 32-bit words
|
|
* @param words pointer to a words array
|
|
* @param handler callback handler
|
|
* @return number of successfully processed words
|
|
*/
|
|
template <ParseSections sections = ParseSections::All>
|
|
size_t parsePackets (const size_t numWords, const uint32_t* words,
|
|
const IUniversalMidiPacketHandler& handler);
|
|
|
|
//------------------------------------------------------------------------
|
|
struct IUniversalMidiPacketHandler
|
|
{
|
|
using Group = uint8_t;
|
|
using Channel = uint8_t;
|
|
using Index = uint8_t;
|
|
using NoteNumber = uint8_t;
|
|
using BankNumber = uint8_t;
|
|
using ControllerNumber = uint8_t;
|
|
using Velocity8 = uint8_t;
|
|
using Velocity16 = uint16_t;
|
|
using AttributeType = uint8_t;
|
|
using AttributeValue = uint16_t;
|
|
using OptionFlags = uint8_t;
|
|
using Data8 = uint8_t;
|
|
using Data32 = uint32_t;
|
|
using Program = uint8_t;
|
|
using BankMSB = uint8_t;
|
|
using BankLSB = uint8_t;
|
|
using Timestamp = uint16_t;
|
|
using Timecode = uint8_t;
|
|
using StreamID = uint8_t;
|
|
using SysEx6ByteData = const std::array<uint8_t, 6>&;
|
|
using SysEx13ByteData = const std::array<uint8_t, 13>&;
|
|
using MixedData = const std::array<uint8_t, 14>&;
|
|
|
|
// UTILITY
|
|
virtual void onNoop (Group group) const = 0;
|
|
virtual void onJitterClock (Group group, Timestamp time) const = 0;
|
|
virtual void onJitterTimestamp (Group group, Timestamp time) const = 0;
|
|
|
|
// SYSTEM COMMON
|
|
virtual void onMIDITimeCode (Group group, Timecode timecode) const = 0;
|
|
virtual void onSongPositionPointer (Group group, uint8_t posLSB, uint8_t posMSB) const = 0;
|
|
virtual void onSongSelect (Group group, uint8_t songIndex) const = 0;
|
|
virtual void onTuneRequest (Group group) const = 0;
|
|
|
|
// SYSTEM REALTIME
|
|
enum class SystemRealtime
|
|
{
|
|
TimingClock,
|
|
Start,
|
|
Continue,
|
|
Stop,
|
|
ActiveSensing,
|
|
Reset
|
|
};
|
|
virtual void onSystemRealtime (Group group, SystemRealtime which) const = 0;
|
|
|
|
// MIDI 1.0 CHANNEL VOICE Messages
|
|
virtual void onMidi1NoteOff (Group group, Channel channel, NoteNumber note,
|
|
Velocity8 velocity) const = 0;
|
|
virtual void onMidi1NoteOn (Group group, Channel channel, NoteNumber note,
|
|
Velocity8 velocity) const = 0;
|
|
virtual void onMidi1PolyPressure (Group group, Channel channel, NoteNumber note,
|
|
Data8 data) const = 0;
|
|
virtual void onMidi1ControlChange (Group group, Channel channel, ControllerNumber controller,
|
|
Data8 value) const = 0;
|
|
virtual void onMidi1ProgramChange (Group group, Channel channel, Program program) const = 0;
|
|
virtual void onMidi1ChannelPressure (Group group, Channel channel, Data8 pressure) const = 0;
|
|
virtual void onMidi1PitchBend (Group group, Channel channel, Data8 valueLSB,
|
|
Data8 valueMSB) const = 0;
|
|
|
|
// DATA 64 BIT
|
|
virtual void onSysExPacket (Group group, SysEx6ByteData data) const = 0;
|
|
virtual void onSysExStart (Group group, SysEx6ByteData data) const = 0;
|
|
virtual void onSysExContinue (Group group, SysEx6ByteData data) const = 0;
|
|
virtual void onSysExEnd (Group group, SysEx6ByteData data) const = 0;
|
|
|
|
// MIDI 2.0 CHANNEL VOICE Messages
|
|
virtual void onRegisteredPerNoteController (Group group, Channel channel, NoteNumber note,
|
|
ControllerNumber controller, Data32 data) const = 0;
|
|
virtual void onAssignablePerNoteController (Group group, Channel channel, NoteNumber note,
|
|
ControllerNumber controller, Data32 data) const = 0;
|
|
virtual void onRegisteredController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const = 0;
|
|
virtual void onAssignableController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const = 0;
|
|
virtual void onRelativeRegisteredController (Group group, Channel channel, BankNumber bank,
|
|
Index index, Data32 data) const = 0;
|
|
virtual void onRelativeAssignableController (Group group, Channel channel, BankNumber bank,
|
|
Index index, Data32 data) const = 0;
|
|
virtual void onPerNotePitchBend (Group group, Channel channel, NoteNumber note,
|
|
Data32 data) const = 0;
|
|
virtual void onNoteOff (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
|
|
AttributeType attr, AttributeValue attrValue) const = 0;
|
|
virtual void onNoteOn (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
|
|
AttributeType attr, AttributeValue attrValue) const = 0;
|
|
virtual void onPolyPressure (Group group, Channel channel, NoteNumber note,
|
|
Data32 data) const = 0;
|
|
virtual void onControlChange (Group group, Channel channel, ControllerNumber controller,
|
|
Data32 data) const = 0;
|
|
virtual void onProgramChange (Group group, Channel channel, OptionFlags options,
|
|
Program program, BankMSB bankMSB, BankLSB bankLSB) const = 0;
|
|
virtual void onChannelPressure (Group group, Channel channel, Data32 data) const = 0;
|
|
virtual void onPitchBend (Group group, Channel channel, Data32 data) const = 0;
|
|
virtual void onPerNoteManagement (Group group, Channel channel, NoteNumber note,
|
|
OptionFlags options) const = 0;
|
|
|
|
// DATA 128 BIT
|
|
virtual void onSysEx8Packet (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const = 0;
|
|
virtual void onSysEx8Start (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const = 0;
|
|
virtual void onSysEx8Continue (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const = 0;
|
|
virtual void onSysEx8End (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const = 0;
|
|
virtual void onMixedDataSetHeader (Group group, Index mdsID, MixedData data) const = 0;
|
|
virtual void onMixedDataSetPayload (Group group, Index mdsID, MixedData data) const = 0;
|
|
|
|
// invalid input data
|
|
virtual ParsingAction onInvalidInputData (size_t index) const = 0;
|
|
// insufficient input data
|
|
virtual void onInsufficentInputData (size_t index, size_t numMissingWords) const = 0;
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
enum class ParsingAction : uint32_t
|
|
{
|
|
Break,
|
|
Continue
|
|
};
|
|
|
|
namespace Detail {
|
|
|
|
//------------------------------------------------------------------------
|
|
enum class MessageType : uint8_t
|
|
{
|
|
Utility = 0x0, // 1 word
|
|
System = 0x1, // 1 word
|
|
ChannelVoice1 = 0x2, // 1 word
|
|
SysEx = 0x3, // 2 words
|
|
ChannelVoice2 = 0x4, // 2 words
|
|
Data128 = 0x5, // 4 words
|
|
Unknown6 = 0x6, // 1 word
|
|
Unknown7 = 0x7, // 1 word
|
|
Unknown8 = 0x8, // 2 words
|
|
Unknown9 = 0x9, // 2 words
|
|
UnknownA = 0xa, // 2 words
|
|
UnknownB = 0xb, // 3 words
|
|
UnknownC = 0xc, // 3 words
|
|
UnknownD = 0xd, // 4 words
|
|
UnknownE = 0xe, // 4 words
|
|
UnknownF = 0xf, // 4 words
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessage
|
|
{
|
|
uint32_t data;
|
|
|
|
SMTG_ALWAYS_INLINE constexpr MessageType type () const
|
|
{
|
|
auto val = (data & 0xf0000000ul) >> 28;
|
|
return static_cast<MessageType> (val);
|
|
}
|
|
|
|
SMTG_ALWAYS_INLINE constexpr size_t messageWordCount () const
|
|
{
|
|
auto index = static_cast<size_t> (type ());
|
|
return wordCounts[index];
|
|
}
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t group () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 4, 4> (data));
|
|
}
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte3_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 17, 7> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte4_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 25, 7> (data));
|
|
}
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte1 () const { return value<uint32_t, 0, 8> (data); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte2 () const { return value<uint32_t, 8, 8> (data); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte3 () const { return value<uint32_t, 16, 8> (data); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte4 () const { return value<uint32_t, 24, 8> (data); }
|
|
|
|
protected:
|
|
static constexpr std::array wordCounts = {1u, 1u, 1u, 2u, 2u, 4u, 1u, 1u,
|
|
2u, 2u, 2u, 3u, 3u, 4u, 4u, 4u};
|
|
|
|
template <typename T, size_t pos, size_t bits>
|
|
SMTG_ALWAYS_INLINE constexpr T bitMask () const
|
|
{
|
|
return ((static_cast<T> (1) << bits) - 1) << (((sizeof (T) * 8) - bits) - pos);
|
|
}
|
|
|
|
template <typename T, size_t pos, size_t bits>
|
|
SMTG_ALWAYS_INLINE constexpr T value (T data) const
|
|
{
|
|
return (data & bitMask<T, pos, bits> ()) >> (((sizeof (T) * 8) - bits) - pos);
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessage2 : UMPMessage
|
|
{
|
|
uint32_t data2;
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte5_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 1, 7> (data2));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte6_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 9, 7> (data2));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte7_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 17, 7> (data2));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte8_7bits () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 25, 7> (data2));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint16_t byte5_16bits () const
|
|
{
|
|
return static_cast<uint16_t> (value<uint32_t, 0, 16> (data2));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint16_t byte7_16bits () const
|
|
{
|
|
return static_cast<uint16_t> (value<uint32_t, 16, 16> (data2));
|
|
}
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte5 () const { return value<uint32_t, 0, 8> (data2); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte6 () const { return value<uint32_t, 8, 8> (data2); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte7 () const { return value<uint32_t, 16, 8> (data2); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte8 () const { return value<uint32_t, 24, 8> (data2); }
|
|
|
|
#if SMTG_OS_MACOS
|
|
// TO REMOVE: We had to add a duplicate this function from UMPMessage
|
|
// for older XCode than 15 to fix a linker issue
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t group () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 4, 4> (data));
|
|
}
|
|
#endif
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessage4 : UMPMessage2
|
|
{
|
|
uint32_t data3;
|
|
uint32_t data4;
|
|
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte9 () const { return value<uint32_t, 0, 8> (data3); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte10 () const { return value<uint32_t, 8, 8> (data3); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte11 () const { return value<uint32_t, 16, 8> (data3); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte12 () const { return value<uint32_t, 24, 8> (data3); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte13 () const { return value<uint32_t, 0, 8> (data4); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte14 () const { return value<uint32_t, 8, 8> (data4); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte15 () const { return value<uint32_t, 16, 8> (data4); }
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t byte16 () const { return value<uint32_t, 24, 8> (data4); }
|
|
};
|
|
|
|
static_assert (sizeof (UMPMessage) == sizeof (uint32_t), "");
|
|
static_assert (sizeof (UMPMessage2) == sizeof (uint32_t) * 2, "");
|
|
static_assert (sizeof (UMPMessage4) == sizeof (uint32_t) * 4, "");
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageUtility : UMPMessage
|
|
{
|
|
enum class Status
|
|
{
|
|
Noop = 0x0,
|
|
JrClock = 0x1,
|
|
JrTimestamp = 0x2,
|
|
};
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr IUniversalMidiPacketHandler::Timestamp timestamp () const
|
|
{
|
|
return static_cast<IUniversalMidiPacketHandler::Timestamp> (value<uint32_t, 16, 16> (data));
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageSystem : UMPMessage
|
|
{
|
|
enum class Status
|
|
{
|
|
Reserved = 0xf0,
|
|
MIDITimeCode = 0xf1,
|
|
SongPositionPointer = 0xf2,
|
|
SongSelect = 0xf3,
|
|
Reserved2 = 0xf4,
|
|
Reserved3 = 0xf5,
|
|
TuneRequest = 0xf6,
|
|
Reserved4 = 0xf7,
|
|
TimingClock = 0xf8,
|
|
Reserved5 = 0xf9,
|
|
Start = 0xfa,
|
|
Continue = 0xfb,
|
|
Stop = 0xfc,
|
|
Reserved6 = 0xfd,
|
|
ActiveSensing = 0xfe,
|
|
Reset = 0xff,
|
|
};
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageChannelVoice1 : UMPMessage
|
|
{
|
|
enum class Status
|
|
{
|
|
NoteOff = 0x8,
|
|
NoteOn = 0x9,
|
|
PolyPressure = 0xa,
|
|
ControlChange = 0xb,
|
|
ProgramChange = 0xc,
|
|
ChannelPressure = 0xd,
|
|
PitchBend = 0xe,
|
|
};
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint32_t channel () const { return value<uint32_t, 12, 4> (data); }
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageSysEx : UMPMessage2
|
|
{
|
|
enum class Status
|
|
{
|
|
Packet = 0x0,
|
|
Start = 0x1,
|
|
Continue = 0x2,
|
|
End = 0x3,
|
|
};
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageChannelVoice2 : UMPMessage2
|
|
{
|
|
enum class Status
|
|
{
|
|
RegisteredPerNoteController = 0x0,
|
|
AssignablePerNoteController = 0x1,
|
|
RegisteredController = 0x2,
|
|
AssignableController = 0x3,
|
|
RelativeRegisteredController = 0x4,
|
|
RelativeAssignableController = 0x5,
|
|
PerNotePitchBend = 0x6,
|
|
NoteOff = 0x8,
|
|
NoteOn = 0x9,
|
|
PolyPressure = 0xa,
|
|
ControlChange = 0xb,
|
|
ProgramChange = 0xc,
|
|
ChannelPressure = 0xd,
|
|
PitchBend = 0xe,
|
|
PerNoteManagement = 0xf
|
|
};
|
|
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint32_t channel () const { return value<uint32_t, 12, 4> (data); }
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UMPMessageData128 : UMPMessage4
|
|
{
|
|
enum class Status
|
|
{
|
|
Packet = 0x0,
|
|
Start = 0x1,
|
|
Continue = 0x2,
|
|
End = 0x3,
|
|
MixedHeader = 0x8,
|
|
MixedPayload = 0x9
|
|
};
|
|
SMTG_ALWAYS_INLINE constexpr Status status () const
|
|
{
|
|
return static_cast<Status> (value<uint32_t, 8, 4> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t numBytes () const
|
|
{
|
|
return static_cast<uint8_t> (value<uint32_t, 12, 4> (data));
|
|
}
|
|
SMTG_ALWAYS_INLINE constexpr uint8_t mdsId () const { return numBytes (); }
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onUtilityMessage (const UMPMessageUtility& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
auto status = msg.status ();
|
|
using Status = UMPMessageUtility::Status;
|
|
switch (status)
|
|
{
|
|
case Status::Noop:
|
|
{
|
|
handler.onNoop (msg.group ());
|
|
break;
|
|
}
|
|
case Status::JrClock:
|
|
{
|
|
handler.onJitterClock (msg.group (), msg.timestamp ());
|
|
break;
|
|
}
|
|
case Status::JrTimestamp:
|
|
{
|
|
handler.onJitterTimestamp (msg.group (), msg.timestamp ());
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onSystemMessage (const UMPMessageSystem& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
auto status = msg.status ();
|
|
using Status = UMPMessageSystem::Status;
|
|
using SystemRealtime = IUniversalMidiPacketHandler::SystemRealtime;
|
|
switch (status)
|
|
{
|
|
case Status::MIDITimeCode:
|
|
{
|
|
handler.onMIDITimeCode (msg.group (), msg.byte3_7bits ());
|
|
break;
|
|
}
|
|
case Status::SongPositionPointer:
|
|
{
|
|
handler.onSongPositionPointer (msg.group (), msg.byte3_7bits (), msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
case Status::SongSelect:
|
|
{
|
|
handler.onSongSelect (msg.group (), msg.byte3_7bits ());
|
|
break;
|
|
}
|
|
case Status::TuneRequest:
|
|
{
|
|
handler.onTuneRequest (msg.group ());
|
|
break;
|
|
}
|
|
case Status::TimingClock:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::TimingClock);
|
|
break;
|
|
}
|
|
case Status::Start:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::Start);
|
|
break;
|
|
}
|
|
case Status::Continue:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::Continue);
|
|
break;
|
|
}
|
|
case Status::Stop:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::Stop);
|
|
break;
|
|
}
|
|
case Status::ActiveSensing:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::ActiveSensing);
|
|
break;
|
|
}
|
|
case Status::Reset:
|
|
{
|
|
handler.onSystemRealtime (msg.group (), SystemRealtime::Reset);
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onChannelVoice1Message (const UMPMessageChannelVoice1& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
using Status = UMPMessageChannelVoice1::Status;
|
|
switch (msg.status ())
|
|
{
|
|
case Status::NoteOff:
|
|
{
|
|
handler.onMidi1NoteOff (msg.group (), msg.channel (), msg.byte3_7bits (),
|
|
msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
case Status::NoteOn:
|
|
{
|
|
handler.onMidi1NoteOn (msg.group (), msg.channel (), msg.byte3_7bits (),
|
|
msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
case Status::PolyPressure:
|
|
{
|
|
handler.onMidi1PolyPressure (msg.group (), msg.channel (), msg.byte3_7bits (),
|
|
msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
case Status::ControlChange:
|
|
{
|
|
handler.onMidi1ControlChange (msg.group (), msg.channel (), msg.byte3_7bits (),
|
|
msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
case Status::ProgramChange:
|
|
{
|
|
handler.onMidi1ProgramChange (msg.group (), msg.channel (), msg.byte3_7bits ());
|
|
break;
|
|
}
|
|
case Status::ChannelPressure:
|
|
{
|
|
handler.onMidi1ChannelPressure (msg.group (), msg.channel (), msg.byte3_7bits ());
|
|
break;
|
|
}
|
|
case Status::PitchBend:
|
|
{
|
|
handler.onMidi1PitchBend (msg.group (), msg.channel (), msg.byte3_7bits (),
|
|
msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onSysExMessage (const UMPMessageSysEx& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
using Status = UMPMessageSysEx::Status;
|
|
switch (msg.status ())
|
|
{
|
|
case Status::Packet:
|
|
{
|
|
handler.onSysExPacket (msg.group (),
|
|
{msg.byte3_7bits (), msg.byte4_7bits (), msg.byte5_7bits (),
|
|
msg.byte6_7bits (), msg.byte7_7bits (), msg.byte8_7bits ()});
|
|
break;
|
|
}
|
|
case Status::Start:
|
|
{
|
|
handler.onSysExStart (msg.group (),
|
|
{msg.byte3_7bits (), msg.byte4_7bits (), msg.byte5_7bits (),
|
|
msg.byte6_7bits (), msg.byte7_7bits (), msg.byte8_7bits ()});
|
|
break;
|
|
}
|
|
case Status::Continue:
|
|
{
|
|
handler.onSysExContinue (msg.group (),
|
|
{msg.byte3_7bits (), msg.byte4_7bits (), msg.byte5_7bits (),
|
|
msg.byte6_7bits (), msg.byte7_7bits (), msg.byte8_7bits ()});
|
|
break;
|
|
}
|
|
case Status::End:
|
|
{
|
|
handler.onSysExEnd (msg.group (),
|
|
{msg.byte3_7bits (), msg.byte4_7bits (), msg.byte5_7bits (),
|
|
msg.byte6_7bits (), msg.byte7_7bits (), msg.byte8_7bits ()});
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onChannelVoice2Message (const UMPMessageChannelVoice2& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
auto group = msg.group ();
|
|
auto status = msg.status ();
|
|
auto channel = msg.channel ();
|
|
|
|
using Status = UMPMessageChannelVoice2::Status;
|
|
switch (status)
|
|
{
|
|
case Status::RegisteredPerNoteController:
|
|
{
|
|
handler.onRegisteredPerNoteController (group, channel, msg.byte3_7bits (),
|
|
msg.byte4_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::AssignablePerNoteController:
|
|
{
|
|
handler.onAssignablePerNoteController (group, channel, msg.byte3_7bits (),
|
|
msg.byte4_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::RegisteredController:
|
|
{
|
|
handler.onRegisteredController (group, channel, msg.byte3_7bits (), msg.byte4_7bits (),
|
|
msg.data2);
|
|
break;
|
|
}
|
|
case Status::AssignableController:
|
|
{
|
|
handler.onAssignableController (group, channel, msg.byte3_7bits (), msg.byte4_7bits (),
|
|
msg.data2);
|
|
break;
|
|
}
|
|
case Status::RelativeRegisteredController:
|
|
{
|
|
handler.onRelativeRegisteredController (group, channel, msg.byte3_7bits (),
|
|
msg.byte4_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::RelativeAssignableController:
|
|
{
|
|
handler.onRelativeAssignableController (group, channel, msg.byte3_7bits (),
|
|
msg.byte4_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::PerNotePitchBend:
|
|
{
|
|
handler.onPerNotePitchBend (group, channel, msg.byte3_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::NoteOn:
|
|
{
|
|
handler.onNoteOn (group, channel, msg.byte3_7bits (), msg.byte5_16bits (),
|
|
msg.byte4_7bits (), msg.byte7_16bits ());
|
|
break;
|
|
}
|
|
case Status::NoteOff:
|
|
{
|
|
handler.onNoteOff (group, channel, msg.byte3_7bits (), msg.byte5_16bits (),
|
|
msg.byte4_7bits (), msg.byte7_16bits ());
|
|
break;
|
|
}
|
|
case Status::PolyPressure:
|
|
{
|
|
handler.onPolyPressure (group, channel, msg.byte3_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::ControlChange:
|
|
{
|
|
handler.onControlChange (group, channel, msg.byte3_7bits (), msg.data2);
|
|
break;
|
|
}
|
|
case Status::ProgramChange:
|
|
{
|
|
handler.onProgramChange (group, channel, msg.byte4_7bits (), msg.byte5_7bits (),
|
|
msg.byte7_7bits (), msg.byte8_7bits ());
|
|
break;
|
|
}
|
|
case Status::ChannelPressure:
|
|
{
|
|
handler.onChannelPressure (group, channel, msg.data2);
|
|
break;
|
|
}
|
|
case Status::PitchBend:
|
|
{
|
|
handler.onPitchBend (group, channel, msg.data2);
|
|
break;
|
|
}
|
|
case Status::PerNoteManagement:
|
|
{
|
|
handler.onPerNoteManagement (group, channel, msg.byte3_7bits (), msg.byte4_7bits ());
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
SMTG_ALWAYS_INLINE bool onData128Message (const UMPMessageData128& msg,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
using Status = UMPMessageData128::Status;
|
|
switch (msg.status ())
|
|
{
|
|
case Status::Packet:
|
|
{
|
|
handler.onSysEx8Packet (msg.group (), msg.numBytes (), msg.byte3 (),
|
|
{msg.byte4 (), msg.byte5 (), msg.byte6 (), msg.byte7 (),
|
|
msg.byte8 (), msg.byte9 (), msg.byte10 (), msg.byte12 (),
|
|
msg.byte13 (), msg.byte14 (), msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
case Status::Start:
|
|
{
|
|
handler.onSysEx8Start (msg.group (), msg.numBytes (), msg.byte3 (),
|
|
{msg.byte4 (), msg.byte5 (), msg.byte6 (), msg.byte7 (),
|
|
msg.byte8 (), msg.byte9 (), msg.byte10 (), msg.byte12 (),
|
|
msg.byte13 (), msg.byte14 (), msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
case Status::Continue:
|
|
{
|
|
handler.onSysEx8Continue (msg.group (), msg.numBytes (), msg.byte3 (),
|
|
{msg.byte4 (), msg.byte5 (), msg.byte6 (), msg.byte7 (),
|
|
msg.byte8 (), msg.byte9 (), msg.byte10 (), msg.byte12 (),
|
|
msg.byte13 (), msg.byte14 (), msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
case Status::End:
|
|
{
|
|
handler.onSysEx8End (msg.group (), msg.numBytes (), msg.byte3 (),
|
|
{msg.byte4 (), msg.byte5 (), msg.byte6 (), msg.byte7 (),
|
|
msg.byte8 (), msg.byte9 (), msg.byte10 (), msg.byte12 (),
|
|
msg.byte13 (), msg.byte14 (), msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
case Status::MixedHeader:
|
|
{
|
|
handler.onMixedDataSetHeader (msg.group (), msg.mdsId (),
|
|
{msg.byte3 (), msg.byte4 (), msg.byte5 (), msg.byte6 (),
|
|
msg.byte7 (), msg.byte8 (), msg.byte9 (), msg.byte10 (),
|
|
msg.byte12 (), msg.byte13 (), msg.byte14 (),
|
|
msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
case Status::MixedPayload:
|
|
{
|
|
handler.onMixedDataSetPayload (msg.group (), msg.mdsId (),
|
|
{msg.byte3 (), msg.byte4 (), msg.byte5 (), msg.byte6 (),
|
|
msg.byte7 (), msg.byte8 (), msg.byte9 (), msg.byte10 (),
|
|
msg.byte12 (), msg.byte13 (), msg.byte14 (),
|
|
msg.byte15 (), msg.byte16 ()});
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
template <uint32_t Sections>
|
|
SMTG_ALWAYS_INLINE size_t parse (const size_t numWords, const uint32_t* words,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
auto msg = reinterpret_cast<const UMPMessage*> (words);
|
|
for (size_t index = 0; index < numWords;)
|
|
{
|
|
auto numMsgWords = msg->messageWordCount ();
|
|
if (index + numMsgWords > numWords)
|
|
{
|
|
handler.onInsufficentInputData (index, (index + numMsgWords) - numWords);
|
|
return index;
|
|
}
|
|
switch (msg->type ())
|
|
{
|
|
case MessageType::Utility:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::Utility) != 0)
|
|
{
|
|
auto utilityMsg = reinterpret_cast<const UMPMessageUtility*> (msg);
|
|
if (!onUtilityMessage (*utilityMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::System:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::System) != 0)
|
|
{
|
|
auto systemMsg = reinterpret_cast<const UMPMessageSystem*> (msg);
|
|
if (!onSystemMessage (*systemMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::ChannelVoice1:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::ChannelVoice1) != 0)
|
|
{
|
|
auto channelMsg = reinterpret_cast<const UMPMessageChannelVoice1*> (msg);
|
|
if (!onChannelVoice1Message (*channelMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::SysEx:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::SysEx) != 0)
|
|
{
|
|
assert (index < numWords - 1);
|
|
auto sysExMsg = reinterpret_cast<const UMPMessageSysEx*> (msg);
|
|
if (!onSysExMessage (*sysExMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::ChannelVoice2:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::ChannelVoice2) != 0)
|
|
{
|
|
assert (index < numWords - 1);
|
|
auto channelMsg = reinterpret_cast<const UMPMessageChannelVoice2*> (msg);
|
|
if (!onChannelVoice2Message (*channelMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::Data128:
|
|
{
|
|
if
|
|
constexpr ((Sections & ParseSections::Data128) != 0)
|
|
{
|
|
assert (index < numWords - 3);
|
|
auto dataMsg = reinterpret_cast<const UMPMessageData128*> (msg);
|
|
if (!onData128Message (*dataMsg, handler))
|
|
{
|
|
if (handler.onInvalidInputData (index) == ParsingAction::Break)
|
|
return index;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
index += numMsgWords;
|
|
msg += numMsgWords;
|
|
}
|
|
return numWords;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
} // Detail
|
|
|
|
//------------------------------------------------------------------------
|
|
template <ParseSections Sections>
|
|
SMTG_ALWAYS_INLINE size_t parsePackets (const size_t numWords, const uint32_t* words,
|
|
const IUniversalMidiPacketHandler& handler)
|
|
{
|
|
return Detail::parse<Sections> (numWords, words, handler);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
struct UniversalMidiPacketHandlerAdapter : IUniversalMidiPacketHandler
|
|
{
|
|
void onNoop (Group group) const override {}
|
|
void onJitterClock (Group group, Timestamp time) const override {}
|
|
void onJitterTimestamp (Group group, Timestamp time) const override {}
|
|
void onMIDITimeCode (Group group, Timecode timecode) const override {}
|
|
void onSongPositionPointer (Group group, uint8_t posLSB, uint8_t posMSB) const override {}
|
|
void onSongSelect (Group group, uint8_t songIndex) const override {}
|
|
void onTuneRequest (Group group) const override {}
|
|
void onSystemRealtime (Group group, SystemRealtime which) const override {}
|
|
void onMidi1NoteOff (Group group, Channel channel, NoteNumber note,
|
|
Velocity8 velocity) const override
|
|
{
|
|
}
|
|
void onMidi1NoteOn (Group group, Channel channel, NoteNumber note,
|
|
Velocity8 velocity) const override
|
|
{
|
|
}
|
|
void onMidi1PolyPressure (Group group, Channel channel, NoteNumber note,
|
|
Data8 data) const override
|
|
{
|
|
}
|
|
void onMidi1ControlChange (Group group, Channel channel, ControllerNumber controller,
|
|
Data8 value) const override
|
|
{
|
|
}
|
|
void onMidi1ProgramChange (Group group, Channel channel, Program program) const override {}
|
|
void onMidi1ChannelPressure (Group group, Channel channel, Data8 pressure) const override {}
|
|
void onMidi1PitchBend (Group group, Channel channel, Data8 valueLSB,
|
|
Data8 valueMSB) const override
|
|
{
|
|
}
|
|
void onSysExPacket (Group group, SysEx6ByteData data) const override {}
|
|
void onSysExStart (Group group, SysEx6ByteData data) const override {}
|
|
void onSysExContinue (Group group, SysEx6ByteData data) const override {}
|
|
void onSysExEnd (Group group, SysEx6ByteData data) const override {}
|
|
void onRegisteredPerNoteController (Group group, Channel channel, NoteNumber note,
|
|
ControllerNumber controller, Data32 data) const override
|
|
{
|
|
}
|
|
void onAssignablePerNoteController (Group group, Channel channel, NoteNumber note,
|
|
ControllerNumber controller, Data32 data) const override
|
|
{
|
|
}
|
|
void onRegisteredController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onAssignableController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onRelativeRegisteredController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onRelativeAssignableController (Group group, Channel channel, BankNumber bank, Index index,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onPerNotePitchBend (Group group, Channel channel, NoteNumber note,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onNoteOff (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
|
|
AttributeType attr, AttributeValue attrValue) const override
|
|
{
|
|
}
|
|
void onNoteOn (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
|
|
AttributeType attr, AttributeValue attrValue) const override
|
|
{
|
|
}
|
|
void onPolyPressure (Group group, Channel channel, NoteNumber note, Data32 data) const override
|
|
{
|
|
}
|
|
void onControlChange (Group group, Channel channel, ControllerNumber controller,
|
|
Data32 data) const override
|
|
{
|
|
}
|
|
void onProgramChange (Group group, Channel channel, OptionFlags options, Program program,
|
|
BankMSB bankMSB, BankLSB bankLSB) const override
|
|
{
|
|
}
|
|
void onChannelPressure (Group group, Channel channel, Data32 data) const override {}
|
|
void onPitchBend (Group group, Channel channel, Data32 data) const override {}
|
|
void onPerNoteManagement (Group group, Channel channel, NoteNumber note,
|
|
OptionFlags options) const override
|
|
{
|
|
}
|
|
void onSysEx8Packet (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const override
|
|
{
|
|
}
|
|
void onSysEx8Start (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const override
|
|
{
|
|
}
|
|
void onSysEx8Continue (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const override
|
|
{
|
|
}
|
|
void onSysEx8End (Group group, Data8 numBytes, Index streamID,
|
|
SysEx13ByteData data) const override
|
|
{
|
|
}
|
|
void onMixedDataSetHeader (Group group, Index mdsID, MixedData data) const override {}
|
|
void onMixedDataSetPayload (Group group, Index mdsID, MixedData data) const override {}
|
|
ParsingAction onInvalidInputData (size_t index) const override
|
|
{
|
|
return ParsingAction::Continue;
|
|
}
|
|
void onInsufficentInputData (size_t index, size_t numMissingWords) const override {}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
} // Steinberg::Vst::EditorHost
|