synthesizer 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/.gitignore +11 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/adsr.ipynb +90 -0
- data/examples/curves.ipynb +105 -0
- data/examples/mono.rb +85 -0
- data/examples/poly.rb +79 -0
- data/examples/shapes.ipynb +116 -0
- data/lib/audio_stream/audio_input_synth.rb +26 -0
- data/lib/synthesizer.rb +22 -0
- data/lib/synthesizer/amplifier.rb +27 -0
- data/lib/synthesizer/modulation.rb +4 -0
- data/lib/synthesizer/modulation/adsr.rb +136 -0
- data/lib/synthesizer/modulation/curve.rb +9 -0
- data/lib/synthesizer/modulation/glide.rb +72 -0
- data/lib/synthesizer/modulation/lfo.rb +64 -0
- data/lib/synthesizer/modulation_value.rb +83 -0
- data/lib/synthesizer/mono.rb +76 -0
- data/lib/synthesizer/note.rb +40 -0
- data/lib/synthesizer/note_perform.rb +39 -0
- data/lib/synthesizer/oscillator.rb +41 -0
- data/lib/synthesizer/poly.rb +62 -0
- data/lib/synthesizer/processor.rb +10 -0
- data/lib/synthesizer/processor/high.rb +63 -0
- data/lib/synthesizer/processor/low.rb +63 -0
- data/lib/synthesizer/quality.rb +6 -0
- data/lib/synthesizer/shape.rb +35 -0
- data/lib/synthesizer/shape_pos.rb +29 -0
- data/lib/synthesizer/unison.rb +51 -0
- data/lib/synthesizer/utils.rb +45 -0
- data/lib/synthesizer/version.rb +3 -0
- data/synthesizer.gemspec +39 -0
- metadata +209 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class ModulationValue
|
3
|
+
|
4
|
+
attr_accessor :value
|
5
|
+
attr_reader :mods
|
6
|
+
|
7
|
+
def initialize(value, mods={})
|
8
|
+
@value = value
|
9
|
+
@mods = []
|
10
|
+
|
11
|
+
mods.each {|mod, depth|
|
12
|
+
add(mod, depth: depth)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param mod [Synth::Modulation]
|
17
|
+
# @param depth [Float] (-1.0~1.0)
|
18
|
+
def add(mod, depth: 1.0)
|
19
|
+
depth ||= 1.0
|
20
|
+
if depth<-1.0
|
21
|
+
depth = -1.0
|
22
|
+
elsif 1.0<depth
|
23
|
+
depth = 1.0
|
24
|
+
end
|
25
|
+
|
26
|
+
@mods << [mod, depth]
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.create(value)
|
31
|
+
if ModulationValue===value
|
32
|
+
value
|
33
|
+
else
|
34
|
+
new(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.amp_generator(note_perform, samplerate, *modvals)
|
39
|
+
modvals = modvals.flatten.compact
|
40
|
+
|
41
|
+
# value
|
42
|
+
value = modvals.map(&:value).sum
|
43
|
+
|
44
|
+
# mods
|
45
|
+
mods = []
|
46
|
+
modvals.each {|modval|
|
47
|
+
modval.mods.each {|mod, depth|
|
48
|
+
mods << mod.amp_generator(note_perform, samplerate, depth)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
Enumerator.new do |y|
|
53
|
+
loop {
|
54
|
+
depth = mods.map(&:next).inject(1.0, &:*)
|
55
|
+
y << value * depth
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.balance_generator(note_perform, samplerate, *modvals, center: 0)
|
61
|
+
modvals = modvals.flatten.compact
|
62
|
+
|
63
|
+
# value
|
64
|
+
value = modvals.map(&:value).sum
|
65
|
+
value -= (modvals.length - 1) * center
|
66
|
+
|
67
|
+
# mods
|
68
|
+
mods = []
|
69
|
+
modvals.each {|modval|
|
70
|
+
modval.mods.each {|mod, depth|
|
71
|
+
mods << mod.balance_generator(note_perform, samplerate, depth)
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
Enumerator.new do |y|
|
76
|
+
loop {
|
77
|
+
depth = mods.map(&:next).sum
|
78
|
+
y << value + depth
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class Mono
|
3
|
+
|
4
|
+
attr_reader :oscillators
|
5
|
+
attr_reader :amplifier
|
6
|
+
attr_reader :processor
|
7
|
+
|
8
|
+
attr_reader :quality
|
9
|
+
attr_reader :soundinfo
|
10
|
+
|
11
|
+
attr_reader :glide
|
12
|
+
attr_accessor :pitch_bend
|
13
|
+
|
14
|
+
# @param oscillators [Oscillator] oscillator
|
15
|
+
# @param amplifier [Amplifier] amplifier
|
16
|
+
# @param soundinfo [SoundInfo]
|
17
|
+
def initialize(oscillators:, amplifier:, glide: 0.1, quality: Quality::LOW, soundinfo:)
|
18
|
+
@oscillators = [oscillators].flatten.compact
|
19
|
+
@amplifier = amplifier
|
20
|
+
|
21
|
+
@quality = quality
|
22
|
+
@soundinfo = soundinfo
|
23
|
+
|
24
|
+
@processor = Processor.create(quality)
|
25
|
+
@note_nums = []
|
26
|
+
@perform = nil
|
27
|
+
@glide = Modulation::Glide.new(time: glide)
|
28
|
+
@pitch_bend = 0.0
|
29
|
+
end
|
30
|
+
|
31
|
+
def next
|
32
|
+
if @perform
|
33
|
+
buf = @perform.next
|
34
|
+
|
35
|
+
# delete released note perform
|
36
|
+
if @perform.released?
|
37
|
+
@perform = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
buf
|
41
|
+
else
|
42
|
+
AudioStream::Buffer.float(@soundinfo.window_size, @soundinfo.channels)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def note_on(note)
|
47
|
+
# Note Off
|
48
|
+
note_off(note)
|
49
|
+
|
50
|
+
if @perform && @perform.note_on?
|
51
|
+
# Glide
|
52
|
+
@glide.target = note.num
|
53
|
+
else
|
54
|
+
# Note On
|
55
|
+
@perform = NotePerform.new(self, note)
|
56
|
+
@glide.base = note.num
|
57
|
+
end
|
58
|
+
@note_nums << note.num
|
59
|
+
end
|
60
|
+
|
61
|
+
def note_off(note)
|
62
|
+
# Note Off
|
63
|
+
@note_nums.delete_if {|note_num| note_num==note.num}
|
64
|
+
|
65
|
+
if @perform
|
66
|
+
if @note_nums.length==0
|
67
|
+
# Note Off
|
68
|
+
@perform.note_off!
|
69
|
+
else
|
70
|
+
# Glide
|
71
|
+
@glide.target = @note_nums.last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class Note
|
3
|
+
NOTE_TABLE = [:"C", :"C#/Db", :"D", :"D#/Eb", :"E", :"F", :"F#/Gb", :"G", :"G#/Ab", :"A", :"A#/Bb", :"B"].freeze
|
4
|
+
|
5
|
+
attr_reader :num
|
6
|
+
|
7
|
+
def initialize(num)
|
8
|
+
@num = num.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def hz(semis: 0, cents: 0)
|
12
|
+
6.875 * (2 ** ((@num + semis + (cents / 100.0) + 3) / 12.0))
|
13
|
+
end
|
14
|
+
|
15
|
+
def note_name
|
16
|
+
NOTE_TABLE[@num % 12]
|
17
|
+
end
|
18
|
+
|
19
|
+
def octave_num
|
20
|
+
(@num / 12) - 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create(name, octave)
|
24
|
+
name = name.to_s
|
25
|
+
octave = octave.to_i
|
26
|
+
|
27
|
+
note_index = NOTE_TABLE.index(name)
|
28
|
+
if !note_index
|
29
|
+
raise Error, "not found note name: #{name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
num = (octave + 1) * 12 + note_index
|
33
|
+
if num<0
|
34
|
+
raise Error, "octave #{octave} outside of note"
|
35
|
+
end
|
36
|
+
|
37
|
+
new(num)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class NotePerform
|
3
|
+
|
4
|
+
attr_reader :synth
|
5
|
+
attr_reader :note
|
6
|
+
|
7
|
+
def initialize(synth, note)
|
8
|
+
@synth = synth
|
9
|
+
@processors = synth.oscillators.map {|osc|
|
10
|
+
synth.processor.generator(osc, self)
|
11
|
+
}
|
12
|
+
|
13
|
+
@note = note
|
14
|
+
@note_on = true
|
15
|
+
@released = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def next
|
19
|
+
begin
|
20
|
+
@processors.map(&:next).inject(:+)
|
21
|
+
rescue StopIteration => e
|
22
|
+
@released = true
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def note_on?
|
28
|
+
@note_on
|
29
|
+
end
|
30
|
+
|
31
|
+
def note_off!
|
32
|
+
@note_on = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def released?
|
36
|
+
@released
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class Oscillator
|
3
|
+
|
4
|
+
attr_reader :shape
|
5
|
+
attr_reader :volume
|
6
|
+
attr_reader :pan
|
7
|
+
attr_reader :tune_semis
|
8
|
+
attr_reader :tune_cents
|
9
|
+
attr_reader :sym
|
10
|
+
attr_reader :phase
|
11
|
+
attr_reader :sync
|
12
|
+
attr_reader :uni_num
|
13
|
+
attr_reader :uni_detune
|
14
|
+
|
15
|
+
# @param shape [Synth::Shape] oscillator waveform shape
|
16
|
+
# @param volume [Float] oscillator volume. mute=0.0 max=1.0
|
17
|
+
# @param pan [Float] oscillator pan. left=-1.0 center=0.0 right=1.0 (-1.0~1.0)
|
18
|
+
# @param tune_semis [Integer] oscillator pitch semitone
|
19
|
+
# @param tune_cents [Integer] oscillator pitch cent
|
20
|
+
# @param sym [nil] TODO not implemented
|
21
|
+
# @param phase [Float] oscillator waveform shape start phase percent (0.0~1.0,nil) nil=random
|
22
|
+
# @param sync [Integer] TODO not implemented
|
23
|
+
# @param uni_num [Float] oscillator voicing number (1.0~16.0)
|
24
|
+
# @param uni_detune [Float] oscillator voicing detune percent. 0.01=1cent 1.0=semitone (0.0~1.0)
|
25
|
+
def initialize(shape: Shape::Sine, 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)
|
26
|
+
@shape = shape
|
27
|
+
|
28
|
+
@volume = ModulationValue.create(volume)
|
29
|
+
@pan = ModulationValue.create(pan)
|
30
|
+
@tune_semis = ModulationValue.create(tune_semis)
|
31
|
+
@tune_cents = ModulationValue.create(tune_cents)
|
32
|
+
|
33
|
+
@sym = ModulationValue.create(sym)
|
34
|
+
@phase = ModulationValue.create(phase)
|
35
|
+
@sync = ModulationValue.create(sync)
|
36
|
+
|
37
|
+
@uni_num = ModulationValue.create(uni_num)
|
38
|
+
@uni_detune = ModulationValue.create(uni_detune)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
class Poly
|
3
|
+
|
4
|
+
attr_reader :oscillators
|
5
|
+
attr_reader :amplifier
|
6
|
+
attr_reader :processor
|
7
|
+
|
8
|
+
attr_reader :quality
|
9
|
+
attr_reader :soundinfo
|
10
|
+
|
11
|
+
attr_reader :glide
|
12
|
+
attr_accessor :pitch_bend
|
13
|
+
|
14
|
+
# @param oscillators [Oscillator] Oscillator
|
15
|
+
# @param amplifier [Amplifier] amplifier
|
16
|
+
# @param soundinfo [SoundInfo]
|
17
|
+
def initialize(oscillators:, amplifier:, quality: Quality::LOW, soundinfo:)
|
18
|
+
@oscillators = [oscillators].flatten.compact
|
19
|
+
@amplifier = amplifier
|
20
|
+
|
21
|
+
@quality = quality
|
22
|
+
@soundinfo = soundinfo
|
23
|
+
|
24
|
+
@processor = Processor.create(quality)
|
25
|
+
@performs = {}
|
26
|
+
@pitch_bend = 0.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def next
|
30
|
+
if 0<@performs.length
|
31
|
+
bufs = @performs.values.map(&:next)
|
32
|
+
|
33
|
+
# delete released note performs
|
34
|
+
@performs.delete_if {|note_num, perform| perform.released? }
|
35
|
+
|
36
|
+
bufs.compact.inject(:+)
|
37
|
+
else
|
38
|
+
AudioStream::Buffer.float(@soundinfo.window_size, @soundinfo.channels)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def note_on(note)
|
43
|
+
# Note Off
|
44
|
+
perform = @performs[note.num]
|
45
|
+
if perform
|
46
|
+
perform.note_off!
|
47
|
+
end
|
48
|
+
|
49
|
+
# Note On
|
50
|
+
perform = NotePerform.new(self, note)
|
51
|
+
@performs[note.num] = perform
|
52
|
+
end
|
53
|
+
|
54
|
+
def note_off(note)
|
55
|
+
# Note Off
|
56
|
+
perform = @performs[note.num]
|
57
|
+
if perform
|
58
|
+
perform.note_off!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
module Processor
|
3
|
+
class High
|
4
|
+
def generator(osc, note_perform, &block)
|
5
|
+
Enumerator.new do |y|
|
6
|
+
synth = note_perform.synth
|
7
|
+
amp = synth.amplifier
|
8
|
+
channels = synth.soundinfo.channels
|
9
|
+
window_size = synth.soundinfo.window_size
|
10
|
+
samplerate = synth.soundinfo.samplerate
|
11
|
+
|
12
|
+
volume_mod = ModulationValue.amp_generator(note_perform, samplerate, osc.volume, amp.volume)
|
13
|
+
pan_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.pan, amp.pan)
|
14
|
+
tune_semis_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.tune_semis, amp.tune_semis, synth.glide&.to_modval)
|
15
|
+
tune_cents_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.tune_cents, amp.tune_cents)
|
16
|
+
|
17
|
+
uni_num_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.uni_num, amp.uni_num, center: 1.0)
|
18
|
+
uni_detune_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.uni_detune, amp.uni_detune)
|
19
|
+
unison = Unison.new(note_perform, osc.shape, osc.phase)
|
20
|
+
|
21
|
+
case channels
|
22
|
+
when 1
|
23
|
+
loop {
|
24
|
+
buf = AudioStream::Buffer.float(window_size, channels)
|
25
|
+
|
26
|
+
window_size.times.each {|i|
|
27
|
+
volume = volume_mod.next
|
28
|
+
tune_semis = tune_semis_mod.next + synth.pitch_bend
|
29
|
+
tune_cents = tune_cents_mod.next
|
30
|
+
|
31
|
+
uni_num = uni_num_mod.next
|
32
|
+
uni_detune = uni_detune_mod.next
|
33
|
+
|
34
|
+
val = unison.next(uni_num, uni_detune, volume, 0.0, tune_semis, tune_cents)
|
35
|
+
buf[i] = (val[0] + val[1]) / 2.0
|
36
|
+
}
|
37
|
+
|
38
|
+
y << buf
|
39
|
+
}
|
40
|
+
when 2
|
41
|
+
loop {
|
42
|
+
buf = AudioStream::Buffer.float(window_size, channels)
|
43
|
+
|
44
|
+
window_size.times.each {|i|
|
45
|
+
volume = volume_mod.next
|
46
|
+
pan = pan_mod.next
|
47
|
+
tune_semis = tune_semis_mod.next + synth.pitch_bend
|
48
|
+
tune_cents = tune_cents_mod.next
|
49
|
+
|
50
|
+
uni_num = uni_num_mod.next
|
51
|
+
uni_detune = uni_detune_mod.next
|
52
|
+
|
53
|
+
buf[i] = unison.next(uni_num, uni_detune, volume, pan, tune_semis, tune_cents)
|
54
|
+
}
|
55
|
+
|
56
|
+
y << buf
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end.each(&block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Synthesizer
|
2
|
+
module Processor
|
3
|
+
class Low
|
4
|
+
def generator(osc, note_perform, &block)
|
5
|
+
Enumerator.new do |y|
|
6
|
+
synth = note_perform.synth
|
7
|
+
amp = synth.amplifier
|
8
|
+
channels = synth.soundinfo.channels
|
9
|
+
window_size = synth.soundinfo.window_size
|
10
|
+
samplerate = synth.soundinfo.samplerate.to_f / window_size
|
11
|
+
|
12
|
+
volume_mod = ModulationValue.amp_generator(note_perform, samplerate, osc.volume, amp.volume)
|
13
|
+
pan_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.pan, amp.pan)
|
14
|
+
tune_semis_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.tune_semis, amp.tune_semis, synth.glide&.to_modval)
|
15
|
+
tune_cents_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.tune_cents, amp.tune_cents)
|
16
|
+
|
17
|
+
uni_num_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.uni_num, amp.uni_num, center: 1.0)
|
18
|
+
uni_detune_mod = ModulationValue.balance_generator(note_perform, samplerate, osc.uni_detune, amp.uni_detune)
|
19
|
+
unison = Unison.new(note_perform, osc.shape, osc.phase)
|
20
|
+
|
21
|
+
case channels
|
22
|
+
when 1
|
23
|
+
loop {
|
24
|
+
buf = AudioStream::Buffer.float(window_size, channels)
|
25
|
+
|
26
|
+
volume = volume_mod.next
|
27
|
+
tune_semis = tune_semis_mod.next + synth.pitch_bend
|
28
|
+
tune_cents = tune_cents_mod.next
|
29
|
+
|
30
|
+
uni_num = uni_num_mod.next
|
31
|
+
uni_detune = uni_detune_mod.next
|
32
|
+
|
33
|
+
window_size.times.each {|i|
|
34
|
+
val = unison.next(uni_num, uni_detune, volume, 0.0, tune_semis, tune_cents)
|
35
|
+
buf[i] = (val[0] + val[1]) / 2.0
|
36
|
+
}
|
37
|
+
|
38
|
+
y << buf
|
39
|
+
}
|
40
|
+
when 2
|
41
|
+
loop {
|
42
|
+
buf = AudioStream::Buffer.float(window_size, channels)
|
43
|
+
|
44
|
+
volume = volume_mod.next
|
45
|
+
pan = pan_mod.next
|
46
|
+
tune_semis = tune_semis_mod.next + synth.pitch_bend
|
47
|
+
tune_cents = tune_cents_mod.next
|
48
|
+
|
49
|
+
uni_num = uni_num_mod.next
|
50
|
+
uni_detune = uni_detune_mod.next
|
51
|
+
|
52
|
+
window_size.times.each {|i|
|
53
|
+
buf[i] = unison.next(uni_num, uni_detune, volume, pan, tune_semis, tune_cents)
|
54
|
+
}
|
55
|
+
|
56
|
+
y << buf
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end.each(&block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|