spcore 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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