synthesizer 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/formant_vocoder.rb +1 -1
- data/examples/formant_vocoder_sweep.rb +3 -3
- data/examples/sync.rb +63 -0
- data/lib/synthesizer/amplifier.rb +4 -1
- data/lib/synthesizer/filter/band_pass_filter.rb +3 -3
- data/lib/synthesizer/filter/high_pass_filter.rb +3 -3
- data/lib/synthesizer/filter/high_shelf_filter.rb +4 -4
- data/lib/synthesizer/filter/low_pass_filter.rb +3 -3
- data/lib/synthesizer/filter/low_shelf_filter.rb +4 -4
- data/lib/synthesizer/filter/parallel.rb +2 -2
- data/lib/synthesizer/filter/peaking_filter.rb +4 -4
- data/lib/synthesizer/filter/serial.rb +2 -2
- data/lib/synthesizer/modulation/adsr.rb +33 -32
- data/lib/synthesizer/modulation/glide.rb +9 -8
- data/lib/synthesizer/modulation/lfo.rb +19 -19
- data/lib/synthesizer/modulation_value.rb +5 -5
- data/lib/synthesizer/mono_synth.rb +3 -1
- data/lib/synthesizer/oscillator.rb +4 -2
- data/lib/synthesizer/oscillator_source/base.rb +35 -5
- data/lib/synthesizer/oscillator_source/formant_vocoder.rb +25 -18
- data/lib/synthesizer/oscillator_source/pulse.rb +11 -3
- data/lib/synthesizer/processor.rb +17 -10
- data/lib/synthesizer/shape_pos.rb +22 -19
- data/lib/synthesizer/unison.rb +24 -16
- data/lib/synthesizer/version.rb +1 -1
- data/synthesizer.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb327b711e81b9980b065cb446c278e1b74207ef4f2351646c1dc217c52925de
|
4
|
+
data.tar.gz: 621c2d0970d3d5d5c2663bc56cce7ddc1e4c9e369b9fad56121a597d619a7637
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '028d65fe391817a4e4c52e9dee9bea5803698e348d64a92fa41c239da7219fb60afe0dfb8b7e63fc5a96839544bd264ee3813dfa4c1ba4dd043d09ba9b104c5b'
|
7
|
+
data.tar.gz: b39b4a1860af12bbe5c45c2d972ab4b41c0944ab849380cec8445a8a940a03ae92bad717f7fb50a9573de82a7512f2663e4ff642c021b157e9c34bc25766b8fd
|
data/examples/formant_vocoder.rb
CHANGED
@@ -72,7 +72,7 @@ bufs += 50.times.map {|_| synth.next}
|
|
72
72
|
track1 = AudioInput.buffer(bufs)
|
73
73
|
|
74
74
|
stereo_out = AudioOutput.device(soundinfo: soundinfo)
|
75
|
-
#stereo_out = AudioOutput.file("
|
75
|
+
#stereo_out = AudioOutput.file("formant_vocoder.wav", soundinfo: soundinfo)
|
76
76
|
|
77
77
|
track1
|
78
78
|
.send_to(stereo_out, gain: 1.0)
|
@@ -51,9 +51,9 @@ bufs += 100.times.map {|_| synth.next}
|
|
51
51
|
synth.note_off(Note.new(base+4))
|
52
52
|
bufs += 20.times.map {|_| synth.next}
|
53
53
|
|
54
|
-
synth.note_on(Note.new(base+
|
54
|
+
synth.note_on(Note.new(base+7))
|
55
55
|
bufs += 100.times.map {|_| synth.next}
|
56
|
-
synth.note_off(Note.new(base+
|
56
|
+
synth.note_off(Note.new(base+7))
|
57
57
|
bufs += 20.times.map {|_| synth.next}
|
58
58
|
|
59
59
|
bufs += 50.times.map {|_| synth.next}
|
@@ -62,7 +62,7 @@ bufs += 50.times.map {|_| synth.next}
|
|
62
62
|
track1 = AudioInput.buffer(bufs)
|
63
63
|
|
64
64
|
stereo_out = AudioOutput.device(soundinfo: soundinfo)
|
65
|
-
#stereo_out = AudioOutput.file("
|
65
|
+
#stereo_out = AudioOutput.file("formant_vocoder_sweep.wav", soundinfo: soundinfo)
|
66
66
|
|
67
67
|
track1
|
68
68
|
.send_to(stereo_out, gain: 1.0)
|
data/examples/sync.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'synthesizer'
|
2
|
+
require 'audio_stream'
|
3
|
+
|
4
|
+
include AudioStream
|
5
|
+
include Synthesizer
|
6
|
+
|
7
|
+
soundinfo = SoundInfo.new(
|
8
|
+
channels: 2,
|
9
|
+
samplerate: 44100,
|
10
|
+
window_size: 1024,
|
11
|
+
format: RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
12
|
+
)
|
13
|
+
|
14
|
+
synth = PolySynth.new(
|
15
|
+
oscillators: [
|
16
|
+
Oscillator.new(
|
17
|
+
source: OscillatorSource::Sawtooth.instance,
|
18
|
+
sync: ModulationValue.new(0.0)
|
19
|
+
.add(Modulation::Lfo.new(
|
20
|
+
shape: Shape::PositiveRampUp,
|
21
|
+
delay: 0.0,
|
22
|
+
attack: 0.0,
|
23
|
+
attack_curve: Modulation::Curve::Straight,
|
24
|
+
phase: 0.0,
|
25
|
+
rate: 0.2
|
26
|
+
), depth: 48),
|
27
|
+
),
|
28
|
+
],
|
29
|
+
amplifier: Amplifier.new(
|
30
|
+
volume: ModulationValue.new(1.0)
|
31
|
+
.add(Modulation::Adsr.new(
|
32
|
+
attack: 0.05,
|
33
|
+
hold: 0.1,
|
34
|
+
decay: 0.4,
|
35
|
+
sustain: 0.8,
|
36
|
+
release: 0.2
|
37
|
+
), depth: 1.0),
|
38
|
+
),
|
39
|
+
soundinfo: soundinfo,
|
40
|
+
)
|
41
|
+
bufs = []
|
42
|
+
|
43
|
+
bufs += 50.times.map {|_| synth.next}
|
44
|
+
synth.note_on(Note.new(60))
|
45
|
+
bufs += 200.times.map {|_| synth.next}
|
46
|
+
synth.note_off(Note.new(60))
|
47
|
+
bufs += 50.times.map {|_| synth.next}
|
48
|
+
|
49
|
+
|
50
|
+
track1 = AudioInput.buffer(bufs)
|
51
|
+
|
52
|
+
stereo_out = AudioOutput.device(soundinfo: soundinfo)
|
53
|
+
|
54
|
+
track1
|
55
|
+
.send_to(stereo_out, gain: 0.25)
|
56
|
+
|
57
|
+
|
58
|
+
conductor = Conductor.new(
|
59
|
+
input: [track1],
|
60
|
+
output: [stereo_out]
|
61
|
+
)
|
62
|
+
conductor.connect
|
63
|
+
conductor.join
|
@@ -7,6 +7,7 @@ module Synthesizer
|
|
7
7
|
attr_reader :tune_cents
|
8
8
|
attr_reader :uni_num
|
9
9
|
attr_reader :uni_detune
|
10
|
+
attr_reader :uni_stereo
|
10
11
|
|
11
12
|
# @param volume [Float] master volume. mute=0.0 max=1.0
|
12
13
|
# @param pan [Float] master pan. left=-1.0 center=0.0 right=1.0 (-1.0~1.0)
|
@@ -14,7 +15,8 @@ module Synthesizer
|
|
14
15
|
# @param tune_cents [Integer] master pitch cent
|
15
16
|
# @param uni_num [Float] master voicing number (1.0~16.0)
|
16
17
|
# @param uni_detune [Float] master voicing detune percent. 0.01=1cent 1.0=semitone (0.0~1.0)
|
17
|
-
|
18
|
+
# @param uni_stereo [Float] oscillator voicing spread pan. -1.0=full inv 0.0=mono 1.0=full (-1.0~1.0)
|
19
|
+
def initialize(shape: Shape::Sine, volume: 1.0, pan: 0.0, tune_semis: 0, tune_cents: 0, uni_num: 1.0, uni_detune: 0.0, uni_stereo: 0.0)
|
18
20
|
@volume = ModulationValue.create(volume)
|
19
21
|
@pan = ModulationValue.create(pan)
|
20
22
|
@tune_semis = ModulationValue.create(tune_semis)
|
@@ -22,6 +24,7 @@ module Synthesizer
|
|
22
24
|
|
23
25
|
@uni_num = ModulationValue.create(uni_num)
|
24
26
|
@uni_detune = ModulationValue.create(uni_detune)
|
27
|
+
@uni_stereo = ModulationValue.create(uni_stereo)
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -6,12 +6,12 @@ module Synthesizer
|
|
6
6
|
@bandwidth = ModulationValue.create(bandwidth)
|
7
7
|
end
|
8
8
|
|
9
|
-
def generator(note_perform
|
9
|
+
def generator(note_perform)
|
10
10
|
soundinfo = note_perform.synth.soundinfo
|
11
11
|
filter = AudioStream::Fx::BandPassFilter.new(soundinfo)
|
12
12
|
|
13
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
14
|
-
bandwidth_mod = ModulationValue.balance_generator(note_perform,
|
13
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
14
|
+
bandwidth_mod = ModulationValue.balance_generator(note_perform, @bandwidth)
|
15
15
|
|
16
16
|
-> {
|
17
17
|
filter.update_coef(freq: freq_mod[], bandwidth: bandwidth_mod[])
|
@@ -6,12 +6,12 @@ module Synthesizer
|
|
6
6
|
@q = ModulationValue.create(q)
|
7
7
|
end
|
8
8
|
|
9
|
-
def generator(note_perform
|
9
|
+
def generator(note_perform)
|
10
10
|
soundinfo = note_perform.synth.soundinfo
|
11
11
|
filter = AudioStream::Fx::HighPassFilter.new(soundinfo)
|
12
12
|
|
13
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
14
|
-
q_mod = ModulationValue.balance_generator(note_perform,
|
13
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
14
|
+
q_mod = ModulationValue.balance_generator(note_perform, @q)
|
15
15
|
|
16
16
|
-> {
|
17
17
|
filter.update_coef(freq: freq_mod[], q: q_mod[])
|
@@ -7,13 +7,13 @@ module Synthesizer
|
|
7
7
|
@gain = ModulationValue.create(gain)
|
8
8
|
end
|
9
9
|
|
10
|
-
def generator(note_perform
|
10
|
+
def generator(note_perform)
|
11
11
|
soundinfo = note_perform.synth.soundinfo
|
12
12
|
filter = AudioStream::Fx::HighShelfFilter.new(soundinfo)
|
13
13
|
|
14
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
15
|
-
q_mod = ModulationValue.balance_generator(note_perform,
|
16
|
-
gain_mod = ModulationValue.balance_generator(note_perform,
|
14
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
15
|
+
q_mod = ModulationValue.balance_generator(note_perform, @q)
|
16
|
+
gain_mod = ModulationValue.balance_generator(note_perform, @gain)
|
17
17
|
|
18
18
|
-> {
|
19
19
|
filter.update_coef(freq: freq_mod[], q: q_mod[], gain: gain_mod[])
|
@@ -6,12 +6,12 @@ module Synthesizer
|
|
6
6
|
@q = ModulationValue.create(q)
|
7
7
|
end
|
8
8
|
|
9
|
-
def generator(note_perform
|
9
|
+
def generator(note_perform)
|
10
10
|
soundinfo = note_perform.synth.soundinfo
|
11
11
|
filter = AudioStream::Fx::LowPassFilter.new(soundinfo)
|
12
12
|
|
13
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
14
|
-
q_mod = ModulationValue.balance_generator(note_perform,
|
13
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
14
|
+
q_mod = ModulationValue.balance_generator(note_perform, @q)
|
15
15
|
|
16
16
|
-> {
|
17
17
|
filter.update_coef(freq: freq_mod[], q: q_mod[])
|
@@ -7,13 +7,13 @@ module Synthesizer
|
|
7
7
|
@gain = ModulationValue.create(gain)
|
8
8
|
end
|
9
9
|
|
10
|
-
def generator(note_perform
|
10
|
+
def generator(note_perform)
|
11
11
|
soundinfo = note_perform.synth.soundinfo
|
12
12
|
filter = AudioStream::Fx::LowShelfFilter.new(soundinfo)
|
13
13
|
|
14
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
15
|
-
q_mod = ModulationValue.balance_generator(note_perform,
|
16
|
-
gain_mod = ModulationValue.balance_generator(note_perform,
|
14
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
15
|
+
q_mod = ModulationValue.balance_generator(note_perform, @q)
|
16
|
+
gain_mod = ModulationValue.balance_generator(note_perform, @gain)
|
17
17
|
|
18
18
|
-> {
|
19
19
|
filter.update_coef(freq: freq_mod[], q: q_mod[], gain: gain_mod[])
|
@@ -7,13 +7,13 @@ module Synthesizer
|
|
7
7
|
@gain = ModulationValue.create(gain)
|
8
8
|
end
|
9
9
|
|
10
|
-
def generator(note_perform
|
10
|
+
def generator(note_perform)
|
11
11
|
soundinfo = note_perform.synth.soundinfo
|
12
12
|
filter = AudioStream::Fx::LowShelfFilter.new(soundinfo)
|
13
13
|
|
14
|
-
freq_mod = ModulationValue.balance_generator(note_perform,
|
15
|
-
bandwidth_mod = ModulationValue.balance_generator(note_perform,
|
16
|
-
gain_mod = ModulationValue.balance_generator(note_perform,
|
14
|
+
freq_mod = ModulationValue.balance_generator(note_perform, @freq)
|
15
|
+
bandwidth_mod = ModulationValue.balance_generator(note_perform, @bandwidth)
|
16
|
+
gain_mod = ModulationValue.balance_generator(note_perform, @gain)
|
17
17
|
|
18
18
|
-> {
|
19
19
|
filter.update_coef(freq: freq_mod[], bandwidth: bandwidth_mod[], gain: gain_mod[])
|
@@ -2,15 +2,15 @@ module Synthesizer
|
|
2
2
|
module Modulation
|
3
3
|
class Adsr
|
4
4
|
|
5
|
-
# @param attack [
|
5
|
+
# @param attack [AudioStream::Rate] attack sec (0.0~)
|
6
6
|
# @param attack_curve [Synthesizer::Curve]
|
7
|
-
# @param hold [
|
8
|
-
# @param decay [
|
7
|
+
# @param hold [AudioStream::Rate] hold sec (0.0~)
|
8
|
+
# @param decay [AudioStream::Rate] decay sec (0.0~)
|
9
9
|
# @param sustain_curve [Synthesizer::Curve]
|
10
|
-
# @param sustain [Float] sustain
|
11
|
-
# @param release [
|
10
|
+
# @param sustain [Float] sustain level (0.0~1.0)
|
11
|
+
# @param release [AudioStream::Rate] release sec (0.0~)
|
12
12
|
# @param release_curve [Synthesizer::Curve]
|
13
|
-
def initialize(attack:, attack_curve: Curve::EaseOut, hold: 0.0, decay:, sustain_curve: Curve::EaseOut, sustain:, release:, release_curve: Curve::EaseOut)
|
13
|
+
def initialize(attack:, attack_curve: Curve::EaseOut, hold: AudioStream::Rate.sec(0.0), decay:, sustain_curve: Curve::EaseOut, sustain:, release:, release_curve: Curve::EaseOut)
|
14
14
|
@attack = attack
|
15
15
|
@attack_curve = attack_curve
|
16
16
|
@hold = hold
|
@@ -21,27 +21,26 @@ module Synthesizer
|
|
21
21
|
@release_curve = release_curve
|
22
22
|
end
|
23
23
|
|
24
|
-
def note_on_envelope(
|
24
|
+
def note_on_envelope(soundinfo, sustain: false, &block)
|
25
25
|
Enumerator.new do |yld|
|
26
26
|
# attack
|
27
|
-
|
28
|
-
|
29
|
-
x = i.to_f /
|
27
|
+
attack_len = @attack.frame(soundinfo).to_i
|
28
|
+
attack_len.times {|i|
|
29
|
+
x = i.to_f / attack_len
|
30
30
|
y = @attack_curve[x]
|
31
31
|
yld << y
|
32
32
|
}
|
33
33
|
|
34
34
|
# hold
|
35
|
-
|
36
|
-
rate.to_i.times {|i|
|
35
|
+
@hold.frame(soundinfo).to_i.times {|i|
|
37
36
|
yld << 1.0
|
38
37
|
}
|
39
38
|
|
40
39
|
# decay
|
41
|
-
|
42
|
-
|
43
|
-
x = i.to_f /
|
44
|
-
y = 1.0 - @sustain_curve[x]
|
40
|
+
decay_len = @decay.frame(soundinfo).to_i
|
41
|
+
decay_len.times {|i|
|
42
|
+
x = i.to_f / decay_len
|
43
|
+
y = 1.0 - @sustain_curve[x] * (1.0 - @sustain)
|
45
44
|
yld << y
|
46
45
|
}
|
47
46
|
|
@@ -54,12 +53,12 @@ module Synthesizer
|
|
54
53
|
end.each(&block)
|
55
54
|
end
|
56
55
|
|
57
|
-
def note_off_envelope(
|
56
|
+
def note_off_envelope(soundinfo, sustain: false, &block)
|
58
57
|
Enumerator.new do |yld|
|
59
58
|
# release
|
60
|
-
|
61
|
-
|
62
|
-
x = i.to_f /
|
59
|
+
release_len = @release.frame(soundinfo).to_i
|
60
|
+
release_len.times {|i|
|
61
|
+
x = i.to_f / release_len
|
63
62
|
y = 1.0 - @release_curve[x]
|
64
63
|
yld << y
|
65
64
|
}
|
@@ -73,9 +72,11 @@ module Synthesizer
|
|
73
72
|
end.each(&block)
|
74
73
|
end
|
75
74
|
|
76
|
-
def generator(note_perform,
|
77
|
-
|
78
|
-
|
75
|
+
def generator(note_perform, release_sustain:)
|
76
|
+
soundinfo = note_perform.synth.soundinfo
|
77
|
+
|
78
|
+
note_on = note_on_envelope(soundinfo, sustain: true)
|
79
|
+
note_off = note_off_envelope(soundinfo, sustain: release_sustain)
|
79
80
|
last = 0.0
|
80
81
|
|
81
82
|
-> {
|
@@ -88,26 +89,26 @@ module Synthesizer
|
|
88
89
|
end
|
89
90
|
|
90
91
|
|
91
|
-
def amp_generator(note_perform,
|
92
|
+
def amp_generator(note_perform, depth, &block)
|
92
93
|
bottom = 1.0 - depth
|
93
|
-
gen = generator(note_perform,
|
94
|
+
gen = generator(note_perform, release_sustain: 0.0<bottom)
|
94
95
|
|
95
96
|
-> {
|
96
97
|
gen[] * depth + bottom
|
97
98
|
}
|
98
99
|
end
|
99
100
|
|
100
|
-
def balance_generator(note_perform,
|
101
|
-
gen = generator(note_perform,
|
101
|
+
def balance_generator(note_perform, depth, &block)
|
102
|
+
gen = generator(note_perform, release_sustain: true)
|
102
103
|
|
103
104
|
-> {
|
104
105
|
gen[] * depth
|
105
106
|
}
|
106
107
|
end
|
107
108
|
|
108
|
-
def plot_data(
|
109
|
-
note_on = note_on_envelope(
|
110
|
-
note_off = note_off_envelope(
|
109
|
+
def plot_data(soundinfo)
|
110
|
+
note_on = note_on_envelope(soundinfo, sustain: false)
|
111
|
+
note_off = note_off_envelope(soundinfo, sustain: false)
|
111
112
|
last = 0.0
|
112
113
|
|
113
114
|
xs = []
|
@@ -127,8 +128,8 @@ module Synthesizer
|
|
127
128
|
{x: xs, y: ys}
|
128
129
|
end
|
129
130
|
|
130
|
-
def plot(
|
131
|
-
data = plot_data(
|
131
|
+
def plot(soundinfo)
|
132
|
+
data = plot_data(soundinfo)
|
132
133
|
Plotly::Plot.new(data: [data])
|
133
134
|
end
|
134
135
|
end
|
@@ -2,9 +2,9 @@ module Synthesizer
|
|
2
2
|
module Modulation
|
3
3
|
class Glide
|
4
4
|
|
5
|
-
# @param time [
|
5
|
+
# @param time [AudioStream::Rate] glide time sec (0.0~)
|
6
6
|
def initialize(time:)
|
7
|
-
@time = time
|
7
|
+
@time = time
|
8
8
|
|
9
9
|
@base = 0.0
|
10
10
|
@current = 0.0
|
@@ -26,15 +26,16 @@ module Synthesizer
|
|
26
26
|
@diff = target - @current
|
27
27
|
end
|
28
28
|
|
29
|
-
def generator(note_perform
|
30
|
-
|
29
|
+
def generator(note_perform)
|
30
|
+
soundinfo = note_perform.synth.soundinfo
|
31
|
+
rate = @time.frame(soundinfo)
|
31
32
|
|
32
33
|
-> {
|
33
34
|
ret = nil
|
34
35
|
|
35
|
-
if note_perform.
|
36
|
+
if !note_perform.released?
|
36
37
|
# Note On
|
37
|
-
if 0
|
38
|
+
if 0<rate && @target!=@current
|
38
39
|
# Gliding
|
39
40
|
x = @diff / rate
|
40
41
|
if x.abs<(@target-@current).abs
|
@@ -60,8 +61,8 @@ module Synthesizer
|
|
60
61
|
}
|
61
62
|
end
|
62
63
|
|
63
|
-
def balance_generator(note_perform,
|
64
|
-
gen = generator(note_perform
|
64
|
+
def balance_generator(note_perform, depth)
|
65
|
+
gen = generator(note_perform)
|
65
66
|
|
66
67
|
-> {
|
67
68
|
gen[] * depth
|
@@ -3,12 +3,12 @@ module Synthesizer
|
|
3
3
|
class Lfo
|
4
4
|
|
5
5
|
# @param shape [Synthesizer::Shape]
|
6
|
-
# @param delay [
|
7
|
-
# @param attack [
|
6
|
+
# @param delay [AudioStream::Rate] delay sec (0.0~)
|
7
|
+
# @param attack [AudioStream::Rate] attack sec (0.0~)
|
8
8
|
# @param attack_curve [Synthesizer::Curve]
|
9
9
|
# @param phase [Float] phase percent (0.0~1.0)
|
10
|
-
# @param rate [
|
11
|
-
def initialize(shape: Shape::Sine, delay: 0.0, attack: 0.0, attack_curve: Curve::Straight, phase: 0.0, rate: 3.5)
|
10
|
+
# @param rate [AudioStream::Rate] wave freq (0.0~)
|
11
|
+
def initialize(shape: Shape::Sine, delay: AudioStream::Rate.sec(0.0), attack: AudioStream::Rate.sec(0.0), attack_curve: Curve::Straight, phase: 0.0, rate: AudioStream::Rate.freq(3.5))
|
12
12
|
@shape = shape
|
13
13
|
@delay = delay
|
14
14
|
@attack = attack
|
@@ -17,37 +17,37 @@ module Synthesizer
|
|
17
17
|
@rate = rate
|
18
18
|
end
|
19
19
|
|
20
|
-
def generator(note_perform,
|
21
|
-
|
22
|
-
|
20
|
+
def generator(note_perform, &block)
|
21
|
+
soundinfo = note_perform.synth.soundinfo
|
22
|
+
hz = @rate.freq(soundinfo)
|
23
23
|
|
24
|
-
|
24
|
+
Enumerator.new do |yld|
|
25
|
+
pos = ShapePos.new(soundinfo.samplerate, @phase)
|
25
26
|
|
26
27
|
# delay
|
27
|
-
|
28
|
-
rate.to_i.times {|i|
|
28
|
+
@delay.frame(soundinfo).to_i.times {|i|
|
29
29
|
yld << 0.0
|
30
30
|
}
|
31
31
|
|
32
32
|
# attack
|
33
|
-
|
34
|
-
|
35
|
-
x = i.to_f /
|
33
|
+
attack_len = @attack.frame(soundinfo).to_i
|
34
|
+
attack_len.times {|i|
|
35
|
+
x = i.to_f / attack_len
|
36
36
|
y = @attack_curve[x]
|
37
|
-
yld << @shape[pos.next(
|
37
|
+
yld << @shape[pos.next(hz, 0.0, 0.0)] * y
|
38
38
|
}
|
39
39
|
|
40
40
|
# sustain
|
41
41
|
loop {
|
42
|
-
val = @shape[pos.next(
|
42
|
+
val = @shape[pos.next(hz, 0.0, 0.0)]
|
43
43
|
yld << val
|
44
44
|
}
|
45
45
|
end.each(&block)
|
46
46
|
end
|
47
47
|
|
48
|
-
def amp_generator(note_perform,
|
48
|
+
def amp_generator(note_perform, depth, &block)
|
49
49
|
bottom = 1.0 - depth
|
50
|
-
gen = generator(note_perform
|
50
|
+
gen = generator(note_perform)
|
51
51
|
|
52
52
|
-> {
|
53
53
|
val = (gen.next + 1) / 2
|
@@ -55,8 +55,8 @@ module Synthesizer
|
|
55
55
|
}
|
56
56
|
end
|
57
57
|
|
58
|
-
def balance_generator(note_perform,
|
59
|
-
gen = generator(note_perform
|
58
|
+
def balance_generator(note_perform, depth, &block)
|
59
|
+
gen = generator(note_perform)
|
60
60
|
|
61
61
|
-> {
|
62
62
|
gen.next * depth
|
@@ -9,7 +9,7 @@ module Synthesizer
|
|
9
9
|
@mods = []
|
10
10
|
|
11
11
|
mods.each {|mod, depth|
|
12
|
-
add(mod, depth: depth)
|
12
|
+
add(mod, depth: depth || 1.0)
|
13
13
|
}
|
14
14
|
end
|
15
15
|
|
@@ -35,7 +35,7 @@ module Synthesizer
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def self.amp_generator(note_perform,
|
38
|
+
def self.amp_generator(note_perform, *modvals)
|
39
39
|
modvals = modvals.flatten.compact
|
40
40
|
|
41
41
|
# value
|
@@ -45,7 +45,7 @@ module Synthesizer
|
|
45
45
|
mods = []
|
46
46
|
modvals.each {|modval|
|
47
47
|
modval.mods.each {|mod, depth|
|
48
|
-
mods << mod.amp_generator(note_perform,
|
48
|
+
mods << mod.amp_generator(note_perform, depth)
|
49
49
|
}
|
50
50
|
}
|
51
51
|
|
@@ -55,7 +55,7 @@ module Synthesizer
|
|
55
55
|
}
|
56
56
|
end
|
57
57
|
|
58
|
-
def self.balance_generator(note_perform,
|
58
|
+
def self.balance_generator(note_perform, *modvals, center: 0)
|
59
59
|
modvals = modvals.flatten.compact
|
60
60
|
|
61
61
|
# value
|
@@ -66,7 +66,7 @@ module Synthesizer
|
|
66
66
|
mods = []
|
67
67
|
modvals.each {|modval|
|
68
68
|
modval.mods.each {|mod, depth|
|
69
|
-
mods << mod.balance_generator(note_perform,
|
69
|
+
mods << mod.balance_generator(note_perform, depth)
|
70
70
|
}
|
71
71
|
}
|
72
72
|
|
@@ -12,9 +12,11 @@ module Synthesizer
|
|
12
12
|
attr_accessor :pitch_bend
|
13
13
|
|
14
14
|
# @param oscillators [Synthesizer::Oscillator] oscillator
|
15
|
+
# @param filter [Synthesizer::Filter] filter
|
15
16
|
# @param amplifier [Synthesizer::Amplifier] amplifier
|
17
|
+
# @param glide [AudioStream::Rate] glide time sec (0.0~)
|
16
18
|
# @param soundinfo [AudioStream::SoundInfo]
|
17
|
-
def initialize(oscillators:, filter: nil, amplifier:, glide: 0.1, soundinfo:)
|
19
|
+
def initialize(oscillators:, filter: nil, amplifier:, glide: AudioStream::Rate.sec(0.1), soundinfo:)
|
18
20
|
@oscillators = [oscillators].flatten.compact
|
19
21
|
@filter = filter
|
20
22
|
@amplifier = amplifier
|
@@ -11,6 +11,7 @@ module Synthesizer
|
|
11
11
|
attr_reader :sync
|
12
12
|
attr_reader :uni_num
|
13
13
|
attr_reader :uni_detune
|
14
|
+
attr_reader :uni_stereo
|
14
15
|
|
15
16
|
# @param source [Synthesizer::OscillatorSource] oscillator waveform source
|
16
17
|
# @param volume [Float] oscillator volume. mute=0.0 max=1.0
|
@@ -19,10 +20,11 @@ module Synthesizer
|
|
19
20
|
# @param tune_cents [Integer] oscillator pitch cent
|
20
21
|
# @param sym [nil] TODO not implemented
|
21
22
|
# @param phase [Float] oscillator waveform shape start phase percent (0.0~1.0,nil) nil=random
|
22
|
-
# @param sync [Integer]
|
23
|
+
# @param sync [Integer] oscillator sync pitch 1.0=semitone 12.0=octave (0.0~48.0)
|
23
24
|
# @param uni_num [Float] oscillator voicing number (1.0~16.0)
|
24
25
|
# @param uni_detune [Float] oscillator voicing detune percent. 0.01=1cent 1.0=semitone (0.0~1.0)
|
25
|
-
|
26
|
+
# @param uni_stereo [Float] oscillator voicing spread pan. -1.0=full inv 0.0=mono 1.0=full (-1.0~1.0)
|
27
|
+
def initialize(source: OscillatorSource::Sine.instance, volume: 1.0, pan: 0.0, tune_semis: 0, tune_cents: 0, sym: 0, phase: nil, sync: 0, uni_num: 1.0, uni_detune: 0.0, uni_stereo: 0.0)
|
26
28
|
@source = source
|
27
29
|
|
28
30
|
@volume = ModulationValue.create(volume)
|
@@ -4,13 +4,15 @@ module Synthesizer
|
|
4
4
|
def initialize
|
5
5
|
end
|
6
6
|
|
7
|
-
def next(context,
|
7
|
+
def next(context, rate, sym, sync, l_gain, r_gain)
|
8
|
+
soundinfo = context.soundinfo
|
8
9
|
channels = context.channels
|
9
10
|
window_size = context.window_size
|
10
11
|
pos = context.pos
|
12
|
+
hz = rate.freq(soundinfo)
|
11
13
|
|
12
14
|
dst = window_size.times.map {|i|
|
13
|
-
sample(context, pos.next(
|
15
|
+
sample(context, pos.next(hz, sym, sync))
|
14
16
|
}
|
15
17
|
dst = Vdsp::DoubleArray.create(dst)
|
16
18
|
|
@@ -26,11 +28,39 @@ module Synthesizer
|
|
26
28
|
raise Error, "not implemented abstruct method: #{self.class.name}.sample(context, phase)"
|
27
29
|
end
|
28
30
|
|
29
|
-
def generate_context(soundinfo, note_perform,
|
30
|
-
Context.new(soundinfo
|
31
|
+
def generate_context(soundinfo, note_perform, init_phase)
|
32
|
+
Context.new(soundinfo, note_perform, init_phase)
|
31
33
|
end
|
32
34
|
|
33
|
-
|
35
|
+
class Context
|
36
|
+
attr_reader :soundinfo
|
37
|
+
attr_reader :note_perform
|
38
|
+
attr_reader :init_phase
|
39
|
+
attr_reader :pos
|
40
|
+
|
41
|
+
def initialize(soundinfo, note_perform, init_phase)
|
42
|
+
@soundinfo = soundinfo
|
43
|
+
@note_perform = note_perform
|
44
|
+
@init_phase = init_phase
|
45
|
+
@pos = ShapePos.new(@soundinfo.samplerate, init_phase)
|
46
|
+
end
|
47
|
+
|
48
|
+
def window_size
|
49
|
+
@window_size ||= soundinfo.window_size
|
50
|
+
end
|
51
|
+
|
52
|
+
def channels
|
53
|
+
@channels ||= soundinfo.channels
|
54
|
+
end
|
55
|
+
|
56
|
+
def samplerate
|
57
|
+
@samplerate ||= soundinfo.samplerate
|
58
|
+
end
|
59
|
+
|
60
|
+
def framerate
|
61
|
+
@framerate ||= soundinfo.framerate
|
62
|
+
end
|
63
|
+
end
|
34
64
|
end
|
35
65
|
end
|
36
66
|
end
|
@@ -30,15 +30,25 @@ module Synthesizer
|
|
30
30
|
@pronunciation = ModulationValue.create(pronunciation)
|
31
31
|
end
|
32
32
|
|
33
|
-
def next(context,
|
33
|
+
def next(context, rate, sym, sync, l_gain, r_gain)
|
34
|
+
soundinfo = context.soundinfo
|
34
35
|
channels = context.channels
|
35
36
|
window_size = context.window_size
|
36
37
|
samplerate = context.samplerate
|
37
38
|
tmpbufs = context.tmpbufs
|
38
39
|
pronunciation_mod = context.pronunciation_mod
|
40
|
+
pulse_context = context.pulse_context
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
+
pulse = Pulse.instance.next(pulse_context, rate, sym, sync, 0.5, 0.5).streams[0]
|
43
|
+
|
44
|
+
notediff = Math.log2(rate.freq(soundinfo) / 440.0) * 12 + 69 - 36
|
45
|
+
if notediff<0.0
|
46
|
+
notediff = 0.0
|
47
|
+
end
|
48
|
+
notediff = Math.sqrt(notediff)
|
49
|
+
vowels = @vowels.map {|vowel|
|
50
|
+
vowel.map{|f| f * (ShapePos::SEMITONE_RATIO ** notediff)}
|
51
|
+
}
|
42
52
|
|
43
53
|
r_index = pronunciation_mod[]
|
44
54
|
index = r_index.to_i
|
@@ -46,7 +56,7 @@ module Synthesizer
|
|
46
56
|
dst = 5.times.map {|i|
|
47
57
|
#dst = (1...5).each.map {|i|
|
48
58
|
tmpbuf = tmpbufs[i]
|
49
|
-
freq =
|
59
|
+
freq = vowels[index % @vowels_len][i]+(vowels[(index+1) % @vowels_len][i]-vowels[index % @vowels_len][i])*(r_index-index)
|
50
60
|
w = Math::PI * 2 * freq
|
51
61
|
resfil(pulse, tmpbufs[i], @@gain[i], w, 1.0/samplerate, window_size)
|
52
62
|
}.inject(:+)
|
@@ -82,24 +92,21 @@ module Synthesizer
|
|
82
92
|
Vdsp::DoubleArray.create(y)
|
83
93
|
end
|
84
94
|
|
85
|
-
def generate_context(soundinfo, note_perform,
|
86
|
-
|
87
|
-
tmpbufs = Array.new(5) {|i| Vdsp::DoubleArray.new(soundinfo.window_size+2)}
|
88
|
-
|
89
|
-
FvContext.new(soundinfo, note_perform, phase, tmpbufs, pronunciation_mod)
|
95
|
+
def generate_context(soundinfo, note_perform, init_phase)
|
96
|
+
Context.new(soundinfo, note_perform, init_phase, @pronunciation)
|
90
97
|
end
|
91
98
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
99
|
+
class Context < Base::Context
|
100
|
+
attr_reader :pronunciation_mod
|
101
|
+
attr_reader :tmpbufs
|
102
|
+
attr_reader :pulse_context
|
96
103
|
|
97
|
-
def
|
98
|
-
soundinfo
|
99
|
-
end
|
104
|
+
def initialize(soundinfo, note_perform, init_phase, pronunciation)
|
105
|
+
super(soundinfo, note_perform, init_phase)
|
100
106
|
|
101
|
-
|
102
|
-
soundinfo.
|
107
|
+
@pronunciation_mod = ModulationValue.balance_generator(note_perform, @pronunciation)
|
108
|
+
@tmpbufs = Array.new(5) {|i| Vdsp::DoubleArray.new(soundinfo.window_size+2)}
|
109
|
+
@pulse_context = Pulse.instance.generate_context(soundinfo, note_perform, init_phase)
|
103
110
|
end
|
104
111
|
end
|
105
112
|
end
|
@@ -13,11 +13,19 @@ module Synthesizer
|
|
13
13
|
result
|
14
14
|
end
|
15
15
|
|
16
|
-
def generate_context(soundinfo, note_perform,
|
17
|
-
|
16
|
+
def generate_context(soundinfo, note_perform, init_phase)
|
17
|
+
Context.new(soundinfo, note_perform, init_phase)
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
class Context < Base::Context
|
21
|
+
attr_accessor :prev
|
22
|
+
|
23
|
+
def initialize(soundinfo, note_perform, init_phase)
|
24
|
+
super(soundinfo, note_perform, init_phase)
|
25
|
+
|
26
|
+
@prev = -1.0
|
27
|
+
end
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
@@ -4,23 +4,26 @@ module Synthesizer
|
|
4
4
|
synth = note_perform.synth
|
5
5
|
filter = synth.filter
|
6
6
|
amp = synth.amplifier
|
7
|
-
window_size = synth.soundinfo.window_size
|
8
|
-
framerate = synth.soundinfo.samplerate.to_f / window_size
|
9
7
|
|
10
8
|
# Oscillator, Amplifier
|
11
|
-
volume_mod = ModulationValue.amp_generator(note_perform,
|
12
|
-
pan_mod = ModulationValue.balance_generator(note_perform,
|
13
|
-
tune_semis_mod = ModulationValue.balance_generator(note_perform,
|
14
|
-
tune_cents_mod = ModulationValue.balance_generator(note_perform,
|
9
|
+
volume_mod = ModulationValue.amp_generator(note_perform, osc.volume, amp.volume)
|
10
|
+
pan_mod = ModulationValue.balance_generator(note_perform, osc.pan, amp.pan)
|
11
|
+
tune_semis_mod = ModulationValue.balance_generator(note_perform, osc.tune_semis, amp.tune_semis, synth.glide&.to_modval)
|
12
|
+
tune_cents_mod = ModulationValue.balance_generator(note_perform, osc.tune_cents, amp.tune_cents)
|
13
|
+
|
14
|
+
sym_mod = ModulationValue.balance_generator(note_perform, osc.sym)
|
15
|
+
sync_mod = ModulationValue.balance_generator(note_perform, osc.sync)
|
16
|
+
|
17
|
+
uni_num_mod = ModulationValue.balance_generator(note_perform, osc.uni_num, amp.uni_num, center: 1.0)
|
18
|
+
uni_detune_mod = ModulationValue.balance_generator(note_perform, osc.uni_detune, amp.uni_detune)
|
19
|
+
uni_stereo_mod = ModulationValue.balance_generator(note_perform, osc.uni_stereo, amp.uni_stereo)
|
15
20
|
|
16
|
-
uni_num_mod = ModulationValue.balance_generator(note_perform, framerate, osc.uni_num, amp.uni_num, center: 1.0)
|
17
|
-
uni_detune_mod = ModulationValue.balance_generator(note_perform, framerate, osc.uni_detune, amp.uni_detune)
|
18
21
|
unison = Unison.new(note_perform, osc.source, osc.phase)
|
19
22
|
|
20
23
|
# Filter
|
21
24
|
filter_mod = nil
|
22
25
|
if filter
|
23
|
-
filter_mod = filter.generator(note_perform
|
26
|
+
filter_mod = filter.generator(note_perform)
|
24
27
|
end
|
25
28
|
|
26
29
|
-> {
|
@@ -30,10 +33,14 @@ module Synthesizer
|
|
30
33
|
tune_semis = tune_semis_mod[] + synth.pitch_bend
|
31
34
|
tune_cents = tune_cents_mod[]
|
32
35
|
|
36
|
+
sym = sym_mod[]
|
37
|
+
sync = sync_mod[]
|
38
|
+
|
33
39
|
uni_num = uni_num_mod[]
|
34
40
|
uni_detune = uni_detune_mod[]
|
41
|
+
uni_stereo = uni_stereo_mod[]
|
35
42
|
|
36
|
-
buf = unison.next(uni_num, uni_detune, volume, pan, tune_semis, tune_cents)
|
43
|
+
buf = unison.next(uni_num, uni_detune, uni_stereo, volume, pan, tune_semis, tune_cents, sym, sync)
|
37
44
|
|
38
45
|
# Filter
|
39
46
|
if filter_mod
|
@@ -1,29 +1,32 @@
|
|
1
1
|
module Synthesizer
|
2
2
|
class ShapePos
|
3
|
-
|
4
|
-
@init_phase = phase
|
5
|
-
@sync = sync
|
3
|
+
SEMITONE_RATIO = 2.0 ** (1.0 / 12.0)
|
6
4
|
|
7
|
-
|
8
|
-
@
|
5
|
+
def initialize(samplerate, init_phase)
|
6
|
+
@samplerate = samplerate.to_f
|
7
|
+
|
8
|
+
init_phase = init_phase ? init_phase.to_f : Random.rand(1.0)
|
9
|
+
@sync_phase = init_phase
|
10
|
+
@shape_phase = init_phase
|
9
11
|
end
|
10
12
|
|
11
|
-
def next(
|
12
|
-
|
13
|
+
def next(hz, sym, sync)
|
14
|
+
if sync<0.0
|
15
|
+
sync = 0.0
|
16
|
+
end
|
13
17
|
|
14
|
-
if
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
@phase = Random.rand + delta
|
19
|
-
end
|
20
|
-
# TODO: sync
|
21
|
-
#elsif @sync && @sync<@offset
|
22
|
-
# @offset = 0
|
23
|
-
# @phase = @init_phase
|
24
|
-
else
|
25
|
-
@phase += delta
|
18
|
+
if 1.0<=@sync_phase
|
19
|
+
@sync_phase %= 1.0
|
20
|
+
@shape_phase = @sync_phase
|
26
21
|
end
|
22
|
+
|
23
|
+
sync_hz = hz
|
24
|
+
sync_delta = sync_hz / @samplerate
|
25
|
+
@sync_phase += sync_delta
|
26
|
+
|
27
|
+
shape_hz = hz * (SEMITONE_RATIO ** sync)
|
28
|
+
shape_delta = shape_hz / @samplerate
|
29
|
+
@shape_phase += shape_delta
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
data/lib/synthesizer/unison.rb
CHANGED
@@ -4,7 +4,6 @@ module Synthesizer
|
|
4
4
|
|
5
5
|
def initialize(note_perform, source, phase)
|
6
6
|
synth = note_perform.synth
|
7
|
-
@samplerate = synth.soundinfo.samplerate
|
8
7
|
|
9
8
|
@note_perform = note_perform
|
10
9
|
@source = source
|
@@ -13,32 +12,41 @@ module Synthesizer
|
|
13
12
|
}
|
14
13
|
end
|
15
14
|
|
16
|
-
def next(uni_num, uni_detune, volume, pan, tune_semis, tune_cents)
|
15
|
+
def next(uni_num, uni_detune, uni_stereo, volume, pan, tune_semis, tune_cents, sym, sync)
|
17
16
|
if uni_num<1.0
|
18
17
|
uni_num = 1.0
|
19
18
|
elsif UNI_NUM_MAX<uni_num
|
20
19
|
uni_num = UNI_NUM_MAX
|
21
20
|
end
|
22
21
|
|
23
|
-
uni_num.
|
24
|
-
context = @source_contexts[
|
22
|
+
if uni_num==1.0
|
23
|
+
context = @source_contexts[0]
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
uni_volume = uni_num % 1.0
|
29
|
-
end
|
25
|
+
l_gain, r_gain = Utils.panning(pan)
|
26
|
+
hz = @note_perform.note.hz(semis: tune_semis, cents: tune_cents)
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
@source.next(context, AudioStream::Rate.freq(hz), sym, sync, l_gain * volume, r_gain * volume)
|
29
|
+
else
|
30
|
+
uni_num.ceil.times.map {|i|
|
31
|
+
context = @source_contexts[i]
|
34
32
|
|
35
|
-
|
33
|
+
uni_volume = 1.0
|
34
|
+
if uni_num<i
|
35
|
+
uni_volume = uni_num % 1.0
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
sign = i.even? ? 1 : -1
|
39
|
+
diff = sign * (i + 1.0) / (uni_num + 1.0)
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
detune_cents = uni_detune * diff * 100
|
42
|
+
diff_pan = uni_stereo * diff
|
43
|
+
|
44
|
+
l_gain, r_gain = Utils.panning(pan + diff_pan)
|
45
|
+
hz = @note_perform.note.hz(semis: tune_semis, cents: tune_cents + detune_cents)
|
46
|
+
|
47
|
+
@source.next(context, AudioStream::Rate.freq(hz), sym, sync, l_gain * volume * uni_volume / uni_num, r_gain * volume * uni_volume / uni_num)
|
48
|
+
}.inject(:+)
|
49
|
+
end
|
42
50
|
end
|
43
51
|
end
|
44
52
|
end
|
data/lib/synthesizer/version.rb
CHANGED
data/synthesizer.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: synthesizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yoshida Tetsuya
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 3.
|
61
|
+
version: 3.3.1
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 3.
|
68
|
+
version: 3.3.1
|
69
69
|
description: Synthesizer implemented in Ruby.
|
70
70
|
email:
|
71
71
|
- yoshida.eth0@gmail.com
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- examples/poly.rb
|
92
92
|
- examples/shapes.ipynb
|
93
93
|
- examples/step_editor.rb
|
94
|
+
- examples/sync.rb
|
94
95
|
- lib/audio_stream/audio_input_metronome.rb
|
95
96
|
- lib/audio_stream/audio_input_step_editor.rb
|
96
97
|
- lib/audio_stream/audio_input_synth.rb
|