spcore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ module SPCore
2
+ class CookbookBandpassFilter < BiquadFilter
3
+ def initialize sample_rate
4
+ super(sample_rate)
5
+ end
6
+
7
+ def set_critical_freq_and_bw critical_freq, bandwidth
8
+ @critical_freq = critical_freq
9
+ @bandwidth = bandwidth
10
+
11
+ # setup variables
12
+ omega = 2.0 * Math::PI * @critical_freq / @sample_rate
13
+ sn = Math::sin(omega)
14
+ cs = Math::cos(omega)
15
+ alpha = sn * Math::sinh(BiquadFilter::LN_2 / 2.0 * @bandwidth * omega / sn)
16
+
17
+ b0 = alpha
18
+ b1 = 0.0
19
+ b2 = -alpha
20
+ a0 = 1.0 + alpha
21
+ a1 = -2.0 * cs
22
+ a2 = 1.0 - alpha
23
+
24
+ # precompute the coefficients
25
+ @biquad.b0 = b0 / a0
26
+ @biquad.b1 = b1 / a0
27
+ @biquad.b2 = b2 / a0
28
+ @biquad.a0 = a0 / a0
29
+ @biquad.a1 = a1 / a0
30
+ @biquad.a2 = a2 / a0
31
+
32
+ ## zero initial samples
33
+ #@biquad.x1 = @biquad.x2 = 0
34
+ #@biquad.y1 = @biquad.y2 = 0
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module SPCore
2
+ class CookbookHighpassFilter < BiquadFilter
3
+ def initialize sample_rate
4
+ super(sample_rate)
5
+ end
6
+
7
+ def set_critical_freq_and_bw critical_freq, bandwidth
8
+ @critical_freq = critical_freq
9
+ @bandwidth = bandwidth
10
+
11
+ # setup variables
12
+ omega = 2.0 * Math::PI * @critical_freq / @sample_rate
13
+ sn = Math::sin(omega)
14
+ cs = Math::cos(omega)
15
+ alpha = sn * Math::sinh(BiquadFilter::LN_2 / 2.0 * @bandwidth * omega / sn)
16
+
17
+ b0 = (1.0 + cs) / 2.0
18
+ b1 =-(1.0 + cs)
19
+ b2 = (1.0 + cs) / 2.0
20
+ a0 = 1.0 + alpha
21
+ a1 = -2.0 * cs
22
+ a2 = 1.0 - alpha
23
+
24
+ # precompute the coefficients
25
+ @biquad.b0 = b0 / a0
26
+ @biquad.b1 = b1 / a0
27
+ @biquad.b2 = b2 / a0
28
+ @biquad.a0 = a0 / a0
29
+ @biquad.a1 = a1 / a0
30
+ @biquad.a2 = a2 / a0
31
+
32
+ ## zero initial samples
33
+ #@biquad.x1 = @biquad.x2 = 0
34
+ #@biquad.y1 = @biquad.y2 = 0
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ module SPCore
2
+ class CookbookLowpassFilter < BiquadFilter
3
+ def initialize sample_rate
4
+ super(sample_rate)
5
+ end
6
+
7
+ def set_critical_freq_and_bw critical_freq, bandwidth
8
+ @critical_freq = critical_freq
9
+ @bandwidth = bandwidth
10
+
11
+ # setup variables
12
+ omega = 2.0 * Math::PI * @critical_freq / @sample_rate
13
+ sn = Math::sin(omega)
14
+ cs = Math::cos(omega)
15
+ alpha = sn * Math::sinh(BiquadFilter::LN_2 / 2.0 * @bandwidth * omega / sn)
16
+
17
+ #if(q_is_bandwidth)
18
+ # alpha=tsin*sinh(log(2.0)/2.0*q*omega/tsin);
19
+ #else
20
+ # alpha=tsin/(2.0*q);
21
+ #end
22
+
23
+ b0 = (1.0 - cs) / 2.0
24
+ b1 = 1.0 - cs
25
+ b2 = (1.0 - cs) / 2.0
26
+ a0 = 1.0 + alpha
27
+ a1 = -2.0 * cs
28
+ a2 = 1.0 - alpha
29
+
30
+ # precompute the coefficients
31
+ @biquad.b0 = b0 / a0
32
+ @biquad.b1 = b1 / a0
33
+ @biquad.b2 = b2 / a0
34
+ @biquad.a0 = a0 / a0
35
+ @biquad.a1 = a1 / a0
36
+ @biquad.a2 = a2 / a0
37
+
38
+ ## zero initial samples
39
+ #@biquad.x1 = @biquad.x2 = 0
40
+ #@biquad.y1 = @biquad.y2 = 0
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ module SPCore
2
+ class CookbookNotchFilter < BiquadFilter
3
+ def initialize sample_rate
4
+ super(sample_rate)
5
+ end
6
+
7
+ def set_critical_freq_and_bw critical_freq, bandwidth
8
+ @critical_freq = critical_freq
9
+ @bandwidth = bandwidth
10
+
11
+ # setup variables
12
+ omega = 2.0 * Math::PI * @critical_freq / @sample_rate
13
+ sn = Math::sin(omega)
14
+ cs = Math::cos(omega)
15
+ alpha = sn * Math::sinh(BiquadFilter::LN_2 / 2.0 * @bandwidth * omega / sn)
16
+
17
+ b0 = 1.0
18
+ b1 = -2.0 * cs
19
+ b2 = 1.0
20
+ a0 = 1.0 + alpha
21
+ a1 = -2.0 * cs
22
+ a2 = 1.0 - alpha
23
+
24
+ # precompute the coefficients
25
+ @biquad.b0 = b0 / a0
26
+ @biquad.b1 = b1 / a0
27
+ @biquad.b2 = b2 / a0
28
+ @biquad.a0 = a0 / a0
29
+ @biquad.a1 = a1 / a0
30
+ @biquad.a2 = a2 / a0
31
+
32
+ ## zero initial samples
33
+ #@biquad.x1 = @biquad.x2 = 0
34
+ #@biquad.y1 = @biquad.y2 = 0
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module SPCore
2
+ class DelayLine
3
+ include Hashmake::HashMakeable
4
+
5
+ attr_reader :sample_rate, :max_delay_seconds, :delay_seconds, :delay_samples
6
+
7
+ ARG_SPECS = [
8
+ Hashmake::ArgSpec.new(:reqd => true, :key => :sample_rate, :type => Float, :validator => ->(a){ a > 0.0 } ),
9
+ Hashmake::ArgSpec.new(:reqd => true, :key => :max_delay_seconds, :type => Float, :validator => ->(a){ (a > 0.0) } ),
10
+ Hashmake::ArgSpec.new(:reqd => false, :key => :delay_seconds, :type => Float, :default => 0.0, :validator => ->(a){ a >= 0.0 } ),
11
+ ]
12
+
13
+ def initialize args
14
+ hash_make DelayLine::ARG_SPECS, args
15
+ raise ArgumentError, "delay_seconds #{delay_seconds} is greater than max_delay_seconds #{max_delay_seconds}" if @delay_seconds > @max_delay_seconds
16
+ @buffer = CircularBuffer.new((@sample_rate * @max_delay_seconds) + 1, :override_when_full => true)
17
+ @buffer.push_ary Array.new(@buffer.size, 0.0)
18
+ self.delay_seconds=(@delay_seconds)
19
+ end
20
+
21
+ def delay_seconds= delay_seconds
22
+ delay_samples_floor = (@sample_rate * delay_seconds).floor
23
+ @delay_samples = delay_samples_floor.to_i
24
+ @delay_seconds = delay_samples_floor / @sample_rate
25
+
26
+ #if @buffer.fill_count < @delay_samples
27
+ # @buffer.push_ary Array.new(@delay_samples - @buffer.fill_count, 0.0)
28
+ #end
29
+ end
30
+
31
+ def push_sample sample
32
+ @buffer.push sample
33
+ end
34
+
35
+ def delayed_sample
36
+ return @buffer.newest(@delay_samples)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ module SPCore
2
+ class EnvelopeDetector
3
+ include Hashmake::HashMakeable
4
+
5
+ ARG_SPECS = [
6
+ Hashmake::ArgSpec.new(:reqd => true, :key => :sample_rate, :type => Float, :validator => ->(a){ a > 0.0 } ),
7
+ Hashmake::ArgSpec.new(:reqd => true, :key => :attack_time, :type => Float, :validator => ->(a){ a > 0.0 } ),
8
+ Hashmake::ArgSpec.new(:reqd => true, :key => :release_time, :type => Float, :validator => ->(a){ a > 0.0 } ),
9
+ ]
10
+
11
+ attr_reader :envelope, :sample_rate, :attack_time, :release_time
12
+
13
+ def initialize args
14
+ hash_make EnvelopeDetector::ARG_SPECS, args
15
+
16
+ @g_attack = Math.exp(-1.0 / (sample_rate * attack_time))
17
+ @g_release = Math.exp(-1.0 / (sample_rate * release_time))
18
+
19
+ @envelope = 0.0
20
+ end
21
+
22
+ def process_sample sample
23
+ input_abs = sample.abs
24
+
25
+ if @envelope < input_abs
26
+ @envelope = (@envelope * @g_attack) + ((1.0 - @g_attack) * input_abs)
27
+ else
28
+ @envelope = (@envelope * @g_release) + ((1.0 - @g_release) * input_abs)
29
+ end
30
+
31
+ return @envelope
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,182 @@
1
+ module SPCore
2
+ class Gain
3
+
4
+ #MAX_DB = 72
5
+ #NUM_DB_HALF_STEPS=146
6
+ #DB_TO_SCALAR_TABLE = [
7
+ # 1.0, # 0 dB
8
+ # 1.059253725, # 0.5
9
+ # 1.122018454, # 1
10
+ # 1.188502227, # 1.5
11
+ # 1.258925412, # 2
12
+ # 1.333521432, # 2.5
13
+ # 1.412537545, # 3
14
+ # 1.496235656, # 3.5
15
+ # 1.584893192, # 4
16
+ # 1.678804018, # 4.5
17
+ # 1.77827941, # 5
18
+ # 1.883649089, # 5.5
19
+ # 1.995262315, # 6
20
+ # 2.11348904, # 6.5
21
+ # 2.238721139, # 7
22
+ # 2.371373706, # 7.5
23
+ # 2.511886432, # 8
24
+ # 2.66072506, # 8.5
25
+ # 2.818382931, # 9
26
+ # 2.985382619, # 9.5
27
+ # 3.16227766, # 10 dB
28
+ # 3.349654392, # 10.5
29
+ # 3.548133892, # 11
30
+ # 3.758374043, # 11.5
31
+ # 3.981071706, # 12
32
+ # 4.216965034, # 12.5
33
+ # 4.466835922, # 13
34
+ # 4.73151259, # /13.5
35
+ # 5.011872336, # 14
36
+ # 5.308844442, # 14.5
37
+ # 5.623413252, # 15
38
+ # 5.956621435, # 15.5
39
+ # 6.309573445, # 16
40
+ # 6.683439176, # 16.5
41
+ # 7.079457844, # 17
42
+ # 7.498942093, # 17.5
43
+ # 7.943282347, # 18
44
+ # 8.413951416, # 18.5
45
+ # 8.912509381, # 19
46
+ # 9.440608763, # 19.5
47
+ # 10.0, # 20 dB
48
+ # 10.59253725, # 20.5
49
+ # 11.22018454, # 21
50
+ # 11.88502227, # 21.5
51
+ # 12.58925412, # 22
52
+ # 13.33521432, # 22.5
53
+ # 14.12537545, # 23
54
+ # 14.96235656, # 23.5
55
+ # 15.84893192, # 24
56
+ # 16.78804018, # 24.5
57
+ # 17.7827941, # 25
58
+ # 18.83649089, # 25.5
59
+ # 19.95262315, # 26
60
+ # 21.1348904, # 26.5
61
+ # 22.38721139, # 27
62
+ # 23.71373706, # 27.5
63
+ # 25.11886432, # 28
64
+ # 26.6072506, # 28.5
65
+ # 28.18382931, # 29
66
+ # 29.85382619, # 29.5
67
+ # 31.6227766, # 30 dB
68
+ # 33.49654392, # 30.5
69
+ # 35.48133892, # 31
70
+ # 37.58374043, # 31.5
71
+ # 39.81071706, # 32
72
+ # 42.16965034, # 32.5
73
+ # 44.66835922, # 33
74
+ # 47.3151259, # 33.5
75
+ # 50.11872336, # 34
76
+ # 53.08844442, # 34.5
77
+ # 56.23413252, # 35
78
+ # 59.56621435, # 35.5
79
+ # 63.09573445, # 36
80
+ # 66.83439176, # 36.5
81
+ # 70.79457844, # 37
82
+ # 74.98942093, # 37.5
83
+ # 79.43282347, # 38
84
+ # 84.13951416, # 38.5
85
+ # 89.12509381, # 39
86
+ # 94.40608763, # 39.5
87
+ # 100.0, # 40 dB
88
+ # 105.9253725, # 40.5
89
+ # 112.2018454, # 41
90
+ # 118.8502227, # 41.5
91
+ # 125.8925412, # 42
92
+ # 133.3521432, # 42.5
93
+ # 141.2537545, # 43
94
+ # 149.6235656, # 43.5
95
+ # 158.4893192, # 44
96
+ # 167.8804018, # 44.5
97
+ # 177.827941, # 45
98
+ # 188.3649089, # 45.5
99
+ # 199.5262315, # 46
100
+ # 211.348904, # 46.5
101
+ # 223.8721139, # 47
102
+ # 237.1373706, # 47.5
103
+ # 251.1886432, # 48
104
+ # 266.072506, # 48.5
105
+ # 281.8382931, # 49
106
+ # 298.5382619, # 49.5
107
+ # 316.227766, # 50 dB
108
+ # 334.9654392, # 50.5
109
+ # 354.8133892, # 51
110
+ # 375.8374043, # 51.5
111
+ # 398.1071706, # 52
112
+ # 421.6965034, # 52.5
113
+ # 446.6835922, # 53
114
+ # 473.151259, # 53.5
115
+ # 501.1872336, # 54
116
+ # 530.8844442, # 54.5
117
+ # 562.3413252, # 55
118
+ # 595.6621435, # 55.5
119
+ # 630.9573445, # 56
120
+ # 668.3439176, # 56.5
121
+ # 707.9457844, # 57
122
+ # 749.8942093, # 57.5
123
+ # 794.3282347, # 58
124
+ # 841.3951416, # 58.5
125
+ # 891.2509381, # 59
126
+ # 944.0608763, # 59.5
127
+ # 1000.0, # 60 dB
128
+ # 1059.253725, # 60.5
129
+ # 1122.018454, # 61
130
+ # 1188.502227, # 61.5
131
+ # 1258.925412, # 62
132
+ # 1333.521432, # 62.5
133
+ # 1412.537545, # 63
134
+ # 1496.235656, # 63.5
135
+ # 1584.893192, # 64
136
+ # 1678.804018, # 64.5
137
+ # 1778.27941, # 65
138
+ # 1883.649089, # 65.5
139
+ # 1995.262315, # 66
140
+ # 2113.48904, # 66.5
141
+ # 2238.721139, # 67
142
+ # 2371.373706, # 67.5
143
+ # 2511.886432, # 68
144
+ # 2660.72506, # 68.5
145
+ # 2818.382931, # 69
146
+ # 2985.382619, # 69.5
147
+ # 3162.27766, # 70 dB
148
+ # 3349.654392, # 70.5
149
+ # 3548.133892, # 71
150
+ # 3758.374043, # 71.5
151
+ # 3981.071706, # 72
152
+ #]
153
+ #
154
+ #def self.db_to_linear db
155
+ # raise ArgumentError, "#{db} db is not between -#{MAX_DB} and #{MAX_DB}" unless db.between(-MAX_DB, MAX_DB)
156
+ # db_half_step = (dB * 2.0).to_i
157
+ #
158
+ # if(db_half_step >= 0)
159
+ # linear = DB_TO_SCALAR_TABLE[db_half_step]
160
+ # elsif
161
+ # linear = DB_TO_SCALAR_TABLE[-db_half_step]
162
+ # scalar = 1.0 / scalar
163
+ # end
164
+ #
165
+ # return scalar
166
+ #end
167
+
168
+ MAX_DB_ABS = 6000.0
169
+
170
+ def self.db_to_linear db
171
+ db_abs = db.abs
172
+ raise ArgumentError, "|db| is #{db_abs}, which is greater than the allowed #{MAX_DB_ABS}" if db_abs > MAX_DB_ABS
173
+ return 10.0**(db / 20.0)
174
+ end
175
+
176
+ def self.linear_to_db linear
177
+ raise ArgumentError, "linear value #{linear} is less than or equal to 0.0" if linear <= 0.0
178
+ return 20.0 * Math::log10(linear)
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,15 @@
1
+ module SPCore
2
+ class Interpolation
3
+ # Linear Interpolation Equation:
4
+ #
5
+ # (x3 - x1)(y2 - y1)
6
+ # y3 = ------------------ + y1
7
+ # (x2 - x1)
8
+ #
9
+ def self.linear x1, y1, x2, y2, x3
10
+ y3 = ((x3 - x1) * (y2 - y1)) / (x2 - x1);
11
+ y3 += y1;
12
+ return y3;
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,117 @@
1
+ module SPCore
2
+ class Oscillator
3
+ include Hashmake::HashMakeable
4
+ attr_accessor :wave_type, :amplitude, :dc_offset
5
+ attr_reader :frequency, :sample_rate, :phase_offset
6
+
7
+ WAVE_SINE = :waveSine
8
+ WAVE_TRIANGLE = :waveTriangle
9
+ WAVE_SAWTOOTH = :waveSawtooth
10
+ WAVE_SQUARE = :waveSquare
11
+
12
+ WAVES = [WAVE_SINE, WAVE_TRIANGLE, WAVE_SAWTOOTH, WAVE_SQUARE]
13
+
14
+ ARG_SPECS = [
15
+ Hashmake::ArgSpec.new(:reqd => true, :key => :sample_rate, :type => Float, :validator => ->(a){ a > 0.0 } ),
16
+ Hashmake::ArgSpec.new(:reqd => false, :key => :wave_type, :type => Symbol, :default => WAVE_SINE, :validator => ->(a){ WAVES.include? a } ),
17
+ Hashmake::ArgSpec.new(:reqd => false, :key => :frequency, :type => Float, :default => 1.0, :validator => ->(a){ a > 0.0 } ),
18
+ Hashmake::ArgSpec.new(:reqd => false, :key => :amplitude, :type => Float, :default => 1.0 ),
19
+ Hashmake::ArgSpec.new(:reqd => false, :key => :phase_offset, :type => Float, :default => 0.0 ),
20
+ Hashmake::ArgSpec.new(:reqd => false, :key => :dc_offset, :type => Float, :default => 0.0 ),
21
+ ]
22
+
23
+ def initialize args
24
+ hash_make Oscillator::ARG_SPECS, args
25
+
26
+ @phase_angle_incr = (@frequency * TWO_PI) / @sample_rate
27
+ @current_phase_angle = @phase_offset
28
+ end
29
+
30
+ def sample_rate= sample_rate
31
+ @sample_rate = sample_rate
32
+ self.frequency = @frequency
33
+ end
34
+
35
+ def frequency= frequency
36
+ @frequency = frequency
37
+ @phase_angle_incr = (@frequency * TWO_PI) / @sample_rate
38
+ end
39
+
40
+ def phase_offset= phase_offset
41
+ @current_phase_angle += (phase_offset - @phase_offset);
42
+ @phase_offset = phase_offset
43
+ end
44
+
45
+ def sample
46
+ output = 0.0
47
+
48
+ while(@current_phase_angle < NEG_PI)
49
+ @current_phase_angle += TWO_PI
50
+ end
51
+
52
+ while(@current_phase_angle > Math::PI)
53
+ @current_phase_angle -= TWO_PI
54
+ end
55
+
56
+ case @wave_type
57
+ when WAVE_SINE
58
+ output = @amplitude * sine(@current_phase_angle) + @dc_offset
59
+ when WAVE_TRIANGLE
60
+ output = @amplitude * triangle(@current_phase_angle) + @dc_offset
61
+ when WAVE_SQUARE
62
+ output = @amplitude * square(@current_phase_angle) + @dc_offset
63
+ when WAVE_SAWTOOTH
64
+ output = @amplitude * sawtooth(@current_phase_angle) + @dc_offset
65
+ else
66
+ raise "Encountered unexpected wave type #{@wave_type}"
67
+ end
68
+
69
+ @current_phase_angle += @phase_angle_incr
70
+ return output
71
+ end
72
+
73
+ K_SINE_B = 4.0 / Math::PI
74
+ K_SINE_C = -4.0 / (Math::PI * Math::PI)
75
+ # Q = 0.775
76
+ K_SINE_P = 0.225
77
+
78
+ # generate a sine wave:
79
+ # input range: -PI to PI
80
+ # ouput range: -1 to 1
81
+ def sine x
82
+ y = K_SINE_B * x + K_SINE_C * x * x.abs
83
+ # for extra precision
84
+ y = K_SINE_P * (y * y.abs - y) + y # Q * y + P * y * y.abs
85
+
86
+ # sin normally output outputs -1 to 1, so to adjust
87
+ # it to output 0 to 1, return (y*0.5)+0.5
88
+ return y
89
+ end
90
+
91
+ K_TRIANGLE_A = 2.0 / Math::PI;
92
+
93
+ # generate a triangle wave:
94
+ # input range: -PI to PI
95
+ # ouput range: -1 to 1
96
+ def triangle x
97
+ (K_TRIANGLE_A * x).abs - 1.0
98
+ end
99
+
100
+ # generate a square wave (50% duty cycle):
101
+ # input range: -PI to PI
102
+ # ouput range: 0 to 1
103
+ def square x
104
+ (x >= 0.0) ? 1.0 : -1.0
105
+ end
106
+
107
+ K_SAWTOOTH_A = 1.0 / Math::PI
108
+
109
+ # generate a sawtooth wave:
110
+ # input range: -PI to PI
111
+ # ouput range: -1 to 1
112
+ def sawtooth x
113
+ K_SAWTOOTH_A * x
114
+ end
115
+
116
+ end
117
+ end