synth_blocks 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +40 -0
- data/lib/synth_blocks.rb +24 -0
- data/lib/synth_blocks/core/moog_filter.rb +35 -0
- data/lib/synth_blocks/core/one_pole_lowpass.rb +14 -0
- data/lib/synth_blocks/core/oscillator.rb +36 -0
- data/lib/synth_blocks/core/sound.rb +187 -0
- data/lib/synth_blocks/core/state_variable_filter.rb +41 -0
- data/lib/synth_blocks/core/wave_writer.rb +28 -0
- data/lib/synth_blocks/drum/hihat.rb +54 -0
- data/lib/synth_blocks/drum/kick_drum.rb +54 -0
- data/lib/synth_blocks/drum/snare_drum.rb +87 -0
- data/lib/synth_blocks/drum/tuned_drum.rb +25 -0
- data/lib/synth_blocks/fx/chorus.rb +85 -0
- data/lib/synth_blocks/fx/compressor.rb +121 -0
- data/lib/synth_blocks/fx/delay.rb +42 -0
- data/lib/synth_blocks/fx/eq.rb +92 -0
- data/lib/synth_blocks/fx/g_verb.rb +275 -0
- data/lib/synth_blocks/fx/limiter.rb +15 -0
- data/lib/synth_blocks/fx/waveshaper.rb +24 -0
- data/lib/synth_blocks/mixer/mixer_channel.rb +106 -0
- data/lib/synth_blocks/mixer/send_channel.rb +25 -0
- data/lib/synth_blocks/mod/adsr.rb +83 -0
- data/lib/synth_blocks/mod/envelope.rb +35 -0
- data/lib/synth_blocks/sequencer/sequencer_dsl.rb +219 -0
- data/lib/synth_blocks/synth/monosynth.rb +77 -0
- data/lib/synth_blocks/synth/polysynth.rb +89 -0
- data/lib/synth_blocks/utils.rb +17 -0
- metadata +113 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
module SynthBlocks
|
2
|
+
module Mod
|
3
|
+
##
|
4
|
+
# Implementation of a linear ADSR envelope generator with a tracking
|
5
|
+
# value so that envelope restarts don't click
|
6
|
+
class Adsr
|
7
|
+
##
|
8
|
+
# attack time in seconds
|
9
|
+
attr_accessor :attack
|
10
|
+
|
11
|
+
##
|
12
|
+
# decay time in seconds
|
13
|
+
attr_accessor :decay
|
14
|
+
##
|
15
|
+
# sustain level (0.0-1.0)
|
16
|
+
attr_accessor :sustain
|
17
|
+
|
18
|
+
##
|
19
|
+
# release time in seconds
|
20
|
+
attr_accessor :release
|
21
|
+
|
22
|
+
##
|
23
|
+
# Creates new ADSR envelope
|
24
|
+
#
|
25
|
+
# attack, decay and release are times in seconds (as float)
|
26
|
+
#
|
27
|
+
# sustain should be between 0 and 1
|
28
|
+
def initialize(attack, decay, sustain, release)
|
29
|
+
@value = 0
|
30
|
+
@start_value = 0
|
31
|
+
@attack = attack
|
32
|
+
@decay = decay
|
33
|
+
@sustain = sustain
|
34
|
+
@release = release
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# run the envelope.
|
39
|
+
#
|
40
|
+
# if released is given (should be <= t), the envelope will enter the release stage
|
41
|
+
# returns the current value between 0 and 1
|
42
|
+
def run(t, released)
|
43
|
+
attack_decay = attack + decay
|
44
|
+
if !released
|
45
|
+
if t < 0.0001 # initialize start value (slightly hacky, but works)
|
46
|
+
@start_value = @value
|
47
|
+
return @start_value
|
48
|
+
end
|
49
|
+
if t <= attack # attack
|
50
|
+
return @value = linear(@start_value, 1, attack, t)
|
51
|
+
end
|
52
|
+
if t > attack && t < attack_decay # decay
|
53
|
+
return @value = linear(1.0, sustain, decay, t - attack)
|
54
|
+
end
|
55
|
+
if t >= attack + decay # sustain
|
56
|
+
return @value = sustain
|
57
|
+
end
|
58
|
+
else # release
|
59
|
+
if t <= attack # when released in attack phase
|
60
|
+
attack_level = linear(@start_value, 1, attack, released)
|
61
|
+
return linear(attack_level, 0, release, t - released)
|
62
|
+
end
|
63
|
+
if t > attack && t < attack_decay # when released in decay phase
|
64
|
+
decay_level = linear(1.0, sustain, decay, released - attack)
|
65
|
+
return @value = linear(decay_level, 0, release, t - released)
|
66
|
+
end
|
67
|
+
if t >= attack_decay && t < released + release # normal release
|
68
|
+
return @value = linear(sustain, 0, release, t - released)
|
69
|
+
end
|
70
|
+
if t >= released + release # after release
|
71
|
+
return @value = 0.0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def linear(start, target, length, time)
|
79
|
+
(target - start) / length * time + start
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SynthBlocks
|
2
|
+
module Mod
|
3
|
+
##
|
4
|
+
# Simple Attack / Release envelope
|
5
|
+
class Envelope
|
6
|
+
##
|
7
|
+
# attack time in seconds
|
8
|
+
attr_accessor :attack
|
9
|
+
|
10
|
+
##
|
11
|
+
# release time in seconds
|
12
|
+
attr_accessor :release
|
13
|
+
##
|
14
|
+
# create new attack/release envelope
|
15
|
+
def initialize(attack,release)
|
16
|
+
@attack = attack
|
17
|
+
@release = release
|
18
|
+
end
|
19
|
+
##
|
20
|
+
# run the attack/release envelope
|
21
|
+
# You can override attack and decay
|
22
|
+
def run(t, a=@attack, r=@release)
|
23
|
+
@a = a
|
24
|
+
@r = r
|
25
|
+
if t > @a + @r
|
26
|
+
return 0
|
27
|
+
elsif t > @a #release
|
28
|
+
return 1 - ((1 / @r) * (t - @a))
|
29
|
+
else # attack
|
30
|
+
return 1 / @a * t
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
|
2
|
+
module SynthBlocks
|
3
|
+
module Sequencer
|
4
|
+
##
|
5
|
+
# A module that implements a sequencer DSL
|
6
|
+
# === Usage
|
7
|
+
#
|
8
|
+
# include SequencerDSL
|
9
|
+
# def_pattern(:pattern_name, 16) do
|
10
|
+
# drum_pattern kickdrum, '*---*---*---*---'
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# my_song = song(bpm: 125) do
|
14
|
+
# pattern(:pattern_name, at: 0, repeat: 4)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# output = my_song.render(44100) do |sample|
|
18
|
+
# kickdrum.run(sample)
|
19
|
+
# end
|
20
|
+
# print output.pack('e*')
|
21
|
+
#
|
22
|
+
module SequencerDSL
|
23
|
+
|
24
|
+
P = nil # :nodoc:
|
25
|
+
|
26
|
+
##
|
27
|
+
# The Pattern class is instantiated by the def_pattern helper
|
28
|
+
class Pattern
|
29
|
+
NOTES=%w(C C# D D# E F F# G G# A A# B) # :nodoc:
|
30
|
+
|
31
|
+
attr_reader :sounds, :steps # :nodoc:
|
32
|
+
def initialize(steps) # :nodoc:
|
33
|
+
@steps = steps
|
34
|
+
@sounds = []
|
35
|
+
end
|
36
|
+
|
37
|
+
def run(block) # :nodoc:
|
38
|
+
instance_eval(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Define a drum pattern
|
43
|
+
# - sound is the sound generator object
|
44
|
+
# - pattern is a pattern in the form of a string
|
45
|
+
# === Defining patterns
|
46
|
+
#
|
47
|
+
# drum_pattern bass_drum, '*---*---*---!---'
|
48
|
+
#
|
49
|
+
# - <tt>*</tt> represents a normal drum hit (velocity: 0.5)
|
50
|
+
# - <tt>!</tt> represents an accented drum hit (velocity 1.0)
|
51
|
+
# - <tt>-</tt> represents a pause (no hit)
|
52
|
+
|
53
|
+
def drum_pattern(sound, pattern)
|
54
|
+
events = []
|
55
|
+
@steps.times do |i|
|
56
|
+
if pattern.chars[i] == '*'
|
57
|
+
events << [i, [:start, 36, 0.5]]
|
58
|
+
elsif pattern.chars[i] == '!'
|
59
|
+
events << [i, [:start, 36, 1.0]]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
@sounds.push([sound, events])
|
63
|
+
end
|
64
|
+
|
65
|
+
def str2note(str) # :nodoc:
|
66
|
+
match = str.upcase.strip.match(/([ABCDEFGH]#?)(-?\d)/)
|
67
|
+
return nil unless match
|
68
|
+
octave = match[2].to_i + 2
|
69
|
+
note = NOTES.index(match[1])
|
70
|
+
if note >= 0 && octave > 0 && octave < 10
|
71
|
+
return 12 * octave + note
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Define a note pattern
|
77
|
+
# [sound] sound generator base class
|
78
|
+
# [pattern] a note pattern
|
79
|
+
#
|
80
|
+
# === Defining a note pattern
|
81
|
+
#
|
82
|
+
# note_pattern monosynth, [
|
83
|
+
# ['C4, D#4, G4', 2], P, P, P,
|
84
|
+
# P, P, P, P,
|
85
|
+
# P, P, P, P,
|
86
|
+
# P, P, P, P
|
87
|
+
# ]
|
88
|
+
#
|
89
|
+
# - <tt>P</tt> is a pause
|
90
|
+
# - a note step in the pattern is an array containing the note and the
|
91
|
+
# length of the note in steps
|
92
|
+
# - a note is a note name as a string, which consists of the note and the
|
93
|
+
# octave. To play chords, concatenate notes with commas
|
94
|
+
def note_pattern(sound, pattern)
|
95
|
+
events = []
|
96
|
+
@steps.times do |i|
|
97
|
+
if pattern[i]
|
98
|
+
notes, len = pattern[i]
|
99
|
+
notes.split(',').each do |note|
|
100
|
+
note_num = str2note(note)
|
101
|
+
events << [i, [:start, note_num, 1.0]]
|
102
|
+
events << [i + len, [:stop, note_num]]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
@sounds.push([sound, events])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Define a note pattern
|
112
|
+
def def_pattern(name, steps, &block)
|
113
|
+
@patterns ||= {}
|
114
|
+
p = Pattern.new(steps)
|
115
|
+
p.run(block)
|
116
|
+
@patterns[name] = p
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# A
|
121
|
+
class Song
|
122
|
+
attr_reader :events, :per_bar, :per_beat # :nodoc:
|
123
|
+
def initialize(bpm, patterns) # :nodoc:
|
124
|
+
@tempo = bpm
|
125
|
+
@events = []
|
126
|
+
@per_beat = 60.0 / @tempo.to_f
|
127
|
+
@per_bar = @per_beat * 4.0
|
128
|
+
@per_step = @per_beat / 4.0
|
129
|
+
@patterns = patterns
|
130
|
+
@latest_time = 0
|
131
|
+
end
|
132
|
+
|
133
|
+
def run(block) # :nodoc:
|
134
|
+
instance_eval(&block)
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# inserts a pattern into the song
|
139
|
+
# [name] pattern needs to be defined by <tt>def_pattern</tt>
|
140
|
+
# [at] Position in bars to insert the pattern to
|
141
|
+
# [repeat] number of times the pattern should repeat
|
142
|
+
# [length] if you want to only use part of the pattern
|
143
|
+
#
|
144
|
+
|
145
|
+
def pattern(name, at: 0, repeat: 1, length: nil)
|
146
|
+
p = @patterns[name]
|
147
|
+
pattern_length = length || p.steps
|
148
|
+
start = at.to_f * @per_bar
|
149
|
+
|
150
|
+
p.sounds.each do |sound, events|
|
151
|
+
repeat.times do |rep|
|
152
|
+
|
153
|
+
events.each do |event|
|
154
|
+
step, data = event
|
155
|
+
next if step > pattern_length
|
156
|
+
|
157
|
+
time = start + (rep.to_f * pattern_length.to_f * @per_step.to_f) + step.to_f * @per_step
|
158
|
+
@latest_time = time if time > @latest_time
|
159
|
+
type, *rest = data
|
160
|
+
@events << [sound, [type, time, *rest]]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Returns the length of the song in seconds plus 2 seconds to allow for
|
168
|
+
# reverb tails etc.
|
169
|
+
def length
|
170
|
+
(@latest_time + 2.0).ceil
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Sends all scheduled events to the instruments
|
175
|
+
def play
|
176
|
+
@events.each do |event|
|
177
|
+
instrument, data = event
|
178
|
+
instrument.send(*data)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Define a song in the given tempo (in BPM)
|
185
|
+
# using the Song#pattern method
|
186
|
+
|
187
|
+
def song(bpm: 120, &block)
|
188
|
+
song = Song.new(bpm, @patterns)
|
189
|
+
song.run(block)
|
190
|
+
song.play
|
191
|
+
# File.open("DEBUG.txt", 'wb') do |f|
|
192
|
+
# f.print song.events.inspect
|
193
|
+
# end
|
194
|
+
song
|
195
|
+
end
|
196
|
+
##
|
197
|
+
# render the song
|
198
|
+
# the actual rendering needs to be done
|
199
|
+
# manually in the block passed
|
200
|
+
# start & length in bars
|
201
|
+
# block gets an offset in samples it should render
|
202
|
+
def render(sfreq, start=0, len=nil)
|
203
|
+
start_time = start * @per_bar
|
204
|
+
end_time = len ? start_time + len * @per_bar : length
|
205
|
+
start_sample = (sfreq * start_time).floor
|
206
|
+
end_sample = (sfreq * end_time).ceil
|
207
|
+
sample = start_sample
|
208
|
+
sample_len = end_sample - start_sample
|
209
|
+
output = Array.new(sample_len)
|
210
|
+
loop do
|
211
|
+
output[sample - start_sample] = yield sample
|
212
|
+
break if sample > end_sample
|
213
|
+
sample += 1
|
214
|
+
end
|
215
|
+
output
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'synth_blocks/core/sound'
|
2
|
+
require 'synth_blocks/core/state_variable_filter'
|
3
|
+
require 'synth_blocks/core/oscillator'
|
4
|
+
require 'synth_blocks/mod/adsr'
|
5
|
+
|
6
|
+
module SynthBlocks
|
7
|
+
module Synth
|
8
|
+
##
|
9
|
+
# A monosynth sound generator
|
10
|
+
class Monosynth < SynthBlocks::Core::Sound
|
11
|
+
##
|
12
|
+
# === Parameters
|
13
|
+
# - amp_attack, _decay, _sustain, _release - Amp Envelope params
|
14
|
+
# - flt_attack, _decay, _sustain, _release - Filter Envelope params
|
15
|
+
# - flt_envmod - filter envelope modulation amount in Hz
|
16
|
+
# - flt_frequency, flt_Q - filter params
|
17
|
+
# - osc_waveform - waveform to generate (see Oscillator class)
|
18
|
+
def initialize(sfreq, preset={})
|
19
|
+
super(sfreq, mode: :monophonic)
|
20
|
+
@preset = {
|
21
|
+
amp_attack: 0.001,
|
22
|
+
amp_decay: 0.2,
|
23
|
+
amp_sustain: 0.8,
|
24
|
+
amp_release: 0.2,
|
25
|
+
flt_attack: 0.001,
|
26
|
+
flt_decay: 0.05,
|
27
|
+
flt_sustain: 0.0,
|
28
|
+
flt_release: 0.2,
|
29
|
+
flt_envmod: 1000,
|
30
|
+
flt_frequency: 2000,
|
31
|
+
flt_Q: 2,
|
32
|
+
osc_waveform: :square,
|
33
|
+
lfo_waveform: :sine,
|
34
|
+
lfo_frequency: 2
|
35
|
+
}.merge(preset)
|
36
|
+
@oscillator = SynthBlocks::Core::Oscillator.new(@sampling_frequency)
|
37
|
+
@filter = SynthBlocks::Core::StateVariableFilter.new(@sampling_frequency)
|
38
|
+
@filter2 = SynthBlocks::Core::StateVariableFilter.new(@sampling_frequency)
|
39
|
+
@lfo = SynthBlocks::Core::Oscillator.new(@sampling_frequency)
|
40
|
+
@amp_env = SynthBlocks::Mod::Adsr.new(
|
41
|
+
@preset[:amp_attack],
|
42
|
+
@preset[:amp_decay],
|
43
|
+
@preset[:amp_sustain],
|
44
|
+
@preset[:amp_release]
|
45
|
+
)
|
46
|
+
@flt_env = SynthBlocks::Mod::Adsr.new(
|
47
|
+
@preset[:flt_attack],
|
48
|
+
@preset[:flt_decay],
|
49
|
+
@preset[:flt_sustain],
|
50
|
+
@preset[:flt_release]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
##
|
56
|
+
# run generator
|
57
|
+
def run(offset)
|
58
|
+
# time in seconds
|
59
|
+
t = time(offset)
|
60
|
+
events = active_events(t)
|
61
|
+
if events.empty?
|
62
|
+
0.0
|
63
|
+
else
|
64
|
+
note = events.keys.last
|
65
|
+
event = events[note]
|
66
|
+
# lfo_out = (@lfo.run(@preset[:lfo_frequency], waveform: @preset[:lfo_waveform]) + 1) / 8 + 0.5
|
67
|
+
osc_out = @oscillator.run(frequency(note), waveform: @preset[:osc_waveform])
|
68
|
+
local_started = t - event[:started]
|
69
|
+
local_stopped = event[:stopped] && event[:stopped] - event[:started]
|
70
|
+
osc_out = @filter.run(osc_out, @preset[:flt_frequency] + @flt_env.run(local_started, local_stopped) * @preset[:flt_envmod], @preset[:flt_Q])
|
71
|
+
# osc_out = @filter2.run(osc_out, @preset[:flt_frequency] + @flt_env.run(local_started, local_stopped) * @preset[:flt_envmod], @preset[:flt_Q])
|
72
|
+
0.3 * osc_out * @amp_env.run(local_started, local_stopped)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'synth_blocks/core/sound'
|
2
|
+
require 'synth_blocks/core/state_variable_filter'
|
3
|
+
require 'synth_blocks/core/oscillator'
|
4
|
+
require 'synth_blocks/mod/adsr'
|
5
|
+
|
6
|
+
module SynthBlocks
|
7
|
+
module Synth
|
8
|
+
class PolyVoice # :nodoc:
|
9
|
+
def initialize(sfreq, parent, preset)
|
10
|
+
@sampling_frequency = sfreq
|
11
|
+
@parent = parent
|
12
|
+
@preset = preset
|
13
|
+
@oscillator = SynthBlocks::Core::Oscillator.new(sfreq)
|
14
|
+
@filter = SynthBlocks::Core::StateVariableFilter.new(sfreq)
|
15
|
+
@amp_env = SynthBlocks::Mod::Adsr.new(@preset[:amp_env_attack], @preset[:amp_env_decay], @preset[:amp_env_sustain], @preset[:amp_env_release])
|
16
|
+
@flt_env = SynthBlocks::Mod::Adsr.new(@preset[:flt_env_attack], @preset[:flt_env_decay], @preset[:flt_env_sustain], @preset[:flt_env_release])
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(started, stopped, frequency, velocity)
|
20
|
+
osc_out = @oscillator.run(frequency, waveform: @preset[:osc_waveform])
|
21
|
+
osc_out = @filter.run(osc_out, @parent.get(:flt_frequency, started) + @flt_env.run(started, stopped) * @parent.get(:flt_envmod, started), @preset[:flt_Q])
|
22
|
+
osc_out = osc_out * @amp_env.run(started, stopped) * velocity
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# A simple polyphonic synthesizer
|
29
|
+
#
|
30
|
+
# OSC > Filter > Amp
|
31
|
+
#
|
32
|
+
|
33
|
+
class Polysynth < SynthBlocks::Core::Sound
|
34
|
+
# === Parameters
|
35
|
+
# - amp_attack, _decay, _sustain, _release - Amp Envelope params
|
36
|
+
# - flt_attack, _decay, _sustain, _release - Filter Envelope params
|
37
|
+
# - flt_envmod - filter envelope modulation amount in Hz
|
38
|
+
# - flt_frequency, flt_Q - filter params
|
39
|
+
# - osc_waveform - waveform to generate (see Oscillator class)
|
40
|
+
def initialize(sfreq, preset = {})
|
41
|
+
@preset = {
|
42
|
+
osc_waveform: :sawtooth,
|
43
|
+
amp_env_attack: 0.2,
|
44
|
+
amp_env_decay: 0.2,
|
45
|
+
amp_env_sustain: 0.8,
|
46
|
+
amp_env_release: 0.5,
|
47
|
+
flt_env_attack: 0.5,
|
48
|
+
flt_env_decay: 0.7,
|
49
|
+
flt_env_sustain: 0.4,
|
50
|
+
flt_env_release: 0.5,
|
51
|
+
flt_frequency: 1000,
|
52
|
+
flt_envmod: 2000,
|
53
|
+
flt_Q: 3
|
54
|
+
}.merge(preset)
|
55
|
+
super(sfreq, mode: :polyphonic)
|
56
|
+
@active_voices = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def live_params # :nodoc:
|
60
|
+
[:flt_frequency, :flt_envmod]
|
61
|
+
end
|
62
|
+
|
63
|
+
def release(t) # :nodoc:
|
64
|
+
get(:flt_env_release, t)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# run sound generator
|
69
|
+
def run(offset)
|
70
|
+
t = time(offset)
|
71
|
+
events = active_events(t)
|
72
|
+
voice_results = []
|
73
|
+
events.each do |note, event|
|
74
|
+
local_started = t - event[:started]
|
75
|
+
next if local_started < 0
|
76
|
+
local_stopped = event[:stopped] && event[:stopped] - event[:started]
|
77
|
+
note_key = "#{note}:#{event[:started]}"
|
78
|
+
if @active_voices[note_key].nil?
|
79
|
+
@active_voices[note_key] = PolyVoice.new(@sampling_frequency, self, @preset)
|
80
|
+
end
|
81
|
+
if @active_voices[note_key]
|
82
|
+
voice_results << @active_voices[note_key].run(local_started, local_stopped, frequency(note), event[:velocity])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
0.3 * voice_results.inject(0) {|sum, result| sum + result}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|