audio 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|