synth_blocks 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ require 'synth_blocks/core/sound'
2
+ require 'synth_blocks/core/oscillator'
3
+ require 'synth_blocks/mod/envelope'
4
+
5
+ module SynthBlocks
6
+ module Drum
7
+ ##
8
+ # A simple kick drum generator
9
+ class KickDrum < SynthBlocks::Core::Sound
10
+
11
+ ##
12
+ # === Structure
13
+ # Pitch Env > Sine wave OSC >
14
+ #
15
+ # === parameter:
16
+ # - pitch_attack, pitch_decay - Pitch envelope params in s
17
+ # - amp_attack, amp_decay - Amp envelope params in s
18
+ # - base_frequency - base frequency in Hz
19
+ # - pitch_mod - frequency modulation amount in Hz
20
+ def initialize(sfreq, preset = {})
21
+ super(sfreq, mode: :polyphonic)
22
+ @preset = {
23
+ pitch_attack: 0.001,
24
+ pitch_decay: 0.05,
25
+ amp_attack: 0.001,
26
+ amp_decay: 0.1,
27
+ base_frequency: 50,
28
+ pitch_mod: 200
29
+ }.merge(preset)
30
+ @oscillator = SynthBlocks::Core::Oscillator.new(@sampling_frequency)
31
+ @pitch_env = SynthBlocks::Mod::Envelope.new(@preset[:pitch_attack], @preset[:pitch_decay])
32
+ @amp_env = SynthBlocks::Mod::Envelope.new(@preset[:amp_attack], @preset[:amp_decay])
33
+ end
34
+
35
+ def duration(_) # :nodoc:
36
+ @preset[:amp_attack] + @preset[:amp_decay]
37
+ end
38
+ ##
39
+ # Run generator
40
+ def run(offset)
41
+ t = time(offset)
42
+ events = active_events(t)
43
+ if events.empty?
44
+ 0.0
45
+ else
46
+ event = events[events.keys.last]
47
+ local_started = t - event[:started]
48
+ osc_out = @oscillator.run(@preset[:base_frequency].to_f + @pitch_env.run(local_started) * @preset[:pitch_mod].to_f, waveform: :sine)
49
+ osc_out = osc_out * 1.0 * @amp_env.run(local_started)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,87 @@
1
+ require 'synth_blocks/core/sound'
2
+ require 'synth_blocks/core/state_variable_filter'
3
+ require 'synth_blocks/drum/kick_drum'
4
+ require 'synth_blocks/mod/envelope'
5
+
6
+ module SynthBlocks
7
+ module Drum
8
+ ##
9
+ # Simple snare drum generator
10
+ class SnareDrum < SynthBlocks::Core::Sound
11
+ ##
12
+ # === Parameters
13
+ # [flt_frequency] Noise filter frequency
14
+ # [flt_envmod] Noise filter frequency modulation by envelope
15
+ # [flt_attack, flt_decay] Noise filter envelope params
16
+ # [flt_Q] Noise filter Q/resonance
17
+ # [noise_amp_attack, noise_amp_decay] Noise amp envelope params
18
+ # [noise_vol, drum_vol] Noise and Drum body volumes
19
+ # [base_frequency] Drum body base frequency (see KickDrum#initialize)
20
+ # [pitch_mod] Drum body pitch mod (see KickDrum#initialize)
21
+ # [pitch_attack, pitch_decay] Drum body pitch env params
22
+ #
23
+ def initialize(sfreq, preset = {})
24
+ super(sfreq, mode: :polyphonic)
25
+ @preset = {
26
+ flt_frequency: 4000,
27
+ flt_envmod: 6000,
28
+ flt_attack: 0.001,
29
+ flt_decay: 0.1,
30
+ flt_Q: 2,
31
+ noise_amp_attack: 0.001,
32
+ noise_amp_decay: 0.15,
33
+ noise_vol: 0.5,
34
+ drum_vol: 0.3,
35
+ base_frequency: 200,
36
+ pitch_mod: 200,
37
+ pitch_decay: 0.07
38
+
39
+ }.merge(preset)
40
+ @drum = SynthBlocks::Drum::KickDrum.new(sfreq, @preset)
41
+ @filter = SynthBlocks::Core::StateVariableFilter.new(sfreq)
42
+ @flt_env = SynthBlocks::Mod::Envelope.new(@preset[:flt_attack], @preset[:flt_decay])
43
+ @amp_env = SynthBlocks::Mod::Envelope.new(@preset[:noise_amp_attack], @preset[:noise_amp_decay])
44
+ end
45
+
46
+ # create a note on event
47
+ # [t] time in seconds since song start
48
+ # [note] MIDI note number
49
+ # [velocity] velocity (currently unused)
50
+ def start(t, note = 36, velocity = 1.0)
51
+ super(t, note, velocity)
52
+ @drum.start(t, note, velocity)
53
+ end
54
+
55
+ # create a note off event
56
+ # [t] time in seconds since song start
57
+ # [note] MIDI note number
58
+ def stop(t, note = 36)
59
+ super(t, note)
60
+ @drum.stop(t, note)
61
+ end
62
+
63
+ def duration(t) # :nodoc:
64
+ [@preset[:noise_amp_attack] + @preset[:noise_amp_decay], @drum.duration(t)].max
65
+ end
66
+
67
+ # run the generator
68
+ def run(offset)
69
+ drum_out = @drum.run(offset)
70
+ # time in seconds
71
+ t = time(offset)
72
+ events = active_events(t)
73
+ if events.empty?
74
+ 0.0
75
+ else
76
+ event = events[events.keys.last]
77
+ # lfo_out = (@lfo.run(@preset[:lfo_frequency], waveform: @preset[:lfo_waveform]) + 1) / 8 + 0.5
78
+ noise_out = rand * 2.0 - 1.0
79
+ local_started = t - event[:started]
80
+ noise_out = @filter.run(noise_out, @preset[:flt_frequency] + @flt_env.run(local_started) * @preset[:flt_envmod], @preset[:flt_Q])
81
+ noise_out = 0.3 * noise_out * @amp_env.run(local_started)
82
+ noise_out * @preset[:noise_vol] + drum_out * @preset[:drum_vol]
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,25 @@
1
+ require 'synth_blocks/drum/kick_drum'
2
+
3
+ module SynthBlocks
4
+ module Drum
5
+ ##
6
+ # Special case of the kick drum that allows to run it from a note pattern to
7
+ # create percussive sounds
8
+ class TunedDrum < KickDrum
9
+ def run(offset)
10
+ t = time(offset)
11
+ events = active_events(t)
12
+ if events.empty?
13
+ 0.0
14
+ else
15
+ event = events[events.keys.last]
16
+ note = events.keys.last
17
+ base_freq = frequency(note)
18
+ local_started = t - event[:started]
19
+ osc_out = @oscillator.run(base_freq + @pitch_env.run(local_started) * @preset[:pitch_mod].to_f, waveform: :sine)
20
+ osc_out = osc_out * 1.0 * @amp_env.run(local_started)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,85 @@
1
+ require 'synth_blocks/core/one_pole_lowpass'
2
+
3
+ module SynthBlocks
4
+ module Fx
5
+ ##
6
+ # A simple chorus
7
+ class Chorus
8
+ ##
9
+ attr_writer :phase, :rate, :delay_time, :mix # :nodoc:
10
+ ##
11
+ # Create new Chorus instance
12
+ #
13
+ # phase allows you to shift the phase of the delayed signal additionally
14
+ #
15
+ # rate is the LFO rate in Hz
16
+ #
17
+ # delay_time is the maximum delay time in ms
18
+ #
19
+ # mix is the ratio between original and delayed signal. 1.0 would mean only
20
+ # delayed signal (which wouldn't make any sense)
21
+ def initialize(sample_rate, phase: 0.0, rate: 0.5, delay_time: 7.0, mix: 0.5)
22
+ @sample_rate = sample_rate
23
+ @rate = rate
24
+ @delay_time = delay_time
25
+ @mix = mix
26
+
27
+ @z1 = 0.0
28
+ @sign = 0
29
+ @lfo_phase = phase * 2.0 - 1.0
30
+ @lfo_step_size = (4.0 * @rate / @sample_rate)
31
+ @lfo_sign = 1.0
32
+
33
+ # Compute required buffer size for desired delay and allocate it
34
+ # Add extra point to aid in interpolation later
35
+ @delay_line_length = ((@delay_time * @sample_rate * 0.001).floor * 2).to_i
36
+ @delay_line = [0.0] * @delay_line_length
37
+ @write_ptr = @delay_line_length - 1
38
+ @lp = SynthBlocks::Core::OnePoleLP.new
39
+ @output = 0.0
40
+ end
41
+
42
+ ##
43
+ # run the chorus
44
+ def run(input)
45
+ # Get delay time
46
+ offset = (next_lfo() * 0.3 + 0.4) * @delay_time * @sample_rate * 0.001
47
+
48
+ # Compute the largest read pointer based on the offset. If ptr
49
+ # is before the first delayline location, wrap around end point
50
+ ptr = @write_ptr - offset.floor;
51
+ ptr += @delay_line_length - 1 if ptr < 0
52
+
53
+
54
+ ptr2 = ptr - 1
55
+ ptr2 += @delay_line_length - 1 if ptr2 < 0
56
+
57
+ frac = offset - offset.floor.to_f
58
+ @output = @delay_line[ptr2] + @delay_line[ptr] * (1.0 - frac) - (1.0 - frac) * @z1
59
+ @z1 = @output
60
+
61
+ # Low pass
62
+ @lp.run(@output, 0.95)
63
+
64
+ # Write the input sample and any feedback to delayline
65
+ @delay_line[@write_ptr] = input
66
+
67
+ # Increment buffer index and wrap if necesary
68
+ @write_ptr += 1
69
+ @write_ptr = 0 if @write_ptr >= @delay_line_length
70
+ return (@output * @mix) + (input * (1.0-@mix))
71
+ end
72
+
73
+ private
74
+
75
+ def next_lfo()
76
+ if @lfo_phase >= 1.0
77
+ @lfo_sign = -1.0
78
+ elsif @lfo_phase <= -1.0
79
+ @lfo_sign = 1.0
80
+ end
81
+ @lfo_phase += @lfo_step_size * @lfo_sign
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,121 @@
1
+ module SynthBlocks
2
+ module Fx
3
+
4
+ class EnvelopeDetector # :nodoc:
5
+ def initialize(srate, tc: ms)
6
+ @sample_rate = srate
7
+ @ms = tc
8
+ set_coef()
9
+ end
10
+
11
+ def set_coef
12
+ @coef = Math.exp( -1000.0 / ( @ms * @sample_rate) )
13
+ end
14
+
15
+ def tc=(tc)
16
+ @ms = tc
17
+ set_coef()
18
+ end
19
+
20
+ def run(input, state)
21
+ input + @coef * ( state - input )
22
+ end
23
+ end
24
+
25
+
26
+ class AttRelEnvelope # :nodoc:
27
+ def initialize(srate, attack:, release:)
28
+ @attack = EnvelopeDetector.new(srate, tc: attack)
29
+ @release = EnvelopeDetector.new(srate, tc: release)
30
+ end
31
+
32
+ def attack=(attack)
33
+ @attack.tc = attack
34
+ end
35
+
36
+ def release=(decay)
37
+ @release.tc = decay
38
+ end
39
+
40
+ def run(input, state)
41
+ if input > state
42
+ @attack.run( input, state )
43
+ else
44
+ @release.run( input, state )
45
+ end
46
+ end
47
+ end
48
+
49
+ ##
50
+ # simple compresor
51
+ # taken from http://www.musicdsp.org/en/latest/Effects/204-simple-compressor-class-c.html
52
+
53
+ class Compressor
54
+ DC_OFFSET = 1.0E-25 # :nodoc:
55
+ LOG_2_DB = 8.6858896380650365530225783783321 # :nodoc:
56
+ DB_2_LOG = 0.11512925464970228420089957273422 # :nodoc:
57
+ attr_writer :ratio, :threshold, :window # :nodoc:
58
+ ##
59
+ # Create compressor instance
60
+ #
61
+ # attack is the attack time in ms
62
+ #
63
+ # release is the release time in ms
64
+ #
65
+ # ratio is the compresor ratio
66
+ #
67
+ # threshold is the knee threshold
68
+ def initialize(srate, attack: 10.0, release: 100.0, ratio: 1.0, threshold: 0.0)
69
+ @sample_rate = srate
70
+ @envelope = AttRelEnvelope.new(srate, attack: attack, release: release)
71
+ @env_db = DC_OFFSET
72
+ @ratio = ratio
73
+ @threshold = threshold
74
+ end
75
+
76
+ ##
77
+ # set attack
78
+ def attack=(attack)
79
+ @envelope.attack = attack
80
+ end
81
+
82
+ ##
83
+ # set release
84
+ def release=(release)
85
+ @envelope.release = release
86
+ end
87
+
88
+
89
+ ##
90
+ # run compressor
91
+ def run(input)
92
+ rect = input.abs
93
+ rect += DC_OFFSET
94
+ key_db = lin2db(rect)
95
+
96
+ over_db = key_db - @threshold
97
+ over_db = 0.0 if over_db < 0.0
98
+
99
+ # attack/release
100
+ over_db += DC_OFFSET
101
+ @env_db = @envelope.run(over_db, @env_db)
102
+ over_db = @env_db - DC_OFFSET
103
+
104
+ gr = over_db * @ratio - 1.0
105
+ gr = db2lin(gr)
106
+ input * gr
107
+ end
108
+
109
+ private
110
+
111
+ def lin2db(lin)
112
+ return Math.log( lin ) * LOG_2_DB
113
+ end
114
+
115
+ def db2lin(db)
116
+ return Math.exp( db * DB_2_LOG )
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,42 @@
1
+ module SynthBlocks
2
+ module Fx
3
+ ##
4
+ # Simple delay with mix and feedback parameters
5
+ # Currently doesn't really have a variable delay time, I'll tackle that when I
6
+ # need it. It uses a simple ring buffer implementation and delay time is only
7
+ # exact down to the sample.
8
+ class Delay
9
+ attr_reader :mix, :feedback # :nodoc:
10
+
11
+ ##
12
+ # time is given in seconds
13
+ #
14
+ # mix (0 = no delay, 1 = only delay)
15
+ #
16
+ # feedback (0 = zero feedback, 1 = full feedback (not advised))
17
+ #
18
+ # if given a block it will call the block from the run method to process the
19
+ # feedback signal
20
+ def initialize(sample_rate, time: 0.2, mix: 0.5, feedback: 0.4, &block)
21
+ @buffer = Array.new((sample_rate.to_f * time).floor)
22
+ @block = block
23
+ @pointer = 0
24
+ @mix = mix
25
+ @feedback = feedback
26
+ end
27
+
28
+ ##
29
+ # run delay
30
+ def run(input)
31
+ old_pointer = @pointer
32
+ @pointer = (@pointer + 1) % @buffer.length
33
+ delayed = (@buffer[@pointer] || 0.0)
34
+ if @block
35
+ delayed = @block.call(delayed)
36
+ end
37
+ @buffer[old_pointer] = input + (feedback * delayed)
38
+ input * (1.0 - mix) + delayed * mix
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,92 @@
1
+ module SynthBlocks
2
+ module Fx
3
+ ##
4
+ # Simple 3 band Equalizer with variable shelving frequencies
5
+ #
6
+ # Source: http://www.musicdsp.org/en/latest/Filters/236-3-band-equaliser.html
7
+ class Eq
8
+ VSA = 1.0 / 4294967295.0 # very small amount (Denormal fix) :nodoc:
9
+
10
+ attr_accessor :low_gain, :mid_gain, :high_gain # :nodoc: Low Gain, Mid Gain, High Gain
11
+
12
+ ##
13
+ # Create new Equalizer instance
14
+ #
15
+ # lowfreq and highfreq are the shelving frequencies in Hz
16
+ def initialize(sample_rate=44100, lowfreq: 880, highfreq: 5000)
17
+
18
+ # Poles Lowpass
19
+ @f1p0 = 0.0
20
+ @f1p1 = 0.0
21
+ @f1p2 = 0.0
22
+ @f1p3 = 0.0
23
+
24
+ # Poles Highpass
25
+ @f2p0 = 0.0
26
+ @f2p1 = 0.0
27
+ @f2p2 = 0.0
28
+ @f2p3 = 0.0
29
+
30
+ # Sample history buffer
31
+
32
+ @sdm1 = 0.0 # Sample data minus 1
33
+ @sdm2 = 0.0 # 2
34
+ @sdm3 = 0.0 # 3
35
+
36
+ # Gain Controls
37
+ # Set Low/Mid/High gains to unity
38
+
39
+ @low_gain = 1.0 # low gain
40
+ @mid_gain = 1.0 # mid gain
41
+ @high_gain = 1.0 # high gain
42
+
43
+ # Calculate filter cutoff frequencies
44
+
45
+ @lf = 2 * Math.sin(Math::PI * (lowfreq.to_f / sample_rate.to_f))
46
+ @hf = 2 * Math.sin(Math::PI * (highfreq.to_f / sample_rate.to_f))
47
+ end
48
+
49
+ ##
50
+ # run equalizer
51
+ def run(sample)
52
+ # Filter #1 (lowpass)
53
+
54
+ @f1p0 += (@lf * (sample - @f1p0)) + VSA;
55
+ @f1p1 += (@lf * (@f1p0 - @f1p1));
56
+ @f1p2 += (@lf * (@f1p1 - @f1p2));
57
+ @f1p3 += (@lf * (@f1p2 - @f1p3));
58
+
59
+ l = @f1p3;
60
+
61
+ # Filter #2 (highpass)
62
+
63
+ @f2p0 += (@hf * (sample - @f2p0)) + VSA;
64
+ @f2p1 += (@hf * (@f2p0 - @f2p1));
65
+ @f2p2 += (@hf * (@f2p1 - @f2p2));
66
+ @f2p3 += (@hf * (@f2p2 - @f2p3));
67
+
68
+ h = @sdm3 - @f2p3;
69
+
70
+ # Calculate midrange (signal - (low + high))
71
+
72
+ m = @sdm3 - (h + l);
73
+
74
+ # Scale, Combine and store
75
+
76
+ l *= @low_gain
77
+ m *= @mid_gain
78
+ h *= @high_gain
79
+
80
+ # Shuffle history buffer
81
+
82
+ @sdm3 = @sdm2;
83
+ @sdm2 = @sdm1;
84
+ @sdm1 = sample;
85
+
86
+ # Return result
87
+
88
+ l + m + h
89
+ end
90
+ end
91
+ end
92
+ end