Class: Wavify::DSL::SongDefinition
- Inherits:
-
Object
- Object
- Wavify::DSL::SongDefinition
- Defined in:
- lib/wavify/dsl.rb,
sig/dsl.rbs
Overview
Immutable compiled song definition returned by build_definition.
Defined Under Namespace
Classes: Section
Instance Attribute Summary collapse
-
#beats_per_bar ⇒ Integer
readonly
Returns the value of attribute beats_per_bar.
-
#default_bars ⇒ Integer
readonly
Returns the value of attribute default_bars.
-
#format ⇒ Core::Format
readonly
Returns the value of attribute format.
-
#random_seed ⇒ Integer
readonly
Returns the value of attribute random_seed.
-
#sections ⇒ Array[untyped]
readonly
Returns the value of attribute sections.
-
#swing ⇒ Float
readonly
Returns the value of attribute swing.
-
#tempo ⇒ Float
readonly
Returns the value of attribute tempo.
-
#tracks ⇒ Array[untyped]
readonly
Returns the value of attribute tracks.
Instance Method Summary collapse
-
#arrangement ⇒ Array<Hash>
Returns arrangement sections as hashes accepted by the sequencer engine.
- #arrangement? ⇒ Boolean
-
#duration(default_bars: @default_bars) ⇒ Wavify::Core::Duration
(also: #length)
Planned song duration derived from arrangement/default bars.
-
#engine ⇒ Wavify::Sequencer::Engine
Returns a sequencer engine configured from the song definition.
-
#initialize(format:, tempo:, beats_per_bar:, swing:, default_bars:, tracks:, sections:, random_seed:) ⇒ SongDefinition
constructor
A new instance of SongDefinition.
-
#render(default_bars: @default_bars, stems: false) ⇒ Wavify::Audio
Renders the song definition to an Audio instance.
-
#sequencer_tracks ⇒ Array<Wavify::Sequencer::Track>
Returns sequencer tracks converted from DSL track definitions.
-
#timeline(default_bars: @default_bars) ⇒ Array<Hash>
Builds a sequencer event timeline without rendering audio.
-
#timeline_json(default_bars: @default_bars) ⇒ String
Serializes the sequencer timeline to JSON for visualization tooling.
-
#timeline_text(default_bars: @default_bars) ⇒ String
Renders the sequencer timeline as a compact text table.
-
#validate!(deep: false) ⇒ true
Validates the compiled song without rendering audio.
-
#write(path, default_bars: @default_bars) ⇒ Wavify::Audio
Renders and writes the song to disk.
-
#write_stems(directory, default_bars: @default_bars, overwrite: true) ⇒ Hash<Symbol, String>
Renders each track and writes stems as WAV files under a directory.
Constructor Details
#initialize(format:, tempo:, beats_per_bar:, swing:, default_bars:, tracks:, sections:, random_seed:) ⇒ SongDefinition
Returns a new instance of SongDefinition.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/wavify/dsl.rb', line 19 def initialize(format:, tempo:, beats_per_bar:, swing:, default_bars:, tracks:, sections:, random_seed:) @format = format @tempo = tempo @beats_per_bar = @swing = swing @default_bars = @random_seed = random_seed @tracks = tracks.map(&:finalize!).freeze @sections = sections.map do |section| section.tracks = section.tracks.dup.freeze section.markers = section.markers.dup.freeze section.freeze end.freeze freeze end |
Instance Attribute Details
#beats_per_bar ⇒ Integer (readonly)
Returns the value of attribute beats_per_bar.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def @beats_per_bar end |
#default_bars ⇒ Integer (readonly)
Returns the value of attribute default_bars.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def @default_bars end |
#format ⇒ Core::Format (readonly)
Returns the value of attribute format.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def format @format end |
#random_seed ⇒ Integer (readonly)
Returns the value of attribute random_seed.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def random_seed @random_seed end |
#sections ⇒ Array[untyped] (readonly)
Returns the value of attribute sections.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def sections @sections end |
#swing ⇒ Float (readonly)
Returns the value of attribute swing.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def swing @swing end |
#tempo ⇒ Float (readonly)
Returns the value of attribute tempo.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def tempo @tempo end |
#tracks ⇒ Array[untyped] (readonly)
Returns the value of attribute tracks.
17 18 19 |
# File 'lib/wavify/dsl.rb', line 17 def tracks @tracks end |
Instance Method Details
#arrangement ⇒ Array<Hash>
Returns arrangement sections as hashes accepted by the sequencer engine.
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/wavify/dsl.rb', line 84 def arrangement @sections.map do |section| { name: section.name, bars: section., tracks: section.tracks, repeat: section.repeat || 1, tempo: section.tempo, beats_per_bar: section., markers: section.markers }.freeze end end |
#arrangement? ⇒ Boolean
35 36 37 |
# File 'lib/wavify/dsl.rb', line 35 def arrangement? !@sections.empty? end |
#duration(default_bars: @default_bars) ⇒ Wavify::Core::Duration Also known as: length
Planned song duration derived from arrangement/default bars.
102 103 104 |
# File 'lib/wavify/dsl.rb', line 102 def duration(default_bars: @default_bars) Core::Duration.new(total_seconds(default_bars: )) end |
#engine ⇒ Wavify::Sequencer::Engine
Returns a sequencer engine configured from the song definition.
42 43 44 45 46 47 48 49 |
# File 'lib/wavify/dsl.rb', line 42 def engine Wavify::Sequencer::Engine.new( tempo: @tempo, format: @format, beats_per_bar: @beats_per_bar, swing: @swing ) end |
#render(default_bars: @default_bars, stems: false) ⇒ Wavify::Audio
Renders the song definition to an Audio instance.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/wavify/dsl.rb', line 143 def render(default_bars: @default_bars, stems: false) return render_stems(default_bars: ) if stems playable_tracks = sequencer_tracks.select { |track| sequencer_track_playable?(track) } sequencer_audio = if playable_tracks.empty? Wavify::Audio.silence(0, format: @format) else playable_names = playable_tracks.map(&:name) playable_arrangement = if arrangement? arrangement.map do |section| section.merge(tracks: section.fetch(:tracks) & playable_names) end end engine.render( tracks: playable_tracks, arrangement: playable_arrangement, default_bars: ) end sample_audio = render_sample_tracks(default_bars: ) rendered = if sample_audio.nil? sequencer_audio elsif sequencer_audio.sample_frame_count.zero? sample_audio else Wavify::Audio.mix(sequencer_audio, sample_audio, strategy: :none) end pad_to_song_duration(rendered, default_bars: ) end |
#sequencer_tracks ⇒ Array<Wavify::Sequencer::Track>
Returns sequencer tracks converted from DSL track definitions.
54 55 56 |
# File 'lib/wavify/dsl.rb', line 54 def sequencer_tracks @tracks.map { |track| with_track_context(track) { track.to_sequencer_track } } end |
#timeline(default_bars: @default_bars) ⇒ Array<Hash>
Builds a sequencer event timeline without rendering audio.
112 113 114 115 116 117 118 |
# File 'lib/wavify/dsl.rb', line 112 def timeline(default_bars: @default_bars) engine.build_timeline( tracks: sequencer_tracks, arrangement: arrangement? ? arrangement : nil, default_bars: ) end |
#timeline_json(default_bars: @default_bars) ⇒ String
Serializes the sequencer timeline to JSON for visualization tooling.
124 125 126 |
# File 'lib/wavify/dsl.rb', line 124 def timeline_json(default_bars: @default_bars) JSON.generate(timeline(default_bars: )) end |
#timeline_text(default_bars: @default_bars) ⇒ String
Renders the sequencer timeline as a compact text table.
132 133 134 135 136 |
# File 'lib/wavify/dsl.rb', line 132 def timeline_text(default_bars: @default_bars) events = timeline(default_bars: ) rows = events.map { |event| timeline_text_row(event) } (["time\tbar\ttrack\tkind\tdetail"] + rows).join("\n") end |
#validate!(deep: false) ⇒ true
Validates the compiled song without rendering audio.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/wavify/dsl.rb', line 61 def validate!(deep: false) raise Wavify::SequencerError, "song must define at least one track" if @tracks.empty? @tracks.each do |track| with_track_context(track) do track.to_sequencer_track track.sample_pattern_map if deep track.samples.each_value do |path| Wavify::Audio.(path) rescue Wavify::Error, SystemCallError => e raise Wavify::SequencerError, "failed to inspect sample #{path.inspect}: #{e.}" end end end end timeline(default_bars: @default_bars) true end |
#write(path, default_bars: @default_bars) ⇒ Wavify::Audio
Renders and writes the song to disk.
179 180 181 |
# File 'lib/wavify/dsl.rb', line 179 def write(path, default_bars: @default_bars) render(default_bars: ).write(path) end |
#write_stems(directory, default_bars: @default_bars, overwrite: true) ⇒ Hash<Symbol, String>
Renders each track and writes stems as WAV files under a directory.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/wavify/dsl.rb', line 189 def write_stems(directory, default_bars: @default_bars, overwrite: true) raise Wavify::InvalidParameterError, "directory must be a String" unless directory.is_a?(String) = File.(directory) FileUtils.mkdir_p(File.dirname()) stems = render(default_bars: , stems: true) filenames = stems.keys.to_h { |track_name| [track_name, stem_filename(track_name)] } if filenames.values.uniq.length != filenames.length raise Wavify::SequencerError, "track names produce duplicate stem filenames" end paths = filenames.transform_values { |filename| File.join(, filename) } if !overwrite && (existing = paths.values.find { |path| File.exist?(path) }) raise Wavify::InvalidParameterError, "output file already exists: #{existing}" end Dir.mktmpdir(".wavify-stems-", File.dirname()) do |temporary_directory| stems.each do |track_name, audio| audio.write(File.join(temporary_directory, filenames.fetch(track_name))) end FileUtils.mkdir_p() paths.each do |track_name, path| File.rename(File.join(temporary_directory, filenames.fetch(track_name)), path) end end paths end |