Class: Deftones::Music::Midi

Inherits:
Object
  • Object
show all
Defined in:
lib/deftones/music/midi.rb

Defined Under Namespace

Classes: OutputSession

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, transport: Deftones.transport) ⇒ Midi

Returns a new instance of Midi.



8
9
10
11
12
# File 'lib/deftones/music/midi.rb', line 8

def initialize(value, transport: Deftones.transport)
  @value = value
  @transport = transport
  @disposed = false
end

Instance Attribute Details

#transportObject (readonly)

Returns the value of attribute transport.



6
7
8
# File 'lib/deftones/music/midi.rb', line 6

def transport
  @transport
end

#valueObject (readonly)

Returns the value of attribute value.



6
7
8
# File 'lib/deftones/music/midi.rb', line 6

def value
  @value
end

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


101
102
103
104
# File 'lib/deftones/music/midi.rb', line 101

def available?
  load_backend!
  !!defined?(UniMIDI)
end

.control_change(controller, value, channel: 1, device: nil) ⇒ Object



165
166
167
168
169
170
# File 'lib/deftones/music/midi.rb', line 165

def control_change(controller, value, channel: 1, device: nil)
  send_message(
    [status_byte(0xB0, channel), normalize_data_byte(controller), normalize_data_byte(value)],
    device: device
  )
end

.find_device(devices, name) ⇒ Object (private)



226
227
228
229
230
231
232
233
234
235
# File 'lib/deftones/music/midi.rb', line 226

def find_device(devices, name)
  return devices.first if name.nil?

  matched_by_id = devices.find { |device| matches_device_id?(device, name) }
  return matched_by_id if matched_by_id
  return devices[name] if name.is_a?(Integer) && name >= 0 && name < devices.length

  matcher = name.is_a?(Regexp) ? name : Regexp.new(Regexp.escape(name.to_s), Regexp::IGNORECASE)
  devices.find { |device| device.respond_to?(:name) && device.name.to_s.match?(matcher) }
end

.find_input(name = nil) ⇒ Object



118
119
120
# File 'lib/deftones/music/midi.rb', line 118

def find_input(name = nil)
  find_device(input_devices, name)
end

.find_output(name = nil) ⇒ Object



122
123
124
# File 'lib/deftones/music/midi.rb', line 122

def find_output(name = nil)
  find_device(output_devices, name)
end

.input_devicesObject



106
107
108
109
110
# File 'lib/deftones/music/midi.rb', line 106

def input_devices
  return [] unless available?

  UniMIDI::Input.all
end

.load_backend!Object (private)



212
213
214
215
216
217
218
219
# File 'lib/deftones/music/midi.rb', line 212

def load_backend!
  return true if defined?(UniMIDI)

  require "unimidi"
  true
rescue LoadError
  false
end

.matches_device_id?(device, selector) ⇒ Boolean (private)

Returns:

  • (Boolean)


237
238
239
240
241
242
243
244
245
246
# File 'lib/deftones/music/midi.rb', line 237

def matches_device_id?(device, selector)
  return false if selector.is_a?(Regexp)

  candidates = []
  candidates << device.id if device.respond_to?(:id)
  candidates << device.device_id if device.respond_to?(:device_id)
  candidates << device.index if device.respond_to?(:index)
  candidates << device.device_index if device.respond_to?(:device_index)
  candidates.compact.any? { |candidate| candidate.to_s == selector.to_s }
end

.message_data(message) ⇒ Object (private)



221
222
223
224
# File 'lib/deftones/music/midi.rb', line 221

def message_data(message)
  data = message.is_a?(Hash) ? message.fetch(:data, message) : message
  Array(data).map(&:to_i)
end

.normalize_channel(channel) ⇒ Object (private)

Raises:

  • (ArgumentError)


280
281
282
283
284
285
# File 'lib/deftones/music/midi.rb', line 280

def normalize_channel(channel)
  integer = channel.to_i
  raise ArgumentError, "MIDI channel must be between 1 and 16" unless (1..16).cover?(integer)

  integer - 1
end

.normalize_data_byte(value) ⇒ Object (private)



266
267
268
# File 'lib/deftones/music/midi.rb', line 266

def normalize_data_byte(value)
  value.to_i.clamp(0, 127)
end

.normalize_note(note) ⇒ Object (private)



262
263
264
# File 'lib/deftones/music/midi.rb', line 262

def normalize_note(note)
  normalize_data_byte(parse(note))
end

.note_off(note, velocity: 0, channel: 1, device: nil) ⇒ Object



161
162
163
# File 'lib/deftones/music/midi.rb', line 161

def note_off(note, velocity: 0, channel: 1, device: nil)
  send_message([status_byte(0x80, channel), normalize_note(note), normalize_data_byte(velocity)], device: device)
end

.note_on(note, velocity: 100, channel: 1, device: nil) ⇒ Object



157
158
159
# File 'lib/deftones/music/midi.rb', line 157

def note_on(note, velocity: 100, channel: 1, device: nil)
  send_message([status_byte(0x90, channel), normalize_note(note), normalize_data_byte(velocity)], device: device)
end

.open_device(device, *args, &block) ⇒ Object (private)



248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/deftones/music/midi.rb', line 248

def open_device(device, *args, &block)
  raise Deftones::MissingMidiBackendError, "MIDI support is unavailable. Install the unimidi gem to enable MIDI I/O." unless available?
  raise ArgumentError, "No matching MIDI device found" unless device

  return device.open(*args) unless block

  device.open(*args)
  begin
    block.call(device)
  ensure
    device.close if device.respond_to?(:close)
  end
end

.open_input(name = nil, *args, &block) ⇒ Object



126
127
128
# File 'lib/deftones/music/midi.rb', line 126

def open_input(name = nil, *args, &block)
  open_device(find_input(name), *args, &block)
end

.open_output(name = nil, *args, &block) ⇒ Object



130
131
132
# File 'lib/deftones/music/midi.rb', line 130

def open_output(name = nil, *args, &block)
  open_device(find_output(name), *args, &block)
end

.open_output_session(name = nil, *args) ⇒ Object



134
135
136
137
138
139
140
141
142
143
# File 'lib/deftones/music/midi.rb', line 134

def open_output_session(name = nil, *args)
  session = OutputSession.new(open_output(name, *args))
  return session unless block_given?

  begin
    yield session
  ensure
    session.close
  end
end

.output_devicesObject



112
113
114
115
116
# File 'lib/deftones/music/midi.rb', line 112

def output_devices
  return [] unless available?

  UniMIDI::Output.all
end

.parse(value) ⇒ Object



95
96
97
98
99
# File 'lib/deftones/music/midi.rb', line 95

def parse(value)
  return value.to_i if value.is_a?(Numeric)

  Note.to_midi(value)
end

.receive(name = nil, *args) ⇒ Object



145
146
147
148
149
# File 'lib/deftones/music/midi.rb', line 145

def receive(name = nil, *args)
  open_input(name) do |input|
    input.gets(*args)
  end
end

.send_message(message, device: nil) ⇒ Object



151
152
153
154
155
# File 'lib/deftones/music/midi.rb', line 151

def send_message(message, device: nil)
  open_output(device) do |output|
    output.puts(message)
  end
end

.status_byte(base, channel) ⇒ Object (private)



270
271
272
# File 'lib/deftones/music/midi.rb', line 270

def status_byte(base, channel)
  base + normalize_channel(channel)
end

.sync_transport(message, transport: Deftones.transport) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/deftones/music/midi.rb', line 172

def sync_transport(message, transport: Deftones.transport)
  status = message_data(message).first.to_i
  case status
  when 0xF8
    transport.ticks = transport.ticks + (transport.ppq / 24.0)
    :clock
  when 0xFA
    transport.ticks = 0
    transport.start(0)
    :start
  when 0xFB
    transport.start(transport.seconds)
    :continue
  when 0xFC
    transport.stop
    :stop
  else
    :ignored
  end
end

.trigger_from_message(message, target:, time: nil, velocity_scale: 127.0) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/deftones/music/midi.rb', line 193

def trigger_from_message(message, target:, time: nil, velocity_scale: 127.0)
  data = message_data(message)
  status = data.first.to_i
  command = status & 0xF0
  return :ignored unless [0x80, 0x90].include?(command)

  note = Note.from_midi(normalize_data_byte(data[1]))
  velocity = normalize_data_byte(data[2]) / [velocity_scale.to_f, 1.0].max
  if command == 0x90 && velocity.positive?
    target.trigger_attack(note, time, velocity)
    :note_on
  else
    trigger_release(target, note, time)
    :note_off
  end
end

.trigger_release(target, note, time) ⇒ Object (private)



274
275
276
277
278
# File 'lib/deftones/music/midi.rb', line 274

def trigger_release(target, note, time)
  target.trigger_release(note, time)
rescue ArgumentError
  target.trigger_release(time)
end

Instance Method Details

#disposeObject



71
72
73
74
# File 'lib/deftones/music/midi.rb', line 71

def dispose
  @disposed = true
  self
end

#disposed?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/deftones/music/midi.rb', line 76

def disposed?
  @disposed
end

#from_type(type) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/deftones/music/midi.rb', line 59

def from_type(type)
  @value =
    if type.respond_to?(:to_midi)
      type.to_midi
    elsif type.respond_to?(:value_of)
      type.value_of
    else
      type
    end
  self
end

#harmonize(intervals) ⇒ Object



50
51
52
# File 'lib/deftones/music/midi.rb', line 50

def harmonize(intervals)
  Array(intervals).map { |interval| transpose(interval) }
end

#quantize(subdiv, percent = 1.0) ⇒ Object



54
55
56
57
# File 'lib/deftones/music/midi.rb', line 54

def quantize(subdiv, percent = 1.0)
  quantized_seconds = UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
  Note.to_midi(Note.from_frequency(1.0 / [quantized_seconds, 1.0e-6].max))
end

#to_bars_beats_sixteenthsObject



30
31
32
# File 'lib/deftones/music/midi.rb', line 30

def to_bars_beats_sixteenths
  transport.seconds_to_position(to_seconds)
end

#to_frequencyObject



18
19
20
# File 'lib/deftones/music/midi.rb', line 18

def to_frequency
  Note.to_frequency(to_note)
end

#to_iObject



14
15
16
# File 'lib/deftones/music/midi.rb', line 14

def to_i
  self.class.parse(value)
end

#to_millisecondsObject



34
35
36
# File 'lib/deftones/music/midi.rb', line 34

def to_milliseconds
  to_seconds * 1000.0
end

#to_notationObject



42
43
44
# File 'lib/deftones/music/midi.rb', line 42

def to_notation
  UnitHelpers.closest_notation(to_seconds, transport: transport)
end

#to_noteObject



86
87
88
# File 'lib/deftones/music/midi.rb', line 86

def to_note
  Note.from_midi(to_i)
end

#to_sObject Also known as: toString



80
81
82
# File 'lib/deftones/music/midi.rb', line 80

def to_s
  value.to_s
end

#to_samples(sample_rate = Deftones.context.sample_rate) ⇒ Object



38
39
40
# File 'lib/deftones/music/midi.rb', line 38

def to_samples(sample_rate = Deftones.context.sample_rate)
  UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
end

#to_secondsObject



22
23
24
# File 'lib/deftones/music/midi.rb', line 22

def to_seconds
  1.0 / to_frequency
end

#to_ticksObject



26
27
28
# File 'lib/deftones/music/midi.rb', line 26

def to_ticks
  transport.seconds_to_ticks(to_seconds)
end

#transpose(interval) ⇒ Object



46
47
48
# File 'lib/deftones/music/midi.rb', line 46

def transpose(interval)
  self.class.new(to_i + interval.to_i, transport: transport)
end

#value_ofObject



90
91
92
# File 'lib/deftones/music/midi.rb', line 90

def value_of
  to_i
end