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.
@@ -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,10 @@
1
+ require 'synthesizer/processor/low'
2
+ require 'synthesizer/processor/high'
3
+
4
+ module Synthesizer
5
+ module Processor
6
+ def self.create(quality)
7
+ const_get(quality, false).new
8
+ end
9
+ end
10
+ 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