Slide 47
Slide 47 text
require 'io/console'
require 'ffi-portaudio'
require 'drb/drb'
require 'midilib'
SAMPLE_RATE = 44100
BUFFER_SIZE = 128
require_relative "groovebox"
require_relative "drum_rack"
require_relative "synthesizer"
require_relative "note"
require_relative "vca"
require_relative "step"
require_relative "presets/bass"
require_relative "presets/kick"
require_relative "presets/snare"
require_relative "presets/hihat_closed"
require_relative "presets/piano"
require_relative "sidechain"
class Sequencer
attr_reader :tracks, :steps_per_track
DEFAULT_NOTES = ["C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",]
NOTE_TO_MIDI = {}
DEFAULT_NOTES.each_with_index do |note_name, idx|
NOTE_TO_MIDI[note_name] = 48 + idx
end
def initialize(groovebox = nil, mid_file_path = nil)
@groovebox = groovebox
@current_position = 0
@current_track = 0
@current_voice = 0 # ݱࡏฤूதͷ෦ʢϙϦϑΥχʔͷΠϯσοΫεʣ
@steps_per_track = 32
@tracks = []
@playing = false
@bpm = 120
initialize_tracks
if mid_file_path
puts "Loading MIDI file: #{mid_file_path}"
load_midi_file(mid_file_path)
end
end
def initialize_tracks
return if @groovebox.nil?
@instruments = @groovebox.instruments
@instruments.each_with_index do |instrument, idx|
track_name = "Track #{idx}"
if instrument.respond_to?(:pad_notes)
# DrumRackͷ߹ैདྷ௨Γ
instrument.pad_notes.sort.each do |pad_note|
track = Array.new(@steps_per_track) { Step.new }
@tracks << {
name: "Drum #{pad_note}",
instrument_index: idx,
midi_note: pad_note,
steps: track,
polyphony: 1, # υϥϜৗʹ୯Ի
voices: [0], # ෦ΠϯσοΫε
}
end
else
# γϯηαΠβʔͷ߹ϙϦϑΥχʔରԠ
polyphony = @groovebox.get_polyphony(idx)
voices = (0...polyphony).to_a
# ֤෦ʢϙϦϑΥχʔʣʹରԠ͢ΔεςοϓྻΛੜ
poly_steps = voices.map { Array.new(@steps_per_track) { Step.new } }
@tracks << {
name: track_name,
instrument_index: idx,
midi_note: nil,
steps: poly_steps,
polyphony: polyphony,
voices: voices,
}
end
end
if @tracks.empty?
@tracks = [
{
name: "Default Track",
instrument_index: 0,
midi_note: nil,
steps: [Array.new(@steps_per_track) { Step.new }],
polyphony: 1,
voices: [0],
},
]
end
end
def load_midi_file(midi_file_path)
seq = MIDI::Sequence.new
File.open(midi_file_path, 'rb') do |file|
seq.read(file)
end
puts "Loaded #{seq.tracks.size} tracks"
# τϥοΫͷॳظԽʢϙϦϑΥχʔରԠʣ
@tracks.each do |track_info|
if track_info[:midi_note]
# υϥϜτϥοΫͷ߹
track_info[:steps].each do |step|
step.active = false
step.note = nil
step.velocity = nil
end
else
# γϯηαΠβʔτϥοΫͷ߹ʢྻͷྻʣ
track_info[:steps].each do |voice_steps|
voice_steps.each do |step|
step.active = false
step.note = nil
step.velocity = nil
end
end
end
end
# Find BPM from tempo events
seq.tracks.each do |track|
tempo_events = track.events.select { |e| e.kind_of?(MIDI::Tempo) }
if tempo_events.any?
tempo_event = tempo_events.first
@bpm = 60_000_000 / tempo_event.tempo
break
end
end
# See if we have any drum tracks to map to
synth_tracks = @tracks.select { |t| t[:midi_note].nil? }
drum_tracks = @tracks.select { |t| t[:midi_note] }
# Skip the first track (usually just tempo/timing info)
seq.tracks.each_with_index do |midi_track, track_index|
next if track_index == 0
puts "MIDI Track #{track_index}: #{midi_track.name}"
# Find all note-on events with velocity > 0
note_on_events = midi_track.events.select do |event|
Sequencer is HARD