-
Notifications
You must be signed in to change notification settings - Fork 1
/
tr808-pi.rb
180 lines (155 loc) · 4.58 KB
/
tr808-pi.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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
173
174
175
176
177
178
179
180
# global map for sample to:
# - path (location of sample dir)
# - version (which file to pick for sample when there are multiple)
# - hits (whether to play the instrument or not)
# then some custom params for sample arguments like BD_amp for amp
# which will initially set by `setup_samples()`
$samples_map = {}
$tr808_abbrevs = [
"BD", # has multiple versions
"SD", # has multiple versions
"LT", # has multiple versions
"MT", # has multiple versions
"HT", # has multiple versions
"LC", # has multiple versions
"MC", # has multiple versions
"HC", # has multiple versions
"RS",
"CL",
"CP",
"MA",
"CB",
"CY", # has multiple versions
"OH", # has multiple versions
"CH"
]
def setup_samples()
"""
Initializes the $samples_map hash to map instrument -> params.
Returns:
nil
"""
sample_location = File.dirname(__FILE__) + '/TR808_Samples'
$tr808_abbrevs.each do |s|
amp_param = "#{s}_amp".to_sym
path = "#{sample_location}/#{s}"
sym_key = s.to_sym
$samples_map[sym_key] = {
:path => path,
:version => 1, # which file to pick for samples
:hits => []
}
set amp_param, 1.5
end
end
def inst_to_binary(splitlines)
# first, we map each instrument to its hit pattern
inst_to_hits = splitlines.map{
|line|
line.split(" ")
}
# helper function to split notes of an instrument Integero an array of 1s and 0s
def make_hits(hit_strs)
splits = hit_strs.tr('x-', '10').split('')
splits = splits.reject { |item| item == "|" }
return splits.map(&:to_i)
end
# then, create a dictionary of inst => [0s, 1s]
return inst_to_hits.map{
|k, v| [k.to_sym, make_hits(v)]
}.to_h
end
def parse_beat(src)
"""
Return a dictionary representing a beat pattern for the TR-808.
Args:
src (Integer or Array): The string(s) representing the beat pattern.
Returns:
The dictionary.
"""
setup_samples()
def set_hits(hits)
hits.each do |k, v|
$samples_map[k][:hits].push(v)
end
end
if src.class == String
# remove indents, leading/trailing newlines, and split on newlines
splitlines = src.gsub(/^\s+/, '').strip.split("\n")
hits = inst_to_binary(splitlines)
set_hits(hits)
elsif src.class == Array
# for multiple patterns, create hits for each pattern
src.each do |pattern|
pattern = pattern.gsub(/^\s+/, '').strip
hits = inst_to_binary(pattern.split("\n"))
set_hits(hits)
end
end
end
# we can use a function that takes a mapping of instrument to filepath
# to sample the instrument with some special logic to choose a particular
# version for ones with multiple versions
def sample_tr808(inst)
"""
Return a dictionary representing a beat pattern for the TR-808.
Note: some instruments like bass drum or 'BD' have multiple versions.
For now, we have hand-picked one version to pick but in the future we
could leave that up to the user to pick.
Args:
inst (String): The string representing the abbreviated TR-808 instrument.
For example, 'BD' for bass drum
Returns:
nil
"""
sound = $samples_map[inst]
sym_key = "#{inst}_amp".to_sym
sample sound[:path], sound[:version], amp: get[sym_key]
end
def tr808(src, bpm: 90, pattern: [0])
"""
Play a live loop of the TR-808 given a beat pattern and bpm.
Args:
src (String): The string representing the TR-808 beat pattern.
bpm (Integer): The bpm, 90 by default.
pattern (Array): An array of which pattern to play if there are
multiple patterns, [0] by default for one single pattern
Returns:
nil
"""
parse_beat(src)
note_value = 0.5 # eighth note
live_loop :tr808 do
use_bpm bpm
# tick through the pattern array to switch between different patterns
cur_pattern = pattern.tick
# tr808 is 16 notes per measure
16.times do |i|
# for each instrument, play the sample if it's a hit
$samples_map.each do |inst, values|
hits = values[:hits]
if hits.length && cur_pattern < hits.length && hits[cur_pattern][i] == 1
sample_tr808(inst)
end
end
# 16th notes
sleep note_value / 2
end
end
end
def tweak(inst, **params)
"""
Tweak a particular instrument's parameter.
Currently only allows changing `amp`
Args:
inst (String): The string representing the instrument (e.g. 'bd' or 'BD')
params (Hash): Any number of keyword args representing {param: value}
"""
inst_sym = inst.upcase.to_sym
if $samples_map.has_key?(inst_sym)
params.each do |arg, value|
arg_sym_key = "#{inst_sym.to_s}_#{arg}".to_sym
set arg_sym_key, value
end
end
end