2025-02-16

日曜日
groovebox-ruby
どういうふうに設計するかでウンウン悩んでいたけど以下のような感じでいきたい

groovebox = Groovebox.new
synth1 = Synthesizer.new
synth1.harmonics = [[1.0, 1.0], [2.0, 0.4]]
synth1.osillator.waveform = :sawtooth
synth1.envelope.attack = 0.01
synth1.envelope.decay = 0.1
synth1.envelope.sustain = 0.7
synth1.envelope.release = 0.5
synth1.effectors.add(Effector::Distortion.new)
synth1.effectors.add(Effector::Reverb.new)

groovebox.add_track synth1
synth2 = Synthesizer.new #snip
groovebox.add_track synth2

compressor = Effector::compressor.new
groovebox.add_effector compressor

DRb.start_service
seq = DRbObject.new_with_uri('druby:://localhost:8787')
groovebox.set_sequencer seq

Thread.new do
handle_midi_signal(groovebox)
end

grooveboxの中にトラックを生やして、一度に出せる音を増やしていくようなイメージ。今はシンセ部分の実装に集中してるからシンセ部分のコードは多め。将来的にはgroovebox内にミキサーを司る係が生えてきてよしなになるはず。
シーケンサー、

handle_midi_signalの中身は自分で書くようなイメージ
例えば今のgroovebox-rubyでは以下のようになっている

def handle_midi_signals(synthesizer, sequencer_player, config)
midi_input = UniMIDI::Input.use(config'midi_device''index')
midi_output = UniMIDI::Output.use(config'midi_device''index')
puts "Listening for MIDI signals from #{midi_input.name}…"

white_keys = 0, 2, 4, 5, 7, 9, 11 # C, D, E, F, G, A, B

loop do

白鍵のパッドを光らせる

(config'keyboard''note_range''start'..config'keyboard''note_range''end').each do |midi_note|
if white_keys.include?(midi_note % 12)
midi_output.puts(0x90, midi_note, 120)
else
midi_output.puts(0x80, midi_note, 0)
end
end

midi_input.gets.each do |message|
data = message:data

TODO: 254を受信出来なくなったらエラーを出して終了させる

next if data0 == 254 # Active Sensing を無視

case data0 & 0xF0
when 0x90 # Note On
midi_note = data1
velocity = data2
if velocity > 0
if (config'keyboard''note_range''start'..config'keyboard''note_range''end').include?(midi_note)
frequency = 440.0 * (2 ** ((midi_note - 69) / 12.0))
synthesizer.note_on(midi_note, frequency)
puts "Note On: #{Note::NOTE_NAMES(midi_note % 12)} (#{frequency.round(2)} Hz)"
elsif config'switches'.key?(midi_note.to_s)
synthesizer.oscillator.waveform = config'switches'midi_note.to_s.to_sym
puts "Waveform changed to: #{synthesizer.oscillator.waveform.capitalize}"
end
end
when 0x80 # Note Off or Note On with velocity 0
midi_note = data1
if (config'keyboard''note_range''start'..config'keyboard''note_range''end').include?(midi_note)
synthesizer.note_off(midi_note)
puts "Note Off: #{Note::NOTE_NAMES(midi_note % 12)}"
end
when 0xB0 # Control Change
control = data1
value = data2

LPF / HPF

cutoff_change = value == 127 ? 10 : -10
if control == 71
synthesizer.vcf.low_pass_cutoff += cutoff_change
puts "VCF Low Pass Cutoff: #{synthesizer.vcf.low_pass_cutoff.round(2)} Hz"
elsif control == 72
synthesizer.vcf.high_pass_cutoff += cutoff_change
puts "VCF High Pass Cutoff: #{synthesizer.vcf.high_pass_cutoff.round(2)} Hz"
end

ADSR

case control
when 73 # Attack
new_attack = 0.01 + (value / 127.0) * 2.49
synthesizer.envelope.attack = new_attack
puts "Attack: #{new_attack.round(2)}"

when 74 # Decay
new_decay = 0.01 + (value / 127.0) * 2.49
synthesizer.envelope.decay = new_decay
puts "Decay: #{new_decay.round(2)}"

when 75 # Sustain
new_sustain = value / 127.0
synthesizer.envelope.sustain = new_sustain
puts "Sustain: #{new_sustain.round(2)}"

when 76 # Release
new_release = 0.01 + (value / 127.0) * 2.49
synthesizer.envelope.release = new_release
puts "Release: #{new_release.round(2)}"
end

if control == config'start' && value == 127
Thread.new do
sequencer_player.play
end
elsif control == config'stop' && value == 127
sequencer_player.stop
end
end
end
end
end

最初はyamlの中にMIDIの信号ごとに辞書的に定義していたけど、最近はもうベタ書きでいいやという感じになっている。一貫性がないのであとで直したい。

自分でハードをつくるとなるとこのあたりのMIDI信号のルールセットは自分で決められるんだけど、今はソフトウェアの部分だけなので書きたい人が自由に設定できるようになる予定。

あとこのgroovebox-rubyの開発に使っているMIDIデバイスがAbleton Moveでこれ自体がGrooveboxなのでそこはちょっとおもろい。曲を作ろうと思うとパッドやロータリーエンコーダーなどが結構な数が必要でそういうのはDAWで使う想定のMIDIコントローラーだと地味に足りないことがある。MIDIキーボードだと充分そろっていることもあるけど、鍵盤な分デカいのだよな。

202502 0216 日記