radio 0.0.1 → 0.0.2
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/LICENSE +202 -0
- data/README.md +67 -1
- data/bin/radio-server +8 -0
- data/lib/radio.rb +41 -1
- data/lib/radio/controls/null.rb +32 -0
- data/lib/radio/controls/si570avr.rb +77 -0
- data/lib/radio/filter.rb +69 -0
- data/lib/radio/filters/fir.rb +65 -0
- data/lib/radio/gif.rb +84 -0
- data/lib/radio/http/file.rb +88 -0
- data/lib/radio/http/script.rb +133 -0
- data/lib/radio/http/server.rb +94 -0
- data/lib/radio/http/session.rb +43 -0
- data/lib/radio/input.rb +62 -0
- data/lib/radio/inputs/alsa.rb +135 -0
- data/lib/radio/inputs/coreaudio.rb +102 -0
- data/lib/radio/inputs/file.rb +60 -0
- data/lib/radio/inputs/wav.rb +124 -0
- data/lib/radio/psk31/bit_detect.rb +19 -4
- data/lib/radio/psk31/decoder.rb +18 -3
- data/lib/radio/psk31/fir_coef.rb +6 -1
- data/lib/radio/psk31/rx.rb +27 -32
- data/lib/radio/psk31/varicode.rb +15 -0
- data/lib/radio/rig.rb +46 -0
- data/lib/radio/rig/lo.rb +57 -0
- data/lib/radio/rig/rx.rb +61 -0
- data/lib/radio/rig/spectrum.rb +96 -0
- data/lib/radio/version.rb +3 -0
- data/test/test.rb +76 -0
- data/test/wav/bpsk8k.wav +0 -0
- data/test/wav/qpsk8k.wav +0 -0
- data/test/wav/ssb.wav +0 -0
- data/www/index.erb +40 -0
- data/www/jquery-1.7.js +9300 -0
- data/www/lo.erb +21 -0
- data/www/setup/input.erb +64 -0
- data/www/setup/lo.erb +36 -0
- data/www/waterfall.erb +20 -0
- metadata +77 -9
- data/lib/radio/psk31/filters.rb +0 -220
@@ -1,3 +1,18 @@
|
|
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
|
+
|
1
16
|
class Radio
|
2
17
|
|
3
18
|
class PSK31
|
@@ -8,7 +23,7 @@ class Radio
|
|
8
23
|
CHANGE_DELAY = 5
|
9
24
|
|
10
25
|
def initialize
|
11
|
-
@averages = Array.new
|
26
|
+
@averages = Array.new 16
|
12
27
|
reset
|
13
28
|
end
|
14
29
|
|
@@ -20,10 +35,10 @@ class Radio
|
|
20
35
|
@change_at = 0
|
21
36
|
end
|
22
37
|
|
23
|
-
def call
|
24
|
-
yield if @phase == @peak
|
38
|
+
def call iq
|
39
|
+
yield iq if @phase == @peak
|
25
40
|
@peak = @next_peak if @phase == @change_at
|
26
|
-
energy =
|
41
|
+
energy = iq.real**2 + iq.imag**2
|
27
42
|
@averages[@phase] = (1.0-1.0/AVG_SAMPLES)*@averages[@phase] + (1.0/AVG_SAMPLES)*energy
|
28
43
|
@phase += 1
|
29
44
|
if @phase > 15
|
data/lib/radio/psk31/decoder.rb
CHANGED
@@ -1,3 +1,18 @@
|
|
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
|
+
|
1
16
|
class Radio
|
2
17
|
class PSK31
|
3
18
|
|
@@ -19,11 +34,11 @@ class Radio
|
|
19
34
|
@prev_bit = false
|
20
35
|
end
|
21
36
|
|
22
|
-
def call
|
37
|
+
def call iq
|
23
38
|
@prev_i = @this_i
|
24
39
|
@prev_q = @this_q
|
25
|
-
@this_i =
|
26
|
-
@this_q =
|
40
|
+
@this_i = iq.real
|
41
|
+
@this_q = iq.imag
|
27
42
|
vect_y = @prev_i*@this_i + @prev_q*@this_q
|
28
43
|
if @mode == :bpsk
|
29
44
|
bit = vect_y >= 0.0 ? 1 : 0
|
data/lib/radio/psk31/fir_coef.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
# These are from LGPL licensed programs if they are even copyrightable.
|
2
|
+
# They will be removed after I implement remez in Ruby.
|
3
|
+
|
4
|
+
|
1
5
|
class Radio
|
2
6
|
class PSK31
|
3
7
|
|
8
|
+
# 48000 to 8000 decimation
|
4
9
|
FIR_DEC6 = [
|
5
10
|
-0.001026086585,
|
6
11
|
-0.002131424398,
|
@@ -165,7 +170,7 @@ class Radio
|
|
165
170
|
# edge edge
|
166
171
|
# 1 0.0 .0125 1.0 1
|
167
172
|
# 2 .125 .5 .000001 10
|
168
|
-
|
173
|
+
FIR_DEC = [
|
169
174
|
-0.00021203644,
|
170
175
|
-0.00070252426,
|
171
176
|
-0.0016680526,
|
data/lib/radio/psk31/rx.rb
CHANGED
@@ -1,49 +1,44 @@
|
|
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
|
+
|
1
16
|
class Radio
|
2
17
|
|
3
18
|
class PSK31
|
4
19
|
|
5
20
|
class Rx
|
6
21
|
|
7
|
-
def initialize
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
:dec4 => FIR_DEC4,
|
12
|
-
:bit => FIR_BIT
|
22
|
+
def initialize frequency
|
23
|
+
phase_inc = PI2 * frequency / 8000
|
24
|
+
@dec_filter = Filter.new mix:phase_inc, decimate:16, fir:FIR_DEC16
|
25
|
+
@bit_filter = Filter.new fir:FIR_BIT
|
13
26
|
@bit_detect = BitDetect.new
|
14
27
|
@decoder = Decoder.new
|
15
28
|
end
|
16
29
|
|
17
|
-
def call
|
30
|
+
def call data
|
18
31
|
decoded = ''
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
|
32
|
+
@dec_filter.call data do |iq|
|
33
|
+
@bit_filter.call iq do |iq|
|
34
|
+
@bit_detect.call iq do |iq|
|
35
|
+
@decoder.call iq do |symbol|
|
36
|
+
decoded += symbol
|
37
|
+
end
|
23
38
|
end
|
24
39
|
end
|
25
40
|
end
|
26
|
-
decoded
|
27
|
-
end
|
28
|
-
|
29
|
-
def frequency= frequency
|
30
|
-
if frequency != @filter.frequency
|
31
|
-
@filter.frequency = frequency
|
32
|
-
@filter.recalc_phase_inc
|
33
|
-
reset
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# To compensate for bad clock in A-D conversion
|
38
|
-
def adjust_sample_clock ppm
|
39
|
-
@filter.clock = 8000.0 * ppm / 1000000 + 8000
|
40
|
-
@filter.recalc_phase_inc
|
41
|
-
end
|
42
|
-
|
43
|
-
def reset
|
44
|
-
@filter.reset
|
45
|
-
@bit_detect.reset
|
46
|
-
@decoder.reset
|
41
|
+
yield decoded
|
47
42
|
end
|
48
43
|
|
49
44
|
end
|
data/lib/radio/psk31/varicode.rb
CHANGED
@@ -1,3 +1,18 @@
|
|
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
|
+
|
1
16
|
class Radio
|
2
17
|
|
3
18
|
class PSK31
|
data/lib/radio/rig.rb
ADDED
@@ -0,0 +1,46 @@
|
|
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
|
+
include Spectrum
|
21
|
+
include Rx
|
22
|
+
include LO
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@semaphore = Mutex.new
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def rate
|
30
|
+
@semaphore.synchronize do
|
31
|
+
return @rx.rate if @rx
|
32
|
+
return @tx.rate if @tx
|
33
|
+
return 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def iq?
|
38
|
+
@semaphore.synchronize do
|
39
|
+
return @rx.channels == 2 if @rx
|
40
|
+
return @tx.channels == 2 if @tx
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/radio/rig/lo.rb
ADDED
@@ -0,0 +1,57 @@
|
|
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
|
+
class Rig
|
18
|
+
module LO
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@lo = Controls::Null.new
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accepts an instance of any LO control to use.
|
26
|
+
# Float for the frequency in MHz (sub-Hz ok).
|
27
|
+
# Integer for the frequency in Hz.
|
28
|
+
def lo= freq_or_control
|
29
|
+
@semaphore.synchronize do
|
30
|
+
if Numeric === freq_or_control
|
31
|
+
@lo.lo = if freq_or_control.integer?
|
32
|
+
freq_or_control.to_f / 1000000
|
33
|
+
else
|
34
|
+
freq_or_control
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@lo.stop if @lo
|
38
|
+
@lo = freq_or_control
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a float of the LO frequency in MHz.
|
44
|
+
# This will read from the actual device for the case of an
|
45
|
+
# operator adjusting outside this application, such as with
|
46
|
+
# the main dial of a stand-alone radio.
|
47
|
+
def lo
|
48
|
+
@semaphore.synchronize do
|
49
|
+
@lo.lo
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
data/lib/radio/rig/rx.rb
ADDED
@@ -0,0 +1,61 @@
|
|
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 Rx
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@rx = @rx_thread = nil
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def rx= input
|
28
|
+
old_rx_thread = new_thread = false
|
29
|
+
@semaphore.synchronize do
|
30
|
+
if @rx
|
31
|
+
@rx.stop
|
32
|
+
old_rx_thread = @rx_thread
|
33
|
+
old_rx_thread.kill
|
34
|
+
end
|
35
|
+
if @rx = input
|
36
|
+
@rx_thread = Thread.new &method(:rx_thread)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
old_rx_thread.join if old_rx_thread
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# DSP under an OS requires chunked and streaming processing
|
45
|
+
# as opposed to handling every sample as it arrives.
|
46
|
+
# We want to work with powers of two but sample rates
|
47
|
+
# are multiples of 8000. Let's use a lag of 32ms or 31.25 baud.
|
48
|
+
def rx_thread
|
49
|
+
qty_samples = @rx.rate / 125 * 4
|
50
|
+
loop do
|
51
|
+
samples = @rx.call qty_samples
|
52
|
+
spectrum_collect samples
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,96 @@
|
|
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
|
+
class Rig
|
18
|
+
module Spectrum
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@spectrum_buf = {}
|
22
|
+
@spectrum_pending = Hash.new {|h,k|h[k]=[]}
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Block until the next collection of data is ready (size*frequency samples).
|
27
|
+
# Result is NArray of floats suitable for display purposes.
|
28
|
+
# size must be a power of 2 for good performance (use GPU for visual scaling)
|
29
|
+
# frequency may be greater or less than 1.0
|
30
|
+
# keep_alive in secs set high enough so intervals between results are accurate
|
31
|
+
# Results are not queued; missed processing is skipped.
|
32
|
+
# This is meant to be an ideal interface for Web UI work, not signal analysis.
|
33
|
+
def spectrum size, frequency=1.0, keep_alive=2.0
|
34
|
+
@semaphore.synchronize do
|
35
|
+
@spectrum_pending[[size, frequency, keep_alive]] << Thread.current
|
36
|
+
end
|
37
|
+
sleep
|
38
|
+
@spectrum_buf[[size, frequency, keep_alive]][2]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def spectrum_collect samples
|
44
|
+
time_now = Time.now
|
45
|
+
@semaphore.synchronize do
|
46
|
+
# Ensure buffers for all requested setups are in place
|
47
|
+
@spectrum_pending.keys.each do |size, frequency, keep_alive|
|
48
|
+
@spectrum_buf[[size, frequency, keep_alive]] ||= [nil,[],nil]
|
49
|
+
@spectrum_buf[[size, frequency, keep_alive]][0] = time_now
|
50
|
+
end
|
51
|
+
# Stop running anything that's not being used anymore
|
52
|
+
@spectrum_buf.delete_if { |k,v| v[0] < time_now - k[2] }
|
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
|
66
|
+
end
|
67
|
+
pending_key = [size, frequency, keep_alive]
|
68
|
+
if @spectrum_pending.has_key? pending_key
|
69
|
+
spectrum_data = NArray.to_na data
|
70
|
+
spectrum_data.reshape! spectrum_data.total
|
71
|
+
spectrum_out = FFTW3.fft(spectrum_data[-collect_size..-1])
|
72
|
+
if spectrum_out.size == size
|
73
|
+
result = NArray.sfloat size
|
74
|
+
result[0...size/2] = spectrum_out[size/2..-1].div!(size)
|
75
|
+
result[size/2..-1] = spectrum_out[0...size/2].div!(size)
|
76
|
+
v[2] = result.abs
|
77
|
+
else
|
78
|
+
v[2] = spectrum_out[0...size].div!(size).abs
|
79
|
+
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
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|