audio 0.1 → 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 +4 -4
- data/README.md +10 -2
- data/audio.gemspec +1 -1
- data/lib/audio.rb +2 -1
- data/lib/macos/audio_toolbox.rb +55 -0
- data/lib/macos/audio_toolbox/audio_converter.rb +55 -0
- data/lib/{core_audio.rb → macos/core_audio.rb} +26 -19
- data/lib/{core_audio → macos/core_audio}/audio_device.rb +74 -4
- data/lib/{core_audio → macos/core_audio}/audio_object.rb +5 -15
- data/lib/macos/core_audio/audio_stream.rb +95 -0
- data/lib/macos/core_audio/audio_types.rb +73 -0
- data/lib/{core_foundation.rb → macos/core_foundation.rb} +2 -0
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3cab4fbb003f03b5a3803b5c7df3b59cebc5d23
|
4
|
+
data.tar.gz: b9be9609b5ba0bae0f4bc73d45e535a343ad253a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a79f5be46891faa3ac670afcc4f151f5c8deb57bac77dd3a6a1041dc6c1efb5084c941a4ffac0037215cb9f9590e84be9b75949b0162a715a14c7e26d831e5d
|
7
|
+
data.tar.gz: 7341e4f79c1f4bdddcb8cfd9ee30c2ee56cd620d68e89e97f953f08549b6872546a98ad3a6ab19c57f48900ef5d2e9bc903f8f3ecf7dec5f1bad9f25d25e63c9
|
data/README.md
CHANGED
@@ -40,6 +40,15 @@ Device Name: Built-in Output
|
|
40
40
|
Device Name: Blue Snowball
|
41
41
|
```
|
42
42
|
|
43
|
+
### Using the default Audio Devices
|
44
|
+
|
45
|
+
If you just want to use a default device, Audio has you covered.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
Audio.default_input # => Device Name: Built-in Microphone
|
49
|
+
Audio.default_output # => Device Name: Built-in Output
|
50
|
+
```
|
51
|
+
|
43
52
|
### Recording Audio
|
44
53
|
|
45
54
|
You can record audio from a particular device by calling its `start` method.
|
@@ -48,8 +57,7 @@ called. If you provide a block parameter to `start`, it will be called whenever
|
|
48
57
|
the host OS provides a new buffer of audio samples.
|
49
58
|
|
50
59
|
```ruby
|
51
|
-
|
52
|
-
device.start do |*args|
|
60
|
+
Audio.default_input.start do |*args|
|
53
61
|
# Do something fancy
|
54
62
|
end
|
55
63
|
|
data/audio.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "audio"
|
7
|
-
spec.version = '0.
|
7
|
+
spec.version = '0.2'
|
8
8
|
spec.authors = ["Brandon Fosdick"]
|
9
9
|
spec.email = ["bfoz@bfoz.net"]
|
10
10
|
spec.summary = %q{Cross-platform Audio Device Input and Output}
|
data/lib/audio.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require_relative 'audio_toolbox/audio_converter'
|
4
|
+
require_relative 'core_foundation'
|
5
|
+
require_relative 'core_audio/audio_stream'
|
6
|
+
|
7
|
+
module AudioToolbox
|
8
|
+
extend FFI::Library
|
9
|
+
ffi_lib '/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox'
|
10
|
+
|
11
|
+
OSStatus = CoreFoundation::OSStatus
|
12
|
+
AudioStreamBasicDescription = CoreAudio::AudioStreamBasicDescription
|
13
|
+
|
14
|
+
typedef :pointer, :AudioConverterRef
|
15
|
+
|
16
|
+
# OSStatus (*AudioConverterComplexInputDataProc)(AudioConverterRef inAudioConverter,
|
17
|
+
# UInt32* ioNumberDataPackets,
|
18
|
+
# AudioBufferList* ioData,
|
19
|
+
# AudioStreamPacketDescription** outDataPacketDescription,
|
20
|
+
# void* inUserData)
|
21
|
+
callback :AudioConverterComplexInputDataProc, [:AudioConverterRef, :pointer, :pointer, :pointer, :pointer], OSStatus
|
22
|
+
|
23
|
+
# OSStatus AudioConverterNew(const AudioStreamBasicDescription* inSourceFormat,
|
24
|
+
# const AudioStreamBasicDescription* inDestinationFormat,
|
25
|
+
# AudioConverterRef* outAudioConverter)
|
26
|
+
attach_function :AudioConverterNew, [AudioStreamBasicDescription.by_ref, AudioStreamBasicDescription.by_ref, :pointer], OSStatus
|
27
|
+
|
28
|
+
# OSStatus AudioConverterFillComplexBuffer(AudioConverterRef inAudioConverter,
|
29
|
+
# AudioConverterComplexInputDataProc inInputDataProc,
|
30
|
+
# void* inInputDataProcUserData,
|
31
|
+
# UInt32* ioOutputDataPacketSize,
|
32
|
+
# AudioBufferList* outOutputData,
|
33
|
+
# AudioStreamPacketDescription* outPacketDescription)
|
34
|
+
attach_function :AudioConverterFillComplexBuffer, [:AudioConverterRef, :AudioConverterComplexInputDataProc, :pointer, :pointer, :pointer, :pointer], OSStatus
|
35
|
+
|
36
|
+
# OSStatus AudioConverterSetProperty(AudioConverterRef inAudioConverter,
|
37
|
+
# AudioConverterPropertyID inPropertyID,
|
38
|
+
# UInt32 inPropertyDataSize,
|
39
|
+
# const void* inPropertyData)
|
40
|
+
attach_function :AudioConverterSetProperty, [:AudioConverterRef, :uint32, :uint32, :pointer], OSStatus
|
41
|
+
|
42
|
+
# Create a new {AudioConverter} that convertes between the given stream formats
|
43
|
+
# @param from [AudioStreamBasicDescription] the stream format to convert from
|
44
|
+
# @param to [AudioStreamBasicDescription] the stream format to convert to
|
45
|
+
# @return [AudioConverterRef]
|
46
|
+
def self.converter(from, to)
|
47
|
+
reference = FFI::MemoryPointer.new(AudioConverterRef)
|
48
|
+
status = AudioToolbox.AudioConverterNew(from, to, reference)
|
49
|
+
raise "No converter '#{[status].pack('L').reverse}'" unless status.zero?
|
50
|
+
AudioConverterRef.new(reference.get_pointer(0)).tap do |converter|
|
51
|
+
converter.from = from
|
52
|
+
converter.to = to
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module AudioToolbox
|
2
|
+
class AudioConverterRef < FFI::Pointer
|
3
|
+
attr_accessor :from
|
4
|
+
attr_accessor :to
|
5
|
+
|
6
|
+
# @group AudioConverter.h
|
7
|
+
PrimeMethod = 'prmm'
|
8
|
+
# @endgroup
|
9
|
+
|
10
|
+
# @param buffer [AudioBufferList] the list of buffers to be converted
|
11
|
+
# @return [AudioBufferList] the converted data
|
12
|
+
def convert(buffer)
|
13
|
+
num_from_packets = (buffer.bytesize / from[:mBytesPerPacket]).floor
|
14
|
+
remaining_packets = num_from_packets
|
15
|
+
|
16
|
+
block = Proc.new do |_, num_packets, buffer_list, _|
|
17
|
+
unless remaining_packets.zero?
|
18
|
+
buffer_list = CoreAudio::AudioBufferList.new(buffer_list)
|
19
|
+
buffer_list.buffers.first[:mData] = buffer.buffers.first[:mData]
|
20
|
+
buffer_list.buffers.first[:mDataByteSize] = buffer.buffers.first[:mDataByteSize]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Report the number of packets actually sent
|
24
|
+
num_packets.put_uint32(0, remaining_packets)
|
25
|
+
|
26
|
+
remaining_packets = 0 # No more packets remaining
|
27
|
+
|
28
|
+
0 # All is well
|
29
|
+
end
|
30
|
+
|
31
|
+
bytes_per_packet = to[:mBytesPerPacket]
|
32
|
+
num_output_packets = (num_from_packets * to.sample_rate / from.sample_rate).floor
|
33
|
+
|
34
|
+
output_list = CoreAudio::AudioBufferList.buffer_list(size:bytes_per_packet * num_output_packets)
|
35
|
+
raise("No buffer list") unless output_list
|
36
|
+
|
37
|
+
num_packets = FFI::MemoryPointer.new(:uint32).put_uint32(0, num_output_packets)
|
38
|
+
status = AudioToolbox.AudioConverterFillComplexBuffer(self, block, nil, num_packets, output_list, nil)
|
39
|
+
raise("Convert failed: '#{[status].pack('L').reverse}'") unless status.zero?
|
40
|
+
|
41
|
+
output_list
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param method [Symbol] :pre, :normal, or :none
|
45
|
+
def prime_method=(method=:normal)
|
46
|
+
method = case method
|
47
|
+
when :pre then 0
|
48
|
+
when :normal then 1
|
49
|
+
when :none then 2
|
50
|
+
end
|
51
|
+
data = FFI::MemoryPointer.new(:uint32).put_uint32(0, method)
|
52
|
+
AudioToolbox.AudioConverterSetProperty(self, PrimeMethod.reverse.unpack('L').first, 4, data)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,34 +1,18 @@
|
|
1
1
|
require 'ffi'
|
2
2
|
|
3
|
+
require_relative 'core_audio/audio_types'
|
4
|
+
|
3
5
|
module CoreAudio
|
4
6
|
extend FFI::Library
|
5
7
|
ffi_lib '/System/Library/Frameworks/CoreAudio.framework/CoreAudio'
|
6
8
|
|
7
9
|
typedef :uint32, :OSStatus
|
8
|
-
|
9
|
-
# @group CoreAudioTypes.h
|
10
|
-
class AudioBuffer < FFI::Struct
|
11
|
-
layout :mNumberChannels, :uint32,
|
12
|
-
:mDataByteSize, :uint32,
|
13
|
-
:mData, :pointer
|
14
|
-
|
15
|
-
# @return [String] the raw bytes
|
16
|
-
def bytes
|
17
|
-
self[:mData].get_bytes(0, self[:mDataByteSize])
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class AudioBufferList < FFI::Struct
|
22
|
-
layout :mNumberBuffers, :uint32,
|
23
|
-
:mBuffers, AudioBuffer
|
24
|
-
end
|
25
|
-
# @endgroup
|
26
10
|
end
|
27
11
|
|
28
12
|
require_relative 'core_audio/audio_device'
|
29
13
|
|
30
14
|
module CoreAudio
|
31
|
-
# AudioHardware.h
|
15
|
+
# @group AudioHardware.h
|
32
16
|
# OSStatus AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
33
17
|
# const AudioObjectPropertyAddress* inAddress,
|
34
18
|
# UInt32 inQualifierDataSize,
|
@@ -44,6 +28,15 @@ module CoreAudio
|
|
44
28
|
# void* outData)
|
45
29
|
attach_function :AudioObjectGetPropertyData, [AudioObject::ObjectID, AudioObject::PropertyAddress.by_ref, :uint32, :pointer, :pointer, :pointer], :OSStatus
|
46
30
|
|
31
|
+
# OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID,
|
32
|
+
# const AudioObjectPropertyAddress* inAddress,
|
33
|
+
# UInt32 inQualifierDataSize,
|
34
|
+
# const void* inQualifierData,
|
35
|
+
# UInt32 inDataSize,
|
36
|
+
# const void* inData)
|
37
|
+
attach_function :AudioObjectSetPropertyData, [AudioObject::ObjectID, AudioObject::PropertyAddress.by_ref, :uint32, :pointer, :uint32, :pointer], :OSStatus
|
38
|
+
# @endgroup
|
39
|
+
|
47
40
|
# @return [Array<AudioObject>] the list of available audio devices
|
48
41
|
def self.devices
|
49
42
|
address = AudioObject::PropertyAddress.global_master(AudioHardware::PropertyDevices)
|
@@ -51,4 +44,18 @@ module CoreAudio
|
|
51
44
|
device_IDs = buffer.get_array_of_int32(0, buffer.size/4)
|
52
45
|
device_IDs.map {|id| AudioDevice.new(id)}
|
53
46
|
end
|
47
|
+
|
48
|
+
# @return [AudioDevice] the default input device
|
49
|
+
def self.default_input
|
50
|
+
address = AudioObject::PropertyAddress.global_master(AudioHardware::PropertyDefaultInputDevice)
|
51
|
+
buffer = AudioObject.system.get_property(address)
|
52
|
+
AudioDevice.new(buffer.get_uint32(0))
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [AudioDevice] the default output device
|
56
|
+
def self.default_output
|
57
|
+
address = AudioObject::PropertyAddress.global_master(AudioHardware::PropertyDefaultOutputDevice)
|
58
|
+
buffer = AudioObject.system.get_property(address)
|
59
|
+
AudioDevice.new(buffer.get_uint32(0))
|
60
|
+
end
|
54
61
|
end
|
@@ -35,8 +35,35 @@ module CoreAudio
|
|
35
35
|
|
36
36
|
# @endgroup
|
37
37
|
|
38
|
+
# Split the constants so that AudioStream can see 'PropertyLatency'
|
38
39
|
class AudioDevice < AudioObject
|
40
|
+
# @group AudioHardwareBase.h: AudioDevice Properties
|
41
|
+
PropertyConfigurationApplication = 'capp'
|
42
|
+
PropertyDeviceUID = 'uid '
|
43
|
+
PropertyModelUID = 'muid'
|
44
|
+
PropertyTransportType = 'tran'
|
45
|
+
PropertyRelatedDevices = 'akin'
|
46
|
+
PropertyClockDomain = 'clkd'
|
47
|
+
PropertyDeviceIsAlive = 'livn'
|
48
|
+
PropertyDeviceIsRunning = 'goin'
|
49
|
+
PropertyDeviceCanBeDefaultDevice = 'dflt'
|
50
|
+
PropertyDeviceCanBeDefaultSystemDevice = 'sflt'
|
51
|
+
PropertyLatency = 'ltnc'
|
52
|
+
PropertyStreams = 'stm#'
|
53
|
+
PropertyControlList = 'ctrl'
|
54
|
+
PropertySafetyOffset = 'saft'
|
55
|
+
PropertyNominalSampleRate = 'nsrt'
|
56
|
+
PropertyAvailableNominalSampleRates = 'nsr#'
|
57
|
+
PropertyIcon = 'icon'
|
58
|
+
PropertyIsHidden = 'hidn'
|
59
|
+
PropertyPreferredChannelsForStereo = 'dch2'
|
60
|
+
PropertyPreferredChannelLayout = 'srnd'
|
61
|
+
# @endgroup
|
62
|
+
end
|
39
63
|
|
64
|
+
require_relative 'audio_stream'
|
65
|
+
|
66
|
+
class AudioDevice
|
40
67
|
# @group AudioHardware.h: AudioDevice Properties
|
41
68
|
PropertyPlugIn = 'plug'
|
42
69
|
PropertyDeviceHasChanged = 'diff'
|
@@ -54,20 +81,63 @@ module CoreAudio
|
|
54
81
|
# @endgroup
|
55
82
|
|
56
83
|
# @group Properties
|
57
|
-
def actual_sample_rate
|
58
|
-
address = PropertyAddress.global_master(PropertyActualSampleRate)
|
59
|
-
get_property(address).get_float64(0)
|
60
|
-
end
|
61
84
|
|
62
85
|
def buffer_frame_size
|
63
86
|
address = PropertyAddress.global_master(PropertyBufferFrameSize)
|
64
87
|
get_property(address).get_uint32(0)
|
65
88
|
end
|
66
89
|
|
90
|
+
# @return [Bool] true if the device is running
|
91
|
+
def running?
|
92
|
+
address = PropertyAddress.global_master(PropertyDeviceIsRunning)
|
93
|
+
0 != get_property(address).get_uint32(0)
|
94
|
+
end
|
95
|
+
|
67
96
|
def running_somewhere?
|
68
97
|
address = PropertyAddress.global_master(PropertyDeviceIsRunningSomewhere)
|
69
98
|
0 != get_property(address).get_uint32(0)
|
70
99
|
end
|
100
|
+
|
101
|
+
# @return [Array<AudioStream>] an array of {AudioStream}s, one for each stream provided by the device
|
102
|
+
def streams
|
103
|
+
address = PropertyAddress.global_master(PropertyStreams)
|
104
|
+
buffer = get_property(address)
|
105
|
+
buffer.get_array_of_uint32(0, buffer.size/FFI::Type::UINT32.size).map {|stream_id| AudioStream.new(stream_id)}
|
106
|
+
end
|
107
|
+
|
108
|
+
# @group Sample Rate
|
109
|
+
|
110
|
+
# @return [Float] the measured sample rate in Hertz
|
111
|
+
def actual_sample_rate
|
112
|
+
address = PropertyAddress.global_master(PropertyActualSampleRate)
|
113
|
+
get_property(address).get_float64(0)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Array<Number,Range>] the available sampling rates, or sample-rate-ranges
|
117
|
+
def available_sample_rates
|
118
|
+
address = PropertyAddress.global_master(PropertyAvailableNominalSampleRates)
|
119
|
+
buffer = get_property(address)
|
120
|
+
buffer = buffer.get_array_of_float64(0, buffer.size / FFI::Type::DOUBLE.size)
|
121
|
+
|
122
|
+
# Convert the range pairs into actual Ranges, unless the Range is empty
|
123
|
+
buffer.each_slice(2).map {|a,b| (a==b) ? a : (a..b)}
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [Float] the device's nominal sample rate
|
127
|
+
def sample_rate
|
128
|
+
address = PropertyAddress.global_master(PropertyNominalSampleRate)
|
129
|
+
get_property(address).get_float64(0)
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param rate [Float] the new sample rate in Hertz
|
133
|
+
def sample_rate=(rate)
|
134
|
+
address = PropertyAddress.global_master(PropertyNominalSampleRate)
|
135
|
+
ffi_rate = FFI::MemoryPointer.new(:double)
|
136
|
+
ffi_rate.put_float64(0, rate)
|
137
|
+
status = set_property(address, ffi_rate)
|
138
|
+
raise "status #{status} => '#{[status].pack('L').reverse}'" unless 0 == status
|
139
|
+
end
|
140
|
+
# @endgroup
|
71
141
|
# @endgroup
|
72
142
|
|
73
143
|
# Start the AudioDevice
|
@@ -51,21 +51,6 @@ module CoreAudio
|
|
51
51
|
PropertyUserSessionIsActiveOrHeadless = 'user'
|
52
52
|
PropertyServiceRestarted = 'srst'
|
53
53
|
PropertyPowerHint = 'powh'
|
54
|
-
|
55
|
-
# AudioHardware.h: AudioDevice Properties
|
56
|
-
AudioDevicePropertyPlugIn = 'plug'
|
57
|
-
AudioDevicePropertyDeviceHasChanged = 'diff'
|
58
|
-
AudioDevicePropertyDeviceIsRunningSomewhere = 'gone'
|
59
|
-
AudioDeviceProcessorOverload = 'over'
|
60
|
-
AudioDevicePropertyIOStoppedAbnormally = 'stpd'
|
61
|
-
AudioDevicePropertyHogMode = 'oink'
|
62
|
-
AudioDevicePropertyBufferFrameSize = 'fsiz'
|
63
|
-
AudioDevicePropertyBufferFrameSizeRange = 'fsz#'
|
64
|
-
AudioDevicePropertyUsesVariableBufferFrameSizes = 'vfsz'
|
65
|
-
AudioDevicePropertyIOCycleUsage = 'ncyc'
|
66
|
-
AudioDevicePropertyStreamConfiguration = 'slay'
|
67
|
-
AudioDevicePropertyIOProcStreamUsage = 'suse'
|
68
|
-
AudioDevicePropertyActualSampleRate = 'asrt'
|
69
54
|
end
|
70
55
|
|
71
56
|
class AudioObject
|
@@ -123,6 +108,11 @@ module CoreAudio
|
|
123
108
|
buffer
|
124
109
|
end
|
125
110
|
|
111
|
+
def set_property(address, buffer, qualifier=nil)
|
112
|
+
qualifier_size = qualifier.size rescue 0
|
113
|
+
CoreAudio.AudioObjectSetPropertyData(id, address, 0, nil, buffer.size, buffer)
|
114
|
+
end
|
115
|
+
|
126
116
|
# @group Convenience Attributes
|
127
117
|
def external?
|
128
118
|
not internal?
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module CoreAudio
|
2
|
+
class AudioStreamBasicDescription < FFI::Struct
|
3
|
+
# @group CoreAudioTypes.h
|
4
|
+
FormatFlagIsFloat = (1 << 0)
|
5
|
+
FormatFlagIsSignedInteger = (1 << 2)
|
6
|
+
# @endgroup
|
7
|
+
|
8
|
+
layout :mSampleRate, :double,
|
9
|
+
:mFormatID, :uint32,
|
10
|
+
:mFormatFlags, :uint32,
|
11
|
+
:mBytesPerPacket, :uint32,
|
12
|
+
:mFramesPerPacket, :uint32,
|
13
|
+
:mBytesPerFrame, :uint32,
|
14
|
+
:mChannelsPerFrame, :uint32,
|
15
|
+
:mBitsPerChannel, :uint32,
|
16
|
+
:mReserved, :uint32
|
17
|
+
|
18
|
+
# @!attribute channels
|
19
|
+
# @return [Integer] the number of channels per frame
|
20
|
+
def channels
|
21
|
+
self[:mChannelsPerFrame]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param number [Integer] the number of channels per frame
|
25
|
+
def channels=(number)
|
26
|
+
self[:mChannelsPerFrame] = number.to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!attribute channel_width
|
30
|
+
# @return [Integer] the number of bits per channel
|
31
|
+
def channel_width=(bits)
|
32
|
+
self[:mBitsPerChannel] = bits
|
33
|
+
self[:mBytesPerFrame] = self[:mChannelsPerFrame] * bits / 8
|
34
|
+
self[:mBytesPerPacket] = self[:mBytesPerFrame] * self[:mFramesPerPacket]
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!attribute float?
|
38
|
+
# @return [Bool] true if the stream samples are {Float}
|
39
|
+
def float?
|
40
|
+
(self[:mFormatFlags] & FormatFlagIsFloat) != 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def integer
|
44
|
+
self[:mFormatFlags] &= ~FormatFlagIsFloat
|
45
|
+
self[:mFormatFlags] |= FormatFlagIsSignedInteger
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!attribute sample_rate
|
49
|
+
# @return [Float] the number of sample frames per second
|
50
|
+
def sample_rate; self[:mSampleRate]; end
|
51
|
+
def sample_rate=(rate)
|
52
|
+
self[:mSampleRate] = rate
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class AudioValueRange < FFI::Struct
|
57
|
+
layout :minimum, :double,
|
58
|
+
:maximum, :double
|
59
|
+
end
|
60
|
+
|
61
|
+
class AudioStreamRangedDescription < FFI::Struct
|
62
|
+
layout :mFormat, AudioStreamBasicDescription,
|
63
|
+
:mSampleRateRange, AudioValueRange
|
64
|
+
end
|
65
|
+
|
66
|
+
class AudioStream < AudioObject
|
67
|
+
PropertyIsActive = 'sact',
|
68
|
+
PropertyDirection = 'sdir',
|
69
|
+
PropertyTerminalType = 'term',
|
70
|
+
PropertyStartingChannel = 'schn',
|
71
|
+
PropertyLatency = CoreAudio::AudioDevice::PropertyLatency,
|
72
|
+
PropertyVirtualFormat = 'sfmt',
|
73
|
+
PropertyAvailableVirtualFormats = 'sfma',
|
74
|
+
PropertyPhysicalFormat = 'pft ',
|
75
|
+
PropertyAvailablePhysicalFormats = 'pfta'
|
76
|
+
|
77
|
+
def virtual_format
|
78
|
+
address = PropertyAddress.global_master(PropertyVirtualFormat)
|
79
|
+
buffer = get_property(address)
|
80
|
+
AudioStreamBasicDescription.new(buffer)
|
81
|
+
end
|
82
|
+
|
83
|
+
def virtual_formats
|
84
|
+
address = PropertyAddress.global_master(PropertyAvailableVirtualFormats)
|
85
|
+
buffer = get_property(address)
|
86
|
+
count = buffer.size/AudioStreamRangedDescription.size
|
87
|
+
output = []
|
88
|
+
count.times do |i|
|
89
|
+
output << AudioStreamRangedDescription.new(buffer)
|
90
|
+
buffer += AudioStreamRangedDescription.size
|
91
|
+
end
|
92
|
+
output
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CoreAudio
|
2
|
+
class AudioBuffer < FFI::Struct
|
3
|
+
layout :mNumberChannels, :uint32,
|
4
|
+
:mDataByteSize, :uint32,
|
5
|
+
:mData, :pointer
|
6
|
+
|
7
|
+
# @return [String] the raw bytes
|
8
|
+
def bytes
|
9
|
+
self[:mData].get_bytes(0, self[:mDataByteSize])
|
10
|
+
end
|
11
|
+
|
12
|
+
def bytesize
|
13
|
+
self[:mDataByteSize]
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array<Integer>] an array of samples, converted to signed integers
|
17
|
+
def samples_int(bytes_per_channel)
|
18
|
+
self[:mData].get_array_of_int16(0, self[:mDataByteSize]/bytes_per_channel)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Array<Float>] an array of samples, converted to {Float}
|
22
|
+
def samples_float(bytes_per_channel)
|
23
|
+
self[:mData].get_array_of_float32(0, self[:mDataByteSize]/bytes_per_channel)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class AudioBufferList < FFI::Struct
|
28
|
+
layout :mNumberBuffers, :uint32,
|
29
|
+
:mBuffers, AudioBuffer
|
30
|
+
|
31
|
+
def self.buffer_list(channels:1, size:nil)
|
32
|
+
self.new.tap do |list|
|
33
|
+
list[:mNumberBuffers] = 1
|
34
|
+
list[:mBuffers][:mNumberChannels] = channels
|
35
|
+
|
36
|
+
if( size )
|
37
|
+
list[:mBuffers][:mData] = FFI::MemoryPointer.new(size)
|
38
|
+
list[:mBuffers][:mDataByteSize] = size
|
39
|
+
else
|
40
|
+
list[:mBuffers][:mDataByteSize] = 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array] the buffers
|
46
|
+
def buffers
|
47
|
+
raise("Can't handle multiple buffers yet") if self[:mNumberBuffers] > 1
|
48
|
+
[self[:mBuffers]]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String] the raw bytes
|
52
|
+
def bytes
|
53
|
+
buffers.map(&:bytes).join
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Number] the total number of bytes in the buffer list
|
57
|
+
def bytesize
|
58
|
+
buffers.map(&:bytesize).reduce(&:+)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Retrieve the samples as an {Array}, after converting to the requested type
|
62
|
+
# @param type [Symbol] :float or :int. Convert the samples to the given type.
|
63
|
+
# @param bytes_per_channel [Number] the number of bytes for each sample
|
64
|
+
# @return [Array<Float,Integer>] an array of samples, converted to {Float} or {Integer}
|
65
|
+
def samples(type, bytes_per_channel)
|
66
|
+
if type == :float
|
67
|
+
buffers.map {|buffer| buffer.samples_float(bytes_per_channel)}.flatten
|
68
|
+
else
|
69
|
+
buffers.map {|buffer| buffer.samples_int(bytes_per_channel)}.flatten
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: audio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Fosdick
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -51,10 +51,14 @@ files:
|
|
51
51
|
- Rakefile
|
52
52
|
- audio.gemspec
|
53
53
|
- lib/audio.rb
|
54
|
-
- lib/
|
55
|
-
- lib/
|
56
|
-
- lib/core_audio
|
57
|
-
- lib/
|
54
|
+
- lib/macos/audio_toolbox.rb
|
55
|
+
- lib/macos/audio_toolbox/audio_converter.rb
|
56
|
+
- lib/macos/core_audio.rb
|
57
|
+
- lib/macos/core_audio/audio_device.rb
|
58
|
+
- lib/macos/core_audio/audio_object.rb
|
59
|
+
- lib/macos/core_audio/audio_stream.rb
|
60
|
+
- lib/macos/core_audio/audio_types.rb
|
61
|
+
- lib/macos/core_foundation.rb
|
58
62
|
homepage: http://github.com/bfoz/audio-ruby
|
59
63
|
licenses:
|
60
64
|
- BSD
|