radio 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -6
- data/lib/radio.rb +5 -4
- data/lib/radio/controls/civ.rb +284 -0
- data/lib/radio/controls/si570avr.rb +0 -1
- data/lib/radio/filter.rb +33 -34
- data/lib/radio/filters/agc.rb +65 -0
- data/lib/radio/filters/fir.rb +264 -33
- data/lib/radio/filters/iq.rb +142 -0
- data/lib/radio/gif.rb +38 -40
- data/lib/radio/psk31/rx.rb +17 -7
- data/lib/radio/rig.rb +25 -3
- data/lib/radio/rig/rx.rb +11 -6
- data/lib/radio/rig/spectrum.rb +54 -43
- data/lib/radio/rig/ssb.rb +190 -0
- data/lib/radio/rig/ssb.rb_ +150 -0
- data/lib/radio/{input.rb → signal.rb} +16 -12
- data/lib/radio/{inputs → signals}/alsa.rb +20 -23
- data/lib/radio/signals/coreaudio.rb +136 -0
- data/lib/radio/{inputs → signals}/file.rb +8 -8
- data/lib/radio/{inputs → signals}/wav.rb +41 -28
- data/lib/radio/utils/firpm.rb +395 -0
- data/lib/radio/utils/misc.rb +39 -0
- data/lib/radio/utils/window.rb +37 -0
- data/lib/radio/version.rb +1 -1
- data/www/index.erb +81 -12
- data/www/lo.erb +2 -2
- data/www/setup/af.erb +72 -0
- data/www/setup/lo.erb +31 -0
- data/www/setup/{input.erb → rx.erb} +11 -10
- data/www/ssb.erb +20 -0
- data/www/tune.erb +5 -0
- data/www/waterfall.erb +1 -1
- metadata +38 -26
- data/lib/radio/inputs/coreaudio.rb +0 -102
- data/lib/radio/psk31/fir_coef.rb +0 -292
- data/test/test.rb +0 -76
- data/test/wav/bpsk8k.wav +0 -0
- data/test/wav/qpsk8k.wav +0 -0
- data/test/wav/ssb.wav +0 -0
data/lib/radio/gif.rb
CHANGED
@@ -20,64 +20,62 @@
|
|
20
20
|
class Radio
|
21
21
|
class Gif
|
22
22
|
|
23
|
-
# This is
|
23
|
+
# This is a very fast GIF generator for waterfalls.
|
24
24
|
# It requires 128 RGB colors, the first is unused and
|
25
25
|
# reserved for transparency if we ever need it.
|
26
|
-
|
27
|
-
|
26
|
+
# data is expected to be an NArray
|
27
|
+
def self.waterfall colors, data, min=nil, max=nil
|
28
|
+
width = data[true,0].size
|
29
|
+
height = data[0,true].size
|
28
30
|
gif = [
|
29
31
|
'GIF87a', # Start Header
|
30
|
-
|
31
|
-
|
32
|
+
width,
|
33
|
+
height,
|
32
34
|
0xF6, # 128 24-bit colors
|
33
35
|
0x00, # background color index
|
34
36
|
0x00 # aspect ratio
|
35
37
|
].pack 'a6vvCCC'
|
36
|
-
|
37
38
|
gif += colors.flatten.pack 'C*'
|
38
|
-
|
39
39
|
gif += [
|
40
40
|
0x2C, # Start Image Block
|
41
41
|
0x0000, # Left position
|
42
42
|
0x0000, # Top position
|
43
|
-
|
44
|
-
|
43
|
+
width,
|
44
|
+
height,
|
45
45
|
0x00, # No color table, not interlaced
|
46
46
|
0x07 # LZW code size
|
47
47
|
].pack('CvvvvCC')
|
48
|
-
|
49
|
-
data.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
]
|
66
|
-
|
67
|
-
|
48
|
+
data = data.reshape(data.size)
|
49
|
+
min ||= data.min
|
50
|
+
max ||= data.max
|
51
|
+
range = [1e-99, max - min].max
|
52
|
+
data -= min # will dup
|
53
|
+
data.div!(range).mul!(126).add!(1)
|
54
|
+
# add in a frame+reset every 126 plus the 4 byte end block
|
55
|
+
predict = gif.size + (data.size+125) / 126 * 2 + data.size + 4
|
56
|
+
out = NArray.byte predict
|
57
|
+
out[0...gif.size] = NArray.to_na gif, NArray::BYTE, gif.size
|
58
|
+
i = 0
|
59
|
+
pos = gif.size
|
60
|
+
while i < data.size
|
61
|
+
qty = [126,data.size-i].min
|
62
|
+
out[pos] = qty+1
|
63
|
+
pos+=1
|
64
|
+
if qty == 1
|
65
|
+
out[pos] = data[i]
|
66
|
+
else
|
67
|
+
out[pos...pos+qty] = data[i...i+qty]
|
68
68
|
end
|
69
|
-
|
69
|
+
pos+=qty
|
70
|
+
out[pos] = 0x80 # LZW reset
|
71
|
+
pos+=1
|
72
|
+
i += qty
|
70
73
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
0x3B # end gif
|
77
|
-
].pack('C*')
|
78
|
-
|
79
|
-
return gif
|
80
|
-
|
74
|
+
out[pos] = 0x01, # end image blocks
|
75
|
+
out[pos+1] = 0x81, # final image block: LZW end
|
76
|
+
out[pos+2] = 0x00, # end image blocks
|
77
|
+
out[pos+3] = 0x3B # end gif
|
78
|
+
out.to_s
|
81
79
|
end
|
82
80
|
|
83
81
|
end
|
data/lib/radio/psk31/rx.rb
CHANGED
@@ -18,10 +18,18 @@ class Radio
|
|
18
18
|
class PSK31
|
19
19
|
|
20
20
|
class Rx
|
21
|
+
|
22
|
+
include Utils
|
23
|
+
|
24
|
+
# 16 Hz bw LP filter for data recovery
|
25
|
+
FIR_BIT = FirPM.new numtaps: 65, type: :bandpass,
|
26
|
+
bands: [0.0,0.03125,0.0625,0.5], desired: [1,1,0,0], weights: [1,286]
|
21
27
|
|
22
28
|
def initialize frequency
|
23
|
-
|
24
|
-
|
29
|
+
mix = frequency.to_f / 8000
|
30
|
+
fir_500hz = firpm numtaps: 35, type: :bandpass,
|
31
|
+
bands: [0,0.0125,0.125,0.5], desired: [1,1,0,0], weights: [1,10]
|
32
|
+
@dec_filter = Filter.new mix:mix, decimate:16, fir:fir_500hz
|
25
33
|
@bit_filter = Filter.new fir:FIR_BIT
|
26
34
|
@bit_detect = BitDetect.new
|
27
35
|
@decoder = Decoder.new
|
@@ -29,11 +37,13 @@ class Radio
|
|
29
37
|
|
30
38
|
def call data
|
31
39
|
decoded = ''
|
32
|
-
@dec_filter.call data do |
|
33
|
-
@bit_filter.call
|
34
|
-
|
35
|
-
@
|
36
|
-
|
40
|
+
@dec_filter.call data do |data|
|
41
|
+
@bit_filter.call data do |data|
|
42
|
+
data.each do |iq|
|
43
|
+
@bit_detect.call iq do |iq|
|
44
|
+
@decoder.call iq do |symbol|
|
45
|
+
decoded += symbol
|
46
|
+
end
|
37
47
|
end
|
38
48
|
end
|
39
49
|
end
|
data/lib/radio/rig.rb
CHANGED
@@ -17,30 +17,52 @@ class Radio
|
|
17
17
|
|
18
18
|
class Rig
|
19
19
|
|
20
|
+
include Utils
|
20
21
|
include Spectrum
|
21
22
|
include Rx
|
22
23
|
include LO
|
24
|
+
include SSB
|
23
25
|
|
24
26
|
def initialize
|
25
27
|
@semaphore = Mutex.new
|
28
|
+
@listeners = {}
|
29
|
+
@listeners_mutex = Mutex.new
|
26
30
|
super
|
27
31
|
end
|
28
32
|
|
33
|
+
def register queue
|
34
|
+
@listeners_mutex.synchronize do
|
35
|
+
@listeners[queue] = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def deregister queue
|
40
|
+
@listeners_mutex.synchronize do
|
41
|
+
@listeners.delete queue
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
29
45
|
def rate
|
30
46
|
@semaphore.synchronize do
|
31
47
|
return @rx.rate if @rx
|
32
|
-
return @tx.rate if @tx
|
33
48
|
return 0
|
34
49
|
end
|
35
50
|
end
|
36
51
|
|
37
52
|
def iq?
|
38
53
|
@semaphore.synchronize do
|
39
|
-
return @rx.
|
40
|
-
return @tx.channels == 2 if @tx
|
54
|
+
return @rx.input_channels == 2 if @rx
|
41
55
|
return false
|
42
56
|
end
|
43
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def distribute_to_listeners data
|
62
|
+
@listeners_mutex.synchronize do
|
63
|
+
@listeners.each {|k,v| k << data }
|
64
|
+
end
|
65
|
+
end
|
44
66
|
|
45
67
|
end
|
46
68
|
end
|
data/lib/radio/rig/rx.rb
CHANGED
@@ -18,14 +18,14 @@ class Radio
|
|
18
18
|
class Rig
|
19
19
|
|
20
20
|
module Rx
|
21
|
-
|
21
|
+
|
22
22
|
def initialize
|
23
23
|
@rx = @rx_thread = nil
|
24
24
|
super
|
25
25
|
end
|
26
26
|
|
27
27
|
def rx= input
|
28
|
-
old_rx_thread =
|
28
|
+
old_rx_thread = false
|
29
29
|
@semaphore.synchronize do
|
30
30
|
if @rx
|
31
31
|
@rx.stop
|
@@ -46,10 +46,15 @@ class Radio
|
|
46
46
|
# We want to work with powers of two but sample rates
|
47
47
|
# are multiples of 8000. Let's use a lag of 32ms or 31.25 baud.
|
48
48
|
def rx_thread
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
begin
|
50
|
+
qty_samples = @rx.rate / 125 * 4
|
51
|
+
loop do
|
52
|
+
distribute_to_listeners @rx.in qty_samples
|
53
|
+
end
|
54
|
+
rescue Exception => e
|
55
|
+
p "ERROR #{e.message}: #{e.backtrace.first}" #TODO logger
|
56
|
+
# This will be picked up by next call to rx=
|
57
|
+
raise e
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
data/lib/radio/rig/spectrum.rb
CHANGED
@@ -19,7 +19,10 @@ class Radio
|
|
19
19
|
|
20
20
|
def initialize
|
21
21
|
@spectrum_buf = {}
|
22
|
+
@spectrum_semaphore = Mutex.new
|
22
23
|
@spectrum_pending = Hash.new {|h,k|h[k]=[]}
|
24
|
+
register @spectrum_queue = Queue.new
|
25
|
+
Thread.start &method(:spectrum_collector)
|
23
26
|
super
|
24
27
|
end
|
25
28
|
|
@@ -31,7 +34,7 @@ class Radio
|
|
31
34
|
# Results are not queued; missed processing is skipped.
|
32
35
|
# This is meant to be an ideal interface for Web UI work, not signal analysis.
|
33
36
|
def spectrum size, frequency=1.0, keep_alive=2.0
|
34
|
-
@
|
37
|
+
@spectrum_semaphore.synchronize do
|
35
38
|
@spectrum_pending[[size, frequency, keep_alive]] << Thread.current
|
36
39
|
end
|
37
40
|
sleep
|
@@ -40,53 +43,61 @@ class Radio
|
|
40
43
|
|
41
44
|
private
|
42
45
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# Handle the data for each active buffer
|
54
|
-
@spectrum_buf.each do |k,v|
|
55
|
-
time, data, result = v
|
56
|
-
size, frequency, keep_alive = k
|
57
|
-
collect_size = size * ((Float === samples[0]) ? 2 : 1)
|
58
|
-
data.push samples
|
59
|
-
buf_size = data.reduce(0){|a,b|a+b.size}
|
60
|
-
size_freq = collect_size*frequency
|
61
|
-
# Wait until we have enough data and the time is right
|
62
|
-
if buf_size > collect_size and buf_size > size_freq
|
63
|
-
# Discard any extra data we won't use (frequency>1)
|
64
|
-
while buf_size - data.first.size > collect_size
|
65
|
-
buf_size -= data.shift.size
|
46
|
+
def spectrum_collector
|
47
|
+
begin
|
48
|
+
loop do
|
49
|
+
samples = @spectrum_queue.pop
|
50
|
+
time_now = Time.now
|
51
|
+
@spectrum_semaphore.synchronize do
|
52
|
+
# Ensure buffers for all requested setups are in place
|
53
|
+
@spectrum_pending.keys.each do |size, frequency, keep_alive|
|
54
|
+
@spectrum_buf[[size, frequency, keep_alive]] ||= [nil,[],nil]
|
55
|
+
@spectrum_buf[[size, frequency, keep_alive]][0] = time_now
|
66
56
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
57
|
+
# Stop running anything that's not being used anymore
|
58
|
+
@spectrum_buf.delete_if { |k,v| v[0] < time_now - k[2] }
|
59
|
+
# Handle the data for each active buffer
|
60
|
+
@spectrum_buf.each do |k,v|
|
61
|
+
time, data, result = v
|
62
|
+
size, frequency, keep_alive = k
|
63
|
+
collect_size = size * ((Float === samples[0]) ? 2 : 1)
|
64
|
+
data.push samples
|
65
|
+
buf_size = data.reduce(0){|a,b|a+b.size}
|
66
|
+
size_freq = collect_size*frequency
|
67
|
+
# Wait until we have enough data and the time is right
|
68
|
+
if buf_size > collect_size and buf_size > size_freq
|
69
|
+
# Discard any extra data we won't use (frequency>1)
|
70
|
+
while buf_size - data.first.size > collect_size
|
71
|
+
buf_size -= data.shift.size
|
72
|
+
end
|
73
|
+
pending_key = [size, frequency, keep_alive]
|
74
|
+
if @spectrum_pending.has_key? pending_key
|
75
|
+
spectrum_data = NArray.to_na data
|
76
|
+
spectrum_data.reshape! spectrum_data.total
|
77
|
+
spectrum_out = FFTW3.fft(spectrum_data[-collect_size..-1])
|
78
|
+
if spectrum_out.size == size
|
79
|
+
result = NArray.sfloat size
|
80
|
+
result[0...size/2] = spectrum_out[size/2..-1].div!(size)
|
81
|
+
result[size/2..-1] = spectrum_out[0...size/2].div!(size)
|
82
|
+
v[2] = result.abs
|
83
|
+
else
|
84
|
+
v[2] = spectrum_out[0...size].div!(size).abs
|
85
|
+
end
|
86
|
+
@spectrum_pending[pending_key].each(&:wakeup)
|
87
|
+
@spectrum_pending.delete pending_key
|
88
|
+
end
|
89
|
+
# Discard enough old buffers to accommodate the frequency
|
90
|
+
trim_size = [0, collect_size - size*frequency].max
|
91
|
+
while buf_size > trim_size
|
92
|
+
buf_size -= data.shift.size
|
93
|
+
end
|
79
94
|
end
|
80
|
-
@spectrum_pending[pending_key].each(&:wakeup)
|
81
|
-
@spectrum_pending.delete pending_key
|
82
|
-
end
|
83
|
-
# Discard enough old buffers to accommodate the frequency
|
84
|
-
trim_size = [0, collect_size - size*frequency].max
|
85
|
-
while buf_size > trim_size
|
86
|
-
buf_size -= data.shift.size
|
87
95
|
end
|
88
96
|
end
|
89
97
|
end
|
98
|
+
rescue Exception => e
|
99
|
+
p "ERROR #{e.message}: #{e.backtrace.first}" #TODO logger
|
100
|
+
raise e
|
90
101
|
end
|
91
102
|
end
|
92
103
|
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# Copyright 2012 The ham21/radio Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
class Radio
|
17
|
+
|
18
|
+
class Rig
|
19
|
+
|
20
|
+
module SSB
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@ssb_semaphore = Mutex.new
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def af= output
|
28
|
+
if output and !iq?
|
29
|
+
raise 'requires i/q signal'
|
30
|
+
end
|
31
|
+
old_af_thread = false
|
32
|
+
@ssb_semaphore.synchronize do
|
33
|
+
deregister @af_queue if @af_queue
|
34
|
+
@af_queue = nil
|
35
|
+
old_rate = 0
|
36
|
+
if @af
|
37
|
+
@af.stop
|
38
|
+
old_af_thread = @af_thread
|
39
|
+
old_af_thread.kill if old_af_thread
|
40
|
+
end
|
41
|
+
if @af = output
|
42
|
+
@bfo_filter = bfo_mixer
|
43
|
+
@af_filter = af_generate_filter
|
44
|
+
@agc = Filter.new :agc => true
|
45
|
+
@iq = Filter.new :iq => true
|
46
|
+
@mix = mixmix
|
47
|
+
|
48
|
+
#TODO need a nicer pattern to force JIT compile
|
49
|
+
@bfo_filter.call(NArray.scomplex(1)) {}
|
50
|
+
@af_filter.call(NArray.sfloat(1)) {}
|
51
|
+
@agc.call(NArray.sfloat(1)) {}
|
52
|
+
@iq.call(NArray.scomplex(1)) {}
|
53
|
+
|
54
|
+
register @af_queue = Queue.new
|
55
|
+
@af_thread = Thread.new &method(:af_thread)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
old_af_thread.join if old_af_thread
|
59
|
+
end
|
60
|
+
|
61
|
+
def tune= freq
|
62
|
+
@ssb_semaphore.synchronize do
|
63
|
+
@freq = freq
|
64
|
+
set_mixers
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_lsb
|
69
|
+
@ssb_semaphore.synchronize do
|
70
|
+
if @bfo_filter
|
71
|
+
@ssb_mode = :lsb
|
72
|
+
@bfo_filter.decimation_fir = @lsb_coef
|
73
|
+
set_mixers
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_usb
|
79
|
+
@ssb_semaphore.synchronize do
|
80
|
+
if @bfo_filter
|
81
|
+
@ssb_mode = :usb
|
82
|
+
@bfo_filter.decimation_fir = @usb_coef
|
83
|
+
set_mixers
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def set_mixers
|
91
|
+
return unless @af and @bfo_filter
|
92
|
+
freq = @freq.to_f
|
93
|
+
if @ssb_mode == :usb
|
94
|
+
@bfo_filter.decimation_mix = (freq-1300) / rate
|
95
|
+
@mix.mix = +1300.0 / 6000
|
96
|
+
else
|
97
|
+
@bfo_filter.decimation_mix = (freq+1300) / rate
|
98
|
+
@mix.mix = -1300.0 / 6000
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def af_thread
|
103
|
+
begin
|
104
|
+
loop do
|
105
|
+
in_data = @af_queue.pop
|
106
|
+
@ssb_semaphore.synchronize do
|
107
|
+
if @af_filter and @af
|
108
|
+
@bfo_filter.call(in_data) do |iq|
|
109
|
+
|
110
|
+
@mix.call!(iq) do |iq|
|
111
|
+
@iq.call!(iq) do |iq|
|
112
|
+
|
113
|
+
@agc.call(iq.real + iq.imag) do |pcm|
|
114
|
+
@af_filter.call(pcm) do |data|
|
115
|
+
@af.out data
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
rescue Exception => e
|
125
|
+
p "ERROR #{e.message}: #{e.backtrace.first}" #TODO logger
|
126
|
+
raise e
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def mixmix
|
131
|
+
rate = 6000
|
132
|
+
bands = []
|
133
|
+
bands[0] = 0.0 / rate
|
134
|
+
bands[1] = 2400.0 / rate
|
135
|
+
bands[2] = 2600.0 / rate
|
136
|
+
bands[3] = 0.5
|
137
|
+
taps = kaiser_estimate passband:0.01, stopband:0.01, transition:bands[2]-bands[1]
|
138
|
+
fir = firpm numtaps: taps, type: :bandpass,
|
139
|
+
bands: bands, desired: [1,1,0,0], weights: [1,1000]
|
140
|
+
Filter.new :mix => (1300.0 / rate), :fir => fir
|
141
|
+
end
|
142
|
+
|
143
|
+
def bfo_mixer
|
144
|
+
rate = self.rate.to_f
|
145
|
+
decimate = rate / 6000
|
146
|
+
unless decimate == decimate.floor
|
147
|
+
raise "unable to filter #{rate} to 6000"
|
148
|
+
end
|
149
|
+
bands = []
|
150
|
+
bands[0] = 0.0 / rate
|
151
|
+
bands[1] = 1200.0 / rate
|
152
|
+
bands[2] = 1500.0 / rate
|
153
|
+
bands[3] = 0.5
|
154
|
+
taps = kaiser_estimate passband:0.1, stopband:0.1, transition:bands[2]-bands[1]
|
155
|
+
p taps
|
156
|
+
taps = 271
|
157
|
+
p taps
|
158
|
+
fir1 = firpm numtaps: taps, type: :bandpass,
|
159
|
+
bands: bands, desired: [1,1,0,0], weights: [1,10000]
|
160
|
+
fir2 = firpm numtaps: taps, type: :hilbert,
|
161
|
+
bands: bands, desired: [1,1,0,0], weights: [1,10000]
|
162
|
+
@usb_coef = NArray.scomplex fir1.size
|
163
|
+
@usb_coef[true] = fir1.to_a
|
164
|
+
@usb_coef.imag = fir2.to_a
|
165
|
+
@lsb_coef = @usb_coef.conj
|
166
|
+
@ssb_mode = :usb
|
167
|
+
Filter.new fir:@usb_coef, decimate:decimate, mix:0
|
168
|
+
end
|
169
|
+
|
170
|
+
def af_generate_filter
|
171
|
+
return nil unless @af
|
172
|
+
bands = [0,nil,nil,0.5]
|
173
|
+
bands[1] = 2400.0 / @af.rate
|
174
|
+
bands[2] = 3000.0 / @af.rate
|
175
|
+
taps = kaiser_estimate passband:0.01, stopband:0.01, transition:bands[2]-bands[1]
|
176
|
+
fir = firpm numtaps: taps, type: :bandpass,
|
177
|
+
bands: bands, desired: [1,1,0,0], weights: [1,1000]
|
178
|
+
interpolate = @af.rate.to_f / 6000
|
179
|
+
unless interpolate == interpolate.floor
|
180
|
+
raise "unable to filter 6000 to #{@af.rate}"
|
181
|
+
end
|
182
|
+
Filter.new fir:fir, interpolate:interpolate
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|