cw 0.3.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.cw_config +9 -18
- data/.gitignore +1 -0
- data/README.md +10 -4
- data/VERSION +1 -1
- data/calculator.rb +63 -0
- data/chart.rb +33 -0
- data/chart2.rb +35 -0
- data/chart3.rb +47 -0
- data/chart4.rb +45 -0
- data/cw.gemspec +5 -3
- data/data/callsign/callsign.yaml +78 -0
- data/data/code/code.yaml +215 -0
- data/example.rb +18 -0
- data/lib/cw.rb +3 -0
- data/lib/cw/alphabet.rb +21 -6
- data/lib/cw/audio_player.rb +5 -8
- data/lib/cw/book.rb +0 -4
- data/lib/cw/book_details.rb +19 -8
- data/lib/cw/callsign.rb +59 -0
- data/lib/cw/cl.rb +12 -11
- data/lib/cw/common_words.rb +26 -3
- data/lib/cw/config.rb +14 -3
- data/lib/cw/cw_dsl.rb +11 -0
- data/lib/cw/cw_encoding.rb +49 -53
- data/lib/cw/file_details.rb +92 -19
- data/lib/cw/print.rb +10 -0
- data/lib/cw/read.rb +341 -0
- data/lib/cw/tone_generator.rb +6 -11
- data/lib/cw/words.rb +1 -1
- data/run_script_tests.rb +1 -1
- data/test/test_config.rb +0 -20
- data/test/test_cw.rb +14 -14
- metadata +20 -11
data/lib/cw/file_details.rb
CHANGED
@@ -3,29 +3,102 @@
|
|
3
3
|
module CWG
|
4
4
|
|
5
5
|
module FileDetails
|
6
|
-
HERE
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
HERE = File.dirname(__FILE__)
|
7
|
+
WORK_DIR = Dir.pwd
|
8
|
+
ROOT = File.expand_path File.join(HERE,'..','..')
|
9
|
+
DATA = File.join(ROOT,'data')
|
10
|
+
AUDIO_DIR = File.join(WORK_DIR,'audio')
|
11
|
+
TEXT = File.join(DATA,'text')
|
12
|
+
CODE = File.join(DATA,'code')
|
13
|
+
CALLS = File.join(DATA,'callsign')
|
14
|
+
DOT_CW_DIR = File.join WORK_DIR, ".cw"
|
15
|
+
DOT_AUDIO_DIR = File.join DOT_CW_DIR, "audio"
|
16
|
+
DICT_FILENAME = "english.txt"
|
17
|
+
CONFIG_FILENAME = ".cw_config"
|
18
|
+
DEF_AUDIO_FILENAME = "audio_output.wav"
|
19
|
+
CODE_FILENAME = File.join CODE, "code.yaml"
|
20
|
+
CALLS_FILENAME = File.join CALLS, "callsign.yaml"
|
21
|
+
DOT_FILENAME = "dot.wav"
|
22
|
+
DASH_FILENAME = "dash.wav"
|
23
|
+
SPACE_FILENAME = "space.wav"
|
24
|
+
E_SPACE_FILENAME = "e_space.wav"
|
25
|
+
BOOKMARK_FILE = "bookmark.txt"
|
26
|
+
DICT_DIR = TEXT
|
27
|
+
ABBREVIATIONS = File.join TEXT, "abbreviations.txt"
|
28
|
+
Q_CODES = File.join TEXT, "q_codes.txt"
|
29
|
+
CONFIG_PATH = File.join ROOT, CONFIG_FILENAME
|
30
|
+
USER_CONFIG_PATH = File.join WORK_DIR, CONFIG_FILENAME
|
19
31
|
|
20
32
|
def init_filenames
|
21
|
-
@repeat_tone
|
22
|
-
@r_tone
|
23
|
-
@text_folder = TEXT
|
24
|
-
@progress_file = 'progress.txt'
|
33
|
+
@repeat_tone = File.join(AUDIO_DIR, "rpt.mp3")
|
34
|
+
@r_tone = File.join(AUDIO_DIR, "r.mp3")
|
25
35
|
end
|
26
36
|
|
27
|
-
def
|
28
|
-
File.
|
37
|
+
def process_dot_cw
|
38
|
+
Dir.mkdir(DOT_CW_DIR) unless File.exists? DOT_CW_DIR
|
39
|
+
DOT_CW_DIR
|
29
40
|
end
|
41
|
+
|
42
|
+
def dot_cw_dir
|
43
|
+
@dot_cw_dir ||= process_dot_cw
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_dot_audio
|
47
|
+
Dir.mkdir(DOT_AUDIO_DIR) unless(dot_cw_dir && File.exists?(DOT_AUDIO_DIR))
|
48
|
+
DOT_AUDIO_DIR
|
49
|
+
end
|
50
|
+
|
51
|
+
def dot_audio_dir
|
52
|
+
@dot_audio_dir ||= process_dot_audio
|
53
|
+
end
|
54
|
+
|
55
|
+
def dot_path
|
56
|
+
File.join dot_audio_dir, DOT_FILENAME
|
57
|
+
end
|
58
|
+
|
59
|
+
def dash_path
|
60
|
+
File.join dot_audio_dir, DASH_FILENAME
|
61
|
+
end
|
62
|
+
|
63
|
+
def space_path
|
64
|
+
File.join dot_audio_dir, SPACE_FILENAME
|
65
|
+
end
|
66
|
+
|
67
|
+
def e_space_path
|
68
|
+
File.join dot_audio_dir, E_SPACE_FILENAME
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_audio_dir
|
72
|
+
Dir.mkdir(AUDIO_DIR) unless File.exists? AUDIO_DIR
|
73
|
+
AUDIO_DIR
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_audio_dir
|
77
|
+
Cfg.config['audio_dir'] ? user_audio_dir : default_audio_dir
|
78
|
+
end
|
79
|
+
|
80
|
+
def user_audio_dir
|
81
|
+
@user_audio_dir ||=
|
82
|
+
unless File.exists? Cfg.config['audio_dir']
|
83
|
+
Dir.mkdir Cfg.config['audio_dir']
|
84
|
+
end
|
85
|
+
Cfg.config['audio_dir']
|
86
|
+
end
|
87
|
+
|
88
|
+
def audio_dir
|
89
|
+
@audio_dir ||= process_audio_dir
|
90
|
+
end
|
91
|
+
|
92
|
+
def audio_filename
|
93
|
+
@audio_filename ||=
|
94
|
+
Cfg.config["audio_filename"] ?
|
95
|
+
Cfg.config["audio_filename"] :
|
96
|
+
DEF_AUDIO_FILENAME
|
97
|
+
end
|
98
|
+
|
99
|
+
def progress_file
|
100
|
+
File.join(dot_cw_dir, BOOKMARK_FILE)
|
101
|
+
end
|
102
|
+
|
30
103
|
end
|
31
104
|
end
|
data/lib/cw/print.rb
CHANGED
@@ -111,6 +111,16 @@ module CWG
|
|
111
111
|
print paint("#{word}", fail_colour)
|
112
112
|
end
|
113
113
|
|
114
|
+
def speculative word
|
115
|
+
print paint("#{word}", fail_colour)
|
116
|
+
end
|
117
|
+
def stable word
|
118
|
+
print paint("#{word}", :yellow)
|
119
|
+
end
|
120
|
+
def optimum word
|
121
|
+
print paint("#{word}", success_colour)
|
122
|
+
end
|
123
|
+
|
114
124
|
def list word
|
115
125
|
print paint("#{word}", list_colour)
|
116
126
|
end
|
data/lib/cw/read.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#require 'wavefile'
|
4
|
+
require 'coreaudio'
|
5
|
+
|
6
|
+
module CWG
|
7
|
+
|
8
|
+
SAMPLE_RATE = 44100
|
9
|
+
MAGNITUDE_CUTOFF = 50000 # 200000
|
10
|
+
N = 441
|
11
|
+
TONE = 882
|
12
|
+
START = 0
|
13
|
+
VALUE = 1
|
14
|
+
BLANKING = 2
|
15
|
+
FILTERED = 3
|
16
|
+
PREV = 4
|
17
|
+
PERIOD = 1
|
18
|
+
AVG = 2
|
19
|
+
class Read
|
20
|
+
|
21
|
+
def n ; N ; end
|
22
|
+
def k ; (0.5 + ((n * @tone / @sample_rate))).to_i ; end
|
23
|
+
def w ; ((2 * Math::PI) / n) * k ; end
|
24
|
+
def cosine ; Math.cos(w) ; end
|
25
|
+
def coeff ; 2 * cosine ; end
|
26
|
+
def n_delay_ms ; n * 1000/SAMPLE_RATE; end
|
27
|
+
def print ; @print ; end
|
28
|
+
|
29
|
+
def initialize(filename)
|
30
|
+
@tone = TONE
|
31
|
+
@mag_max = 0
|
32
|
+
@mag_min = 999999999
|
33
|
+
@sample_rate = SAMPLE_RATE
|
34
|
+
@n_val = n
|
35
|
+
@coeff = coeff
|
36
|
+
@n_delay_ms = n_delay_ms
|
37
|
+
@filename = filename
|
38
|
+
@code = []
|
39
|
+
@q1 = 0
|
40
|
+
@q2 = 0
|
41
|
+
@magnitude_set_point = 10000
|
42
|
+
@magnitude_set_point_low = @magnitude_set_point
|
43
|
+
@wpm = 40
|
44
|
+
@noise_blanking_ms = 6
|
45
|
+
@last_start_time = 0
|
46
|
+
@state = Array.new(5)
|
47
|
+
@high = Array.new(3)
|
48
|
+
@low = Array.new(2)
|
49
|
+
@queue = Queue.new
|
50
|
+
@cw_encoding = CwEncoding.new
|
51
|
+
@print = Print.new
|
52
|
+
@state[START] = 0
|
53
|
+
@state[VALUE] = :low
|
54
|
+
@state[BLANKING] = false
|
55
|
+
@state[FILTERED] = false
|
56
|
+
@state[PREV] = false
|
57
|
+
@high[START] = 0
|
58
|
+
@high[PERIOD] = 0
|
59
|
+
@high[AVG] = 120
|
60
|
+
@low[START] = 0
|
61
|
+
@low[PERIOD] = 0
|
62
|
+
@low[AVG] = 0
|
63
|
+
@millisecs = 0
|
64
|
+
@last = 0
|
65
|
+
@need_space = false
|
66
|
+
puts "@n_val #{@n_val}"
|
67
|
+
puts "@coeff #{@coeff}"
|
68
|
+
puts "@n_delay_ms #{@n_delay_ms}"
|
69
|
+
input
|
70
|
+
end
|
71
|
+
|
72
|
+
def open_sound_device
|
73
|
+
soundflower = nil
|
74
|
+
CoreAudio.devices.each do |device|
|
75
|
+
if device.name == 'Soundflower (2ch)'
|
76
|
+
soundflower = device
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@buf_in = soundflower.input_buffer(44100)
|
80
|
+
@buf_in.start
|
81
|
+
end
|
82
|
+
|
83
|
+
def input
|
84
|
+
dly = @n_delay_ms
|
85
|
+
open_sound_device
|
86
|
+
nval = @n_val
|
87
|
+
buf = @buf_in
|
88
|
+
bufs = nil
|
89
|
+
block_size = nval * 28
|
90
|
+
thr = Thread.fork do
|
91
|
+
loop do
|
92
|
+
@queue.push buf.read(block_size)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
loop do
|
96
|
+
loop do
|
97
|
+
bufs = @queue.pop
|
98
|
+
break if @queue.empty?
|
99
|
+
sleep 0.001
|
100
|
+
end
|
101
|
+
count = 0
|
102
|
+
28.times do
|
103
|
+
nval.times do
|
104
|
+
temp = bufs[count] + bufs[count + 1]
|
105
|
+
# puts temp
|
106
|
+
calc_coeff temp
|
107
|
+
count += 2
|
108
|
+
end
|
109
|
+
@millisecs += dly
|
110
|
+
# @millisecs += dly
|
111
|
+
per_block_processing
|
112
|
+
calc_real_state
|
113
|
+
calc_filtered_state
|
114
|
+
decode_signal
|
115
|
+
end
|
116
|
+
end
|
117
|
+
@buf_in.stop
|
118
|
+
$stdout.puts "done."
|
119
|
+
end
|
120
|
+
|
121
|
+
def dbg_print message
|
122
|
+
if @millisecs > @last
|
123
|
+
@last = @millisecs + 1000
|
124
|
+
puts
|
125
|
+
puts " " + message
|
126
|
+
@mag_min = 999999999
|
127
|
+
@mag_max = 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def per_block_processing
|
132
|
+
@magnitude = (@q1 * @q1) + (@q2 * @q2) - @q1 * @q2 * @coeff
|
133
|
+
@magnitude = @magnitude.to_i / 1000000
|
134
|
+
@magnitude = 0 if @magnitude < MAGNITUDE_CUTOFF
|
135
|
+
# if @magnitude >= 1000000000000.0
|
136
|
+
# @magnitude = Math.sqrt(magnitude_squared).to_i
|
137
|
+
# else
|
138
|
+
# @magnitude = 0
|
139
|
+
# end
|
140
|
+
@q1, @q2 = 0, 0;
|
141
|
+
# p @magnitude
|
142
|
+
end
|
143
|
+
|
144
|
+
def magnitude_filter
|
145
|
+
if(@magnitude > @magnitude_set_point_low)
|
146
|
+
@magnitude_set_point = (@magnitude_set_point + ((@magnitude - @magnitude_set_point) / 6)) # moving average filter
|
147
|
+
else
|
148
|
+
@magnitude_set_point = @magnitude_set_point_low
|
149
|
+
end
|
150
|
+
# dbg_print "@magnitude: #{@magnitude.to_s}\n @magnitude_set_point: #{@magnitude_set_point.to_s}\n @magnitude_set_point_low = #{@magnitude_set_point_low}"
|
151
|
+
|
152
|
+
@mag_max = @magnitude if @magnitude > @mag_max
|
153
|
+
@mag_min = @magnitude if @magnitude < @mag_min
|
154
|
+
|
155
|
+
# dbg_print "@magnitude: #{@magnitude.to_s}\n" +
|
156
|
+
# " @mag_max: #{@mag_max.to_s}\n" +
|
157
|
+
# " @mag_min: #{@mag_min.to_s}\n"
|
158
|
+
end
|
159
|
+
|
160
|
+
def calc_real_state
|
161
|
+
@state[VALUE] =
|
162
|
+
(@magnitude > (@magnitude_set_point * 0.6)) ?
|
163
|
+
:high : :low
|
164
|
+
end
|
165
|
+
|
166
|
+
def calc_filtered_state
|
167
|
+
if real_state_change?
|
168
|
+
reset_noise_blanker
|
169
|
+
end
|
170
|
+
|
171
|
+
store_real_state
|
172
|
+
|
173
|
+
if @state[BLANKING]
|
174
|
+
if noise_blanked
|
175
|
+
@state[FILTERED] = true
|
176
|
+
if(@state[VALUE] == :high)
|
177
|
+
@high[START] = @millisecs
|
178
|
+
@high_mag = @magnitude
|
179
|
+
# dbg_print "high: #{@high_mag}\n low: #{@low_mag}\n set point: #{@magnitude_set_point}"
|
180
|
+
@low[PERIOD] = (@millisecs - @low[START])
|
181
|
+
else
|
182
|
+
@low[START] = @millisecs;
|
183
|
+
@low_mag = @magnitude
|
184
|
+
@high[PERIOD] = @millisecs - @high[START]
|
185
|
+
if @high[PERIOD] < (2 * @high[AVG])
|
186
|
+
@high[AVG] = ((@high[PERIOD] + @high[AVG] + @high[AVG]) / 3) # now we know avg dit time ( rolling 3 avg)
|
187
|
+
elsif(@high[PERIOD] > (5 * @high[AVG]))
|
188
|
+
@high[AVG] = @high[PERIOD] + @high[AVG]; # if speed decrease fast ..
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def decode_signal
|
196
|
+
if(@state[FILTERED])
|
197
|
+
@state[FILTERED] = false
|
198
|
+
@need_space = true
|
199
|
+
#dbg_print "wpm #{@wpm.to_s}\nhigh period: #{@high[PERIOD]}\nhigh avg: #{@high[AVG]}"
|
200
|
+
if(@state[VALUE] == :low) # we did end a HIGH
|
201
|
+
if high_avg_compare?(@high[PERIOD], 0.6, 2.0)
|
202
|
+
# 0.6 filter out false dits
|
203
|
+
@code << :dot
|
204
|
+
# $stdout.print '.'
|
205
|
+
end
|
206
|
+
if high_avg_compare?(@high[PERIOD], 2.0, 6.0)
|
207
|
+
@code << :dash
|
208
|
+
# $stdout.print '_'
|
209
|
+
if @millisecs % 10 == 0
|
210
|
+
@wpm = (@wpm + (1200 / ((@high[PERIOD]) / 3))) / 2; # the most precise we can do ;o)
|
211
|
+
# dbg_print "high #{@high[PERIOD]}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
else # we did end a LOW
|
215
|
+
@need_space = false
|
216
|
+
if high_avg_compare?(@low[PERIOD], 2.0, 4.8) # letter space
|
217
|
+
print_char
|
218
|
+
elsif(high_avg_compare?(@low[PERIOD], 4.8, 6.0)) # word space
|
219
|
+
print_char
|
220
|
+
$stdout.print ' '
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
# dbg_print "millisecs #{@millisecs}"
|
225
|
+
if @need_space
|
226
|
+
if high_avg_compare?(@millisecs - @low[START], 6.0, 10)
|
227
|
+
@need_space = false
|
228
|
+
if @state[BLANKING] = false
|
229
|
+
end
|
230
|
+
print_char
|
231
|
+
$stdout.print ' '
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def high_avg_compare?(period, avg_x_low, avg_x_high)
|
237
|
+
(period <= (@high[AVG] * avg_x_high).to_i) &&
|
238
|
+
(period >= (@high[AVG] * avg_x_low).to_i)
|
239
|
+
end
|
240
|
+
|
241
|
+
def noise_blanked
|
242
|
+
if((@millisecs - @state[START]) > @noise_blanking_ms)
|
243
|
+
@state[BLANKING] = false
|
244
|
+
return true
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def print_space
|
249
|
+
@code = [:space]
|
250
|
+
print_char
|
251
|
+
end
|
252
|
+
|
253
|
+
def calc_coeff(data)
|
254
|
+
q0 = @coeff * @q1 - @q2 + data
|
255
|
+
@q2, @q1 = @q1, q0
|
256
|
+
end
|
257
|
+
|
258
|
+
def reset_noise_blanker
|
259
|
+
@state[BLANKING] = true
|
260
|
+
@state[START] = @millisecs
|
261
|
+
end
|
262
|
+
|
263
|
+
def real_state_change?
|
264
|
+
# if @real_state != @real_state_prev
|
265
|
+
# $stdout.print 'hi' if @real_state == true
|
266
|
+
# $stdout.print 'low' if @real_state == false
|
267
|
+
# end
|
268
|
+
@state[VALUE] != @state[PREV]
|
269
|
+
end
|
270
|
+
|
271
|
+
def store_real_state
|
272
|
+
@state[PREV] = @state[VALUE]
|
273
|
+
end
|
274
|
+
|
275
|
+
# def matched_char
|
276
|
+
# @cw_encoding.fetch_char @code
|
277
|
+
# end
|
278
|
+
|
279
|
+
def print_char
|
280
|
+
char = @cw_encoding.fetch_char @code
|
281
|
+
@code = []
|
282
|
+
print.optimum char
|
283
|
+
# if char == ' '
|
284
|
+
# puts "\n high: #{@high_mag}\n"
|
285
|
+
# end
|
286
|
+
|
287
|
+
if false # char == '*'
|
288
|
+
# @wpm -= 5
|
289
|
+
@state[VALUE] = false
|
290
|
+
@state[FILTERED] = false
|
291
|
+
@state[PREV] = false
|
292
|
+
# @awaiting_space = false
|
293
|
+
@low[PERIOD] = 0
|
294
|
+
|
295
|
+
@high[PERIOD] = 0
|
296
|
+
@high[PREV] = 0
|
297
|
+
@high[AVG] = 0
|
298
|
+
@low[START] = 0
|
299
|
+
@millisecs = 0
|
300
|
+
@last = 0
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# def print_char
|
305
|
+
# index = 0
|
306
|
+
# dash_jump = 64
|
307
|
+
# if @code.length < 6
|
308
|
+
# @code.each do |ele|
|
309
|
+
## puts "ele: #{ele}"
|
310
|
+
# dash_jump = dash_jump / 2
|
311
|
+
# index = index + ((ele == :dot) ? 1 : dash_jump)
|
312
|
+
# end
|
313
|
+
# char = @char_lookup[index].to_s
|
314
|
+
## char = @cw_encoding.fetch_char(@code) if char == '*'
|
315
|
+
# # if char == ' '
|
316
|
+
# # puts "\n high: #{@high_mag}\n"
|
317
|
+
# # end
|
318
|
+
# else
|
319
|
+
# char = @cw_encoding.fetch_char(@code)
|
320
|
+
# end
|
321
|
+
|
322
|
+
# if false # char == '*'
|
323
|
+
# # @wpm -= 5
|
324
|
+
# @state[VALUE] = false
|
325
|
+
# @state[FILTERED] = false
|
326
|
+
# @state[PREV] = false
|
327
|
+
# # @awaiting_space = false
|
328
|
+
# @low[PERIOD] = 0
|
329
|
+
#
|
330
|
+
# @high[PERIOD] = 0
|
331
|
+
# @high[PREV] = 0
|
332
|
+
# @high[AVG] = 0
|
333
|
+
# @low[START] = 0
|
334
|
+
# @millisecs = 0
|
335
|
+
# @last = 0
|
336
|
+
# end
|
337
|
+
# print.optimum char
|
338
|
+
# @code = []
|
339
|
+
# end
|
340
|
+
end
|
341
|
+
end
|