Class: Song
- Inherits:
-
Object
- Object
- Song
- Defined in:
- lib/song.rb
Overview
Domain object which models the ‘sheet music’ for a full song. Models the Patterns that should be played, in which order (i.e. the flow), and at which tempo.
This is the top-level model object that is used by the AudioEngine to produce actual audio data. A Song tells the AudioEngine what sounds to trigger and when. A Kit provides the sample data for each of these sounds. With a Song and a Kit the AudioEngine can produce the audio data that is saved to disk.
Constant Summary collapse
- DEFAULT_TEMPO =
120
Instance Attribute Summary collapse
-
#flow ⇒ Object
Returns the value of attribute flow.
-
#patterns ⇒ Object
readonly
Returns the value of attribute patterns.
Instance Method Summary collapse
-
#copy_ignoring_patterns_and_flow ⇒ Object
Returns a new Song that is identical but with no patterns or flow.
-
#initialize ⇒ Song
constructor
A new instance of Song.
-
#pattern(name) ⇒ Object
Adds a new pattern to the song, with the specified name.
-
#remove_unused_patterns ⇒ Object
Removes any patterns that aren’t referenced in the flow.
-
#split ⇒ Object
Splits a Song object into multiple Song objects, where each new Song only has 1 track.
- #tempo ⇒ Object
- #tempo=(new_tempo) ⇒ Object
-
#to_yaml(kit) ⇒ Object
Serializes the current Song to a YAML string.
-
#total_tracks ⇒ Object
The number of tracks that the pattern with the greatest number of tracks has.
-
#track_names ⇒ Object
The unique track names used in each of the song’s patterns.
Constructor Details
#initialize ⇒ Song
Returns a new instance of Song.
14 15 16 17 18 |
# File 'lib/song.rb', line 14 def initialize() self.tempo = DEFAULT_TEMPO @patterns = {} @flow = [] end |
Instance Attribute Details
#flow ⇒ Object
Returns the value of attribute flow.
126 127 128 |
# File 'lib/song.rb', line 126 def flow @flow end |
#patterns ⇒ Object (readonly)
Returns the value of attribute patterns.
125 126 127 |
# File 'lib/song.rb', line 125 def patterns @patterns end |
Instance Method Details
#copy_ignoring_patterns_and_flow ⇒ Object
Returns a new Song that is identical but with no patterns or flow.
66 67 68 69 70 71 |
# File 'lib/song.rb', line 66 def copy_ignoring_patterns_and_flow copy = Song.new() copy.tempo = @tempo return copy end |
#pattern(name) ⇒ Object
Adds a new pattern to the song, with the specified name.
21 22 23 24 |
# File 'lib/song.rb', line 21 def pattern(name) @patterns[name] = Pattern.new(name) return @patterns[name] end |
#remove_unused_patterns ⇒ Object
Removes any patterns that aren’t referenced in the flow.
103 104 105 106 |
# File 'lib/song.rb', line 103 def remove_unused_patterns # Using reject() here because for some reason select() returns an Array not a Hash. @patterns.reject! {|k, pattern| !@flow.member?(pattern.name) } end |
#split ⇒ Object
Splits a Song object into multiple Song objects, where each new Song only has 1 track. For example, if a Song has 5 tracks, this will return a hash of 5 songs, each with one of the original Song’s tracks.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/song.rb', line 76 def split() split_songs = {} track_names = track_names() track_names.each do |track_name| new_song = copy_ignoring_patterns_and_flow() @patterns.each do |name, original_pattern| new_pattern = new_song.pattern(name) if original_pattern.tracks.has_key?(track_name) original_track = original_pattern.tracks[track_name] new_pattern.track(original_track.name, original_track.rhythm) else new_pattern.track(track_name, "." * original_pattern.step_count) end end new_song.flow = @flow split_songs[track_name] = new_song end return split_songs end |
#tempo ⇒ Object
53 54 55 |
# File 'lib/song.rb', line 53 def tempo return @tempo end |
#tempo=(new_tempo) ⇒ Object
57 58 59 60 61 62 63 |
# File 'lib/song.rb', line 57 def tempo=(new_tempo) unless new_tempo.class == Fixnum && new_tempo > 0 raise InvalidTempoError, "Invalid tempo: '#{new_tempo}'. Tempo must be a number greater than 0." end @tempo = new_tempo end |
#to_yaml(kit) ⇒ Object
Serializes the current Song to a YAML string. This string can then be used to construct a new Song using the SongParser class. This lets you save a Song to disk, to be re-loaded later. Produces nicer looking output than the default version of to_yaml().
111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/song.rb', line 111 def to_yaml(kit) # This implementation intentionally builds up a YAML string manually instead of using YAML::dump(). # Ruby 1.8 makes it difficult to ensure a consistent ordering of hash keys, which makes the output ugly # and also hard to test. yaml_output = "Song:\n" yaml_output += " Tempo: #{@tempo}\n" yaml_output += flow_to_yaml() yaml_output += kit.to_yaml(2) yaml_output += patterns_to_yaml() return yaml_output end |
#total_tracks ⇒ Object
The number of tracks that the pattern with the greatest number of tracks has. TODO: Is it a problem that an optimized song can have a different total_tracks() value than the original? Or is that actually a good thing? TODO: Investigate replacing this with a method max_sounds_playing_at_once() or something like that. Would look each pattern along with it’s incoming overflow.
32 33 34 |
# File 'lib/song.rb', line 32 def total_tracks @patterns.keys.collect {|pattern_name| @patterns[pattern_name].tracks.length }.max || 0 end |
#track_names ⇒ Object
The unique track names used in each of the song’s patterns. Sorted in alphabetical order. For example calling this method for this song:
Verse:
- bass: X...
- snare: ..X.
Chorus:
- bass: X.X.
- snare: X.X.
- hihat: XXXX
Will return: [“bass”, “hihat”, “snare”]
49 50 51 |
# File 'lib/song.rb', line 49 def track_names @patterns.values.inject([]) {|track_names, pattern| track_names | pattern.tracks.keys }.sort end |