radio 0.0.2 → 0.0.3
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.
- 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
|
+
|