Class: Wavesync::SetlistEditor
- Inherits:
-
Object
- Object
- Wavesync::SetlistEditor
- Defined in:
- lib/wavesync/setlist_editor.rb
Constant Summary collapse
- KEY_MAP =
{ 'a' => :add, 'u' => :move_up, 'd' => :move_down, 'r' => :remove, 'q' => :quit, ' ' => :toggle_play, 'j' => :jump_to_next_cue, "\e[A" => :cursor_up, "\e[B" => :cursor_down }.freeze
- EMPTY_MARKERS =
steep:ignore UnannotatedEmptyCollection
{ cues: [], loops: [] }.freeze
Instance Attribute Summary collapse
-
#player_index ⇒ Object
writeonly
Sets the attribute player_index.
-
#player_offset ⇒ Object
writeonly
Sets the attribute player_offset.
-
#player_pid ⇒ Object
writeonly
Sets the attribute player_pid.
-
#player_started_at ⇒ Object
writeonly
Sets the attribute player_started_at.
-
#player_state ⇒ Object
: Symbol.
-
#player_track ⇒ Object
writeonly
Sets the attribute player_track.
-
#selected ⇒ Object
readonly
: untyped.
-
#setlist ⇒ Object
readonly
: untyped.
-
#ui ⇒ Object
readonly
: untyped.
Instance Method Summary collapse
-
#advance_and_play ⇒ Object
: () -> void.
-
#check_player ⇒ Object
: () -> void.
-
#display_name(relative) ⇒ Object
: (String relative) -> String.
-
#format_duration(seconds) ⇒ Object
: (Float? seconds) -> String?.
-
#format_pitch_shift(semitones) ⇒ Object
: (Float semitones) -> String.
-
#handle_action(action) ⇒ Object
: (Symbol action) -> Symbol?.
-
#initialize(setlist, library_path) ⇒ SetlistEditor
constructor
: (Setlist setlist, String library_path) -> void.
-
#kill_player ⇒ Object
: () -> void.
-
#pitch_shift_semitones(source_bpm, target_bpm) ⇒ Object
: ((String | Integer)? source_bpm, (String | Integer)? target_bpm) -> Float?.
- #playback_bar(elapsed, total_duration, bar_width, cue_fractions: [], loop_fractions: []) ⇒ Object
-
#playback_elapsed ⇒ Object
: () -> Float.
-
#relative_path(absolute) ⇒ Object
: (String absolute) -> String.
-
#run ⇒ Object
: () -> void.
-
#track_bpm(path) ⇒ Object
: (String? path) -> (String | Integer)?.
-
#track_cue_fractions(path) ⇒ Object
: (String? path) -> Array.
-
#track_duration(path) ⇒ Object
: (String? path) -> Float?.
-
#track_loop_fractions(path) ⇒ Object
: (String? path) -> Array.
-
#track_markers(path) ⇒ Object
: (String? path) -> Array[Float], loops: Array[{start_fraction: Float, end_fraction: Float]}.
-
#visible_length(string) ⇒ Object
: (String? string) -> Integer.
Constructor Details
#initialize(setlist, library_path) ⇒ SetlistEditor
: (Setlist setlist, String library_path) -> void
29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/wavesync/setlist_editor.rb', line 29 def initialize(setlist, library_path) @setlist = setlist #: Setlist @library_path = library_path #: String Logger.configure(@library_path) @prompt = TTY::Prompt.new(interrupt: :exit, active_color: :red) #: untyped @ui = UI.new #: UI @selected = @setlist.tracks.empty? ? nil : 0 #: Integer? @player_pid = nil #: Integer? @player_track = nil #: String? @player_index = nil #: Integer? @player_state = :stopped #: Symbol @player_offset = 0 #: Numeric @player_started_at = nil #: Time? end |
Instance Attribute Details
#player_index=(value) ⇒ Object (writeonly)
Sets the attribute player_index
26 27 28 |
# File 'lib/wavesync/setlist_editor.rb', line 26 def player_index=(value) @player_index = value end |
#player_offset=(value) ⇒ Object (writeonly)
Sets the attribute player_offset
26 27 28 |
# File 'lib/wavesync/setlist_editor.rb', line 26 def player_offset=(value) @player_offset = value end |
#player_pid=(value) ⇒ Object (writeonly)
Sets the attribute player_pid
26 27 28 |
# File 'lib/wavesync/setlist_editor.rb', line 26 def player_pid=(value) @player_pid = value end |
#player_started_at=(value) ⇒ Object (writeonly)
Sets the attribute player_started_at
26 27 28 |
# File 'lib/wavesync/setlist_editor.rb', line 26 def player_started_at=(value) @player_started_at = value end |
#player_state ⇒ Object
: Symbol
24 25 26 |
# File 'lib/wavesync/setlist_editor.rb', line 24 def player_state @player_state end |
#player_track=(value) ⇒ Object (writeonly)
Sets the attribute player_track
26 27 28 |
# File 'lib/wavesync/setlist_editor.rb', line 26 def player_track=(value) @player_track = value end |
#selected ⇒ Object (readonly)
: untyped
25 26 27 |
# File 'lib/wavesync/setlist_editor.rb', line 25 def selected @selected end |
#setlist ⇒ Object (readonly)
: untyped
25 26 27 |
# File 'lib/wavesync/setlist_editor.rb', line 25 def setlist @setlist end |
#ui ⇒ Object (readonly)
: untyped
25 26 27 |
# File 'lib/wavesync/setlist_editor.rb', line 25 def ui @ui end |
Instance Method Details
#advance_and_play ⇒ Object
: () -> void
534 535 536 537 538 539 |
# File 'lib/wavesync/setlist_editor.rb', line 534 def advance_and_play return if @selected.nil? || @selected >= @setlist.tracks.size - 1 @selected += 1 start_player(@setlist.tracks[@selected]) end |
#check_player ⇒ Object
: () -> void
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/wavesync/setlist_editor.rb', line 510 def check_player return unless @player_pid player_pid = @player_pid result = Process.waitpid(@player_pid, Process::WNOHANG) return unless result @player_pid = nil @player_track = nil @player_index = nil @player_state = :stopped @player_offset = 0 advance_and_play rescue Errno::ECHILD => e Logger.log_error(e, call_site: 'SetlistEditor#check_player', arguments: { player_pid: }) @player_pid = nil @player_track = nil @player_index = nil @player_state = :stopped @player_offset = 0 advance_and_play end |
#display_name(relative) ⇒ Object
: (String relative) -> String
290 291 292 |
# File 'lib/wavesync/setlist_editor.rb', line 290 def display_name(relative) File.basename(relative, '.*').sub(/\A\d+[\s.\-_]+/, '') end |
#format_duration(seconds) ⇒ Object
: (Float? seconds) -> String?
204 205 206 207 208 209 210 211 |
# File 'lib/wavesync/setlist_editor.rb', line 204 def format_duration(seconds) return nil unless seconds total_seconds = seconds.to_i mins = total_seconds / 60 secs = total_seconds % 60 "#{mins}:#{secs.to_s.rjust(2, '0')}" end |
#format_pitch_shift(semitones) ⇒ Object
: (Float semitones) -> String
125 126 127 128 |
# File 'lib/wavesync/setlist_editor.rb', line 125 def format_pitch_shift(semitones) sign = semitones >= 0 ? '+' : '' "#{sign}#{semitones.round(2)}" end |
#handle_action(action) ⇒ Object
: (Symbol action) -> Symbol?
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/wavesync/setlist_editor.rb', line 401 def handle_action(action) case action when :cursor_up @selected = [@selected - 1, 0].max unless @setlist.tracks.empty? nil when :cursor_down @selected = [@selected + 1, @setlist.tracks.size - 1].min unless @setlist.tracks.empty? nil when :toggle_play toggle_playback nil when :add add_track nil when :remove remove_track nil when :move_up move_track(:up) nil when :move_down move_track(:down) nil when :jump_to_next_cue jump_to_next_cue nil when :quit @setlist.save :quit end end |
#kill_player ⇒ Object
: () -> void
488 489 490 491 492 493 494 495 496 497 |
# File 'lib/wavesync/setlist_editor.rb', line 488 def kill_player return unless @player_pid player_pid = @player_pid Process.kill('TERM', @player_pid) @player_pid = nil rescue Errno::ESRCH => e Logger.log_error(e, call_site: 'SetlistEditor#kill_player', arguments: { player_pid: }) @player_pid = nil end |
#pitch_shift_semitones(source_bpm, target_bpm) ⇒ Object
: ((String | Integer)? source_bpm, (String | Integer)? target_bpm) -> Float?
114 115 116 117 118 119 120 121 122 |
# File 'lib/wavesync/setlist_editor.rb', line 114 def pitch_shift_semitones(source_bpm, target_bpm) return nil unless source_bpm && target_bpm source = source_bpm.to_f target = target_bpm.to_f return nil if source.zero? 12.0 * Math.log2(target / source) end |
#playback_bar(elapsed, total_duration, bar_width, cue_fractions: [], loop_fractions: []) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/wavesync/setlist_editor.rb', line 240 def (elapsed, total_duration, , cue_fractions: [], loop_fractions: []) ratio = total_duration.positive? ? [elapsed / total_duration, 1.0].min : 0.0 filled = (ratio * ).round = Array.new() { |i| i < filled ? '█' : '░' } cell_colors = Array.new(, :surface) #: Array[Symbol] marker_positions = {} #: Hash[Integer, bool] loop_fractions.each do |loop_range| loop_start_position = (loop_range[:start_fraction] * ( - 1)).round.clamp(0, - 1) loop_end_position = (loop_range[:end_fraction] * ( - 1)).round.clamp(0, - 1) next if loop_end_position <= loop_start_position (loop_start_position..loop_end_position).each { |position| cell_colors[position] = :extra } [loop_start_position] = '⟨' [loop_end_position] = '⟩' marker_positions[loop_start_position] = true marker_positions[loop_end_position] = true end cue_fractions.each do |fraction| position = (fraction * ( - 1)).round.clamp(0, - 1) [position] = '◆' cell_colors[position] = position == filled ? :highlight : :surface marker_positions[position] = true end result = +'' run_start = 0 while run_start < if marker_positions[run_start] result << @ui.color([run_start], cell_colors[run_start]) run_start += 1 else run_end = run_start + 1 run_end += 1 while run_end < && !marker_positions[run_end] && cell_colors[run_end] == cell_colors[run_start] result << @ui.color(([run_start...run_end] || []).join, cell_colors[run_start]) run_start = run_end end end result end |
#playback_elapsed ⇒ Object
: () -> Float
214 215 216 217 218 219 220 221 222 223 |
# File 'lib/wavesync/setlist_editor.rb', line 214 def playback_elapsed case @player_state when :playing (@player_offset + (Time.now - @player_started_at)).to_f when :paused @player_offset.to_f else 0.0 end end |
#relative_path(absolute) ⇒ Object
: (String absolute) -> String
199 200 201 |
# File 'lib/wavesync/setlist_editor.rb', line 199 def relative_path(absolute) absolute.sub("#{@library_path}/", '') end |
#run ⇒ Object
: () -> void
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/wavesync/setlist_editor.rb', line 45 def run enter_fullscreen loop do check_player render action = KEY_MAP[read_key] next unless action result = handle_action(action) break if result == :quit end ensure exit_fullscreen stop_playback end |
#track_bpm(path) ⇒ Object
: (String? path) -> (String | Integer)?
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/wavesync/setlist_editor.rb', line 62 def track_bpm(path) return nil if path.nil? @track_bpms ||= {} #: Hash[String, (String | Integer)?] return @track_bpms[path] if @track_bpms.key?(path) @track_bpms[path] = begin Audio.new(path).bpm rescue StandardError => e Logger.log_error(e, call_site: 'SetlistEditor#track_bpm', arguments: { path: }) nil end end |
#track_cue_fractions(path) ⇒ Object
: (String? path) -> Array
94 95 96 |
# File 'lib/wavesync/setlist_editor.rb', line 94 def track_cue_fractions(path) track_markers(path)[:cues] end |
#track_duration(path) ⇒ Object
: (String? path) -> Float?
77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/wavesync/setlist_editor.rb', line 77 def track_duration(path) return nil if path.nil? @track_durations ||= {} #: Hash[String, Float?] return @track_durations[path] if @track_durations.key?(path) @track_durations[path] = begin Audio.new(path).duration rescue StandardError => e Logger.log_error(e, call_site: 'SetlistEditor#track_duration', arguments: { path: }) nil end end |
#track_loop_fractions(path) ⇒ Object
: (String? path) -> Array
99 100 101 |
# File 'lib/wavesync/setlist_editor.rb', line 99 def track_loop_fractions(path) track_markers(path)[:loops] end |
#track_markers(path) ⇒ Object
: (String? path) -> Array[Float], loops: Array[{start_fraction: Float, end_fraction: Float]}
104 105 106 107 108 109 110 111 |
# File 'lib/wavesync/setlist_editor.rb', line 104 def track_markers(path) return EMPTY_MARKERS if path.nil? @track_markers ||= {} #: Hash[String, {cues: Array[Float], loops: Array[{start_fraction: Float, end_fraction: Float}]}] return @track_markers[path] if @track_markers.key?(path) @track_markers[path] = compute_track_markers(path) end |
#visible_length(string) ⇒ Object
: (String? string) -> Integer
233 234 235 236 237 |
# File 'lib/wavesync/setlist_editor.rb', line 233 def visible_length(string) return 0 unless string string.gsub(/\e\[[0-9;]*[A-Za-z]/, '').length end |