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.
@@ -3,29 +3,102 @@
3
3
  module CWG
4
4
 
5
5
  module FileDetails
6
- HERE = File.dirname(__FILE__)
7
- ROOT = File.expand_path File.join(HERE,'..','..')
8
- TEXT = File.join(ROOT,'data','text')
9
- AUDIO = File.join(ROOT,'audio')
10
-
11
- ENGLISH_DICT = File.join TEXT, "english.txt"
12
- ABBREVIATIONS = File.join TEXT, "abbreviations.txt"
13
- Q_CODES = File.join TEXT, "q_codes.txt"
14
- DOT_FILENAME = File.join AUDIO, "dot.wav"
15
- DASH_FILENAME = File.join AUDIO, "dash.wav"
16
- SPACE_FILENAME = File.join AUDIO, "space.wav"
17
- E_SPACE_FILENAME = File.join AUDIO, "e_space.wav"
18
- CONFIG_FILENAME = File.join ROOT, ".cw_config"
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 = File.join(AUDIO, "rpt.mp3")
22
- @r_tone = File.join(AUDIO, "r.mp3")
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 expand path
28
- File.expand_path path
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
@@ -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
@@ -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