class Juicy::Track
Attributes
notes[RW]
Public Class Methods
new(init_time, notes, tempo)
click to toggle source
# File lib/juicy/track.rb, line 8 def initialize(init_time, notes, tempo) @start_time = init_time @notes = notes.to_a @tempo = tempo @track_length_in_milliseconds = 0 @notes.each do |note| note.distance_from_beat_in_milliseconds = distance_from_beat_in_milliseconds note.plays_during occupying_beat @track_length_in_milliseconds += note.duration_in_milliseconds(@tempo) end end
play_concurrently(tracks, tempo)
click to toggle source
# File lib/juicy/track.rb, line 133 def self.play_concurrently(tracks, tempo) #@song_start_time = Time.now threads = [] # iterate over each track over and over again, preparing notes in the current beat # in each iteration, store the notes you've prepared into an array and store that # array into the prepared_notes array which the playing thread will play notes from # when it has enough to play. # A track is an array of playable musical objects. for now, these are individual # notes or chords. a chord is an array of individual notes to be played simultaneously # prepared_beats = [] out_of_notes_to_prepare = false threads << Thread.new do Thread.current[:name] = "prepare beats thread" current_beat = 1 last_beat = 1 # find the final beat of all tracks combined tracks.each do |track| track.notes.each do |playable_thing| if playable_thing.kind_of?(Note) last_beat = playable_thing.occupying_beat if playable_thing.occupying_beat > last_beat elsif playable_thing.kind_of?(Chord) playable_thing.notes.each do |note| last_beat = note.occupying_beat if note.occupying_beat > last_beat end end end end # until you've prepared all the notes, prepare each note in an array with # other notes who occupy the same beat until current_beat > last_beat # only add/prepare beats if there are fewer than 20 prepared already # so that we don't hit the thread limit. This assumes that a buffer of 20 # is sufficent and that each beat has, on average, fewer than 30 notes. # if there are too many notes, weird things start to happen, so don't do that. if prepared_beats.size <= 20 this_beats_notes = [] tracks.each do |track| while !track.notes[0].nil? && track.notes[0].plays_during?(current_beat) playable_thing = track.notes.shift if playable_thing.kind_of?(Note) this_beats_notes << playable_thing.prepare(duration: playable_thing.duration_in_milliseconds(tempo)) elsif playable_thing.kind_of?(Chord) playable_thing.notes.each do |note| this_beats_notes << note.prepare(duration: note.duration_in_milliseconds(tempo)) end end end end prepared_beats << this_beats_notes current_beat += 1 end # Pass thread execution over to note playing so that there is no delay in playback Thread.pass end out_of_notes_to_prepare = true end threads << Thread.new do Thread.current[:name] = "play prepared beats thread" # when prepared_beats has at least a few beats to play (a measure's worth?) # "start" the song by iterating through each element, waking up each note # and then waiting the remainder of a beat's worth of milliseconds until the # next beat until (prepared_beats.size >= 4) || out_of_notes_to_prepare sleep 0.01 end last_note = Thread.new {} time = Time.now until prepared_beats.empty? && out_of_notes_to_prepare time = Time.now # take the next beat's worth of notes beat = prepared_beats.shift # start each note in the beat as its own thread beat.each do |note| last_note = note.play_prepared end # to ensure simultaneity, sleep for however much longer a beat lasts # at the current tempo sleep_amount = Duration.duration_of_quarter_note_in_milliseconds(tempo)/1000.0 - (Time.now - time) sleep sleep_amount unless sleep_amount < 0 end last_note.join end threads.each {|t| t.join} end
tracks_is_empty(tracks)
click to toggle source
# File lib/juicy/track.rb, line 121 def self.tracks_is_empty(tracks) empty = true tracks.each do |track| #puts track.object_id puts track.notes.inspect unless track.notes.empty? empty = false end end empty end
Public Instance Methods
distance_from_beat_in_milliseconds()
click to toggle source
# File lib/juicy/track.rb, line 23 def distance_from_beat_in_milliseconds (@track_length_in_milliseconds.round % Duration.duration_of_quarter_note_in_milliseconds(@tempo).round) end
occupying_beat()
click to toggle source
# File lib/juicy/track.rb, line 27 def occupying_beat @track_length_in_milliseconds.round / Duration.duration_of_quarter_note_in_milliseconds(@tempo).round + 1 end
play()
click to toggle source
# File lib/juicy/track.rb, line 31 def play # prepare notes ahead of time, and play them at a specified time. # each note must be prepared separately in it's own thread. # only 20 threads are allowed to be alive at a time in a current track, and each track is preparing its notes seperately # th = [] # while(notes_left_to_prepare) # if (th.count {|t| t.alive? }) <= 20 # note = notes_left_to_prepare.shift # th << Thread.new do # note.prepare # sleep_amount = when_the_note_should_be_played - how_far_into_the_song_you_are # unless sleep_amount < 0 # sleep sleep_amount # end # note.play_prepared # end # end # end # th.each {|t| t.join} notes = [] if @notes[0].kind_of? Chord #notes << Thread.new { @notes.each { |chord| 4.times {chord.play} } } chords_left_to_prepare = @notes.dup @sum_of_queued_chord_durations = 0 until chords_left_to_prepare.size == 1 if (notes.count {|t| t.alive?} <= 20) notes << Thread.new do chord = chords_left_to_prepare.shift puts "#{chord}: #{chord.duration}" chord.sum_of_queued_chord_durations = @sum_of_queued_chord_durations @sum_of_queued_chord_durations += chord.duration_in_milliseconds(@tempo) chord.initial_play_time = @start_time + chord.sum_of_queued_chord_durations chord.how_far_into_the_song_you_are = how_far_into_the_song_you_are chord.prepare(duration: chord.duration_in_milliseconds(@tempo)) sleep_amount = (chord.initial_play_time - chord.how_far_into_the_song_you_are)/1000.0 unless sleep_amount < 0 sleep sleep_amount end Thread.pass chord.play_prepared.join #puts "tehe" end end Thread.pass end elsif @notes[0].kind_of? Note # @notes.each do |note| # note.play # end notes_left_to_prepare = @notes.dup @sum_of_queued_note_durations = 0 until notes_left_to_prepare.size == 1 #puts notes_left_to_prepare.size if (notes.count {|t| t.alive? }) <= 20 #Thread.pass notes << Thread.new do note = notes_left_to_prepare.shift puts "#{note}: #{note.duration}" note.sum_of_queued_note_durations = @sum_of_queued_note_durations @sum_of_queued_note_durations += note.duration_in_milliseconds(@tempo) #puts @sum_of_queued_note_durations note.initial_play_time = @start_time + note.sum_of_queued_note_durations #puts "note.initial_play_time: #{note.initial_play_time}" note.how_far_into_the_song_you_are = how_far_into_the_song_you_are #puts "note.how_far_into_the_song_you_are: #{note.how_far_into_the_song_you_are}" note.prepare(duration: note.duration_in_milliseconds(@tempo)) # puts "note.initial_play_time: #{note.initial_play_time}" # puts "note.how_far_into_the_song_you_are: #{note.how_far_into_the_song_you_are}" sleep_amount = (note.initial_play_time - note.how_far_into_the_song_you_are)/1000.0 #puts sleep_amount unless sleep_amount < 0 sleep sleep_amount end Thread.pass note.play_prepared end end Thread.pass end end notes.each {|t| t.join} end
Private Instance Methods
how_far_into_the_song_you_are()
click to toggle source
# File lib/juicy/track.rb, line 227 def how_far_into_the_song_you_are a = (1000*(Time.now - @song_start_time)).round #puts "how_far_into_the_song_you_are: #{a}" a end
total_duration_of_queued_notes_for_this_track()
click to toggle source
# File lib/juicy/track.rb, line 223 def total_duration_of_queued_notes_for_this_track @sum_of_queued_note_durations end