class InevitableCacophony::MidiGenerator
Attributes
frequency_table[R]
Public Class Methods
new(octave_structure, tonic)
click to toggle source
Set up a MIDI generator for a specific octave structure and tonic We need to know the octave structure because it determines how we allocate MIDI note indices to frequencies.
# File lib/inevitable_cacophony/midi_generator.rb, line 16 def initialize(octave_structure, tonic) @frequency_table = FrequencyTable.new(octave_structure, tonic) end
Public Instance Methods
add_phrase(phrase)
click to toggle source
Add a phrase to the MIDI output we will generate.
# File lib/inevitable_cacophony/midi_generator.rb, line 23 def add_phrase(phrase) @phrases ||= [] @phrases << phrase end
notes_track(sequence=build_sequence)
click to toggle source
@return [Midi::Track] Notes to be output to MIDI; mainly for testing.
# File lib/inevitable_cacophony/midi_generator.rb, line 29 def notes_track(sequence=build_sequence) build_notes_track(sequence, @phrases) end
write(io)
click to toggle source
Write MIDI output to the given stream.
# File lib/inevitable_cacophony/midi_generator.rb, line 34 def write(io) sequence = build_sequence sequence.tracks << notes_track(sequence) # Buffer output so this method can be called on stdout. buffer = StringIO.new sequence.write(buffer) io.write(buffer.string) end
Private Instance Methods
build_notes_track(seq, phrases)
click to toggle source
TODO: multiple instruments?
# File lib/inevitable_cacophony/midi_generator.rb, line 71 def build_notes_track(seq, phrases) track = MIDI::Track.new(seq) track.name = 'Cacophony' # TODO: why this particular instrument. track.instrument = MIDI::GM_PATCH_NAMES[0] # TODO: what's this for? track.events << MIDI::ProgramChange.new(0, 1, 0) # Inter-note delay from the end of the previous beat. leftover_delay = 0 phrases.each do |phrase| phrase.notes.each do |note| track.events += midi_events_for_note(leftover_delay, note, seq) leftover_delay = seq.length_to_delta(note.beat.after_delay) end end track end
build_sequence()
click to toggle source
# File lib/inevitable_cacophony/midi_generator.rb, line 47 def build_sequence seq = MIDI::Sequence.new seq.tracks << meta_track(seq) seq end
meta_track(seq)
click to toggle source
TODO: why do I have to pass `seq` in, when I'm then later adding the track back to seq.tracks?
# File lib/inevitable_cacophony/midi_generator.rb, line 55 def meta_track(seq) track = MIDI::Track.new(seq) # TODO: handle tempo changes (how?) track.events << MIDI::Tempo.new( MIDI::Tempo.bpm_to_mpq(@phrases.first.tempo) ) track.events << MIDI::MetaEvent.new( MIDI::META_SEQ_NAME, 'TODO: name sequence' ) track end
midi_events_for_note(delay_before, note, seq)
click to toggle source
TODO: code smell to pass in seq
# File lib/inevitable_cacophony/midi_generator.rb, line 95 def midi_events_for_note(delay_before, note, seq) midi_note = @frequency_table.index_for_ratio(note.ratio) beat = note.beat [ MIDI::NoteOn.new( 0, midi_note, (beat.amplitude * 127).ceil, # TODO: can notes be out of order? # Beat duration 1 conveniently matches # midilib's quarter-note = 1. seq.length_to_delta(beat.start_delay) + delay_before ), MIDI::NoteOff.new( 0, midi_note, 127, seq.length_to_delta(beat.sounding_time) ) ] end