beats 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ == BEATS
2
+
3
+ # Copyright (c) 2010 Joel Strait
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person
6
+ # obtaining a copy of this software and associated documentation
7
+ # files (the "Software"), to deal in the Software without
8
+ # restriction, including without limitation the rights to use,
9
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ # OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,27 @@
1
+ BEATS
2
+ -----
3
+
4
+ BEATS is a drum machine written in pure Ruby. Feed it a song notated in YAML, and it will produce a precision-milled Wave file of impeccable timing and feel. Here's an example song:
5
+
6
+ Song:
7
+ Tempo: 120
8
+ Structure:
9
+ - Verse: x2
10
+ - Chorus: x4
11
+ - Verse: x2
12
+ - Chorus: x4
13
+
14
+ Verse:
15
+ - bass.wav: X...X...X...X...
16
+ - snare.wav: ..............X.
17
+ - hh_closed.wav: X.XXX.XXX.X.X.X.
18
+ - agogo_high.wav: ..............XX
19
+
20
+ Chorus:
21
+ - bass.wav: X...X...X...X...
22
+ - snare.wav: ....X.......X...
23
+ - hh_closed.wav: X.XXX.XXX.XX..X.
24
+ - tom4.wav: ...........X....
25
+ - tom2.wav: ..............X.
26
+
27
+ For installation and usage instructions, visit the BEATS website at [http://beatsdrummachine.com](http://beatsdrummachine.com).
data/bin/beats ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + "/../lib/song"
4
+ require File.dirname(__FILE__) + "/../lib/kit"
5
+ require File.dirname(__FILE__) + "/../lib/pattern"
6
+ require File.dirname(__FILE__) + "/../lib/track"
7
+ require "rubygems"
8
+ require "optparse"
9
+ require "yaml"
10
+ require "wavefile"
11
+
12
+ BEATS_VERSION = "1.0.0"
13
+ SAMPLE_RATE = 44100
14
+
15
+ def parse_options
16
+ options = {:split => false, :pattern => ""}
17
+
18
+ optparse = OptionParser.new do |opts|
19
+ opts.on('-s', '--split', "Save each track to an individual wave file") do
20
+ options[:split] = true
21
+ end
22
+
23
+ opts.on('-p', '--pattern PATTERN_NAME', "Output a single pattern instead of the whole song" ) do |p|
24
+ options[:pattern] = p
25
+ end
26
+
27
+ opts.on('-v', '--version', "Display version number and exit") do
28
+ puts "BEATS v#{BEATS_VERSION}"
29
+ exit
30
+ end
31
+
32
+ opts.on( '-h', '--help', "Display this screen and exit" ) do
33
+ puts opts
34
+ exit
35
+ end
36
+ end
37
+ optparse.parse!
38
+
39
+ return options
40
+ end
41
+
42
+ def save_wave_file(file_name, num_channels, bits_per_sample, sample_data)
43
+ output = WaveFile.new(num_channels, SAMPLE_RATE, bits_per_sample)
44
+ output.sample_data = sample_data
45
+ output.save(file_name)
46
+ return output.duration
47
+ end
48
+
49
+ start = Time.now
50
+
51
+ options = parse_options
52
+ input_file = ARGV[0]
53
+ output_file = ARGV[1]
54
+
55
+ if(input_file == nil)
56
+ ARGV[0] = '-h'
57
+ parse_options()
58
+ end
59
+
60
+ if(output_file == nil)
61
+ output_file = File.basename(input_file, File.extname(input_file)) + ".wav"
62
+ end
63
+
64
+ begin
65
+ parse_start_time = Time.now
66
+ song_from_file = Song.new(File.dirname(input_file), YAML.load_file(input_file))
67
+
68
+ generate_samples_start = Time.now
69
+ sample_data = song_from_file.sample_data(options[:pattern], options[:split])
70
+ #puts "Time to generate sample data: #{Time.now - generate_samples_start}"
71
+
72
+ wave_write_start = Time.now
73
+ if(options[:split])
74
+ duration = nil
75
+ sample_data.keys.each {|track_name|
76
+ extension = File.extname(output_file)
77
+ file_name = File.basename(output_file, extension) + "-" + File.basename(track_name.to_s, extension) + extension
78
+
79
+ duration = save_wave_file(file_name,
80
+ song_from_file.num_channels,
81
+ song_from_file.bits_per_sample,
82
+ sample_data[track_name])
83
+ }
84
+ else
85
+ duration = save_wave_file(output_file, song_from_file.num_channels, song_from_file.bits_per_sample, sample_data)
86
+ end
87
+
88
+ puts "#{duration[:minutes]}:#{duration[:seconds].to_s.rjust(2, '0')} of audio produced in #{Time.now - start} seconds."
89
+ rescue Errno::ENOENT => detail
90
+ puts ""
91
+ puts "Song file '#{input_file}' not found."
92
+ puts ""
93
+ rescue ArgumentError => detail
94
+ puts ""
95
+ puts "Song file '#{input_file}' has an error:"
96
+ puts " Syntax error in YAML file:"
97
+ puts " #{detail}"
98
+ puts ""
99
+ rescue SongParseError => detail
100
+ puts ""
101
+ puts "Song file '#{input_file}' has an error:"
102
+ puts " #{detail}"
103
+ puts ""
104
+ rescue StandardError => detail
105
+ puts ""
106
+ puts "An error occured while generating sound for '#{input_file}':"
107
+ puts " #{detail}"
108
+ puts ""
109
+ end
data/lib/kit.rb ADDED
@@ -0,0 +1,40 @@
1
+ class Kit
2
+ def initialize()
3
+ @sounds = {}
4
+ @num_channels = 0
5
+ @bits_per_sample = 0
6
+ end
7
+
8
+ def add(name, path)
9
+ if(!@sounds.has_key? name)
10
+ w = WaveFile.open(path)
11
+ @sounds[name] = w
12
+
13
+ if w.num_channels > @num_channels
14
+ @num_channels = w.num_channels
15
+ end
16
+ if w.bits_per_sample > @bits_per_sample
17
+ @bits_per_sample = w.bits_per_sample
18
+ end
19
+ end
20
+ end
21
+
22
+ def get_sample_data(name)
23
+ w = @sounds[name]
24
+
25
+ if w == nil
26
+ raise StandardError, "Kit doesn't contain sound '#{name}'."
27
+ else
28
+ w.num_channels = @num_channels
29
+ w.bits_per_sample = @bits_per_sample
30
+
31
+ return w.sample_data
32
+ end
33
+ end
34
+
35
+ def size
36
+ return @sounds.length
37
+ end
38
+
39
+ attr_reader :bits_per_sample, :num_channels
40
+ end
data/lib/pattern.rb ADDED
@@ -0,0 +1,122 @@
1
+ class Pattern
2
+ def initialize(name)
3
+ @name = name
4
+ @tracks = {}
5
+ end
6
+
7
+ def track(name, wave_data, pattern)
8
+ new_track = Track.new(name, wave_data, pattern)
9
+ @tracks[new_track.name] = new_track
10
+
11
+ # If the new track is longer than any of the previously added tracks,
12
+ # pad the other tracks with trailing . to make them all the same length.
13
+ # Necessary to prevent incorrect overflow calculations for tracks.
14
+ longest_track_length = 0
15
+ @tracks.values.each {|track|
16
+ if(track.pattern.length > longest_track_length)
17
+ longest_track_length = track.pattern.length
18
+ end
19
+ }
20
+ @tracks.values.each {|track|
21
+ if(track.pattern.length < longest_track_length)
22
+ track.pattern += "." * (longest_track_length - track.pattern.length)
23
+ end
24
+ }
25
+
26
+ return new_track
27
+ end
28
+
29
+ def sample_length(tick_sample_length)
30
+ @tracks.keys.collect {|track_name| @tracks[track_name].sample_length(tick_sample_length) }.max || 0
31
+ end
32
+
33
+ def sample_length_with_overflow(tick_sample_length)
34
+ @tracks.keys.collect {|track_name| @tracks[track_name].sample_length_with_overflow(tick_sample_length) }.max || 0
35
+ end
36
+
37
+ def sample_data(tick_sample_length, num_channels, num_tracks_in_song, incoming_overflow, split = false)
38
+ if(split)
39
+ return split_sample_data(tick_sample_length, num_channels, incoming_overflow)
40
+ else
41
+ return combined_sample_data(tick_sample_length, num_channels, num_tracks_in_song, incoming_overflow)
42
+ end
43
+ end
44
+
45
+ attr_accessor :tracks, :name
46
+
47
+ private
48
+
49
+ def combined_sample_data(tick_sample_length, num_channels, num_tracks_in_song, incoming_overflow)
50
+ fill_value = (num_channels == 1) ? 0 : [].fill(0, 0, num_channels)
51
+ track_names = @tracks.keys
52
+ primary_sample_data = []
53
+ overflow_sample_data = {}
54
+ actual_sample_length = sample_length(tick_sample_length)
55
+
56
+ if(track_names.length > 0)
57
+ primary_sample_data = [].fill(fill_value, 0, actual_sample_length)
58
+
59
+ track_names.each {|track_name|
60
+ temp = @tracks[track_name].sample_data(tick_sample_length, incoming_overflow[track_name])
61
+
62
+ track_samples = temp[:primary]
63
+ if(num_channels == 1)
64
+ (0...track_samples.length).each {|i| primary_sample_data[i] += track_samples[i] }
65
+ else
66
+ (0...track_samples.length).each {|i|
67
+ primary_sample_data[i] = [primary_sample_data[i][0] + track_samples[i][0],
68
+ primary_sample_data[i][1] + track_samples[i][1]]
69
+ }
70
+ end
71
+
72
+ overflow_sample_data[track_name] = temp[:overflow]
73
+ }
74
+ end
75
+
76
+ # Add samples for tracks with overflow from previous pattern, but not
77
+ # contained in current pattern.
78
+ incoming_overflow.keys.each {|track_name|
79
+ if(!track_names.member?(track_name) && incoming_overflow[track_name].length > 0)
80
+ if(num_channels == 1)
81
+ (0...incoming_overflow[track_name].length).each {|i| primary_sample_data[i] += incoming_overflow[track_name][i]}
82
+ else
83
+ (0...incoming_overflow[track_name].length).each {|i| primary_sample_data[i][0] += incoming_overflow[track_name][i][0]
84
+ primary_sample_data[i][1] += incoming_overflow[track_name][i][1]}
85
+ end
86
+ end
87
+ }
88
+
89
+ # Mix down the tracks into one
90
+ if(num_channels == 1)
91
+ primary_sample_data = primary_sample_data.map {|sample| (sample / num_tracks_in_song).round }
92
+ else
93
+ primary_sample_data = primary_sample_data.map {|sample| [(sample[0] / num_tracks_in_song).round, (sample[1] / num_tracks_in_song).round] }
94
+ end
95
+
96
+ return {:primary => primary_sample_data, :overflow => overflow_sample_data}
97
+ end
98
+
99
+ def split_sample_data(tick_sample_length, num_channels, incoming_overflow)
100
+ fill_value = (num_channels == 1) ? 0 : [].fill(0, 0, num_channels)
101
+ primary_sample_data = {}
102
+ overflow_sample_data = {}
103
+
104
+ @tracks.keys.each {|track_name|
105
+ temp = @tracks[track_name].sample_data(tick_sample_length, incoming_overflow[track_name])
106
+ primary_sample_data[track_name] = temp[:primary]
107
+ overflow_sample_data[track_name] = temp[:overflow]
108
+ }
109
+
110
+ incoming_overflow.keys.each {|track_name|
111
+ if(@tracks[track_name] == nil)
112
+ # TO DO: Add check for when incoming overflow is longer than
113
+ # track full length to prevent track from lengthening.
114
+ primary_sample_data[track_name] = [].fill(fill_value, 0, sample_length(tick_sample_length))
115
+ primary_sample_data[track_name][0...incoming_overflow[track_name].length] = incoming_overflow[track_name]
116
+ overflow_sample_data[track_name] = []
117
+ end
118
+ }
119
+
120
+ return {:primary => primary_sample_data, :overflow => overflow_sample_data}
121
+ end
122
+ end
data/lib/song.rb ADDED
@@ -0,0 +1,294 @@
1
+ class SongParseError < RuntimeError; end
2
+
3
+ class Song
4
+ SAMPLE_RATE = 44100
5
+ SECONDS_PER_MINUTE = 60.0
6
+ PATH_SEPARATOR = File.const_get("SEPARATOR")
7
+
8
+ def initialize(input_path, definition = nil)
9
+ self.tempo = 120
10
+ @input_path = input_path
11
+ @kit = Kit.new()
12
+ @patterns = {}
13
+ @structure = []
14
+
15
+ if(definition != nil)
16
+ parse(definition)
17
+ end
18
+ end
19
+
20
+ def pattern(name)
21
+ @patterns[name] = Pattern.new(name)
22
+ return @patterns[name]
23
+ end
24
+
25
+ def sample_length()
26
+ @structure.inject(0) {|sum, pattern_name|
27
+ sum + @patterns[pattern_name].sample_length(@tick_sample_length)
28
+ }
29
+ end
30
+
31
+ def sample_length_with_overflow()
32
+ if(@structure.length == 0)
33
+ return 0
34
+ end
35
+
36
+ full_sample_length = self.sample_length
37
+ last_pattern_sample_length = @patterns[@structure.last].sample_length(@tick_sample_length)
38
+ last_pattern_overflow_length = @patterns[@structure.last].sample_length_with_overflow(@tick_sample_length)
39
+ overflow = last_pattern_overflow_length - last_pattern_sample_length
40
+
41
+ return sample_length + overflow
42
+ end
43
+
44
+ def total_tracks()
45
+ @patterns.keys.collect {|pattern_name| @patterns[pattern_name].tracks.length }.max || 0
46
+ end
47
+
48
+ def sample_data(pattern_name, split)
49
+ num_tracks_in_song = self.total_tracks()
50
+ fill_value = (@kit.num_channels == 1) ? 0 : [].fill(0, 0, @kit.num_channels)
51
+
52
+ if(pattern_name == "")
53
+ if(split)
54
+ return sample_data_split_all_patterns(fill_value, num_tracks_in_song)
55
+ else
56
+ return sample_data_combined_all_patterns(fill_value, num_tracks_in_song)
57
+ end
58
+ else
59
+ pattern = @patterns[pattern_name.downcase.to_sym]
60
+
61
+ if(pattern == nil)
62
+ raise StandardError, "Pattern '#{pattern_name}' not found in song."
63
+ else
64
+ primary_sample_length = pattern.sample_length(@tick_sample_length)
65
+
66
+ if(split)
67
+ return sample_data_split_single_pattern(fill_value, num_tracks_in_song, pattern, primary_sample_length)
68
+ else
69
+ return sample_data_combined_single_pattern(fill_value, num_tracks_in_song, pattern, primary_sample_length)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def num_channels()
76
+ return @kit.num_channels
77
+ end
78
+
79
+ def bits_per_sample()
80
+ return @kit.bits_per_sample
81
+ end
82
+
83
+ def tempo()
84
+ return @tempo
85
+ end
86
+
87
+ def tempo=(new_tempo)
88
+ if(new_tempo.class != Fixnum || new_tempo <= 0)
89
+ raise SongParseError, "Invalid tempo: '#{new_tempo}'. Tempo must be a number greater than 0."
90
+ end
91
+
92
+ @tempo = new_tempo
93
+ @tick_sample_length = (SAMPLE_RATE * SECONDS_PER_MINUTE) / new_tempo / 4.0
94
+ end
95
+
96
+ attr_reader :input_path, :tick_sample_length
97
+ attr_accessor :structure
98
+
99
+ private
100
+
101
+ def merge_overflow(overflow, num_tracks_in_song)
102
+ merged_sample_data = []
103
+
104
+ if(overflow != {})
105
+ longest_overflow = overflow[overflow.keys.first]
106
+ overflow.keys.each {|track_name|
107
+ if(overflow[track_name].length > longest_overflow.length)
108
+ longest_overflow = overflow[track_name]
109
+ end
110
+ }
111
+
112
+ final_overflow_pattern = Pattern.new(:overflow)
113
+ final_overflow_pattern.track "", [], "."
114
+ final_overflow_sample_data = final_overflow_pattern.sample_data(longest_overflow.length, @kit.num_channels, num_tracks_in_song, overflow, false)
115
+ merged_sample_data = final_overflow_sample_data[:primary]
116
+ end
117
+
118
+ return merged_sample_data
119
+ end
120
+
121
+ # Converts all hash keys to be lowercase
122
+ def downcase_hash_keys(hash)
123
+ return hash.inject({}) {|new_hash, pair|
124
+ new_hash[pair.first.downcase] = pair.last
125
+ new_hash
126
+ }
127
+ end
128
+
129
+ def parse(definition)
130
+ if(definition.class == String)
131
+ song_definition = YAML.load(definition)
132
+ elsif(definition.class == Hash)
133
+ song_definition = definition
134
+ else
135
+ raise StandardError, "Invalid song input"
136
+ end
137
+
138
+ @kit = build_kit(song_definition)
139
+
140
+ song_definition = downcase_hash_keys(song_definition)
141
+
142
+ # Process each pattern
143
+ song_definition.keys.each{|key|
144
+ if(key != "song")
145
+ new_pattern = self.pattern key.to_sym
146
+
147
+ track_list = song_definition[key]
148
+ track_list.each{|track_definition|
149
+ track_name = track_definition.keys.first
150
+ new_pattern.track track_name, @kit.get_sample_data(track_name), track_definition[track_name]
151
+ }
152
+ end
153
+ }
154
+
155
+ # Process song header
156
+ parse_song_header(downcase_hash_keys(song_definition["song"]))
157
+ end
158
+
159
+ def parse_song_header(header_data)
160
+ self.tempo = header_data["tempo"]
161
+
162
+ pattern_list = header_data["structure"]
163
+ structure = []
164
+ pattern_list.each{|pattern_item|
165
+ if(pattern_item.class == String)
166
+ pattern_item = {pattern_item => "x1"}
167
+ end
168
+
169
+ pattern_name = pattern_item.keys.first
170
+ pattern_name_sym = pattern_name.downcase.to_sym
171
+
172
+ if(!@patterns.has_key?(pattern_name_sym))
173
+ raise SongParseError, "Song structure includes non-existant pattern: #{pattern_name}."
174
+ end
175
+
176
+ multiples_str = pattern_item[pattern_name]
177
+ multiples_str.slice!(0)
178
+ multiples = multiples_str.to_i
179
+
180
+ if(multiples_str.match(/[^0-9]/) != nil)
181
+ raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Number of repeats should be a whole number."
182
+ elsif(multiples < 0)
183
+ raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Must be 0 or greater."
184
+ end
185
+
186
+ multiples.times { structure << pattern_name_sym }
187
+ }
188
+
189
+ @structure = structure
190
+ end
191
+
192
+ def build_kit(song_definition)
193
+ kit = Kit.new()
194
+
195
+ song_definition.keys.each{|key|
196
+ if(key.downcase != "song")
197
+ track_list = song_definition[key]
198
+ track_list.each{|track_definition|
199
+ track_name = track_definition.keys.first
200
+ track_path = track_name
201
+ if(!track_path.start_with?(PATH_SEPARATOR))
202
+ track_path = @input_path + PATH_SEPARATOR + track_path
203
+ end
204
+
205
+ if(!File.exists? track_path)
206
+ raise SongParseError, "File '#{track_name}' not found for pattern '#{key}'"
207
+ end
208
+
209
+ kit.add(track_name, track_path)
210
+ }
211
+ end
212
+ }
213
+
214
+ return kit
215
+ end
216
+
217
+ def sample_data_split_all_patterns(fill_value, num_tracks_in_song)
218
+ output_data = {}
219
+
220
+ offset = 0
221
+ overflow = {}
222
+ @structure.each {|pattern_name|
223
+ pattern_sample_length = @patterns[pattern_name].sample_length(@tick_sample_length)
224
+ pattern_sample_data = @patterns[pattern_name].sample_data(@tick_sample_length, @kit.num_channels, num_tracks_in_song, overflow, true)
225
+
226
+ pattern_sample_data[:primary].keys.each {|track_name|
227
+ if(output_data[track_name] == nil)
228
+ output_data[track_name] = [].fill(fill_value, 0, self.sample_length_with_overflow())
229
+ end
230
+
231
+ output_data[track_name][offset...(offset + pattern_sample_length)] = pattern_sample_data[:primary][track_name]
232
+ }
233
+
234
+ overflow.keys.each {|track_name|
235
+ if(pattern_sample_data[:primary][track_name] == nil)
236
+ output_data[track_name][offset...overflow[track_name].length] = overflow[track_name]
237
+ end
238
+ }
239
+
240
+ overflow = pattern_sample_data[:overflow]
241
+ offset += pattern_sample_length
242
+ }
243
+
244
+ overflow.keys.each {|track_name|
245
+ output_data[track_name][offset...overflow[track_name].length] = overflow[track_name]
246
+ }
247
+
248
+ return output_data
249
+ end
250
+
251
+ def sample_data_split_single_pattern(fill_value, num_tracks_in_song, pattern, primary_sample_length)
252
+ output_data = {}
253
+
254
+ pattern_sample_length = pattern.sample_length(@tick_sample_length)
255
+ pattern_sample_data = pattern.sample_data(@tick_sample_length, @kit.num_channels, num_tracks_in_song, {}, true)
256
+
257
+ pattern_sample_data[:primary].keys.each {|track_name|
258
+ overflow_sample_length = pattern_sample_data[:overflow][track_name].length
259
+ full_sample_length = pattern_sample_length + overflow_sample_length
260
+ output_data[track_name] = [].fill(fill_value, 0, full_sample_length)
261
+ output_data[track_name][0...pattern_sample_length] = pattern_sample_data[:primary][track_name]
262
+ output_data[track_name][pattern_sample_length...full_sample_length] = pattern_sample_data[:overflow][track_name]
263
+ }
264
+
265
+ return output_data
266
+ end
267
+
268
+ def sample_data_combined_all_patterns(fill_value, num_tracks_in_song)
269
+ output_data = [].fill(fill_value, 0, self.sample_length_with_overflow)
270
+
271
+ offset = 0
272
+ overflow = {}
273
+ @structure.each {|pattern_name|
274
+ pattern_sample_length = @patterns[pattern_name].sample_length(@tick_sample_length)
275
+ pattern_sample_data = @patterns[pattern_name].sample_data(@tick_sample_length, @kit.num_channels, num_tracks_in_song, overflow)
276
+ output_data[offset...offset + pattern_sample_length] = pattern_sample_data[:primary]
277
+ overflow = pattern_sample_data[:overflow]
278
+ offset += pattern_sample_length
279
+ }
280
+
281
+ # Handle overflow from final pattern
282
+ output_data[offset...output_data.length] = merge_overflow(overflow, num_tracks_in_song)
283
+ return output_data
284
+ end
285
+
286
+ def sample_data_combined_single_pattern(fill_value, num_tracks_in_song, pattern, primary_sample_length)
287
+ output_data = [].fill(fill_value, 0, pattern.sample_length_with_overflow(@tick_sample_length))
288
+ sample_data = pattern.sample_data(tick_sample_length, @kit.num_channels, num_tracks_in_song, {}, false)
289
+ output_data[0...primary_sample_length] = sample_data[:primary]
290
+ output_data[primary_sample_length...output_data.length] = merge_overflow(sample_data[:overflow], num_tracks_in_song)
291
+
292
+ return output_data
293
+ end
294
+ end