synth_blocks 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.
- 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
|