beats 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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