# Yet Another Byte Organ # by Leonard Richardson (leonardr@crummy.com) # Based on "Byte Organ" by Jack Aboutboul, Andy Doro and Adam Parrish # (http://itp.nyu.edu/~ap1607/nbo/) # # Usage: organ.rb [MIDI instrument] [bpm] < data > song.mid require 'rubygems' require 'midilib' # Generic MIDI library, taken from the Ruby Cookbook class TimedTrack < MIDI::Track MIDDLE_C = 60 @@channel_counter=0 def initialize(number, song) super(number) @sequence = song @time = 0 @channel = @@channel_counter @@channel_counter += 1 end # Tell this track's channel to use the given instrument, and # also set the track's instrument display name. def instrument=(instrument) @events << MIDI::ProgramChange.new(@channel, instrument) super(MIDI::GM_PATCH_NAMES[instrument]) end # Add one or more notes to sound simultaneously. Increments the per-track # timer so that subsequent notes will sound after this one finishes. def add_notes(offsets, velocity=127, duration='quarter') offsets = [offsets] unless offsets.respond_to? :each offsets.each do |offset| event(MIDI::NoteOnEvent.new(@channel, MIDDLE_C + offset, velocity)) end @time += @sequence.note_to_delta(duration) offsets.each do |offset| event(MIDI::NoteOffEvent.new(@channel, MIDDLE_C + offset, velocity)) end recalc_delta_from_times end private def event(event) @events << event event.time_from_start = @time end end # The real stuff class ByteOrgan < MIDI::Sequence @@root = -1 # The Bb below middle C @@scale = [0, 2, 3, 7, 9, 12] # Pentatonic scale @@min_velocity = 30 ## @@scale = [0, 3, 5, 6, 7, 11, 12, 15] # Blues scale def initialize(instrument, bpm) super() self.tracks << (@melody = TimedTrack.new(0, self)) @melody.instrument = instrument @melody.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(bpm)) @melody.events << MIDI::MetaEvent.new(MIDI::META_SEQ_NAME, 'The Byte Organ') end def feed(bytes) bytes.each_byte do |byte| # Make the six least significant bits into a chord. chord = [] i = 0 until i == 6 do chord << (@@root + @@scale[i]) if byte & (1 << i) != 0 i += 1 end # Use the two most significant bits to determine note velocity. velocity_bits = byte & (128+64) velocity = @@min_velocity if velocity_bits != 0 velocity += ((127-@@min_velocity)/velocity_bits) end @melody.add_notes(chord, velocity, 'sixteenth') end end end # Command-line interface if ARGV.size > 0 instrument, bpm = ARGV instrument = MIDI::GM_PATCH_NAMES.index(instrument) unless instrument puts "No such instrument. Choose from the following:" puts " " + MIDI::GM_PATCH_NAMES.join("\n ") exit end end instrument ||= 1 # Piano bpm ||= 120 organ = ByteOrgan.new(instrument, bpm) organ.feed(STDIN) organ.write(STDOUT)