audio-playback 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.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +81 -0
- data/bin/playback +66 -0
- data/lib/audio-playback.rb +55 -0
- data/lib/audio-playback/commandline.rb +50 -0
- data/lib/audio-playback/device.rb +61 -0
- data/lib/audio-playback/device/output.rb +113 -0
- data/lib/audio-playback/device/stream.rb +134 -0
- data/lib/audio-playback/file.rb +37 -0
- data/lib/audio-playback/playback.rb +152 -0
- data/lib/audio-playback/playback/frame.rb +72 -0
- data/lib/audio-playback/playback/frame_set.rb +117 -0
- data/lib/audio-playback/playback/stream_data.rb +52 -0
- data/lib/audio-playback/sound.rb +54 -0
- data/test/device/output_test.rb +141 -0
- data/test/device/stream_test.rb +54 -0
- data/test/device_test.rb +75 -0
- data/test/file_test.rb +105 -0
- data/test/helper.rb +59 -0
- data/test/media/1-mono-44100.aiff +0 -0
- data/test/media/1-mono-44100.wav +0 -0
- data/test/media/1-stereo-44100.aiff +0 -0
- data/test/media/1-stereo-44100.wav +0 -0
- data/test/playback/frame_set_test.rb +115 -0
- data/test/playback/frame_test.rb +46 -0
- data/test/playback/stream_data_test.rb +27 -0
- data/test/playback_test.rb +111 -0
- data/test/sound_test.rb +80 -0
- metadata +213 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module AudioPlayback
|
2
|
+
|
3
|
+
module Playback
|
4
|
+
|
5
|
+
# Playback data for the Device::Stream
|
6
|
+
class StreamData
|
7
|
+
|
8
|
+
# A C pointer version of the audio data
|
9
|
+
def self.to_pointer(playback)
|
10
|
+
stream_data = new(playback)
|
11
|
+
stream_data.to_pointer
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Playback::Action] playback
|
15
|
+
def initialize(playback)
|
16
|
+
@playback = playback
|
17
|
+
populate
|
18
|
+
end
|
19
|
+
|
20
|
+
# A C pointer version of the audio data
|
21
|
+
# @return [FFI::Pointer]
|
22
|
+
def to_pointer
|
23
|
+
pointer = FFI::LibC.malloc(@playback.data_size)
|
24
|
+
pointer.write_array_of_float(@data.flatten)
|
25
|
+
pointer
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Populate the playback stream data
|
31
|
+
# @return [FrameSet]
|
32
|
+
def populate
|
33
|
+
@data = FrameSet.new(@playback)
|
34
|
+
add_metadata
|
35
|
+
@data
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add playback metadata to the stream data
|
39
|
+
# @return [FrameSet]
|
40
|
+
def add_metadata
|
41
|
+
@data.unshift(0.0) # 3. is_eof
|
42
|
+
@data.unshift(0.0) # 2. counter
|
43
|
+
@data.unshift(@playback.output.num_channels.to_f) # 1. num_channels
|
44
|
+
@data.unshift(@playback.sound.size.to_f) # 0. sample size
|
45
|
+
@data
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module AudioPlayback
|
2
|
+
|
3
|
+
class Sound
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :audio_file, :data, :size
|
8
|
+
def_delegators :@audio_file, :num_channels, :sample_rate
|
9
|
+
|
10
|
+
# Load a sound from the given file path
|
11
|
+
# @param [::File, String] file_or_path
|
12
|
+
# @param [Hash] options
|
13
|
+
# @option options [IO] logger
|
14
|
+
# @return [Sound]
|
15
|
+
def self.load(file_or_path, options = {})
|
16
|
+
file = AudioPlayback::File.new(file_or_path)
|
17
|
+
new(file, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [AudioPlayback::File] audio_file
|
21
|
+
# @param [Hash] options
|
22
|
+
# @option options [IO] logger
|
23
|
+
def initialize(audio_file, options = {})
|
24
|
+
@audio_file = audio_file
|
25
|
+
populate(options)
|
26
|
+
report(options[:logger]) if options[:logger]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Log a report about the sound
|
30
|
+
# @param [IO] logger
|
31
|
+
# @return [Boolean]
|
32
|
+
def report(logger)
|
33
|
+
logger.puts("Sound report for #{@audio_file.path}")
|
34
|
+
logger.puts(" Sample rate: #{@audio_file.sample_rate}")
|
35
|
+
logger.puts(" Channels: #{@audio_file.num_channels}")
|
36
|
+
logger.puts(" File size: #{@audio_file.size}")
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Populate the sound meta/data
|
43
|
+
# @param [Hash] options
|
44
|
+
# @option options [IO] :logger
|
45
|
+
# @return [Sound]
|
46
|
+
def populate(options = {})
|
47
|
+
@data = @audio_file.read(options)
|
48
|
+
@size = data.size
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class AudioPlayback::Device::OutputTest < Minitest::Test
|
4
|
+
|
5
|
+
context "Output" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@outputs = AudioPlayback::Device::Output.all
|
9
|
+
end
|
10
|
+
|
11
|
+
context ".all" do
|
12
|
+
|
13
|
+
should "return available outputs" do
|
14
|
+
refute_nil @outputs
|
15
|
+
refute_empty @outputs
|
16
|
+
assert_equal TestHelper::OUTPUT_INFO.map { |info| info[:name] }, @outputs.map(&:name)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context ".by_id" do
|
22
|
+
|
23
|
+
should "return an output with the given id" do
|
24
|
+
@outputs.each do |output|
|
25
|
+
assert_equal output, AudioPlayback::Device::Output.by_id(output.id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context ".by_name" do
|
32
|
+
|
33
|
+
should "return an output with the given name" do
|
34
|
+
@outputs.each do |output|
|
35
|
+
assert_equal output, AudioPlayback::Device::Output.by_name(output.name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#populate" do
|
42
|
+
|
43
|
+
setup do
|
44
|
+
@id = 0
|
45
|
+
@output = AudioPlayback::Device::Output.new(@id)
|
46
|
+
end
|
47
|
+
|
48
|
+
should "populate id" do
|
49
|
+
refute_nil @output.id
|
50
|
+
assert_kind_of Fixnum, @output.id
|
51
|
+
end
|
52
|
+
|
53
|
+
should "populate latency" do
|
54
|
+
refute_nil @output.latency
|
55
|
+
assert_kind_of Numeric, @output.latency
|
56
|
+
end
|
57
|
+
|
58
|
+
should "populate number of channels" do
|
59
|
+
refute_nil @output.num_channels
|
60
|
+
assert_kind_of Fixnum, @output.num_channels
|
61
|
+
end
|
62
|
+
|
63
|
+
should "populate name" do
|
64
|
+
refute_nil @output.name
|
65
|
+
assert_kind_of String, @output.name
|
66
|
+
assert_equal "Test Output #{@id+1}", @output.name
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
context "#latency" do
|
72
|
+
|
73
|
+
setup do
|
74
|
+
@test_id = (0..TestHelper::OUTPUT_INFO.size-1).to_a.sample
|
75
|
+
@test_info = TestHelper::OUTPUT_INFO[@test_id]
|
76
|
+
end
|
77
|
+
|
78
|
+
context "latency option" do
|
79
|
+
|
80
|
+
setup do
|
81
|
+
@output = AudioPlayback::Device::Output.new(@test_id, :latency => 20)
|
82
|
+
end
|
83
|
+
|
84
|
+
should "return correct latency" do
|
85
|
+
refute_nil @output.latency
|
86
|
+
assert_kind_of Float, @output.latency
|
87
|
+
assert_equal 20, @output.latency
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
context "no options" do
|
93
|
+
|
94
|
+
setup do
|
95
|
+
@output = AudioPlayback::Device::Output.new(@test_id)
|
96
|
+
end
|
97
|
+
|
98
|
+
should "return correct latency" do
|
99
|
+
refute_nil @output.latency
|
100
|
+
assert_kind_of Float, @output.latency
|
101
|
+
assert_equal @test_info[:defaultHighOutputLatency], @output.latency
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
context "#num_channels" do
|
109
|
+
|
110
|
+
setup do
|
111
|
+
@test_id = (0..TestHelper::OUTPUT_INFO.size-1).to_a.sample
|
112
|
+
@test_info = TestHelper::OUTPUT_INFO[@test_id]
|
113
|
+
@output = AudioPlayback::Device::Output.new(@test_id)
|
114
|
+
end
|
115
|
+
|
116
|
+
should "return correct num_channels" do
|
117
|
+
refute_nil @output.num_channels
|
118
|
+
assert_kind_of Fixnum, @output.num_channels
|
119
|
+
assert_equal @test_info[:maxOutputChannels], @output.num_channels
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
context "#id" do
|
125
|
+
|
126
|
+
setup do
|
127
|
+
@test_id = (0..TestHelper::OUTPUT_INFO.size-1).to_a.sample
|
128
|
+
@output = AudioPlayback::Device::Output.new(@test_id)
|
129
|
+
end
|
130
|
+
|
131
|
+
should "return correct id" do
|
132
|
+
refute_nil @output.id
|
133
|
+
assert_kind_of Fixnum, @output.id
|
134
|
+
assert_equal @test_id, @output.id
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class AudioPlayback::Device::StreamTest < Minitest::Test
|
4
|
+
|
5
|
+
context "Stream" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@path = "test/media/1-mono-44100.wav"
|
9
|
+
@sound = AudioPlayback::Sound.load(@path)
|
10
|
+
@output = AudioPlayback::Device::Output.by_id(0)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#exit_callback" do
|
14
|
+
|
15
|
+
setup do
|
16
|
+
@stream = AudioPlayback::Device::Stream.new(@output)
|
17
|
+
@stream.expects(:close).once
|
18
|
+
end
|
19
|
+
|
20
|
+
teardown do
|
21
|
+
@stream.unstub(:close)
|
22
|
+
end
|
23
|
+
|
24
|
+
should "initialize exit callback" do
|
25
|
+
assert @stream.send(:exit_callback)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#play" do
|
31
|
+
|
32
|
+
setup do
|
33
|
+
@stream = AudioPlayback::Device::Stream.new(@output)
|
34
|
+
@playback = AudioPlayback::Playback.new(@sound, @output)
|
35
|
+
@stream.expects(:report).once
|
36
|
+
@stream.expects(:open_playback).once
|
37
|
+
@stream.expects(:start).once
|
38
|
+
end
|
39
|
+
|
40
|
+
teardown do
|
41
|
+
@stream.unstub(:report)
|
42
|
+
@stream.unstub(:open_playback)
|
43
|
+
@stream.unstub(:start)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "return self" do
|
47
|
+
assert_equal @stream, @stream.play(@playback)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/test/device_test.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class AudioPlayback::DeviceTest < Minitest::Test
|
4
|
+
|
5
|
+
context "Device" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@outputs = AudioPlayback::Device.outputs
|
9
|
+
end
|
10
|
+
|
11
|
+
context ".outputs" do
|
12
|
+
|
13
|
+
should "return available outputs" do
|
14
|
+
refute_nil @outputs
|
15
|
+
refute_empty @outputs
|
16
|
+
assert_equal TestHelper::OUTPUT_INFO.map { |info| info[:name] }, @outputs.map(&:name)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context ".output?" do
|
22
|
+
|
23
|
+
should "return whether device is an output" do
|
24
|
+
assert @outputs.all? { |output| AudioPlayback::Device.send(:output?, output.id) }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
context ".device_info" do
|
30
|
+
|
31
|
+
should "return requested device info" do
|
32
|
+
assert @outputs.all? do |output|
|
33
|
+
info = AudioPlayback::Device.send(:device_info, output.id)
|
34
|
+
TestHelper::OUTPUT_INFO.include?(info)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context ".by_id" do
|
41
|
+
|
42
|
+
should "return an output with the given id" do
|
43
|
+
@outputs.each do |output|
|
44
|
+
assert_equal output, AudioPlayback::Device.by_id(output.id)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context ".by_name" do
|
51
|
+
|
52
|
+
should "return an output with the given name" do
|
53
|
+
@outputs.each do |output|
|
54
|
+
assert_equal output, AudioPlayback::Device.by_name(output.name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context ".default_output" do
|
61
|
+
|
62
|
+
setup do
|
63
|
+
@output = AudioPlayback::Device.default_output
|
64
|
+
end
|
65
|
+
|
66
|
+
should "return the default output" do
|
67
|
+
refute_nil @output
|
68
|
+
assert_equal TestHelper::DEFAULT_OUTPUT_ID, @output.id
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/test/file_test.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class AudioPlayback::FileTest < Minitest::Test
|
4
|
+
|
5
|
+
context "AudioFile" do
|
6
|
+
|
7
|
+
context ".new" do
|
8
|
+
|
9
|
+
context "mono" do
|
10
|
+
|
11
|
+
setup do
|
12
|
+
@path = "test/media/1-mono-44100.wav"
|
13
|
+
@file = AudioPlayback::File.new(@path)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "populate" do
|
17
|
+
refute_nil @file
|
18
|
+
refute_nil @file.num_channels
|
19
|
+
refute_nil @file.sample_rate
|
20
|
+
refute_nil @file.path
|
21
|
+
refute_nil @file.size
|
22
|
+
end
|
23
|
+
|
24
|
+
should "have correct information" do
|
25
|
+
assert_equal 1, @file.num_channels
|
26
|
+
assert_equal 44100, @file.sample_rate.to_i
|
27
|
+
assert_equal @path, @file.path
|
28
|
+
assert_equal File.size(@path), @file.size
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "stereo" do
|
34
|
+
|
35
|
+
setup do
|
36
|
+
@path = "test/media/1-stereo-44100.wav"
|
37
|
+
@file_obj = File.new(@path)
|
38
|
+
@file = AudioPlayback::File.new(@file_obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "populate" do
|
42
|
+
refute_nil @file
|
43
|
+
refute_nil @file.num_channels
|
44
|
+
refute_nil @file.sample_rate
|
45
|
+
refute_nil @file.path
|
46
|
+
refute_nil @file.size
|
47
|
+
end
|
48
|
+
|
49
|
+
should "have correct information" do
|
50
|
+
assert_equal 2, @file.num_channels
|
51
|
+
assert_equal 44100, @file.sample_rate.to_i
|
52
|
+
assert_equal @path, @file.path
|
53
|
+
assert_equal File.size(@path), @file.size
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#read" do
|
61
|
+
|
62
|
+
context "mono" do
|
63
|
+
|
64
|
+
setup do
|
65
|
+
@path = "test/media/1-mono-44100.wav"
|
66
|
+
@file = AudioPlayback::File.new(@path)
|
67
|
+
@data = @file.read
|
68
|
+
end
|
69
|
+
|
70
|
+
should "populate data" do
|
71
|
+
refute_nil @data
|
72
|
+
assert @data.kind_of?(Array)
|
73
|
+
refute_empty @data
|
74
|
+
assert @data.all? { |frame| frame.kind_of?(Float) }
|
75
|
+
assert @data.all? { |frame| frame >= -1 }
|
76
|
+
assert @data.all? { |frame| frame <= 1 }
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context "stereo" do
|
82
|
+
|
83
|
+
setup do
|
84
|
+
@path = "test/media/1-stereo-44100.wav"
|
85
|
+
@file = AudioPlayback::File.new(@path)
|
86
|
+
@data = @file.read
|
87
|
+
end
|
88
|
+
|
89
|
+
should "populate data" do
|
90
|
+
refute_nil @data
|
91
|
+
assert @data.kind_of?(Array)
|
92
|
+
refute_empty @data
|
93
|
+
assert @data.all? { |frame_channels| frame_channels.kind_of?(Array) }
|
94
|
+
assert @data.all? { |frame_channels| frame_channels.all? { |frame| frame.kind_of?(Float) } }
|
95
|
+
assert @data.all? { |frame_channels| frame_channels.all? { |frame| frame >= -1 } }
|
96
|
+
assert @data.all? { |frame_channels| frame_channels.all? { |frame| frame <= 1 } }
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|