Class: Beats::Song

Inherits:
Object
  • Object
show all
Defined in:
lib/beats/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

Instance Method Summary collapse

Constructor Details

#initializeSong

Returns a new instance of Song.



15
16
17
18
19
# File 'lib/beats/song.rb', line 15

def initialize
  self.tempo = DEFAULT_TEMPO
  @patterns = {}
  @flow = []
end

Instance Attribute Details

#flowObject

Returns the value of attribute flow.



123
124
125
# File 'lib/beats/song.rb', line 123

def flow
  @flow
end

#patternsObject (readonly)

Returns the value of attribute patterns.



122
123
124
# File 'lib/beats/song.rb', line 122

def patterns
  @patterns
end

#tempoObject

Returns the value of attribute tempo.



122
123
124
# File 'lib/beats/song.rb', line 122

def tempo
  @tempo
end

Instance Method Details

#copy_ignoring_patterns_and_flowObject

Returns a new Song that is identical but with no patterns or flow.



63
64
65
66
67
68
# File 'lib/beats/song.rb', line 63

def copy_ignoring_patterns_and_flow
  copy = Song.new()
  copy.tempo = @tempo

  copy
end

#pattern(name) ⇒ Object

Adds a new pattern to the song, with the specified name.



22
23
24
25
# File 'lib/beats/song.rb', line 22

def pattern(name)
  @patterns[name] = Pattern.new(name)
  @patterns[name]
end

#remove_unused_patternsObject

Removes any patterns that aren’t referenced in the flow.



100
101
102
103
# File 'lib/beats/song.rb', line 100

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

#splitObject

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.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/beats/song.rb', line 73

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

  split_songs
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().



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/beats/song.rb', line 108

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()

  yaml_output
end

#total_tracksObject

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.



33
34
35
# File 'lib/beats/song.rb', line 33

def total_tracks
  @patterns.keys.collect {|pattern_name| @patterns[pattern_name].tracks.length }.max || 0
end

#track_namesObject

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”]



50
51
52
# File 'lib/beats/song.rb', line 50

def track_names
  @patterns.values.inject([]) {|track_names, pattern| track_names | pattern.tracks.keys }.sort
end