Module: Webmidi::Message::UMP

Defined in:
lib/webmidi/message/ump.rb

Defined Under Namespace

Classes: Base, ChannelVoice32, ChannelVoice64, Data128, Data64, FlexData, Raw, SystemCommon, Utility

Constant Summary collapse

MESSAGE_TYPES =
{
  utility: 0x0,
  system_common: 0x1,
  channel_voice_32: 0x2,
  data_64: 0x3,
  channel_voice_64: 0x4,
  data_128: 0x5,
  flex_data: 0xD
}.freeze
WORD_COUNTS =
{
  utility: 1,
  system_common: 1,
  channel_voice_32: 1,
  data_64: 2,
  channel_voice_64: 2,
  data_128: 4,
  flex_data: 4
}.freeze
STATUS_NIBBLES =
{
  note_off: 0x8,
  note_on: 0x9,
  poly_pressure: 0xA,
  control_change: 0xB,
  program_change: 0xC,
  channel_pressure: 0xD,
  pitch_bend: 0xE
}.freeze
STATUS_BY_NIBBLE =
STATUS_NIBBLES.invert.freeze
SYSTEM_COMMON_STATUSES =
{
  0xF1 => :time_code,
  0xF2 => :song_position,
  0xF3 => :song_select,
  0xF6 => :tune_request,
  0xF8 => :clock,
  0xFA => :start,
  0xFB => :continue,
  0xFC => :stop,
  0xFE => :active_sensing,
  0xFF => :system_reset
}.freeze
DATA_PACKET_FORMATS =
{
  complete: 0x0,
  start: 0x1,
  continue: 0x2,
  end: 0x3
}.freeze
DATA_PACKET_FORMAT_BY_NIBBLE =
DATA_PACKET_FORMATS.invert.freeze
CHANNEL_VOICE_32_STATUSES =
%i[
  note_off note_on poly_pressure control_change program_change channel_pressure pitch_bend
].freeze
CHANNEL_VOICE_64_STATUSES =
%i[
  note_off note_on poly_pressure control_change program_change channel_pressure pitch_bend
].freeze
MIDI1_CHANNEL_VOICE_TO_UMP =
{
  Channel::NoteOff => {status: :note_off, data: :note, value: :velocity, scale: :scale_7_to_16},
  Channel::NoteOn => {status: :note_on, data: :note, value: :velocity, scale: :scale_7_to_16},
  Channel::PolyphonicPressure => {status: :poly_pressure, data: :note, value: :pressure, scale: :scale_7_to_16},
  Channel::ControlChange => {status: :control_change, data: :cc, value: :value, scale: :scale_7_to_32},
  Channel::ProgramChange => {status: :program_change, data: :program},
  Channel::ChannelPressure => {status: :channel_pressure, value: :pressure, scale: :scale_7_to_32},
  Channel::PitchBend => {status: :pitch_bend, value: :value, scale: :scale_14_to_32}
}.freeze
UMP_CHANNEL_VOICE_TO_MIDI1 =
{
  note_off: {class: Channel::NoteOff, fields: {note: :note, velocity: [:velocity, :scale_16_to_7]}},
  note_on: {class: Channel::NoteOn, fields: {note: :note, velocity: [:velocity, :scale_16_to_7]}},
  poly_pressure: {
    class: Channel::PolyphonicPressure,
    fields: {note: :note, pressure: [:velocity, :scale_16_to_7]}
  },
  control_change: {class: Channel::ControlChange, fields: {cc: :note, value: [:velocity, :scale_32_to_7]}},
  program_change: {class: Channel::ProgramChange, fields: {program: :note}},
  channel_pressure: {class: Channel::ChannelPressure, fields: {pressure: [:velocity, :scale_32_to_7]}},
  pitch_bend: {class: Channel::PitchBend, fields: {value: [:velocity, :scale_32_to_14]}}
}.freeze

Class Method Summary collapse

Class Method Details

.downgrade(midi2_message) ⇒ Object



522
523
524
525
526
527
528
529
# File 'lib/webmidi/message/ump.rb', line 522

def downgrade(midi2_message)
  case midi2_message
  when ChannelVoice64
    downgrade_channel_voice64(midi2_message)
  else
    raise InvalidMessageError, "Cannot downgrade #{midi2_message.class} to MIDI 1.0"
  end
end

.from_bytes(*bytes) ⇒ Object



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/webmidi/message/ump.rb', line 484

def from_bytes(*bytes)
  bytes = bytes.flatten
  unless bytes.size.positive? && (bytes.size % 4).zero?
    raise InvalidMessageError, "UMP byte input must be a positive multiple of 4 bytes"
  end

  words = bytes.each_slice(4).map do |slice|
    slice.each_with_index do |byte, index|
      unless byte.is_a?(Integer) && byte.between?(0, 255)
        raise InvalidMessageError, "Byte at index #{index} must be between 0 and 255, got #{byte.inspect}"
      end
    end
    (slice[0] << 24) | (slice[1] << 16) | (slice[2] << 8) | slice[3]
  end
  from_words(*words)
end

.from_words(*words) ⇒ Object



501
502
503
504
505
506
507
508
509
510
511
# File 'lib/webmidi/message/ump.rb', line 501

def from_words(*words)
  words = words.flatten
  validate_words!(words)
  message_type = type_from_word(words.first)
  expected_words = WORD_COUNTS.fetch(message_type) do
    raise InvalidMessageError, "Unsupported UMP message type: #{format("0x%X", words.first >> 28)}"
  end
  return parse_words(message_type, words) if words.size == expected_words

  raise InvalidMessageError, "#{message_type} UMP expects #{expected_words} word(s), got #{words.size}"
end

.upgrade(midi1_message, group: Webmidi.configuration.default_group) ⇒ Object



513
514
515
516
517
518
519
520
# File 'lib/webmidi/message/ump.rb', line 513

def upgrade(midi1_message, group: Webmidi.configuration.default_group)
  spec = MIDI1_CHANNEL_VOICE_TO_UMP.find { |message_class, _| midi1_message.is_a?(message_class) }&.last
  unless spec
    raise InvalidMessageError, "Cannot upgrade #{midi1_message.class} to MIDI 2.0"
  end

  ChannelVoice64.new(**upgrade_attributes(midi1_message, spec, group))
end