cw 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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