Class: Deftones::Event::Transport
- Inherits:
-
Object
- Object
- Deftones::Event::Transport
- Defined in:
- lib/deftones/event/transport.rb
Defined Under Namespace
Classes: TimingContext
Instance Attribute Summary collapse
-
#loop ⇒ Object
Returns the value of attribute loop.
-
#loop_end ⇒ Object
Returns the value of attribute loop_end.
-
#loop_start ⇒ Object
Returns the value of attribute loop_start.
-
#ppq ⇒ Object
Returns the value of attribute ppq.
-
#state ⇒ Object
readonly
Returns the value of attribute state.
-
#swing ⇒ Object
Returns the value of attribute swing.
-
#time_signature ⇒ Object
Returns the value of attribute time_signature.
Instance Method Summary collapse
- #add_event(payload) ⇒ Object private
- #add_state_event(state, time) ⇒ Object private
- #apply_due_state_events(up_to_time) ⇒ Object private
- #apply_pause(time) ⇒ Object private
- #apply_start(time) ⇒ Object private
- #apply_stop(time) ⇒ Object private
- #apply_swing(time, interval, occurrence = 0) ⇒ Object private
- #beats_between(start_time, end_time) ⇒ Object
- #bpm ⇒ Object
- #bpm=(value) ⇒ Object
- #bpm_at(time) ⇒ Object (also: #bpmAt)
- #bpm_events ⇒ Object private
- #bpm_signal ⇒ Object (also: #bpmSignal)
- #cancel(after_time = 0, event_id: nil) ⇒ Object
- #clear(event_id) ⇒ Object
- #clock_time ⇒ Object private
- #due_events(window_start, window_end) ⇒ Object private
- #due_events_without_loop(window_start, window_end) ⇒ Object private
- #first_repeat_occurrence(start_time, interval, window_start) ⇒ Object private
- #future_time?(time) ⇒ Boolean private
- #immediate ⇒ Object
-
#initialize(bpm: 120.0, time_signature: [4, 4], ppq: 192, clock: nil) ⇒ Transport
constructor
A new instance of Transport.
- #integration_points(start_time, end_time) ⇒ Object private
- #loop_active? ⇒ Boolean private
- #looped_due_events(window_start, window_end) ⇒ Object private
- #materialize_one_shot(event, window_start, window_end) ⇒ Object private
- #materialize_repeat_event(event, window_start, window_end) ⇒ Object private
- #next_subdivision(subdivision) ⇒ Object (also: #nextSubdivision)
- #pause(time = nil) ⇒ Object
- #position ⇒ Object
- #position=(value) ⇒ Object
- #prepare_render(duration) ⇒ Object
- #prepare_render_window(start_time, end_time) ⇒ Object (also: #prepareRenderWindow)
- #progress ⇒ Object
- #resolve_time(value) ⇒ Object private
- #schedule(time, &block) ⇒ Object
- #schedule_once(time, &block) ⇒ Object (also: #scheduleOnce)
- #schedule_repeat(interval, start_time: 0, duration: nil, &block) ⇒ Object (also: #scheduleRepeat)
- #seconds ⇒ Object
- #seconds=(value) ⇒ Object
- #seconds_to_position(seconds) ⇒ Object
- #seconds_to_ticks(seconds) ⇒ Object
- #set_bpm_at_time(value, time) ⇒ Object (also: #setBpmAtTime)
- #set_loop_points(start_time, end_time) ⇒ Object (also: #setLoopPoints)
- #start(time = nil) ⇒ Object
- #state_at(time) ⇒ Object (also: #stateAt)
- #state_before_timeline ⇒ Object private
- #state_position_at(time) ⇒ Object private
- #stop(time = nil) ⇒ Object
- #swing_subdivision ⇒ Object
- #swing_subdivision=(value) ⇒ Object
- #swingSubdivision ⇒ Object
- #swingSubdivision=(value) ⇒ Object
- #tempo_automated? ⇒ Boolean private
- #ticks ⇒ Object
- #ticks=(value) ⇒ Object
- #ticks_to_seconds(ticks) ⇒ Object
- #time_in_window?(time, window_start, window_end) ⇒ Boolean private
- #timeSignature ⇒ Object
- #timeSignature=(signature) ⇒ Object
- #toggle(time = nil) ⇒ Object
Constructor Details
#initialize(bpm: 120.0, time_signature: [4, 4], ppq: 192, clock: nil) ⇒ Transport
Returns a new instance of Transport.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/deftones/event/transport.rb', line 15 def initialize(bpm: 120.0, time_signature: [4, 4], ppq: 192, clock: nil) @bpm = Core::Signal.new( value: bpm, units: :number, context: TimingContext.new(Deftones::Context::DEFAULT_SAMPLE_RATE) ) @ppq = ppq.to_i @state = :stopped self.time_signature = time_signature @loop = false @loop_start = 0.0 @loop_end = 0.0 @swing = 0.0 @swing_subdivision = "8n" @timeline = {} @state_timeline = [] @clock = clock @next_id = 0 @next_state_id = 0 @started_at = 0.0 @position_seconds = 0.0 @last_state_update_time = 0.0 end |
Instance Attribute Details
#loop ⇒ Object
Returns the value of attribute loop.
6 7 8 |
# File 'lib/deftones/event/transport.rb', line 6 def loop @loop end |
#loop_end ⇒ Object
Returns the value of attribute loop_end.
6 7 8 |
# File 'lib/deftones/event/transport.rb', line 6 def loop_end @loop_end end |
#loop_start ⇒ Object
Returns the value of attribute loop_start.
6 7 8 |
# File 'lib/deftones/event/transport.rb', line 6 def loop_start @loop_start end |
#ppq ⇒ Object
Returns the value of attribute ppq.
7 8 9 |
# File 'lib/deftones/event/transport.rb', line 7 def ppq @ppq end |
#state ⇒ Object (readonly)
Returns the value of attribute state.
7 8 9 |
# File 'lib/deftones/event/transport.rb', line 7 def state @state end |
#swing ⇒ Object
Returns the value of attribute swing.
6 7 8 |
# File 'lib/deftones/event/transport.rb', line 6 def swing @swing end |
#time_signature ⇒ Object
Returns the value of attribute time_signature.
7 8 9 |
# File 'lib/deftones/event/transport.rb', line 7 def time_signature @time_signature end |
Instance Method Details
#add_event(payload) ⇒ Object (private)
266 267 268 269 270 271 |
# File 'lib/deftones/event/transport.rb', line 266 def add_event(payload) event_id = @next_id @timeline[event_id] = payload @next_id += 1 event_id end |
#add_state_event(state, time) ⇒ Object (private)
273 274 275 276 277 278 |
# File 'lib/deftones/event/transport.rb', line 273 def add_state_event(state, time) event_id = @next_state_id @state_timeline << { id: event_id, state: state, time: time } @next_state_id += 1 event_id end |
#apply_due_state_events(up_to_time) ⇒ Object (private)
357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/deftones/event/transport.rb', line 357 def apply_due_state_events(up_to_time) due, pending = @state_timeline.partition { |event| event[:time] <= up_to_time } @state_timeline = pending due.sort_by { |event| [event[:time], event[:id]] }.each do |event| case event[:state] when :started then apply_start(event[:time]) when :stopped then apply_stop(event[:time]) when :paused then apply_pause(event[:time]) end end end |
#apply_pause(time) ⇒ Object (private)
381 382 383 384 385 |
# File 'lib/deftones/event/transport.rb', line 381 def apply_pause(time) @position_seconds = state_position_at(time) @state = :paused @last_state_update_time = time end |
#apply_start(time) ⇒ Object (private)
369 370 371 372 373 |
# File 'lib/deftones/event/transport.rb', line 369 def apply_start(time) @state = :started @started_at = time - @position_seconds @last_state_update_time = time end |
#apply_stop(time) ⇒ Object (private)
375 376 377 378 379 |
# File 'lib/deftones/event/transport.rb', line 375 def apply_stop(time) @position_seconds = state_position_at(time) @state = :stopped @last_state_update_time = time end |
#apply_swing(time, interval, occurrence = 0) ⇒ Object (private)
404 405 406 407 408 409 410 |
# File 'lib/deftones/event/transport.rb', line 404 def apply_swing(time, interval, occurrence = 0) return time if @swing.zero? return time if resolve_time(@swing_subdivision) != interval return time if occurrence.even? time + (interval * 0.5 * @swing) end |
#beats_between(start_time, end_time) ⇒ Object
456 457 458 459 460 461 462 463 464 465 |
# File 'lib/deftones/event/transport.rb', line 456 def beats_between(start_time, end_time) from = start_time.to_f to = end_time.to_f return 0.0 if to <= from integration_points(from, to).each_cons(2).sum do |left, right| midpoint = (left + right) * 0.5 ((right - left) * bpm_at(midpoint)) / 60.0 end end |
#bpm ⇒ Object
39 40 41 |
# File 'lib/deftones/event/transport.rb', line 39 def bpm @bpm.value end |
#bpm=(value) ⇒ Object
43 44 45 |
# File 'lib/deftones/event/transport.rb', line 43 def bpm=(value) @bpm.value = value end |
#bpm_at(time) ⇒ Object Also known as: bpmAt
139 140 141 |
# File 'lib/deftones/event/transport.rb', line 139 def bpm_at(time) @bpm.get_value_at_time(resolve_time(time)) end |
#bpm_events ⇒ Object (private)
481 482 483 |
# File 'lib/deftones/event/transport.rb', line 481 def bpm_events @bpm.instance_variable_get(:@events) || [] end |
#bpm_signal ⇒ Object Also known as: bpmSignal
47 48 49 |
# File 'lib/deftones/event/transport.rb', line 47 def bpm_signal @bpm end |
#cancel(after_time = 0, event_id: nil) ⇒ Object
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/deftones/event/transport.rb', line 172 def cancel(after_time = 0, event_id: nil) return @timeline.delete(event_id) if event_id threshold = resolve_time(after_time) @timeline.delete_if do |_id, event| event_time = event[:kind] == :repeat ? event[:start_time] : event[:time] event_time >= threshold end self end |
#clear(event_id) ⇒ Object
168 169 170 |
# File 'lib/deftones/event/transport.rb', line 168 def clear(event_id) cancel(event_id: event_id) end |
#clock_time ⇒ Object (private)
418 419 420 421 422 |
# File 'lib/deftones/event/transport.rb', line 418 def clock_time return @clock.current_time if @clock&.respond_to?(:current_time) Deftones.now end |
#due_events(window_start, window_end) ⇒ Object (private)
280 281 282 283 284 285 286 287 |
# File 'lib/deftones/event/transport.rb', line 280 def due_events(window_start, window_end) return looped_due_events(window_start, window_end) if loop_active? events = @timeline.flat_map do |_id, event| event[:kind] == :repeat ? materialize_repeat_event(event, window_start, window_end) : materialize_one_shot(event, window_start, window_end) end events.sort_by { |event| event[:time] } end |
#due_events_without_loop(window_start, window_end) ⇒ Object (private)
308 309 310 311 312 313 314 315 316 |
# File 'lib/deftones/event/transport.rb', line 308 def due_events_without_loop(window_start, window_end) @timeline.flat_map do |_id, event| if event[:kind] == :repeat materialize_repeat_event(event, window_start, window_end) else materialize_one_shot(event, window_start, window_end) end end end |
#first_repeat_occurrence(start_time, interval, window_start) ⇒ Object (private)
341 342 343 344 345 |
# File 'lib/deftones/event/transport.rb', line 341 def first_repeat_occurrence(start_time, interval, window_start) return 0 if window_start <= start_time ((window_start - start_time) / interval).floor + 1 end |
#future_time?(time) ⇒ Boolean (private)
351 352 353 354 355 |
# File 'lib/deftones/event/transport.rb', line 351 def future_time?(time) return false unless @clock time > clock_time end |
#immediate ⇒ Object
193 194 195 |
# File 'lib/deftones/event/transport.rb', line 193 def immediate seconds end |
#integration_points(start_time, end_time) ⇒ Object (private)
471 472 473 474 475 476 477 478 479 |
# File 'lib/deftones/event/transport.rb', line 471 def integration_points(start_time, end_time) points = [start_time, end_time] bpm_events.each do |event| points << event[:time] if event[:time] && event[:time].between?(start_time, end_time) points << event[:start_time] if event[:start_time] && event[:start_time].between?(start_time, end_time) points << event[:end_time] if event[:end_time] && event[:end_time].between?(start_time, end_time) end points.uniq.sort end |
#loop_active? ⇒ Boolean (private)
347 348 349 |
# File 'lib/deftones/event/transport.rb', line 347 def loop_active? @loop && (resolve_time(@loop_end) - resolve_time(@loop_start)).positive? end |
#looped_due_events(window_start, window_end) ⇒ Object (private)
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/deftones/event/transport.rb', line 289 def looped_due_events(window_start, window_end) loop_start_seconds = resolve_time(@loop_start) loop_end_seconds = resolve_time(@loop_end) span = loop_end_seconds - loop_start_seconds return [] unless span.positive? first_iteration = [(window_start - loop_end_seconds) / span, 0.0].max.floor last_iteration = [(window_end - loop_start_seconds) / span, 0.0].max.ceil (first_iteration..last_iteration).flat_map do |iteration| offset = iteration * span due_events_without_loop(loop_start_seconds, loop_end_seconds).filter_map do |event| global_time = event[:time] + offset next unless time_in_window?(global_time, window_start, window_end) event.merge(time: global_time) end end.sort_by { |event| event[:time] } end |
#materialize_one_shot(event, window_start, window_end) ⇒ Object (private)
318 319 320 321 322 |
# File 'lib/deftones/event/transport.rb', line 318 def materialize_one_shot(event, window_start, window_end) return [] unless time_in_window?(event[:time], window_start, window_end) [{ time: apply_swing(event[:time], event[:time]), callback: event[:callback] }] end |
#materialize_repeat_event(event, window_start, window_end) ⇒ Object (private)
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/deftones/event/transport.rb', line 324 def materialize_repeat_event(event, window_start, window_end) interval = [event[:interval], 1.0e-6].max limit = event[:duration] ? [event[:start_time] + event[:duration], window_end].min : window_end events = [] occurrence = first_repeat_occurrence(event[:start_time], interval, window_start) current_time = event[:start_time] + (occurrence * interval) while current_time <= limit actual_time = apply_swing(current_time, interval, occurrence) events << { time: actual_time, callback: event[:callback] } if time_in_window?(actual_time, window_start, window_end) current_time += interval occurrence += 1 end events end |
#next_subdivision(subdivision) ⇒ Object Also known as: nextSubdivision
208 209 210 211 212 213 214 |
# File 'lib/deftones/event/transport.rb', line 208 def next_subdivision(subdivision) interval = resolve_time(subdivision) return nil unless interval.positive? current = seconds (((current / interval).floor) + 1) * interval end |
#pause(time = nil) ⇒ Object
88 89 90 91 92 93 94 95 96 |
# File 'lib/deftones/event/transport.rb', line 88 def pause(time = nil) resolved_time = time.nil? ? clock_time : resolve_time(time) if future_time?(resolved_time) add_state_event(:paused, resolved_time) else apply_pause(resolved_time) end self end |
#position ⇒ Object
111 112 113 |
# File 'lib/deftones/event/transport.rb', line 111 def position seconds_to_position(@position_seconds) end |
#position=(value) ⇒ Object
115 116 117 |
# File 'lib/deftones/event/transport.rb', line 115 def position=(value) @position_seconds = resolve_time(value) end |
#prepare_render(duration) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/deftones/event/transport.rb', line 240 def prepare_render(duration) render_duration = resolve_time(duration) cursor = 0.0 while cursor < render_duration window_end = [cursor + 1.0, render_duration].min prepare_render_window(cursor, window_end) cursor = window_end end self end |
#prepare_render_window(start_time, end_time) ⇒ Object Also known as: prepareRenderWindow
252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/deftones/event/transport.rb', line 252 def prepare_render_window(start_time, end_time) window_start = resolve_time(start_time) window_end = resolve_time(end_time) return self if window_end < window_start apply_due_state_events(window_end) due_events(window_start, window_end).each do |event| event[:callback].call(event[:time]) end self end |
#progress ⇒ Object
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/deftones/event/transport.rb', line 197 def progress return nil unless @loop loop_start_seconds = resolve_time(@loop_start) loop_end_seconds = resolve_time(@loop_end) span = loop_end_seconds - loop_start_seconds return nil unless span.positive? ((seconds - loop_start_seconds) % span) / span end |
#resolve_time(value) ⇒ Object (private)
412 413 414 415 416 |
# File 'lib/deftones/event/transport.rb', line 412 def resolve_time(value) return @position_seconds if value.nil? Deftones::Music::Time.parse(value, bpm: bpm, time_signature: time_signature, ppq: @ppq) end |
#schedule(time, &block) ⇒ Object
143 144 145 146 147 |
# File 'lib/deftones/event/transport.rb', line 143 def schedule(time, &block) raise ArgumentError, "Transport callback is required" unless block add_event(kind: :once, time: resolve_time(time), callback: block) end |
#schedule_once(time, &block) ⇒ Object Also known as: scheduleOnce
149 150 151 |
# File 'lib/deftones/event/transport.rb', line 149 def schedule_once(time, &block) schedule(time, &block) end |
#schedule_repeat(interval, start_time: 0, duration: nil, &block) ⇒ Object Also known as: scheduleRepeat
153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/deftones/event/transport.rb', line 153 def schedule_repeat(interval, start_time: 0, duration: nil, &block) raise ArgumentError, "Transport callback is required" unless block resolved_interval = resolve_time(interval) raise ArgumentError, "repeat interval must be positive" unless resolved_interval.positive? add_event( kind: :repeat, interval: resolved_interval, start_time: resolve_time(start_time), duration: duration.nil? ? nil : resolve_time(duration), callback: block ) end |
#seconds ⇒ Object
132 133 134 135 136 137 |
# File 'lib/deftones/event/transport.rb', line 132 def seconds apply_due_state_events(clock_time) return @position_seconds unless @state == :started [clock_time - @started_at, 0.0].max end |
#seconds=(value) ⇒ Object
119 120 121 |
# File 'lib/deftones/event/transport.rb', line 119 def seconds=(value) @position_seconds = resolve_time(value) end |
#seconds_to_position(seconds) ⇒ Object
424 425 426 427 428 429 430 431 432 |
# File 'lib/deftones/event/transport.rb', line 424 def seconds_to_position(seconds) beats_per_measure = Array(@time_signature).first || 4 beat_duration = 60.0 / bpm total_beats = seconds.to_f / beat_duration = (total_beats / beats_per_measure).floor beats = (total_beats % beats_per_measure).floor sixteenths = (((total_beats - total_beats.floor) / 0.25).round) % 4 "#{}:#{beats}:#{sixteenths}" end |
#seconds_to_ticks(seconds) ⇒ Object
434 435 436 |
# File 'lib/deftones/event/transport.rb', line 434 def seconds_to_ticks(seconds) (beats_between(0.0, seconds.to_f) * @ppq).round end |
#set_bpm_at_time(value, time) ⇒ Object Also known as: setBpmAtTime
51 52 53 54 |
# File 'lib/deftones/event/transport.rb', line 51 def set_bpm_at_time(value, time) @bpm.set_value_at_time(value, resolve_time(time)) self end |
#set_loop_points(start_time, end_time) ⇒ Object Also known as: setLoopPoints
183 184 185 186 187 |
# File 'lib/deftones/event/transport.rb', line 183 def set_loop_points(start_time, end_time) @loop_start = resolve_time(start_time) @loop_end = resolve_time(end_time) self end |
#start(time = nil) ⇒ Object
68 69 70 71 72 73 74 75 76 |
# File 'lib/deftones/event/transport.rb', line 68 def start(time = nil) resolved_time = resolve_time(time) if future_time?(resolved_time) add_state_event(:started, resolved_time) else apply_start(resolved_time) end self end |
#state_at(time) ⇒ Object Also known as: stateAt
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/deftones/event/transport.rb', line 98 def state_at(time) resolved_time = resolve_time(time) effective_state = state_before_timeline @state_timeline.sort_by { |event| [event[:time], event[:id]] }.each do |event| break if event[:time] > resolved_time effective_state = event[:state] end effective_state end |
#state_before_timeline ⇒ Object (private)
393 394 395 |
# File 'lib/deftones/event/transport.rb', line 393 def state_before_timeline @state end |
#state_position_at(time) ⇒ Object (private)
387 388 389 390 391 |
# File 'lib/deftones/event/transport.rb', line 387 def state_position_at(time) return @position_seconds unless @state == :started [time - @started_at, 0.0].max end |
#stop(time = nil) ⇒ Object
78 79 80 81 82 83 84 85 86 |
# File 'lib/deftones/event/transport.rb', line 78 def stop(time = nil) resolved_time = time.nil? ? clock_time : resolve_time(time) if future_time?(resolved_time) add_state_event(:stopped, resolved_time) else apply_stop(resolved_time) end self end |
#swing_subdivision ⇒ Object
56 57 58 |
# File 'lib/deftones/event/transport.rb', line 56 def swing_subdivision @swing_subdivision end |
#swing_subdivision=(value) ⇒ Object
60 61 62 |
# File 'lib/deftones/event/transport.rb', line 60 def swing_subdivision=(value) @swing_subdivision = value end |
#swingSubdivision ⇒ Object
232 233 234 |
# File 'lib/deftones/event/transport.rb', line 232 def swingSubdivision swing_subdivision end |
#swingSubdivision=(value) ⇒ Object
236 237 238 |
# File 'lib/deftones/event/transport.rb', line 236 def swingSubdivision=(value) self.swing_subdivision = value end |
#tempo_automated? ⇒ Boolean (private)
467 468 469 |
# File 'lib/deftones/event/transport.rb', line 467 def tempo_automated? bpm_events.any? end |
#ticks ⇒ Object
123 124 125 |
# File 'lib/deftones/event/transport.rb', line 123 def ticks seconds_to_ticks(@position_seconds) end |
#ticks=(value) ⇒ Object
127 128 129 130 |
# File 'lib/deftones/event/transport.rb', line 127 def ticks=(value) parsed_ticks = Deftones::Music::Ticks.parse(value, bpm: bpm, time_signature: time_signature, ppq: @ppq) @position_seconds = ticks_to_seconds(parsed_ticks) end |
#ticks_to_seconds(ticks) ⇒ Object
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 |
# File 'lib/deftones/event/transport.rb', line 438 def ticks_to_seconds(ticks) target_beats = ticks.to_f / @ppq return target_beats * (60.0 / bpm) unless tempo_automated? upper = [target_beats * (60.0 / [bpm, 1.0].max), 1.0e-6].max upper *= 2.0 while beats_between(0.0, upper) < target_beats lower = 0.0 40.times do midpoint = (lower + upper) * 0.5 if beats_between(0.0, midpoint) < target_beats lower = midpoint else upper = midpoint end end upper end |
#time_in_window?(time, window_start, window_end) ⇒ Boolean (private)
397 398 399 400 401 402 |
# File 'lib/deftones/event/transport.rb', line 397 def time_in_window?(time, window_start, window_end) return false if time > window_end return time >= window_start if window_start.zero? time > window_start end |
#timeSignature ⇒ Object
224 225 226 |
# File 'lib/deftones/event/transport.rb', line 224 def timeSignature time_signature end |
#timeSignature=(signature) ⇒ Object
228 229 230 |
# File 'lib/deftones/event/transport.rb', line 228 def timeSignature=(signature) self.time_signature = signature end |
#toggle(time = nil) ⇒ Object
189 190 191 |
# File 'lib/deftones/event/transport.rb', line 189 def toggle(time = nil) @state == :started ? pause(time) : start(time) end |