radio 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|