synthesizer 3.1.0 → 3.2.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 +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
|