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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0040badaf2c68a1886248157f4448ab24609dccf
|
4
|
+
data.tar.gz: f22b0f7d1615d6b863e493ea5744574ea06a9321
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfdddbf4c6f4ea7050ddeff2a9ddf2a37d1484d0fe15d2f056f86001b6fed3cb2aefd02ae6488ed2fa08ec06eef0125e280ea1f215eb9f53803d443e5b67e350
|
7
|
+
data.tar.gz: 04f79ca3da8f374692d5a79ace294362ecfa79455f46180ac5d8c9e6eff00d779c2a9647218674d9b2b47ead49e09c7ce5da05e37e497a3ee923321ac02b9760
|
data/README.md
CHANGED
@@ -35,10 +35,18 @@ Or if you're using Bundler, add this to your Gemfile
|
|
35
35
|
|
36
36
|
* `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use channels 0 and 1 on the selected device
|
37
37
|
|
38
|
+
* `-d` Duration. Will stop after the given amount of time. Eg `-d 56` stops after 56 seconds of playback
|
39
|
+
|
40
|
+
* `-e` End position. Will stop at the given absolute time, irregardless of seek. Eg `-e 56` stops at 56 seconds. `-s 01:09:30 -e 01:10:00` stops at 1 hour 10 minutes after 30 seconds of playback
|
41
|
+
|
38
42
|
* `-o` Output device id or name. Defaults to the system default
|
39
43
|
|
44
|
+
* `-s` Seek to given time position. Eg `-s 56` seeks to 56 seconds and `-s 01:10:00` seeks to 1 hour 10 min.
|
45
|
+
|
40
46
|
* `-v` or `--verbose` Verbose
|
41
47
|
|
48
|
+
* `--loop` Loop playback continuously
|
49
|
+
|
42
50
|
* `--list-devices` List the available audio output devices
|
43
51
|
|
44
52
|
|
@@ -94,4 +102,4 @@ More Ruby code examples:
|
|
94
102
|
|
95
103
|
Licensed under Apache 2.0, See the file LICENSE
|
96
104
|
|
97
|
-
Copyright (c) 2015-
|
105
|
+
Copyright (c) 2015-2017 [Ari Russo](http://arirusso.com)
|
data/bin/playback
CHANGED
@@ -14,12 +14,19 @@ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
|
14
14
|
#
|
15
15
|
# * `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use channels 0 and 1 on the selected device
|
16
16
|
#
|
17
|
+
# * `-d` Duration. Will stop after the given amount of time. Eg `-d 56` stops after 56 seconds of playback
|
18
|
+
#
|
19
|
+
# * `-e` End position. Will stop at the given absolute time, irregardless of seek. Eg `-e 56` stops at 56 seconds. `-s 01:09:30 -e 01:10:00` stops at 1 hour 10 minutes after 30 seconds of playback
|
20
|
+
#
|
17
21
|
# * `-o` Output device id or name. Defaults to the system default
|
18
22
|
#
|
23
|
+
# * `-s` Seek to given time position. Eg `-s 56` seeks to 56 seconds and `-s 01:10:00` seeks to 1 hour 10 min.
|
24
|
+
#
|
19
25
|
# * `-v` or `--verbose` Verbose
|
20
|
-
#
|
26
|
+
#
|
21
27
|
# * `--list-devices` List the available audio output devices
|
22
28
|
#
|
29
|
+
# * `--loop` Loop playback continuously
|
23
30
|
#
|
24
31
|
|
25
32
|
require "audio-playback"
|
data/lib/audio-playback.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# AudioPlayback
|
3
3
|
# Play audio files at the command line or with Ruby
|
4
4
|
#
|
5
|
-
# (c)2015 Ari Russo
|
5
|
+
# (c)2015-2017 Ari Russo
|
6
6
|
# Apache 2.0 License
|
7
7
|
# https://github.com/arirusso/audio-playback
|
8
8
|
#
|
@@ -19,25 +19,31 @@ require "audio-playback/playback"
|
|
19
19
|
|
20
20
|
# classes
|
21
21
|
require "audio-playback/file"
|
22
|
+
require "audio-playback/position"
|
22
23
|
require "audio-playback/sound"
|
23
24
|
|
24
25
|
# Play audio files
|
25
26
|
module AudioPlayback
|
26
27
|
|
27
|
-
VERSION = "0.0.
|
28
|
+
VERSION = "0.0.8"
|
28
29
|
|
29
30
|
# Convenience method to play an audio file
|
30
31
|
# @param [Array<::File>, Array<String>, ::File, String] file_paths
|
31
32
|
# @param [Hash] options
|
32
|
-
# @option options [
|
33
|
-
# @option options [Array<
|
33
|
+
# @option options [Integer] :buffer_size Buffer size in bytes. Defaults to 4096
|
34
|
+
# @option options [Array<Integer>, Integer] :channels (or: :channel) Output audio to the given channel(s). Eg `:channels => [0,1]` will direct the audio to channels 0 and 1. Defaults to use all available channels
|
35
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
36
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
37
|
+
# @option options [Boolean] :is_looping (or :loop) Whether to loop audio
|
34
38
|
# @option options [Float] :latency Latency in seconds. Defaults to use the default latency for the selected output device
|
35
39
|
# @option options [IO] :logger Logger object
|
36
|
-
# @option options [
|
40
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
41
|
+
# @option options [Integer, String] :output_device (or: :output) Output device id or name
|
37
42
|
def self.play(file_paths, options = {})
|
38
43
|
sounds = Array(file_paths).map { |path| Sound.load(path, options) }
|
39
44
|
requested_device = options[:output_device] || options[:output]
|
40
45
|
output = Device::Output.by_name(requested_device) || Device::Output.by_id(requested_device) || Device.default_output
|
46
|
+
options[:is_looping] ||= options[:loop]
|
41
47
|
Playback.play(sounds, output, options)
|
42
48
|
end
|
43
49
|
|
@@ -18,6 +18,20 @@ module AudioPlayback
|
|
18
18
|
:name => "Direct to channel(s)"
|
19
19
|
},
|
20
20
|
|
21
|
+
:duration => {
|
22
|
+
:short => "-d",
|
23
|
+
:long => "--duration [seconds]",
|
24
|
+
:name => "Duration",
|
25
|
+
:type => String
|
26
|
+
},
|
27
|
+
|
28
|
+
:end_position => {
|
29
|
+
:short => "-e",
|
30
|
+
:long => "--end-position [seconds]",
|
31
|
+
:name => "End position",
|
32
|
+
:type => String
|
33
|
+
},
|
34
|
+
|
21
35
|
:latency => {
|
22
36
|
:short => "-l",
|
23
37
|
:long => "--latency [seconds]",
|
@@ -30,6 +44,18 @@ module AudioPlayback
|
|
30
44
|
:name => "List devices"
|
31
45
|
},
|
32
46
|
|
47
|
+
:loop => {
|
48
|
+
:long => "--loop",
|
49
|
+
:name => "Loop"
|
50
|
+
},
|
51
|
+
|
52
|
+
:seek => {
|
53
|
+
:short => "-s",
|
54
|
+
:long => "--seek [seconds]",
|
55
|
+
:name => "Seek",
|
56
|
+
:type => String
|
57
|
+
},
|
58
|
+
|
33
59
|
:logger => {
|
34
60
|
:short => "-v",
|
35
61
|
:long => "--verbose",
|
@@ -43,7 +69,7 @@ module AudioPlayback
|
|
43
69
|
:type => String,
|
44
70
|
:name => "Output device for playback"
|
45
71
|
}
|
46
|
-
}
|
72
|
+
}.freeze
|
47
73
|
|
48
74
|
end
|
49
75
|
|
@@ -21,7 +21,7 @@ module AudioPlayback
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Get a device by its ID
|
24
|
-
# @param [
|
24
|
+
# @param [Integer] id
|
25
25
|
# @return [Output]
|
26
26
|
def by_id(id)
|
27
27
|
outputs.find { |device| [device, device.id].include?(id) }
|
@@ -41,7 +41,7 @@ module AudioPlayback
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# Get system device info given a device ID
|
44
|
-
# @param [
|
44
|
+
# @param [Integer] id
|
45
45
|
# @return [FFI::PortAudio::API::PaDeviceInfo]
|
46
46
|
def device_info(id)
|
47
47
|
FFI::PortAudio::API.Pa_GetDeviceInfo(id)
|
@@ -50,7 +50,7 @@ module AudioPlayback
|
|
50
50
|
private
|
51
51
|
|
52
52
|
# Is the device with the given ID an output?
|
53
|
-
# @param [
|
53
|
+
# @param [Integer] id
|
54
54
|
# @return [Boolean]
|
55
55
|
def output?(id)
|
56
56
|
device_info(id)[:maxOutputChannels] > 0
|
@@ -42,7 +42,7 @@ module AudioPlayback
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Select an output device by ID
|
45
|
-
# @param [
|
45
|
+
# @param [Integer] id
|
46
46
|
# @return [Output]
|
47
47
|
def self.by_id(id)
|
48
48
|
Device.by_id(id)
|
@@ -55,7 +55,7 @@ module AudioPlayback
|
|
55
55
|
Device.by_name(name)
|
56
56
|
end
|
57
57
|
|
58
|
-
# @param [
|
58
|
+
# @param [Integer] id
|
59
59
|
# @param [Hash] options
|
60
60
|
# @option options [Float] :latency Device latency in seconds
|
61
61
|
def initialize(id, options = {})
|
@@ -71,13 +71,13 @@ module AudioPlayback
|
|
71
71
|
end
|
72
72
|
|
73
73
|
# Number of channels the device supports
|
74
|
-
# @return [
|
74
|
+
# @return [Integer]
|
75
75
|
def num_channels
|
76
76
|
@resource[:channelCount]
|
77
77
|
end
|
78
78
|
|
79
79
|
# ID of the device
|
80
|
-
# @return [
|
80
|
+
# @return [Integer]
|
81
81
|
def id
|
82
82
|
@resource[:device]
|
83
83
|
end
|
@@ -91,7 +91,7 @@ module AudioPlayback
|
|
91
91
|
end
|
92
92
|
|
93
93
|
# Populate the output
|
94
|
-
# @param [
|
94
|
+
# @param [Integer] id
|
95
95
|
# @param [Hash] options
|
96
96
|
# @option options [Float] :latency
|
97
97
|
# @return [FFI::PortAudio::API::PaStreamParameters]
|
@@ -54,10 +54,10 @@ module AudioPlayback
|
|
54
54
|
def block
|
55
55
|
begin
|
56
56
|
while active?
|
57
|
-
sleep(0.
|
57
|
+
sleep(0.001)
|
58
58
|
end
|
59
59
|
while FFI::PortAudio::API.Pa_IsStreamActive(@stream.read_pointer) != :paNoError
|
60
|
-
sleep(1)
|
60
|
+
sleep(0.1)
|
61
61
|
end
|
62
62
|
rescue SystemExit, Interrupt
|
63
63
|
# Control-C
|
@@ -74,7 +74,7 @@ module AudioPlayback
|
|
74
74
|
# @param [Playback] playback
|
75
75
|
# @return [Boolean]
|
76
76
|
def open_stream(playback)
|
77
|
-
@userdata
|
77
|
+
@userdata ||= playback.data.to_pointer
|
78
78
|
FFI::PortAudio::API.Pa_OpenStream(@stream, @input, @output, @freq, @frames, @flags, @method, @userdata)
|
79
79
|
true
|
80
80
|
end
|
@@ -112,15 +112,23 @@ module AudioPlayback
|
|
112
112
|
# @param [Playback] playback
|
113
113
|
# @return [Boolean]
|
114
114
|
def open_playback(playback)
|
115
|
-
|
116
|
-
@frames = playback.buffer_size
|
117
|
-
@flags = FFI::PortAudio::API::NoFlag
|
118
|
-
@stream = FFI::Buffer.new(:pointer)
|
119
|
-
@method = method(:process)
|
115
|
+
populate_stream_playback(playback)
|
120
116
|
open_stream(playback)
|
121
117
|
true
|
122
118
|
end
|
123
119
|
|
120
|
+
# Initialize the stream's playback properties
|
121
|
+
# @param [Playback] playback
|
122
|
+
# @return [Boolean]
|
123
|
+
def populate_stream_playback(playback)
|
124
|
+
@freq ||= playback.sample_rate.to_i
|
125
|
+
@frames ||= playback.buffer_size
|
126
|
+
@flags ||= FFI::PortAudio::API::NoFlag
|
127
|
+
@stream ||= FFI::Buffer.new(:pointer)
|
128
|
+
@method ||= method(:process)
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
124
132
|
# Report about the stream
|
125
133
|
# @param [Playback] playback
|
126
134
|
# @param [IO] logger
|
@@ -135,41 +143,58 @@ module AudioPlayback
|
|
135
143
|
#puts "Entering callback at #{Time.now.to_f}"
|
136
144
|
counter = user_data.get_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE).to_i
|
137
145
|
#puts "Frame: #{counter}"
|
138
|
-
|
139
|
-
#puts "Sample size: #{
|
146
|
+
audio_data_size = user_data.get_float32(Playback::METADATA.index(:size) * Playback::FRAME_SIZE).to_i
|
147
|
+
#puts "Sample size: #{audio_data_size}"
|
140
148
|
num_channels = user_data.get_float32(Playback::METADATA.index(:num_channels) * Playback::FRAME_SIZE).to_i
|
141
149
|
#puts "Num Channels: #{num_channels}"
|
150
|
+
start_frame = user_data.get_float32(Playback::METADATA.index(:start_frame) * Playback::FRAME_SIZE).to_i
|
151
|
+
#puts "Start point: #{start_frame}"
|
152
|
+
end_frame = user_data.get_float32(Playback::METADATA.index(:end_frame) * Playback::FRAME_SIZE).to_i
|
153
|
+
#puts "Duration: #{duration}"
|
154
|
+
is_looping = user_data.get_float32(Playback::METADATA.index(:is_looping) * Playback::FRAME_SIZE).to_i > 0
|
155
|
+
#puts "Is looping: #{is_looping}"
|
156
|
+
end_frame = [end_frame, audio_data_size].min
|
142
157
|
is_eof = false
|
143
|
-
|
144
|
-
|
145
|
-
|
158
|
+
end_window = end_frame - frames_per_buffer
|
159
|
+
if counter >= end_window
|
160
|
+
if counter == end_frame
|
161
|
+
is_eof = true
|
162
|
+
elsif counter < end_frame
|
163
|
+
buffer_size = end_frame.divmod(frames_per_buffer).last
|
146
164
|
#puts "Truncated buffer size: #{buffer_size}"
|
147
165
|
difference = frames_per_buffer - buffer_size
|
148
166
|
#puts "Adding #{difference} frames of null audio"
|
149
167
|
extra_data = [0] * difference * num_channels
|
150
168
|
is_eof = true
|
151
169
|
else
|
170
|
+
# p "Aborting (counter: #{counter}, end_frame: #{end_frame})"
|
152
171
|
return :paAbort
|
153
172
|
end
|
154
173
|
end
|
155
174
|
buffer_size ||= frames_per_buffer
|
156
175
|
#puts "Size per buffer per channel: #{frames_per_buffer}"
|
157
|
-
offset = ((counter * num_channels) + Playback::METADATA.count) * Playback::FRAME_SIZE
|
176
|
+
offset = (((counter + start_frame) * num_channels) + Playback::METADATA.count) * Playback::FRAME_SIZE
|
158
177
|
#puts "Starting at location: #{offset}"
|
159
178
|
data = user_data.get_array_of_float32(offset, buffer_size * num_channels)
|
160
179
|
data += extra_data unless extra_data.nil?
|
161
180
|
#puts "This buffer size: #{data.size}"
|
162
181
|
#puts "Writing to output"
|
163
182
|
output.write_array_of_float(data)
|
164
|
-
counter
|
165
|
-
user_data.put_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE, counter.to_f) # update counter
|
183
|
+
next_counter = counter + frames_per_buffer
|
166
184
|
if is_eof
|
167
|
-
|
168
|
-
|
169
|
-
|
185
|
+
if is_looping
|
186
|
+
#puts "Looping to beginning"
|
187
|
+
next_counter = start_frame
|
188
|
+
:paContinue
|
189
|
+
else
|
190
|
+
#puts "Marking eof"
|
191
|
+
user_data.put_float32(Playback::METADATA.index(:is_eof) * Playback::FRAME_SIZE, 1.0) # mark eof
|
192
|
+
:paComplete
|
193
|
+
end
|
170
194
|
else
|
171
195
|
:paContinue
|
172
196
|
end
|
197
|
+
user_data.put_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE, next_counter.to_f) # update counter
|
173
198
|
#puts "Exiting callback at #{Time.now.to_f}"
|
174
199
|
result
|
175
200
|
end
|
@@ -13,23 +13,41 @@ module AudioPlayback
|
|
13
13
|
|
14
14
|
FRAME_SIZE = FFI::TYPE_FLOAT32.size
|
15
15
|
|
16
|
-
METADATA = [:size, :num_channels, :pointer, :is_eof].freeze
|
16
|
+
METADATA = [:size, :num_channels, :start_frame, :end_frame, :is_looping, :pointer, :is_eof].freeze
|
17
|
+
|
18
|
+
class InvalidChannels < RuntimeError
|
19
|
+
end
|
20
|
+
|
21
|
+
class InvalidTruncation < RuntimeError
|
22
|
+
end
|
17
23
|
|
18
24
|
# Action of playing back an audio file
|
19
25
|
class Action
|
20
26
|
|
21
27
|
extend Forwardable
|
22
28
|
|
23
|
-
attr_reader :buffer_size,
|
29
|
+
attr_reader :buffer_size,
|
30
|
+
:channels,
|
31
|
+
:data,
|
32
|
+
:num_channels,
|
33
|
+
:output,
|
34
|
+
:sounds,
|
35
|
+
:stream,
|
36
|
+
:truncate
|
37
|
+
|
24
38
|
def_delegators :@sounds, :audio_files
|
25
|
-
def_delegators :@data, :reset
|
39
|
+
def_delegators :@data, :reset, :size
|
26
40
|
|
27
41
|
# @param [Array<Sound>, Sound] sounds
|
28
42
|
# @param [Output] output
|
29
43
|
# @param [Hash] options
|
30
|
-
# @option options [
|
31
|
-
# @option options [Array<
|
44
|
+
# @option options [Integer] :buffer_size
|
45
|
+
# @option options [Array<Integer>, Integer] :channels (or: :channel)
|
46
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
47
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
48
|
+
# @option options [Boolean] :is_looping Whether to loop audio
|
32
49
|
# @option options [IO] :logger
|
50
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
33
51
|
# @option options [Stream] :stream
|
34
52
|
def initialize(sounds, output, options = {})
|
35
53
|
@sounds = Array(sounds)
|
@@ -41,17 +59,11 @@ module AudioPlayback
|
|
41
59
|
end
|
42
60
|
|
43
61
|
# Sample rate of the playback sound
|
44
|
-
# @return [
|
62
|
+
# @return [Integer]
|
45
63
|
def sample_rate
|
46
64
|
@sounds.last.sample_rate
|
47
65
|
end
|
48
66
|
|
49
|
-
# Size of the playback sound
|
50
|
-
# @return [Fixnum]
|
51
|
-
def size
|
52
|
-
@sounds.map(&:size).max
|
53
|
-
end
|
54
|
-
|
55
67
|
# Start playback
|
56
68
|
# @return [Playback]
|
57
69
|
def start
|
@@ -66,6 +78,13 @@ module AudioPlayback
|
|
66
78
|
@stream.block
|
67
79
|
end
|
68
80
|
|
81
|
+
# Should playback be truncated?
|
82
|
+
# eg :start 3 seconds, :duration 1 second
|
83
|
+
# @return [Boolean]
|
84
|
+
def truncate?
|
85
|
+
!@truncate.nil? && !@truncate.values.empty?
|
86
|
+
end
|
87
|
+
|
69
88
|
# Log a report about playback
|
70
89
|
# @param [IO] logger
|
71
90
|
# @return [Boolean]
|
@@ -80,9 +99,9 @@ module AudioPlayback
|
|
80
99
|
end
|
81
100
|
|
82
101
|
# Total size of the playback's sound frames in bytes
|
83
|
-
# @return [
|
102
|
+
# @return [Integer]
|
84
103
|
def data_size
|
85
|
-
frames =
|
104
|
+
frames = size * @num_channels
|
86
105
|
frames * FRAME_SIZE.size
|
87
106
|
end
|
88
107
|
|
@@ -92,21 +111,28 @@ module AudioPlayback
|
|
92
111
|
!@channels.nil?
|
93
112
|
end
|
94
113
|
|
114
|
+
# Is audio looping ?
|
115
|
+
# @return [Boolean]
|
116
|
+
def looping?
|
117
|
+
@is_looping
|
118
|
+
end
|
119
|
+
|
95
120
|
private
|
96
121
|
|
97
122
|
# Are the requested channels available in the current environment?
|
98
|
-
# @param [Array<
|
123
|
+
# @param [Array<Integer>] channels
|
99
124
|
# @return [Boolean]
|
100
125
|
def validate_requested_channels(channels)
|
101
126
|
if channels.count > @output.num_channels
|
102
|
-
|
127
|
+
message = "Only #{@output.num_channels} channels available on #{@output.name} output"
|
128
|
+
raise(InvalidChannels.new(message))
|
103
129
|
false
|
104
130
|
end
|
105
131
|
true
|
106
132
|
end
|
107
133
|
|
108
134
|
# Validate and populate the variables containing information about the requested channels
|
109
|
-
# @param [
|
135
|
+
# @param [Integer, Array<Integer>] request Channel(s)
|
110
136
|
# @return [Boolean]
|
111
137
|
def populate_requested_channels(request)
|
112
138
|
request = Array(request)
|
@@ -122,7 +148,7 @@ module AudioPlayback
|
|
122
148
|
|
123
149
|
# Populate the playback channels
|
124
150
|
# @param [Hash] options
|
125
|
-
# @option options [
|
151
|
+
# @option options [Integer, Array<Integer>] :channels (or: :channel)
|
126
152
|
# @return [Boolean]
|
127
153
|
def populate_channels(options = {})
|
128
154
|
request = options[:channels] || options[:channel]
|
@@ -134,12 +160,92 @@ module AudioPlayback
|
|
134
160
|
end
|
135
161
|
end
|
136
162
|
|
163
|
+
# Populate the truncation parameters. Converts the seconds based Position arguments
|
164
|
+
# to number of frames
|
165
|
+
# @param [Position, nil] seek Start at given time position in seconds
|
166
|
+
# @param [Position, nil] duration Play for given time in seconds
|
167
|
+
# @param [Position, nil] end_position Stop at given time position in seconds (will use duration arg if both are included)
|
168
|
+
# @return [Hash]
|
169
|
+
def populate_truncation(seek, duration, end_position)
|
170
|
+
@truncate = {}
|
171
|
+
end_position = if duration.nil?
|
172
|
+
end_position
|
173
|
+
elsif seek.nil?
|
174
|
+
duration || end_position
|
175
|
+
else
|
176
|
+
duration + seek || end_position
|
177
|
+
end
|
178
|
+
unless seek.nil?
|
179
|
+
@truncate[:start_frame] = number_of_seconds_to_number_of_frames(seek)
|
180
|
+
end
|
181
|
+
unless end_position.nil?
|
182
|
+
@truncate[:end_frame] = number_of_seconds_to_number_of_frames(end_position)
|
183
|
+
end
|
184
|
+
@truncate
|
185
|
+
end
|
186
|
+
|
187
|
+
# Convert number of seconds to number of sample frames given the sample rate
|
188
|
+
# @param [Numeric] num_seconds
|
189
|
+
# @return [Integer]
|
190
|
+
def number_of_seconds_to_number_of_frames(num_seconds)
|
191
|
+
(num_seconds * sample_rate).to_i
|
192
|
+
end
|
193
|
+
|
194
|
+
# Are the options for truncation valid? eg is the :end_position option after the
|
195
|
+
# :seek option?
|
196
|
+
# @param [Hash] options
|
197
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
198
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
199
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
200
|
+
# @return [Boolean]
|
201
|
+
def truncate_valid?(options)
|
202
|
+
options[:end_position].nil? || options[:seek].nil? ||
|
203
|
+
options[:end_position] > options[:seek]
|
204
|
+
end
|
205
|
+
|
206
|
+
# Has truncation been requested in the constructor options?
|
207
|
+
# @param [Hash] options
|
208
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
209
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
210
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
211
|
+
# @return [Boolean]
|
212
|
+
def truncate_requested?(options)
|
213
|
+
!options[:seek].nil? || !options[:duration].nil? || !options[:end_position].nil?
|
214
|
+
end
|
215
|
+
|
216
|
+
# Populate Position objects using the the truncation parameters.
|
217
|
+
# @param [Hash] options
|
218
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
219
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
220
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
221
|
+
# @return [Array<Position>]
|
222
|
+
def truncate_options_as_positions(options = {})
|
223
|
+
seek = Position.new(options[:seek]) unless options[:seek].nil?
|
224
|
+
duration = Position.new(options[:duration]) unless options[:duration].nil?
|
225
|
+
end_position = Position.new(options[:end_position]) unless options[:end_position].nil?
|
226
|
+
[seek, duration, end_position]
|
227
|
+
end
|
228
|
+
|
137
229
|
# Populate the playback action
|
138
230
|
# @param [Hash] options
|
139
|
-
# @option options [
|
231
|
+
# @option options [Integer, Array<Integer>] :channels (or: :channel)
|
232
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
233
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
234
|
+
# @option options [Boolean] :is_looping Whether to loop audio
|
235
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
140
236
|
# @return [Playback::Action]
|
141
237
|
def populate(options = {})
|
142
238
|
populate_channels(options)
|
239
|
+
if truncate_requested?(options)
|
240
|
+
if truncate_valid?(options)
|
241
|
+
seek, duration, end_position = *truncate_options_as_positions(options)
|
242
|
+
populate_truncation(seek, duration, end_position)
|
243
|
+
else
|
244
|
+
message = "Truncation options are not valid"
|
245
|
+
raise(InvalidTruncation.new(message))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
@is_looping = !!options[:is_looping]
|
143
249
|
@data = StreamData.new(self)
|
144
250
|
self
|
145
251
|
end
|
@@ -155,8 +261,8 @@ module AudioPlayback
|
|
155
261
|
# @param [Sound] sound
|
156
262
|
# @param [Output] output
|
157
263
|
# @param [Hash] options
|
158
|
-
# @option options [
|
159
|
-
# @option options [Array<
|
264
|
+
# @option options [Integer] :buffer_size
|
265
|
+
# @option options [Array<Integer>, Integer] :channels (or: :channel)
|
160
266
|
# @option options [IO] :logger
|
161
267
|
# @option options [Stream] :stream
|
162
268
|
# @return [Playback]
|