diff --git a/examples/metronome.rb b/examples/metronome.rb index 28f933f..fbd17fd 100644 --- a/examples/metronome.rb +++ b/examples/metronome.rb @@ -2,6 +2,7 @@ tempo = 96 # comment this out and choose another one below if you want +# a second metronome is set to run an octave and a half below at 8/8 signature = [4,4] # signature = [3,4] # signature = [5,4] @@ -12,18 +13,24 @@ require 'rubygems' require 'midiator' require 'lib/ouroubourus' +require 'lib/interfaces/midiator' require 'lib/sequencers/metronome' -@s = Ouroubourus::Scheduler.new :timekeeper => Ouroubourus::LocalTimer.new(tempo, :resolution => 80) -@midi = MIDIator::Interface.new -@midi.autodetect_driver -@midi.program_change 0, 115 # wood block +require 'ruby-debug' +Debugger.start + +@s = Ouroubourus::Scheduler.new :timekeeper => Ouroubourus::LocalTimer.new(tempo) +@midi = Midiator.new +@midi.interface.autodetect_driver +@midi.interface.program_change 0, 115 # wood block @metronome1 = Metronome.new :interface => @midi, :signature => signature -@metronome2 = Metronome.new :interface => @midi, :signature => [6,4], :basenote => 67 +@metronome2 = Metronome.new :interface => @midi, :signature => [8,8], :basenote => 53 @s.subscribers << @metronome1 @s.subscribers << @metronome2 Signal.trap("INT") { @s.timekeeper.thread.exit!; "Interrupt caught, cancelling..." } +# debugger + @s.start @s.timekeeper.thread.join \ No newline at end of file diff --git a/lib/interfaces/midiator.rb b/lib/interfaces/midiator.rb new file mode 100644 index 0000000..96c9990 --- /dev/null +++ b/lib/interfaces/midiator.rb @@ -0,0 +1,15 @@ +require 'midiator' + +class Midiator + + attr_accessor :interface + + def initialize + @interface = MIDIator::Interface.new + end + + def note(channel, pitch, velocity) + @interface.driver.note_on(pitch, channel, velocity) + end + +end \ No newline at end of file diff --git a/lib/ouroubourus.rb b/lib/ouroubourus.rb index 8c8a90b..ed8037d 100644 --- a/lib/ouroubourus.rb +++ b/lib/ouroubourus.rb @@ -6,5 +6,6 @@ module Ouroubourus $:.unshift File.dirname(__FILE__) %w(scheduler schedulable schedule +midi midi/note midi/generator timekeeper/timekeeper timekeeper/local_timer ).each {|r| require "ouroubourus/#{r}.rb" } diff --git a/lib/ouroubourus/midi.rb b/lib/ouroubourus/midi.rb new file mode 100644 index 0000000..0b75f3d --- /dev/null +++ b/lib/ouroubourus/midi.rb @@ -0,0 +1,5 @@ +module Ouroubourus + module Midi + + end +end \ No newline at end of file diff --git a/lib/ouroubourus/midi/generator.rb b/lib/ouroubourus/midi/generator.rb new file mode 100644 index 0000000..03d1ea9 --- /dev/null +++ b/lib/ouroubourus/midi/generator.rb @@ -0,0 +1,27 @@ +module Ouroubourus + module MIDI + module Generator + + def play(notes) + [notes].flatten.each do |note| + if note.start.zero? + note_on(note.channel, note.pitch, note.velocity) + @schedule.in(note.duration, L{ note_off(note.channel, note.pitch)}) + else + @schedule.in(note.start, L{ note_on(note.channel, note.pitch, note.velocity) }) + @schedule.in(note.start + note.duration, L{ note_off(note.channel, note.pitch) }) + end + end + end + + def note_on(channel, pitch, velocity) + @interface.note(channel, pitch, velocity) + end + + def note_off(channel, pitch) + note_on(channel, pitch, 0) + end + + end + end +end \ No newline at end of file diff --git a/lib/ouroubourus/midi/note.rb b/lib/ouroubourus/midi/note.rb new file mode 100644 index 0000000..8a4d057 --- /dev/null +++ b/lib/ouroubourus/midi/note.rb @@ -0,0 +1,45 @@ +module Ouroubourus + module MIDI + class Note + + attr_reader :pitch, :velocity, :duration, :start, :channel + + def initialize(p, vel, dur, chan=1, s=0) + self.pitch = p + self.velocity = vel + self.channel = chan + self.duration = dur + self.start = s + end + + def pitch=(val) + @pitch = seven_bit(val) + end + + def velocity=(val) + @velocity = seven_bit(val) + end + + def channel=(val) + val = 1 if val < 1 + val = 16 if val > 16 + @channel = val + end + + def duration=(val) + @duration = [1,val].max + end + + def start=(val) + @start = [0,val].max + end + + protected + def seven_bit(val) + val = 0 if val < 0 + val = 127 if val > 127 + val + end + end + end +end \ No newline at end of file diff --git a/lib/ouroubourus/schedule.rb b/lib/ouroubourus/schedule.rb index 57e386e..204b18c 100644 --- a/lib/ouroubourus/schedule.rb +++ b/lib/ouroubourus/schedule.rb @@ -11,7 +11,7 @@ def initialize def run(now) @time = now ready, @queue = @queue.partition {|pos, proc| pos <= now } - ready.each {|time, proc| Thread.start { proc.call(time) } } + ready.each {|time, proc| proc.call(time) } end def at(position, block) diff --git a/lib/ouroubourus/scheduler.rb b/lib/ouroubourus/scheduler.rb index eb6fc05..0117f4a 100644 --- a/lib/ouroubourus/scheduler.rb +++ b/lib/ouroubourus/scheduler.rb @@ -10,7 +10,7 @@ def initialize(options={}) def start return false unless timekeeper.kind_of?(Ouroubourus::Timekeeper) - timekeeper.start {|now| @subscribers.each{|s| Thread.start { s.run(now) } }} + timekeeper.start {|now| @subscribers.each{|s| s.run(now) }} end end diff --git a/lib/sequencers/metronome.rb b/lib/sequencers/metronome.rb index 648f936..8bfbd64 100644 --- a/lib/sequencers/metronome.rb +++ b/lib/sequencers/metronome.rb @@ -1,5 +1,6 @@ class Metronome include Ouroubourus::Schedulable + include Ouroubourus::MIDI::Generator attr_accessor :interface, :signature, :channel #, :schedule @@ -9,30 +10,30 @@ def initialize(options={}) @interface = options[:interface] @channel = options[:channel] || 1 @basenote = options[:basenote] || 72 - @run = L{|now| tick(now) } - schedule.next 480, @run - end - - def tick(now) - note = (now % bar_length) == 0 ? one : beat - @interface.driver.note_on(note[:pitch], @channel, note[:velocity]) - @schedule.in(note[:duration], L{ @interface.driver.note_off(note[:pitch], @channel, 0) }) - @schedule.next note_length, @run + @run = L do |now| + play(now % bar_length == 0 ? one : beat) + @schedule.next beat_length, @run + end + schedule.next beat_length, @run end def bar_length - @signature.first * note_length + @signature.first * beat_length end - def note_length + def beat_length 4.0 / @signature.last * 480 end def one - {:pitch => @basenote + 12, :velocity => 100, :duration => 180} + note(@basenote+12, 100, 180) end def beat - {:pitch => @basenote, :velocity => 60, :duration => 90} + note(@basenote, 60, 90) + end + + def note(pitch, velocity, duration) + Ouroubourus::MIDI::Note.new(pitch, velocity, duration, @channel, 0) end end \ No newline at end of file