audio-playback 0.0.6 → 0.0.8
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 +4 -4
- data/README.md +9 -1
- data/bin/playback +8 -1
- data/lib/audio-playback.rb +11 -5
- data/lib/audio-playback/commandline.rb +27 -1
- data/lib/audio-playback/device.rb +3 -3
- data/lib/audio-playback/device/output.rb +5 -5
- data/lib/audio-playback/device/stream.rb +44 -19
- data/lib/audio-playback/playback.rb +127 -21
- data/lib/audio-playback/playback/frame.rb +8 -8
- data/lib/audio-playback/playback/frame_set.rb +3 -3
- data/lib/audio-playback/playback/mixer.rb +8 -8
- data/lib/audio-playback/playback/stream_data.rb +35 -10
- data/lib/audio-playback/position.rb +89 -0
- data/test/device/output_test.rb +4 -4
- data/test/playback/stream_data_test.rb +97 -9
- data/test/playback_test.rb +258 -2
- data/test/position_test.rb +167 -0
- metadata +6 -4
@@ -17,7 +17,7 @@ module AudioPlayback
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Truncate the frame to the given size
|
20
|
-
# @param [
|
20
|
+
# @param [Integer] num
|
21
21
|
# @return [Frame]
|
22
22
|
def truncate(num)
|
23
23
|
@frame.slice!(num..-1)
|
@@ -26,10 +26,10 @@ module AudioPlayback
|
|
26
26
|
|
27
27
|
# Fill up the given number of channels at the end of the frame with duplicate data from the last
|
28
28
|
# existing channel
|
29
|
-
# @param [
|
29
|
+
# @param [Integer] num
|
30
30
|
# @param [Hash] options
|
31
|
-
# @option options [Array<
|
32
|
-
# @option options [
|
31
|
+
# @option options [Array<Integer>] :channels (required if :num_channels is provided)
|
32
|
+
# @option options [Integer] :num_channels (required if :channels is provided)
|
33
33
|
# @return [Boolean]
|
34
34
|
def fill(num, options = {})
|
35
35
|
if (channels = options[:channels]).nil?
|
@@ -43,8 +43,8 @@ module AudioPlayback
|
|
43
43
|
private
|
44
44
|
|
45
45
|
# Zero out the given number of channels in the frame starting with the given index
|
46
|
-
# @param [
|
47
|
-
# @param [
|
46
|
+
# @param [Integer] index
|
47
|
+
# @param [Integer] num_channels
|
48
48
|
# @return [Frame]
|
49
49
|
def silence_channels(index, num_channels)
|
50
50
|
@frame.fill(0, index, num_channels)
|
@@ -52,8 +52,8 @@ module AudioPlayback
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# Fill the entire frame for the given channels
|
55
|
-
# @param [
|
56
|
-
# @param [Array<
|
55
|
+
# @param [Integer] num_channels
|
56
|
+
# @param [Array<Integer>] channels
|
57
57
|
# @return [Boolean]
|
58
58
|
def fill_for_channels(num_channels, channels)
|
59
59
|
values = @frame.dup
|
@@ -7,7 +7,7 @@ module AudioPlayback
|
|
7
7
|
|
8
8
|
extend Forwardable
|
9
9
|
|
10
|
-
def_delegators :@data, :[], :[]=, :flatten, :slice, :to_ary, :unshift
|
10
|
+
def_delegators :@data, :[], :[]=, :flatten, :length, :size, :slice, :to_ary, :unshift
|
11
11
|
|
12
12
|
# @param [Playback::Action] playback
|
13
13
|
def initialize(playback)
|
@@ -83,9 +83,9 @@ module AudioPlayback
|
|
83
83
|
# Ensure that the channel structure of the frameset is according to the given number of channels
|
84
84
|
# and to the given particular channels when provided
|
85
85
|
# @param [Array<Frame>] data
|
86
|
-
# @param [
|
86
|
+
# @param [Integer] num_channels
|
87
87
|
# @param [Hash] options
|
88
|
-
# @option options [Array<
|
88
|
+
# @option options [Array<Integer>] :channels
|
89
89
|
# @return [Array<Frame>]
|
90
90
|
def ensure_num_channels(data, num_channels, options = {})
|
91
91
|
data.each do |frame|
|
@@ -6,21 +6,21 @@ module AudioPlayback
|
|
6
6
|
class Mixer
|
7
7
|
|
8
8
|
# Mix multiple sounds at equal amplitude
|
9
|
-
# @param [Array<Array<Array<
|
10
|
-
# @return [Array<Array<
|
9
|
+
# @param [Array<Array<Array<Integer>>>] sounds_data
|
10
|
+
# @return [Array<Array<Integer>>]
|
11
11
|
def self.mix(sounds_data)
|
12
12
|
mixer = new(sounds_data)
|
13
13
|
mixer.mix
|
14
14
|
end
|
15
15
|
|
16
|
-
# @param [Array<Array<Array<
|
16
|
+
# @param [Array<Array<Array<Integer>>>] sounds_data
|
17
17
|
def initialize(sounds_data)
|
18
18
|
@data = sounds_data
|
19
19
|
populate
|
20
20
|
end
|
21
21
|
|
22
22
|
# Mix multiple sounds at equal amplitude
|
23
|
-
# @return [Array<Array<
|
23
|
+
# @return [Array<Array<Integer>>]
|
24
24
|
def mix
|
25
25
|
(0..@length-1).to_a.map { |index| mix_frame(index) }
|
26
26
|
end
|
@@ -37,8 +37,8 @@ module AudioPlayback
|
|
37
37
|
|
38
38
|
# Get all of the data frames for the given index
|
39
39
|
# For example for index 3, two two channel sounds, frames(3) might give you [[1, 3], [2, 3]]
|
40
|
-
# @param [
|
41
|
-
# @return [Array<Array<
|
40
|
+
# @param [Integer] index
|
41
|
+
# @return [Array<Array<Integer>>]
|
42
42
|
def frames(index)
|
43
43
|
@data.map { |sound_data| sound_data[index] }
|
44
44
|
end
|
@@ -46,8 +46,8 @@ module AudioPlayback
|
|
46
46
|
# Mix the frame with the given index
|
47
47
|
# whereas frames(3) might give you [[1, 3], [2, 3]]
|
48
48
|
# mix_frame(3) might give you [1.5, 3]
|
49
|
-
# @param [
|
50
|
-
# @return [Array<
|
49
|
+
# @param [Integer] index
|
50
|
+
# @return [Array<Integer>]
|
51
51
|
def mix_frame(index)
|
52
52
|
totals = frames(index).compact.transpose.map { |x| x && x.reduce(:+) || 0 }
|
53
53
|
totals.map { |frame| frame / @depth }
|
@@ -5,6 +5,11 @@ module AudioPlayback
|
|
5
5
|
# Playback data for the Device::Stream
|
6
6
|
class StreamData
|
7
7
|
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :num_frames
|
11
|
+
def_delegators :@data, :[], :at, :length, :size
|
12
|
+
|
8
13
|
# A C pointer version of the audio data
|
9
14
|
# @param [Playback::Action] playback
|
10
15
|
# @return [FFI::Pointer]
|
@@ -22,28 +27,40 @@ module AudioPlayback
|
|
22
27
|
# Reset the stream metadata
|
23
28
|
# @param [Boolean]
|
24
29
|
def reset
|
25
|
-
|
26
|
-
Playback::METADATA.index(:pointer),
|
27
|
-
Playback::METADATA.index(:is_eof)
|
28
|
-
]
|
29
|
-
indexes.each { |index| @data[index] = 0.0 }
|
30
|
+
[:is_eof, :pointer].each { |key| set_metadata(key, 0.0) }
|
30
31
|
true
|
31
32
|
end
|
32
33
|
|
33
34
|
# A C pointer version of the audio data
|
34
35
|
# @return [FFI::Pointer]
|
35
36
|
def to_pointer
|
36
|
-
@pointer
|
37
|
-
|
37
|
+
if @pointer.nil?
|
38
|
+
@pointer = FFI::LibC.malloc(@playback.data_size)
|
39
|
+
@pointer.write_array_of_float(@data.flatten)
|
40
|
+
end
|
38
41
|
@pointer
|
39
42
|
end
|
40
43
|
|
41
44
|
private
|
42
45
|
|
46
|
+
# Set the metadata value with the given key to the given value
|
47
|
+
# @param [Symbol] key
|
48
|
+
# @param [Object] value
|
49
|
+
# @return [Object]
|
50
|
+
def set_metadata(key, value)
|
51
|
+
index = Playback::METADATA.index(key)
|
52
|
+
@data[index] = value
|
53
|
+
unless @pointer.nil?
|
54
|
+
@pointer.put_float32(index * Playback::FRAME_SIZE, value)
|
55
|
+
end
|
56
|
+
value
|
57
|
+
end
|
58
|
+
|
43
59
|
# Populate the playback stream data
|
44
60
|
# @return [FrameSet]
|
45
61
|
def populate
|
46
62
|
@data = FrameSet.new(@playback)
|
63
|
+
@num_frames = @data.size
|
47
64
|
add_metadata
|
48
65
|
@data
|
49
66
|
end
|
@@ -51,10 +68,18 @@ module AudioPlayback
|
|
51
68
|
# Add playback metadata to the stream data
|
52
69
|
# @return [FrameSet]
|
53
70
|
def add_metadata
|
54
|
-
@
|
55
|
-
|
71
|
+
if @playback.truncate?
|
72
|
+
end_frame = @playback.truncate[:end_frame]
|
73
|
+
start_frame = @playback.truncate[:start_frame]
|
74
|
+
end
|
75
|
+
@data.unshift(0.0) # 6. is_eof
|
76
|
+
@data.unshift(start_frame || 0.0) # 5. counter
|
77
|
+
loop_value = @playback.looping? ? 1.0 : 0.0
|
78
|
+
@data.unshift(loop_value) # 4. is_looping
|
79
|
+
@data.unshift(end_frame || @num_frames.to_f) # 3. end frame
|
80
|
+
@data.unshift(start_frame || 0.0) # 2. start frame
|
56
81
|
@data.unshift(@playback.output.num_channels.to_f) # 1. num_channels
|
57
|
-
@data.unshift(@
|
82
|
+
@data.unshift(@num_frames.to_f) # 0. frame set size (without metadata)
|
58
83
|
@data
|
59
84
|
end
|
60
85
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module AudioPlayback
|
2
|
+
|
3
|
+
# A time position in a sound
|
4
|
+
class Position
|
5
|
+
|
6
|
+
class InvalidTime < RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
UNITS = [
|
12
|
+
1, # second
|
13
|
+
60, # minute
|
14
|
+
3600 # hour
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
# Time format like (hh:)(mm:)ss(.ss)
|
18
|
+
FORMAT = /((\d+\:)(\d{2}\:)|(\d{2}\:))?\d{1,2}(\.\d+)?/.freeze
|
19
|
+
|
20
|
+
attr_reader :seconds
|
21
|
+
alias_method :to_seconds, :seconds
|
22
|
+
def_delegators :@seconds, :to_f
|
23
|
+
|
24
|
+
# @param [Numeric, String] seconds_or_time Time as (hh:)(mm:)ss(.ss)
|
25
|
+
def initialize(seconds_or_time)
|
26
|
+
seconds_or_time = seconds_or_time.to_s
|
27
|
+
validate_time(seconds_or_time)
|
28
|
+
populate(seconds_or_time)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Multiply the seconds value of this Position by the given value
|
32
|
+
# @param [Numeric, Position] another
|
33
|
+
# @return [Float]
|
34
|
+
def *(another)
|
35
|
+
@seconds * another.to_f
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add the seconds value of this Position to the given value
|
39
|
+
# @param [Numeric, Position] another
|
40
|
+
# @return [Float]
|
41
|
+
def +(another)
|
42
|
+
@seconds + another.to_f
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Validate that the time that was passed into the constructor is in the correct
|
48
|
+
# format. Raises InvalidTime error if not
|
49
|
+
# @param [Numeric, String] seconds_or_time Time as (hh:)(mm:)ss(.ss)
|
50
|
+
# @return [Boolean]
|
51
|
+
def validate_time(seconds_or_time)
|
52
|
+
unless seconds_or_time.match(FORMAT)
|
53
|
+
raise(InvalidTime)
|
54
|
+
end
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
# Validate that the segments of the time that was passed into the constructor
|
59
|
+
# are valid. For example that the minutes and or seconds values are below 60.
|
60
|
+
# Raises InvalidTime error if not
|
61
|
+
# @param [Array<String>] segments Time as [(hh), (mm), ss(.ss)]
|
62
|
+
# @return [Boolean]
|
63
|
+
def validate_segments(segments)
|
64
|
+
seconds = segments[0]
|
65
|
+
minutes = segments[1]
|
66
|
+
[seconds, minutes].compact.each do |segment|
|
67
|
+
if segment >= 60
|
68
|
+
raise(InvalidTime)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
# Populate the seconds ivar using the time that was passed into the constructor
|
75
|
+
# @param [Numeric, String] seconds_or_time Time as (hh:)(mm:)ss(.ss)
|
76
|
+
# @return [Float]
|
77
|
+
def populate(seconds_or_time)
|
78
|
+
segments = seconds_or_time.split(":").map(&:to_f).reverse
|
79
|
+
validate_segments(segments)
|
80
|
+
@seconds = 0
|
81
|
+
segments.each_with_index do |segment, i|
|
82
|
+
@seconds += segment * UNITS[i]
|
83
|
+
end
|
84
|
+
@seconds
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
data/test/device/output_test.rb
CHANGED
@@ -47,7 +47,7 @@ class AudioPlayback::Device::OutputTest < Minitest::Test
|
|
47
47
|
|
48
48
|
should "populate id" do
|
49
49
|
refute_nil @output.id
|
50
|
-
assert_kind_of
|
50
|
+
assert_kind_of Integer, @output.id
|
51
51
|
end
|
52
52
|
|
53
53
|
should "populate latency" do
|
@@ -57,7 +57,7 @@ class AudioPlayback::Device::OutputTest < Minitest::Test
|
|
57
57
|
|
58
58
|
should "populate number of channels" do
|
59
59
|
refute_nil @output.num_channels
|
60
|
-
assert_kind_of
|
60
|
+
assert_kind_of Integer, @output.num_channels
|
61
61
|
end
|
62
62
|
|
63
63
|
should "populate name" do
|
@@ -115,7 +115,7 @@ class AudioPlayback::Device::OutputTest < Minitest::Test
|
|
115
115
|
|
116
116
|
should "return correct num_channels" do
|
117
117
|
refute_nil @output.num_channels
|
118
|
-
assert_kind_of
|
118
|
+
assert_kind_of Integer, @output.num_channels
|
119
119
|
assert_equal @test_info[:maxOutputChannels], @output.num_channels
|
120
120
|
end
|
121
121
|
|
@@ -130,7 +130,7 @@ class AudioPlayback::Device::OutputTest < Minitest::Test
|
|
130
130
|
|
131
131
|
should "return correct id" do
|
132
132
|
refute_nil @output.id
|
133
|
-
assert_kind_of
|
133
|
+
assert_kind_of Integer, @output.id
|
134
134
|
assert_equal @test_id, @output.id
|
135
135
|
end
|
136
136
|
|
@@ -4,20 +4,108 @@ class AudioPlayback::Playback::StreamDataTest < Minitest::Test
|
|
4
4
|
|
5
5
|
context "StreamData" do
|
6
6
|
|
7
|
+
setup do
|
8
|
+
@path = "test/media/1-mono-44100.wav"
|
9
|
+
@file = AudioPlayback::File.new(@path)
|
10
|
+
@sound = AudioPlayback::Sound.new(@file)
|
11
|
+
@output = MockOutput.new(1, :num_channels => 1)
|
12
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, :stream => MockStream.new(@output))
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#add_metadata" do
|
16
|
+
|
17
|
+
setup do
|
18
|
+
@data = AudioPlayback::Playback::StreamData.new(@playback)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "add default metadata" do
|
22
|
+
assert_equal 0.0, @data[AudioPlayback::Playback::METADATA.index(:is_eof)]
|
23
|
+
assert_equal 0.0, @data[AudioPlayback::Playback::METADATA.index(:start_frame)]
|
24
|
+
assert_equal 0.0, @data[AudioPlayback::Playback::METADATA.index(:pointer)]
|
25
|
+
assert_equal @data.num_frames.to_f, @data[AudioPlayback::Playback::METADATA.index(:end_frame)]
|
26
|
+
assert_equal @data.num_frames.to_f, @data[AudioPlayback::Playback::METADATA.index(:size)]
|
27
|
+
assert_equal @playback.output.num_channels.to_f, @data[AudioPlayback::Playback::METADATA.index(:num_channels)]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
context "#set_metadata" do
|
33
|
+
|
34
|
+
setup do
|
35
|
+
@data = AudioPlayback::Playback::StreamData.new(@playback)
|
36
|
+
|
37
|
+
# Validate
|
38
|
+
@pointer_index = AudioPlayback::Playback::METADATA.index(:pointer)
|
39
|
+
@eof_index = AudioPlayback::Playback::METADATA.index(:is_eof)
|
40
|
+
|
41
|
+
assert_equal 0.0, @data[@pointer_index]
|
42
|
+
assert_equal 0.0, @data[@eof_index]
|
43
|
+
|
44
|
+
# Set metadata to non zero values
|
45
|
+
@data.send(:set_metadata, :pointer, 4.0)
|
46
|
+
@data.send(:set_metadata, :is_eof, 1.0)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "change values" do
|
50
|
+
assert_equal 4.0, @data[@pointer_index]
|
51
|
+
assert_equal 1.0, @data[@eof_index]
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
context "#reset" do
|
57
|
+
|
58
|
+
setup do
|
59
|
+
@data = AudioPlayback::Playback::StreamData.new(@playback)
|
60
|
+
|
61
|
+
# Set metadata to non zero values
|
62
|
+
@data.send(:set_metadata, :pointer, 4.0)
|
63
|
+
@data.send(:set_metadata, :is_eof, 1.0)
|
64
|
+
|
65
|
+
# Validate metadata
|
66
|
+
indexes = [:pointer, :is_eof].map do |key|
|
67
|
+
AudioPlayback::Playback::METADATA.index(key)
|
68
|
+
end
|
69
|
+
@nonzero_values = indexes.map { |index| @data[index] }
|
70
|
+
assert @nonzero_values.all? { |value| value > 0.0 }
|
71
|
+
|
72
|
+
# Run reset
|
73
|
+
@data.reset
|
74
|
+
|
75
|
+
# Collect metadata
|
76
|
+
@reset_values = indexes.map { |index| @data[index] }
|
77
|
+
end
|
78
|
+
|
79
|
+
should "reset data" do
|
80
|
+
refute_empty @reset_values
|
81
|
+
assert @reset_values.all? { |value| value == 0.0 }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
7
86
|
context "#to_pointer" do
|
8
87
|
|
9
88
|
setup do
|
10
|
-
@
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
89
|
+
@data = AudioPlayback::Playback::StreamData.new(@playback)
|
90
|
+
@pointer = @data.to_pointer
|
91
|
+
end
|
92
|
+
|
93
|
+
should "return a ffi pointer" do
|
94
|
+
refute_nil @pointer
|
95
|
+
assert_kind_of FFI::Pointer, @pointer
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
context ".to_pointer" do
|
101
|
+
|
102
|
+
setup do
|
103
|
+
@pointer = AudioPlayback::Playback::StreamData.to_pointer(@playback)
|
16
104
|
end
|
17
105
|
|
18
|
-
should "return a
|
19
|
-
refute_nil @
|
20
|
-
assert_kind_of
|
106
|
+
should "return a ffi pointer" do
|
107
|
+
refute_nil @pointer
|
108
|
+
assert_kind_of FFI::Pointer, @pointer
|
21
109
|
end
|
22
110
|
|
23
111
|
end
|
data/test/playback_test.rb
CHANGED
@@ -5,15 +5,225 @@ class AudioPlayback::PlaybackTest < Minitest::Test
|
|
5
5
|
context "Playback" do
|
6
6
|
|
7
7
|
setup do
|
8
|
-
@
|
8
|
+
@sample_rate = 44100
|
9
|
+
@path = "test/media/1-mono-#{@sample_rate}.wav"
|
9
10
|
@sound = AudioPlayback::Sound.load(@path)
|
10
11
|
@output = AudioPlayback::Device::Output.by_id(0)
|
11
12
|
end
|
12
13
|
|
14
|
+
context "#initialize" do
|
15
|
+
|
16
|
+
context "with no truncation" do
|
17
|
+
|
18
|
+
setup do
|
19
|
+
@playback = AudioPlayback::Playback.new(@sound, @output)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "have no truncation params" do
|
23
|
+
assert_nil @playback.truncate
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with truncation" do
|
29
|
+
|
30
|
+
context "with seek only" do
|
31
|
+
|
32
|
+
setup do
|
33
|
+
@seek = 0.1
|
34
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, seek: @seek)
|
35
|
+
end
|
36
|
+
|
37
|
+
should "have truncation params" do
|
38
|
+
refute_nil @playback.truncate
|
39
|
+
assert_kind_of Hash, @playback.truncate
|
40
|
+
end
|
41
|
+
|
42
|
+
should "have correct start frame value" do
|
43
|
+
refute_nil @playback.truncate[:start_frame]
|
44
|
+
assert_equal (@seek * @sample_rate).to_i, @playback.truncate[:start_frame]
|
45
|
+
end
|
46
|
+
|
47
|
+
should "have no duration value" do
|
48
|
+
assert_nil @playback.truncate[:end_frame]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with duration only" do
|
54
|
+
|
55
|
+
setup do
|
56
|
+
@duration = 0.2
|
57
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, duration: @duration)
|
58
|
+
end
|
59
|
+
|
60
|
+
should "have truncation params" do
|
61
|
+
refute_nil @playback.truncate
|
62
|
+
assert_kind_of Hash, @playback.truncate
|
63
|
+
end
|
64
|
+
|
65
|
+
should "have no start frame value" do
|
66
|
+
assert_nil @playback.truncate[:start_frame]
|
67
|
+
end
|
68
|
+
|
69
|
+
should "have correct end frame value" do
|
70
|
+
refute_nil @playback.truncate[:end_frame]
|
71
|
+
assert_equal (@duration * @sample_rate).to_i, @playback.truncate[:end_frame]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with end position only" do
|
77
|
+
|
78
|
+
setup do
|
79
|
+
@end_position = 0.3
|
80
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, end_position: @end_position)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "have truncation params" do
|
84
|
+
refute_nil @playback.truncate
|
85
|
+
assert_kind_of Hash, @playback.truncate
|
86
|
+
end
|
87
|
+
|
88
|
+
should "have no start frame value" do
|
89
|
+
assert_nil @playback.truncate[:start_frame]
|
90
|
+
end
|
91
|
+
|
92
|
+
should "have correct end frame value" do
|
93
|
+
refute_nil @playback.truncate[:end_frame]
|
94
|
+
assert_equal (@end_position * @sample_rate).to_i, @playback.truncate[:end_frame]
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with seek and duration" do
|
100
|
+
|
101
|
+
setup do
|
102
|
+
@seek = 0.4
|
103
|
+
@duration = 0.5
|
104
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, seek: @seek, duration: @duration)
|
105
|
+
end
|
106
|
+
|
107
|
+
should "have truncation params" do
|
108
|
+
refute_nil @playback.truncate
|
109
|
+
assert_kind_of Hash, @playback.truncate
|
110
|
+
end
|
111
|
+
|
112
|
+
should "have correct start frame value" do
|
113
|
+
refute_nil @playback.truncate[:start_frame]
|
114
|
+
assert_equal (@seek * @sample_rate).to_i, @playback.truncate[:start_frame]
|
115
|
+
end
|
116
|
+
|
117
|
+
should "have correct end frame value" do
|
118
|
+
refute_nil @playback.truncate[:end_frame]
|
119
|
+
assert_equal ((@duration + @seek) * @sample_rate).to_i, @playback.truncate[:end_frame]
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with seek and end position" do
|
125
|
+
|
126
|
+
context "valid" do
|
127
|
+
|
128
|
+
setup do
|
129
|
+
@seek = 0.4
|
130
|
+
@end_position = 0.5
|
131
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, seek: @seek, end_position: @end_position)
|
132
|
+
end
|
133
|
+
|
134
|
+
should "have truncation params" do
|
135
|
+
refute_nil @playback.truncate
|
136
|
+
assert_kind_of Hash, @playback.truncate
|
137
|
+
end
|
138
|
+
|
139
|
+
should "have correct start frame value" do
|
140
|
+
refute_nil @playback.truncate[:start_frame]
|
141
|
+
assert_equal (@seek * @sample_rate).to_i, @playback.truncate[:start_frame]
|
142
|
+
end
|
143
|
+
|
144
|
+
should "have correct end frame value" do
|
145
|
+
refute_nil @playback.truncate[:end_frame]
|
146
|
+
assert_equal (@end_position * @sample_rate).to_i, @playback.truncate[:end_frame]
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
context "invalid" do
|
152
|
+
|
153
|
+
setup do
|
154
|
+
@seek = 0.7
|
155
|
+
@end_position = 0.6
|
156
|
+
end
|
157
|
+
|
158
|
+
should "raise exception" do
|
159
|
+
assert_raises AudioPlayback::Playback::InvalidTruncation do
|
160
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, seek: @seek, end_position: @end_position)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context "with duration and end position" do
|
169
|
+
|
170
|
+
setup do
|
171
|
+
@duration = 0.8
|
172
|
+
@end_position = 0.9
|
173
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, duration: @duration, end_position: @end_position)
|
174
|
+
end
|
175
|
+
|
176
|
+
should "have truncation params" do
|
177
|
+
refute_nil @playback.truncate
|
178
|
+
assert_kind_of Hash, @playback.truncate
|
179
|
+
end
|
180
|
+
|
181
|
+
should "have no start frame value" do
|
182
|
+
assert_nil @playback.truncate[:start_frame]
|
183
|
+
end
|
184
|
+
|
185
|
+
should "ignore end_position, use duration" do
|
186
|
+
refute_nil @playback.truncate[:end_frame]
|
187
|
+
assert_equal (@duration * @sample_rate).to_i, @playback.truncate[:end_frame]
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
context "with seek, duration and end position" do
|
193
|
+
|
194
|
+
setup do
|
195
|
+
@duration = 1.0
|
196
|
+
@seek = 1.1
|
197
|
+
@end_position = 1.2
|
198
|
+
@playback = AudioPlayback::Playback.new(@sound, @output, seek: @seek, duration: @duration, end_position: @end_position)
|
199
|
+
end
|
200
|
+
|
201
|
+
should "have truncation params" do
|
202
|
+
refute_nil @playback.truncate
|
203
|
+
assert_kind_of Hash, @playback.truncate
|
204
|
+
end
|
205
|
+
|
206
|
+
should "have correct start frame value" do
|
207
|
+
refute_nil @playback.truncate[:start_frame]
|
208
|
+
assert_equal (@seek * @sample_rate).to_i, @playback.truncate[:start_frame]
|
209
|
+
end
|
210
|
+
|
211
|
+
should "ignore end_position, use duration" do
|
212
|
+
refute_nil @playback.truncate[:end_frame]
|
213
|
+
assert_equal ((@duration + @seek) * @sample_rate).to_i, @playback.truncate[:end_frame]
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
13
222
|
context ".play" do
|
14
223
|
|
15
224
|
setup do
|
16
225
|
AudioPlayback::Device::Stream.any_instance.expects(:start).once.returns(true)
|
226
|
+
@playback = AudioPlayback::Playback.play(@sound, @output)
|
17
227
|
end
|
18
228
|
|
19
229
|
teardown do
|
@@ -21,11 +231,14 @@ class AudioPlayback::PlaybackTest < Minitest::Test
|
|
21
231
|
end
|
22
232
|
|
23
233
|
should "start playback" do
|
24
|
-
@playback = AudioPlayback::Playback.play(@sound, @output)
|
25
234
|
refute_nil @playback
|
26
235
|
assert_kind_of AudioPlayback::Playback::Action, @playback
|
27
236
|
end
|
28
237
|
|
238
|
+
should "not have truncation params" do
|
239
|
+
assert_nil @playback.truncate
|
240
|
+
end
|
241
|
+
|
29
242
|
end
|
30
243
|
|
31
244
|
context "#start" do
|
@@ -81,6 +294,49 @@ class AudioPlayback::PlaybackTest < Minitest::Test
|
|
81
294
|
|
82
295
|
end
|
83
296
|
|
297
|
+
context "#number_of_seconds_to_number_of_frames" do
|
298
|
+
|
299
|
+
setup do
|
300
|
+
@playback = AudioPlayback::Playback.new(@sound, @output)
|
301
|
+
end
|
302
|
+
|
303
|
+
should "convert seconds to frames" do
|
304
|
+
assert_equal 3 * 44100, @playback.send(:number_of_seconds_to_number_of_frames, 3)
|
305
|
+
assert_equal 10 * 44100, @playback.send(:number_of_seconds_to_number_of_frames, 10)
|
306
|
+
assert_equal 30 * 44100, @playback.send(:number_of_seconds_to_number_of_frames, 30)
|
307
|
+
assert_equal 5000 * 44100, @playback.send(:number_of_seconds_to_number_of_frames, 5000)
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
context "#truncate_requested?" do
|
313
|
+
|
314
|
+
context "with truncation" do
|
315
|
+
|
316
|
+
setup do
|
317
|
+
@playback = AudioPlayback::Playback.new(@sound, @output)
|
318
|
+
end
|
319
|
+
|
320
|
+
should "have truncation params" do
|
321
|
+
assert @playback.send(:truncate_requested?, seek: 3, duration: 2)
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
context "without truncation" do
|
327
|
+
|
328
|
+
setup do
|
329
|
+
@playback = AudioPlayback::Playback.new(@sound, @output)
|
330
|
+
end
|
331
|
+
|
332
|
+
should "have truncation params" do
|
333
|
+
refute @playback.send(:truncate_requested?, {})
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
84
340
|
context "#data_size" do
|
85
341
|
|
86
342
|
setup do
|