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
@@ -0,0 +1,43 @@
|
|
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
|
+
# This is unfinished, unused, and needs expiration management.
|
17
|
+
# We'll definitely want sessions working for authentication,
|
18
|
+
|
19
|
+
class Radio
|
20
|
+
class HTTP
|
21
|
+
class Session
|
22
|
+
|
23
|
+
CODES = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
|
24
|
+
COOKIE_KEY = 'ham21-radio-session'
|
25
|
+
|
26
|
+
def self.prepare request, response
|
27
|
+
@sessions ||= {}
|
28
|
+
session_id = request.cookies[COOKIE_KEY]
|
29
|
+
session = @sessions[session_id]
|
30
|
+
unless session
|
31
|
+
session_id = (0...24).collect{CODES.sample}.join
|
32
|
+
session = @sessions[session_id] = new
|
33
|
+
Rack::Utils.set_cookie_header!(response.headers, COOKIE_KEY, session_id)
|
34
|
+
end
|
35
|
+
session
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/radio/input.rb
ADDED
@@ -0,0 +1,62 @@
|
|
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
|
+
module Input
|
18
|
+
|
19
|
+
# Keep this namespace clean because we search inputs by
|
20
|
+
# finding the classes from Radio::Inputs.constants.
|
21
|
+
|
22
|
+
# Drivers are supposed to fail silently if they can't find
|
23
|
+
# dependencies. This allows us to present a basic debug screen.
|
24
|
+
def self.status
|
25
|
+
s = {}
|
26
|
+
Radio::Input.constants.collect do |input_name|
|
27
|
+
s[input_name] = eval(input_name.to_s).status
|
28
|
+
end
|
29
|
+
s
|
30
|
+
end
|
31
|
+
|
32
|
+
# Consolidate all sources from all inputs and add the class name to the keys.
|
33
|
+
def self.sources
|
34
|
+
s = {}
|
35
|
+
Radio::Input.constants.each do |type|
|
36
|
+
eval(type.to_s).sources.each do |id, source|
|
37
|
+
s[[type, id]] = source
|
38
|
+
end
|
39
|
+
end
|
40
|
+
s
|
41
|
+
end
|
42
|
+
|
43
|
+
# You can't new a module so this switches into the specific class.
|
44
|
+
def self.new type, id, rate, channel_i, channel_q=nil
|
45
|
+
# defend the eval
|
46
|
+
unless Radio::Input.constants.include? type.to_sym
|
47
|
+
raise NameError, "uninitialized constant Radio::Input::#{type}"
|
48
|
+
end
|
49
|
+
input = eval(type.to_s).new id, rate, channel_i, channel_q
|
50
|
+
# Ask for and discard the first sample to report errors here
|
51
|
+
begin
|
52
|
+
input.call 1
|
53
|
+
rescue Exception => e
|
54
|
+
input.stop
|
55
|
+
raise e
|
56
|
+
end
|
57
|
+
input
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,135 @@
|
|
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
|
+
begin
|
17
|
+
require 'alsa'
|
18
|
+
rescue LoadError => e
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Radio
|
23
|
+
module Input
|
24
|
+
|
25
|
+
class ALSA
|
26
|
+
|
27
|
+
def self.status
|
28
|
+
if defined? ::ALSA::PCM
|
29
|
+
return "Loaded: %d input devices" % sources.count
|
30
|
+
end
|
31
|
+
unless defined? @is_linux
|
32
|
+
@is_linux = (`uname`.strip == 'Linux') rescue false
|
33
|
+
end
|
34
|
+
return "Unsupported: requires Linux" unless @is_linux
|
35
|
+
if defined? ::ALSA
|
36
|
+
'Unavailable: install ALSA to your OS'
|
37
|
+
else
|
38
|
+
'Unavailable: gem install ruby-alsa'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
CP_REGEX = /:\s*(capture|playback)\s*(\d+)\s*$/
|
43
|
+
CD_REGEX = /^(\d+)-(\d+):\s*/
|
44
|
+
|
45
|
+
def self.sources
|
46
|
+
return {} unless defined? ::ALSA::PCM
|
47
|
+
# Funky read to get around old Linux bug
|
48
|
+
# http://kerneltrap.org/mailarchive/git-commits-head/2009/4/17/5510664
|
49
|
+
pcm = ::File.open('/proc/asound/pcm') do |io|
|
50
|
+
io.read_nonblock 32768
|
51
|
+
end
|
52
|
+
result = {}
|
53
|
+
pcm.each_line do |line|
|
54
|
+
capture = 0
|
55
|
+
2.times do
|
56
|
+
line.gsub! CP_REGEX, ''
|
57
|
+
capture = $2.to_i if $1 == 'capture'
|
58
|
+
end
|
59
|
+
line.gsub! CD_REGEX, ''
|
60
|
+
device = "hw:#{$1.to_i},#{$2.to_i}"
|
61
|
+
::ALSA::PCM::Capture.open(device) do |stream|
|
62
|
+
params = stream.hardware_parameters
|
63
|
+
result[device] = {
|
64
|
+
name: line,
|
65
|
+
rates: [params.sample_rate],
|
66
|
+
channels: params.channels
|
67
|
+
}
|
68
|
+
end rescue nil
|
69
|
+
end
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize id, rate, channel_i, channel_q
|
74
|
+
@stream = ::ALSA::PCM::Capture.new
|
75
|
+
@stream.open id
|
76
|
+
@channel_i = channel_i
|
77
|
+
@channel_q = channel_q
|
78
|
+
end
|
79
|
+
|
80
|
+
def rate
|
81
|
+
@stream.hardware_parameters.sample_rate
|
82
|
+
end
|
83
|
+
|
84
|
+
def channels
|
85
|
+
return 2 if @channel_q and @stream.hardware_parameters.channels > 1
|
86
|
+
1
|
87
|
+
end
|
88
|
+
|
89
|
+
def call samples
|
90
|
+
|
91
|
+
out=nil
|
92
|
+
buf_size = @stream.hw_params.buffer_size_for(samples)
|
93
|
+
FFI::MemoryPointer.new(:char, buf_size) do |buffer|
|
94
|
+
@stream.read_buffer buffer, samples
|
95
|
+
out = buffer.read_string(buf_size)
|
96
|
+
NArray.to_na(out, NArray::SINT).to_f.div! 32767
|
97
|
+
end
|
98
|
+
stream_channels = @stream.hardware_parameters.channels
|
99
|
+
out = case buf_size / samples / stream_channels
|
100
|
+
when 1 then NArray.to_na(out,NArray::BYTE).to_f.collect!{|v|(v-128)/127}
|
101
|
+
when 2 then NArray.to_na(out,NArray::SINT).to_f.div! 32767
|
102
|
+
# when 3 then NArray.to_na(d,NArray::???).to_f.collect!{|v|(v-8388608)/8388607}
|
103
|
+
else
|
104
|
+
raise "Unsupported sample size: #{@bit_sample}"
|
105
|
+
end
|
106
|
+
return out if channels == 1 and stream_channels == 1
|
107
|
+
out.reshape! stream_channels, out.size/stream_channels
|
108
|
+
if channels == 1
|
109
|
+
out[@channel_i,true]
|
110
|
+
else
|
111
|
+
c_out = NArray.scomplex out[0,true].size
|
112
|
+
c_out[0..-1] = out[@channel_i,true]
|
113
|
+
c_out.imag = out[@channel_q,true]
|
114
|
+
c_out
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop
|
119
|
+
@stream.close
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
if $0 == __FILE__
|
129
|
+
|
130
|
+
require 'narray'
|
131
|
+
a = Radio::Input::ALSA
|
132
|
+
i = a.new 'default', 44100, 0, nil
|
133
|
+
p i.call 8
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,102 @@
|
|
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
|
+
begin
|
17
|
+
require 'coreaudio'
|
18
|
+
rescue LoadError => e
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Radio
|
23
|
+
module Input
|
24
|
+
|
25
|
+
class CoreAudio
|
26
|
+
|
27
|
+
def self.status
|
28
|
+
if defined? ::CoreAudio
|
29
|
+
return "Loaded: %d input devices" % sources.count
|
30
|
+
end
|
31
|
+
unless defined? @is_darwin
|
32
|
+
@is_darwin = (`uname`.strip == 'Darwin') rescue false
|
33
|
+
end
|
34
|
+
return "Unsupported: requires Apple OS" unless @is_darwin
|
35
|
+
return 'Unavailable: gem install coreaudio'
|
36
|
+
end
|
37
|
+
|
38
|
+
# I don't see a way to automatically set the CoreAudio CODEC rate.
|
39
|
+
# We'll present the nominal_rate as the only option.
|
40
|
+
def self.sources
|
41
|
+
return {} unless defined? ::CoreAudio
|
42
|
+
result = {}
|
43
|
+
::CoreAudio.devices.each do |dev|
|
44
|
+
channels = dev.input_stream.channels
|
45
|
+
if channels > 0
|
46
|
+
result[dev.devid] = {
|
47
|
+
name: dev.name,
|
48
|
+
rates: [dev.nominal_rate.to_i],
|
49
|
+
channels: channels
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :rate, :channels
|
57
|
+
|
58
|
+
# id is the key from the sources hash.
|
59
|
+
# rate is the desired hardware rate. do not decimate/interpolate here.
|
60
|
+
def initialize id, rate, channel_i, channel_q
|
61
|
+
@device = ::CoreAudio::AudioDevice.new id.to_i
|
62
|
+
@rate = @device.nominal_rate
|
63
|
+
raise 'I channel fail' unless channel_i < @device.input_stream.channels
|
64
|
+
@channel_i = channel_i
|
65
|
+
@channels = 1
|
66
|
+
if channel_q
|
67
|
+
raise 'Q channel fail' unless channel_q < @device.input_stream.channels
|
68
|
+
@channel_q = channel_q
|
69
|
+
extend IQ
|
70
|
+
@channels = 2
|
71
|
+
end
|
72
|
+
# Half second of buffer
|
73
|
+
coreaudio_input_buffer_size = @channels * rate / 2
|
74
|
+
@buf = @device.input_buffer coreaudio_input_buffer_size
|
75
|
+
@buf.start
|
76
|
+
end
|
77
|
+
|
78
|
+
# This is called on its own thread in Rig and is expected to block.
|
79
|
+
def call samples
|
80
|
+
# CoreAudio range of -32767..32767 makes easy conversion to -1.0..1.0
|
81
|
+
@buf.read(samples)[@channel_i,true].to_f.div!(32767)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Once stopped, rig won't attempt starting again on this object.
|
85
|
+
def stop
|
86
|
+
@buf.stop
|
87
|
+
end
|
88
|
+
|
89
|
+
module IQ
|
90
|
+
def call samples
|
91
|
+
b = @buf.read samples
|
92
|
+
c_out = NArray.scomplex samples
|
93
|
+
c_out[0..-1] = b[@channel_i,true].to_f.div!(32767)
|
94
|
+
c_out.imag = b[@channel_q,true].to_f.div!(32767)
|
95
|
+
c_out
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
+
|
17
|
+
class Radio
|
18
|
+
module Input
|
19
|
+
class File
|
20
|
+
|
21
|
+
def self.status
|
22
|
+
"Loaded: %d files found" % sources.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.sources
|
26
|
+
result = {}
|
27
|
+
files = Dir.glob ::File.expand_path '../../../../test/wav/**/*.wav', __FILE__
|
28
|
+
files.each do |file|
|
29
|
+
begin
|
30
|
+
f = new file, 0, 0, 1
|
31
|
+
rescue Exception => e
|
32
|
+
next
|
33
|
+
end
|
34
|
+
result[file] = {
|
35
|
+
name: file,
|
36
|
+
rates: [f.rate],
|
37
|
+
channels: f.channels
|
38
|
+
}
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
# You can load any file, not just the ones in sources.
|
44
|
+
def initialize id, rate, channel_i, channel_q
|
45
|
+
self.class.constants.each do |x|
|
46
|
+
klass = eval x.to_s
|
47
|
+
@reader = klass.new id, rate, channel_i, channel_q rescue nil
|
48
|
+
break if @reader
|
49
|
+
end
|
50
|
+
raise 'Unknown format' unless @reader
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing meth, *args, &block
|
54
|
+
@reader.send meth, *args, &block
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,124 @@
|
|
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
|
+
|
17
|
+
class Radio
|
18
|
+
module Input
|
19
|
+
class File
|
20
|
+
class WAV
|
21
|
+
|
22
|
+
attr_reader :rate
|
23
|
+
|
24
|
+
def initialize id, rate, channel_i, channel_q
|
25
|
+
@file = ::File.new id
|
26
|
+
#TODO validate header instead?
|
27
|
+
@file.read 12 # discard header
|
28
|
+
@rate = rate
|
29
|
+
@channel_i = channel_i
|
30
|
+
@channel_q = channel_q
|
31
|
+
@data = [next_data]
|
32
|
+
@time = Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
def call samples
|
36
|
+
sample_size = @channels * (@bit_sample/8)
|
37
|
+
@time += 1.0/(rate/samples)
|
38
|
+
sleep [0,@time-Time.now].max
|
39
|
+
while @data.reduce(0){|a,b|a+b.size} < samples * sample_size
|
40
|
+
@data.push next_data
|
41
|
+
end
|
42
|
+
if channels > 1
|
43
|
+
out = NArray.scomplex samples
|
44
|
+
else
|
45
|
+
out = NArray.sfloat samples
|
46
|
+
end
|
47
|
+
i = 0
|
48
|
+
while i < samples
|
49
|
+
if @data.first.size/sample_size > samples-i
|
50
|
+
out[i..-1] = convert @data.first[0...(samples-i)*sample_size]
|
51
|
+
@data[0] = @data.first[(samples-i)*sample_size..-1]
|
52
|
+
i = samples
|
53
|
+
else
|
54
|
+
converted_data = convert @data.shift
|
55
|
+
out[i...(i+converted_data.size)] = converted_data
|
56
|
+
i += converted_data.size
|
57
|
+
end
|
58
|
+
end
|
59
|
+
out
|
60
|
+
end
|
61
|
+
|
62
|
+
def channels
|
63
|
+
return 2 if @channel_q and @channels > 1
|
64
|
+
1
|
65
|
+
end
|
66
|
+
|
67
|
+
def stop
|
68
|
+
@file.close
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def convert d
|
74
|
+
out = case @bit_sample
|
75
|
+
when 8 then NArray.to_na(d,NArray::BYTE).to_f.collect!{|v|(v-128)/127}
|
76
|
+
when 16 then NArray.to_na(d,NArray::SINT).to_f.div! 32767
|
77
|
+
# when 24 then NArray.to_na(d,NArray::???).to_f.collect!{|v|(v-8388608)/8388607}
|
78
|
+
else
|
79
|
+
raise "Unsupported sample size: #{@bit_sample}"
|
80
|
+
end
|
81
|
+
return out if channels == 1 and @channels == 1
|
82
|
+
out.reshape! @channels, out.size/@channels
|
83
|
+
if channels == 1
|
84
|
+
out[@channel_i,true]
|
85
|
+
else
|
86
|
+
c_out = NArray.scomplex out[0,true].size
|
87
|
+
c_out[0..-1] = out[@channel_i,true]
|
88
|
+
c_out.imag = out[@channel_q,true]
|
89
|
+
c_out
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#TODO read data in chunks smaller than size (which is often the whole file)
|
94
|
+
def next_data
|
95
|
+
loop do
|
96
|
+
until @file.eof?
|
97
|
+
type = @file.read(4)
|
98
|
+
size = @file.read(4).unpack("V")[0].to_i
|
99
|
+
case type
|
100
|
+
when 'fmt '
|
101
|
+
fmt = @file.read(size)
|
102
|
+
@id = fmt.slice(0,2).unpack('c')[0]
|
103
|
+
@channels = fmt.slice(2,2).unpack('c')[0]
|
104
|
+
@rate = fmt.slice(4,4).unpack('V').join.to_i
|
105
|
+
@byte_sec = fmt.slice(8,4).unpack('V').join.to_i
|
106
|
+
@block_size = fmt.slice(12,2).unpack('c')[0]
|
107
|
+
@bit_sample = fmt.slice(14,2).unpack('c')[0]
|
108
|
+
next
|
109
|
+
when 'data'
|
110
|
+
return @file.read size
|
111
|
+
else
|
112
|
+
raise "Unknown GIF type: #{type}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
@file.rewind
|
116
|
+
@file.read 12 # discard header
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|