radio 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
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 Filter
18
+
19
+ module FirSetup
20
+ # It's ok that not all filter patterns use all instance variables.
21
+ def setup data
22
+ @mix_phase = 0.0
23
+ @mix_phase_inc = @options[:mix]
24
+ @dec_pos = @dec_size = @options[:decimate]
25
+ coef = @options[:fir]
26
+ @fir_pos = 0
27
+ @fir_size = coef.size
28
+ @fir_coef = NArray.to_na coef.reverse*2
29
+ @fir_buf = NArray.complex @fir_size
30
+ super
31
+ end
32
+ end
33
+
34
+ module FloatEachMixDecimateFir
35
+ include FirSetup
36
+ def call data
37
+ data.each do |energy|
38
+ @fir_pos = @fir_size if @fir_pos == 0
39
+ @fir_pos -= 1
40
+ @fir_buf[@fir_pos] = Complex(Math.cos(@mix_phase)*energy, -Math.sin(@mix_phase)*energy)
41
+ @mix_phase += @mix_phase_inc
42
+ @mix_phase -= PI2 if @mix_phase >= PI2
43
+ @dec_pos -= 1
44
+ if @dec_pos == 0
45
+ @dec_pos = @dec_size
46
+ iq = @fir_buf.mul_accum @fir_coef[@fir_size-@fir_pos..-1-@fir_pos],0
47
+ yield iq[0]
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module ComplexFir
54
+ include FirSetup
55
+ def call data
56
+ @fir_pos = @fir_size if @fir_pos == 0
57
+ @fir_pos -= 1
58
+ @fir_buf[@fir_pos] = data
59
+ iq = @fir_buf.mul_accum @fir_coef[@fir_size-@fir_pos..-1-@fir_pos],0
60
+ yield iq[0]
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,84 @@
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
+ # 2D Array of numbers will be mapped to 1..127 colors.
17
+ # This 7-bit data is encoded uncompressed and left for
18
+ # http to compress. Background color reserved for future use.
19
+
20
+ class Radio
21
+ class Gif
22
+
23
+ # This is an optimized GIF generator for waterfalls.
24
+ # It requires 128 RGB colors, the first is unused and
25
+ # reserved for transparency if we ever need it.
26
+ def self.waterfall colors, data
27
+
28
+ gif = [
29
+ 'GIF87a', # Start Header
30
+ data[0].size, # width
31
+ data.size, # height
32
+ 0xF6, # 128 24-bit colors
33
+ 0x00, # background color index
34
+ 0x00 # aspect ratio
35
+ ].pack 'a6vvCCC'
36
+
37
+ gif += colors.flatten.pack 'C*'
38
+
39
+ gif += [
40
+ 0x2C, # Start Image Block
41
+ 0x0000, # Left position
42
+ 0x0000, # Top position
43
+ data[0].size, # width
44
+ data.size, # height
45
+ 0x00, # No color table, not interlaced
46
+ 0x07 # LZW code size
47
+ ].pack('CvvvvCC')
48
+
49
+ data.each_with_index do |vals, row|
50
+ col = 0
51
+ min = vals.min
52
+ range = [1e-99, vals.max - min].max
53
+ while col < vals.size
54
+ # Uncompressed GIF trickery avoids bit packing too
55
+ # 126 byte chunks with reset keeps LZW in 8 bit codes
56
+ col_end = [col+126,vals.size].min
57
+ slice = vals.slice(col...col_end).to_a
58
+ # This 126 because palette is 1..127
59
+ slice = slice.collect { |x| (x - min) / range * 126 + 1 }
60
+ slice = slice.pack 'C*'
61
+ newstuff = [
62
+ slice.size+1,
63
+ slice,
64
+ 0x80 # LZW reset
65
+ ].pack('Ca*C')
66
+ gif += newstuff
67
+ col += 126
68
+ end
69
+
70
+ end
71
+
72
+ gif += [
73
+ 0x01, # end image blocks
74
+ 0x81, # final image block: LZW end
75
+ 0x00, # end image blocks
76
+ 0x3B # end gif
77
+ ].pack('C*')
78
+
79
+ return gif
80
+
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,88 @@
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 HTTP
18
+ class FileResponse
19
+
20
+ def initialize(env, filename, content_type = nil)
21
+ @env = env
22
+ @filename = filename
23
+ @status = 200
24
+ @headers = {}
25
+ @body = []
26
+
27
+ begin
28
+ raise Errno::EPERM unless File.file?(filename) and File.readable?(filename)
29
+ rescue SystemCallError
30
+ @body = ["404 Not Found\n"]
31
+ @headers["Content-Length"] = @body.first.size.to_s
32
+ @headers["Content-Type"] = 'text/plain'
33
+ @headers["X-Cascade"] = 'pass'
34
+ @status = 404
35
+ return
36
+ end
37
+
38
+ # Caching strategy
39
+ mod_since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']) rescue nil
40
+ last_modified = File.mtime(filename)
41
+ @status = 304 and return if last_modified == mod_since
42
+ @headers["Last-Modified"] = last_modified.httpdate
43
+ if env['QUERY_STRING'] =~ /^[0-9]{9,10}$/ and last_modified == Time.at(env['QUERY_STRING'].to_i)
44
+ @headers["Cache-Control"] = 'max-age=86400, public' # one day
45
+ else
46
+ @headers["Cache-Control"] = 'max-age=0, private, must-revalidate'
47
+ end
48
+
49
+ # Sending the file or reading an unknown length stream to send
50
+ @body = self
51
+ unless size = File.size?(filename)
52
+ @body = [File.read(filename)]
53
+ size = @body.first.respond_to?(:bytesize) ? @body.first.bytesize : @body.first.size
54
+ end
55
+ @headers["Content-Length"] = size.to_s
56
+ @headers["Content-Type"] = content_type || Rack::Mime.mime_type(File.extname(filename), 'text/plain')
57
+ end
58
+
59
+ # Support using self as a response body.
60
+ # @yield [String] 8k blocks
61
+ def each
62
+ File.open(@filename, "rb") do |file|
63
+ while part = file.read(8192)
64
+ yield part
65
+ end
66
+ end
67
+ end
68
+
69
+ # Filename attribute.
70
+ # Alias is used by some rack servers to detach from Ruby early.
71
+ # @return [String]
72
+ attr_reader :filename
73
+ alias :to_path :filename
74
+
75
+ # Was the file in the system and ready to be served?
76
+ def found?
77
+ @status == 200 or @status == 304
78
+ end
79
+
80
+ # Present the final response for rack.
81
+ # @return (Array)[status, headers, body]
82
+ def finish
83
+ [@status, @headers, @body]
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,133 @@
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
+ require 'erb'
17
+
18
+ class Radio
19
+
20
+ class HTTP
21
+
22
+ # A Script instance is the context in which scripts are rendered.
23
+ # It inherits everything from Rack::Request and supplies a Response instance
24
+ # you can use for redirects, cookies, and other controller actions.
25
+ class Script < Rack::Request
26
+
27
+ include ERB::Util
28
+
29
+ class NotFound < StandardError
30
+ end
31
+
32
+ class RenderStackOverflow < StandardError
33
+ end
34
+
35
+ def initialize(env, filename)
36
+ super(env)
37
+ @render_stack = []
38
+ @response = original_response = Rack::Response.new
39
+ rendering = render(filename)
40
+ if @response == original_response and @response.empty?
41
+ @response.write rendering
42
+ end
43
+ rescue RenderStackOverflow, NotFound => e
44
+ if @render_stack.size > 0
45
+ # Make errors appear from the render instead of the engine.call
46
+ e.set_backtrace e.backtrace[1..-1]
47
+ raise e
48
+ end
49
+ @response.status = 404
50
+ @response.write "404 Not Found\n"
51
+ @response.header["X-Cascade"] = "pass"
52
+ @response.header["Content-Type"] = "text/plain"
53
+ end
54
+
55
+ # After rendering, #finish will be sent to the client.
56
+ # If you replace the response or add to the response#body,
57
+ # the script engine rendering will not be added.
58
+ # @return [Rack::Response]
59
+ attr_accessor :response
60
+
61
+ # An array of filenames representing the current render stack.
62
+ # @example
63
+ # <%= if render_stack.size == 1
64
+ # render 'html_version'
65
+ # else
66
+ # render 'included_version'
67
+ # end
68
+ # %>
69
+ # @return [<Array>]
70
+ attr_reader :render_stack
71
+
72
+ # Render another Script.
73
+ # @example view_test.erb
74
+ # <%= render 'util/logger_popup' %>
75
+ # @param (String) filename Relative to current Script.
76
+ # @param (Hash) locals Local variables for the Script.
77
+ def render(filename, locals = {})
78
+ if render_stack.size > 100
79
+ # Since nobody sane should recurse through here, this mainly
80
+ # finds a render self that you might get after a copy and paste
81
+ raise RenderStackOverflow
82
+ elsif render_stack.size > 0
83
+ # Hooray for relative paths and easily movable files
84
+ filename = File.expand_path(filename, File.dirname(render_stack.last))
85
+ else
86
+ # Underbar scripts are partials by convention; keep them from rendering at root
87
+ filename = File.expand_path(filename)
88
+ raise NotFound if File.basename(filename) =~ /^_/
89
+ end
90
+ ext = File.extname(filename)
91
+ files1 = [filename]
92
+ files1 << filename + '.html' if ext == ''
93
+ files1 << filename.sub(/.html$/,'') if ext == '.html'
94
+ files1.each do |filename1|
95
+ files2 = [filename1+'.erb']
96
+ files2 << filename1.gsub(/.html$/, '.erb') if File.extname(filename1) == '.html'
97
+ unless filename1 =~ /^_/ or render_stack.empty?
98
+ files2 = files2 + files2.collect {|f| "#{File.dirname(f)}/_#{File.basename(f)}"}
99
+ end
100
+ files2.each do |filename2|
101
+ if File.file?(filename2) and File.readable?(filename2)
102
+ if render_stack.empty?
103
+ response.header["Content-Type"] = Rack::Mime.mime_type(File.extname(filename1), 'text/html')
104
+ end
105
+ render_stack.push filename2
106
+ erb = ::ERB.new(File.read(filename2), nil, '-')
107
+ erb.filename = filename2
108
+ set_locals = locals.keys.map { |k| "#{k}=locals[#{k.inspect}];" }.join
109
+ instance_binding = instance_eval{binding}
110
+ eval set_locals, instance_binding
111
+ result = erb.result instance_binding
112
+ render_stack.pop
113
+ return result
114
+ end
115
+ end
116
+ end
117
+ raise NotFound
118
+ end
119
+
120
+ # Helper for finding files relative to Scripts.
121
+ # @param [String] filename
122
+ # @return [String] absolute filesystem path
123
+ def expand_path(filename, dir=nil)
124
+ dir ||= File.dirname render_stack.last
125
+ File.expand_path filename, dir
126
+ end
127
+
128
+ end
129
+
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,94 @@
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
+ require 'rack'
17
+ require 'erb'
18
+
19
+ class Radio
20
+ class HTTP
21
+
22
+ MOUNTS = [
23
+ ['', File.expand_path(File.join(File.dirname(__FILE__), '../../../www'))]
24
+ ]
25
+
26
+ class Server
27
+
28
+ ENV_THREAD_BYPASS = 'radio.thread_bypass'
29
+
30
+ def initialize
31
+ @working_dir = Dir.getwd
32
+ end
33
+
34
+ # Thin can run some processes in threads if we provide the logic.
35
+ # Static files are served without deferring to a thread.
36
+ # Everything else is tried in the EventMachine thread pool.
37
+ def deferred? env
38
+ path_info = Rack::Utils.unescape(env['PATH_INFO'])
39
+ return false if path_info =~ /\.(\.|erb$)/ # unsafe '..' and '.erb'
40
+ MOUNTS.each do |path, dir|
41
+ if path_info =~ %r{^#{Regexp.escape(path)}(/.*|)$}
42
+ filename = File.join(dir, $1)
43
+ Dir.chdir @working_dir
44
+ response = FileResponse.new(env, filename)
45
+ if !response.found? and File.extname(path_info) == ''
46
+ response = FileResponse.new(env, filename + '.html')
47
+ end
48
+ if response.found?
49
+ env[ENV_THREAD_BYPASS] = response
50
+ return false
51
+ end
52
+ env[ENV_THREAD_BYPASS] = filename
53
+ end
54
+ end
55
+ return true
56
+ end
57
+
58
+ # Rack interface.
59
+ # @param (Hash) env Rack environment.
60
+ # @return (Array)[status, headers, body]
61
+ def call(env)
62
+ # The preprocessing left us with nothing, a response,
63
+ # or a filename that we should try to run.
64
+ case deferred_result = env.delete(ENV_THREAD_BYPASS)
65
+ when String
66
+ filename = deferred_result
67
+ response = Script.new(env, filename).response
68
+ if response.header["X-Cascade"] == 'pass'
69
+ index_response = Script.new(env, filename + '/index').response
70
+ response = index_response unless index_response.header["X-Cascade"] == 'pass'
71
+ end
72
+ response.finish
73
+ when NilClass
74
+ not_found
75
+ else
76
+ deferred_result.finish
77
+ end
78
+ end
79
+
80
+ # Status 404 with X-Cascade => pass.
81
+ # @return (Array)[status, headers, body]
82
+ def not_found
83
+ return @not_found if @not_found
84
+ body = "404 Not Found\n"
85
+ @not_found = [404, {'Content-Type' => 'text/plain',
86
+ 'Content-Length' => body.size.to_s,
87
+ 'X-Cascade' => 'pass'},
88
+ [body]]
89
+ @not_found
90
+ end
91
+
92
+ end
93
+ end
94
+ end