synthesizer 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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