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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de75bd1dce21cbe4d6fb77b3f7dac7b35a5db994a1e7ee3d4109d660cbb69d68
4
- data.tar.gz: 6d4f22b0ee34003ce64ab908f39ab9bfdd5df448c738eed516d2cb8312f2dbbe
3
+ metadata.gz: fb327b711e81b9980b065cb446c278e1b74207ef4f2351646c1dc217c52925de
4
+ data.tar.gz: 621c2d0970d3d5d5c2663bc56cce7ddc1e4c9e369b9fad56121a597d619a7637
5
5
  SHA512:
6
- metadata.gz: df6f09c4951a5925bc77f6821c53327307f9aeb16d4e273dc440c034c261fb06a35abd0f6c9b19735b3a581267afc30d98e3a05f722829bac9f2aa99e0cd5f34
7
- data.tar.gz: 17da6f4b246047632d921e15d5b0b6975cc6c4ecda29ce9b1cdbe9231ea1832bef9b3601a91a98fe044711d5eef5a37e67aa74be6ef3691bf102380efc646b46
6
+ metadata.gz: '028d65fe391817a4e4c52e9dee9bea5803698e348d64a92fa41c239da7219fb60afe0dfb8b7e63fc5a96839544bd264ee3813dfa4c1ba4dd043d09ba9b104c5b'
7
+ data.tar.gz: b39b4a1860af12bbe5c45c2d972ab4b41c0944ab849380cec8445a8a940a03ae92bad717f7fb50a9573de82a7512f2663e4ff642c021b157e9c34bc25766b8fd
@@ -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("formatvocoder.wav", soundinfo: soundinfo)
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+8))
54
+ synth.note_on(Note.new(base+7))
55
55
  bufs += 100.times.map {|_| synth.next}
56
- synth.note_off(Note.new(base+8))
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("formatvocoder_sweep.wav", soundinfo: soundinfo)
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
- 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)
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, framerate)
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, framerate, @freq)
14
- bandwidth_mod = ModulationValue.balance_generator(note_perform, framerate, @bandwidth)
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, framerate)
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, framerate, @freq)
14
- q_mod = ModulationValue.balance_generator(note_perform, framerate, @q)
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, framerate)
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, framerate, @freq)
15
- q_mod = ModulationValue.balance_generator(note_perform, framerate, @q)
16
- gain_mod = ModulationValue.balance_generator(note_perform, framerate, @gain)
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, framerate)
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, framerate, @freq)
14
- q_mod = ModulationValue.balance_generator(note_perform, framerate, @q)
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, framerate)
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, framerate, @freq)
15
- q_mod = ModulationValue.balance_generator(note_perform, framerate, @q)
16
- gain_mod = ModulationValue.balance_generator(note_perform, framerate, @gain)
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[])
@@ -5,9 +5,9 @@ module Synthesizer
5
5
  @filters = filters
6
6
  end
7
7
 
8
- def generator(note_perform, framerate)
8
+ def generator(note_perform)
9
9
  filter_mods = @filters.map {|filter|
10
- filter.generator(note_perform, framerate)
10
+ filter.generator(note_perform)
11
11
  }
12
12
 
13
13
  -> {
@@ -7,13 +7,13 @@ module Synthesizer
7
7
  @gain = ModulationValue.create(gain)
8
8
  end
9
9
 
10
- def generator(note_perform, framerate)
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, framerate, @freq)
15
- bandwidth_mod = ModulationValue.balance_generator(note_perform, framerate, @bandwidth)
16
- gain_mod = ModulationValue.balance_generator(note_perform, framerate, @gain)
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[])
@@ -5,9 +5,9 @@ module Synthesizer
5
5
  @filters = filters
6
6
  end
7
7
 
8
- def generator(note_perform, framerate)
8
+ def generator(note_perform)
9
9
  filter_mods = @filters.map {|filter|
10
- filter.generator(note_perform, framerate)
10
+ filter.generator(note_perform)
11
11
  }
12
12
 
13
13
  -> {
@@ -2,15 +2,15 @@ module Synthesizer
2
2
  module Modulation
3
3
  class Adsr
4
4
 
5
- # @param attack [Float] attack sec (0.0~)
5
+ # @param attack [AudioStream::Rate] attack sec (0.0~)
6
6
  # @param attack_curve [Synthesizer::Curve]
7
- # @param hold [Float] hold sec (0.0~)
8
- # @param decay [Float] decay sec (0.0~)
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 sec (0.0~)
11
- # @param release [Float] release sec (0.0~)
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(framerate, sustain: false, &block)
24
+ def note_on_envelope(soundinfo, sustain: false, &block)
25
25
  Enumerator.new do |yld|
26
26
  # attack
27
- rate = @attack * framerate
28
- rate.to_i.times {|i|
29
- x = i.to_f / rate
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
- rate = @hold * framerate
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
- rate = @decay * framerate
42
- rate.to_i.times {|i|
43
- x = i.to_f / rate
44
- y = 1.0 - @sustain_curve[x] * (1.0 - @sustain)
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(framerate, sustain: false, &block)
56
+ def note_off_envelope(soundinfo, sustain: false, &block)
58
57
  Enumerator.new do |yld|
59
58
  # release
60
- rate = @release * framerate
61
- rate.to_i.times {|i|
62
- x = i.to_f / rate
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, framerate, release_sustain:)
77
- note_on = note_on_envelope(framerate, sustain: true)
78
- note_off = note_off_envelope(framerate, sustain: release_sustain)
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, framerate, depth, &block)
92
+ def amp_generator(note_perform, depth, &block)
92
93
  bottom = 1.0 - depth
93
- gen = generator(note_perform, framerate, release_sustain: 0.0<bottom)
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, framerate, depth, &block)
101
- gen = generator(note_perform, framerate, release_sustain: true)
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(framerate: 44100)
109
- note_on = note_on_envelope(framerate, sustain: false)
110
- note_off = note_off_envelope(framerate, sustain: false)
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(framerate: 44100)
131
- data = plot_data(framerate: framerate)
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 [Float] glide time sec (0.0~)
5
+ # @param time [AudioStream::Rate] glide time sec (0.0~)
6
6
  def initialize(time:)
7
- @time = time.to_f
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, samplerate)
30
- rate = @time * samplerate
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.note_on?
36
+ if !note_perform.released?
36
37
  # Note On
37
- if 0<@time && @target!=@current
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, samplerate, depth)
64
- gen = generator(note_perform, samplerate)
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 [Float] delay sec (0.0~)
7
- # @param attack [Float] attack sec (0.0~)
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 [Float] wave freq (0.0~)
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, framerate, &block)
21
- Enumerator.new do |yld|
22
- delta = @rate / framerate
20
+ def generator(note_perform, &block)
21
+ soundinfo = note_perform.synth.soundinfo
22
+ hz = @rate.freq(soundinfo)
23
23
 
24
- pos = ShapePos.new(phase: @phase)
24
+ Enumerator.new do |yld|
25
+ pos = ShapePos.new(soundinfo.samplerate, @phase)
25
26
 
26
27
  # delay
27
- rate = @delay * framerate
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
- rate = @attack * framerate
34
- rate.to_i.times {|i|
35
- x = i.to_f / rate
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(delta)] * y
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(delta)]
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, framerate, depth, &block)
48
+ def amp_generator(note_perform, depth, &block)
49
49
  bottom = 1.0 - depth
50
- gen = generator(note_perform, framerate)
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, framerate, depth, &block)
59
- gen = generator(note_perform, framerate)
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, framerate, *modvals)
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, framerate, depth)
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, framerate, *modvals, center: 0)
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, framerate, depth)
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] TODO not implemented
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
- def initialize(source: OscillatorSource::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
+ # @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, delta, l_gain, r_gain)
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(delta))
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, phase)
30
- Context.new(soundinfo.window_size, soundinfo.channels, ShapePos.new(phase: phase))
31
+ def generate_context(soundinfo, note_perform, init_phase)
32
+ Context.new(soundinfo, note_perform, init_phase)
31
33
  end
32
34
 
33
- Context = Struct.new("Context", :window_size, :channels, :pos)
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, delta, l_gain, r_gain)
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
- @pulse_context ||= @pulse.generate_context(context.soundinfo, context.note_perform, context.phase)
41
- pulse = @pulse.next(@pulse_context, delta, 0.5, 0.5).streams[0]
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 = @vowels[index % @vowels_len][i]+(@vowels[(index+1) % @vowels_len][i]-@vowels[index % @vowels_len][i])*(r_index-index)
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, phase)
86
- pronunciation_mod = ModulationValue.balance_generator(note_perform, soundinfo.framerate, @pronunciation)
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
- FvContext = Struct.new("FvContext", :soundinfo, :note_perform, :phase, :tmpbufs, :pronunciation_mod) do
93
- def window_size
94
- soundinfo.window_size
95
- end
99
+ class Context < Base::Context
100
+ attr_reader :pronunciation_mod
101
+ attr_reader :tmpbufs
102
+ attr_reader :pulse_context
96
103
 
97
- def channels
98
- soundinfo.channels
99
- end
104
+ def initialize(soundinfo, note_perform, init_phase, pronunciation)
105
+ super(soundinfo, note_perform, init_phase)
100
106
 
101
- def samplerate
102
- soundinfo.samplerate
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, phase)
17
- PulseContext.new(soundinfo.window_size, soundinfo.channels, phase, ShapePos.new(phase: phase), -1.0)
16
+ def generate_context(soundinfo, note_perform, init_phase)
17
+ Context.new(soundinfo, note_perform, init_phase)
18
18
  end
19
19
 
20
- PulseContext = Struct.new("PulseContext", :window_size, :channels, :phase, :pos, :prev)
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, framerate, osc.volume, amp.volume)
12
- pan_mod = ModulationValue.balance_generator(note_perform, framerate, osc.pan, amp.pan)
13
- tune_semis_mod = ModulationValue.balance_generator(note_perform, framerate, osc.tune_semis, amp.tune_semis, synth.glide&.to_modval)
14
- tune_cents_mod = ModulationValue.balance_generator(note_perform, framerate, osc.tune_cents, amp.tune_cents)
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, framerate)
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
- def initialize(phase: 0.0, sync: nil)
4
- @init_phase = phase
5
- @sync = sync
3
+ SEMITONE_RATIO = 2.0 ** (1.0 / 12.0)
6
4
 
7
- @offset = 0
8
- @phase = 0.0
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(delta)
12
- @offset += 1
13
+ def next(hz, sym, sync)
14
+ if sync<0.0
15
+ sync = 0.0
16
+ end
13
17
 
14
- if @offset==1
15
- if @init_phase
16
- @phase = @init_phase + delta
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
@@ -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.ceil.times.map {|i|
24
- context = @source_contexts[i]
22
+ if uni_num==1.0
23
+ context = @source_contexts[0]
25
24
 
26
- uni_volume = 1.0
27
- if uni_num<i
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
- sign = i.even? ? 1 : -1
32
- detune_cents = sign * (i/2) * uni_detune * 100
33
- diff_pan = sign * (i/2) * uni_detune
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
- l_gain, r_gain = Utils.panning(pan + diff_pan)
33
+ uni_volume = 1.0
34
+ if uni_num<i
35
+ uni_volume = uni_num % 1.0
36
+ end
36
37
 
37
- hz = @note_perform.note.hz(semis: tune_semis, cents: tune_cents + detune_cents)
38
- delta = hz / @samplerate
38
+ sign = i.even? ? 1 : -1
39
+ diff = sign * (i + 1.0) / (uni_num + 1.0)
39
40
 
40
- @source.next(context, delta, l_gain * volume * uni_volume / uni_num, r_gain * volume * uni_volume / uni_num)
41
- }.inject(:+)
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
@@ -1,3 +1,3 @@
1
1
  module Synthesizer
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
data/synthesizer.gemspec CHANGED
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "rake", "~> 12.0"
31
31
  spec.add_development_dependency "minitest", "~> 5.0"
32
32
 
33
- spec.add_dependency "audio_stream", ">= 3.2.0"
33
+ spec.add_dependency "audio_stream", ">= 3.3.1"
34
34
  end
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.1.0
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-23 00:00:00.000000000 Z
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.2.0
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.2.0
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