synthesizer 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/.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
|