audio 0 → 0.1
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 +60 -11
- data/audio.gemspec +1 -1
- data/lib/audio.rb +6 -1
- data/lib/core_audio.rb +54 -0
- data/lib/core_audio/audio_device.rb +97 -0
- data/lib/core_audio/audio_object.rb +188 -0
- data/lib/core_foundation.rb +87 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f7750670a7a9f4d0554765012808dbe2f6477c9
|
4
|
+
data.tar.gz: a9e49107f1c2d316f416ecacc9a0f4ede1df1e66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a813230ad12d52963266b54ab57dfef88728af3fc6b6883bc19a1c71b783e9fcae205b2f06dfcecb2f06869e91185dfb2c47c2defbdec07d286b61c2a8ab414
|
7
|
+
data.tar.gz: a6dbf92c29fbce40913e41e099f549b9f314e1c7bed5f50eeaf73f2d79a73a41cf847b94e870c609cc0bc71bb2029d7cddbf21d081d43e9e100a7e148902c6bb
|
data/README.md
CHANGED
@@ -1,6 +1,62 @@
|
|
1
1
|
# Audio
|
2
2
|
|
3
|
-
|
3
|
+
'Audio' is a cross-platform audio device interface that allows you to read and
|
4
|
+
write audio streams from the comfort and safety of Ruby.
|
5
|
+
|
6
|
+
## Cross-Platform Status
|
7
|
+
|
8
|
+
Currently, only support for OS X has been implemented. Support for Windows and
|
9
|
+
Linux will be added in the future. If you feel like working on either of those,
|
10
|
+
I'm more than happy to take Pull Requests!
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
### Listing Available Audio Devices
|
15
|
+
|
16
|
+
The Audio module provides a method that retrieves a list of all of the audio
|
17
|
+
devices available on your system.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'audio'
|
21
|
+
|
22
|
+
devices = Audio.devices # => An Array of Device objects
|
23
|
+
```
|
24
|
+
|
25
|
+
Each Device object has attributes that wrap the various properties provided by
|
26
|
+
the host operating system. For example, to get a list of device names...
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Audio.devices.each do |device|
|
30
|
+
puts "Device Name: #{device.device_name}"
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Running that on a MacBook Pro (late 2013), with a single external microphone
|
35
|
+
connected, produces...
|
36
|
+
|
37
|
+
```
|
38
|
+
Device Name: Built-in Microphone
|
39
|
+
Device Name: Built-in Output
|
40
|
+
Device Name: Blue Snowball
|
41
|
+
```
|
42
|
+
|
43
|
+
### Recording Audio
|
44
|
+
|
45
|
+
You can record audio from a particular device by calling its `start` method.
|
46
|
+
Once started, the device will continue recording until its `stop` method is
|
47
|
+
called. If you provide a block parameter to `start`, it will be called whenever
|
48
|
+
the host OS provides a new buffer of audio samples.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
device = Audio.devices.last # Hopefully that's an input device
|
52
|
+
device.start do |*args|
|
53
|
+
# Do something fancy
|
54
|
+
end
|
55
|
+
|
56
|
+
sleep 5 # Record 5 seconds of audio
|
57
|
+
|
58
|
+
device.stop
|
59
|
+
```
|
4
60
|
|
5
61
|
## Installation
|
6
62
|
|
@@ -18,14 +74,7 @@ Or install it yourself as:
|
|
18
74
|
|
19
75
|
$ gem install audio
|
20
76
|
|
21
|
-
|
22
|
-
|
23
|
-
TODO: Write usage instructions here
|
24
|
-
|
25
|
-
## Contributing
|
77
|
+
License
|
78
|
+
-------
|
26
79
|
|
27
|
-
|
28
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
-
5. Create a new Pull Request
|
80
|
+
Copyright 2015 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
|
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.1'
|
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
data/lib/core_audio.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module CoreAudio
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib '/System/Library/Frameworks/CoreAudio.framework/CoreAudio'
|
6
|
+
|
7
|
+
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
|
+
end
|
27
|
+
|
28
|
+
require_relative 'core_audio/audio_device'
|
29
|
+
|
30
|
+
module CoreAudio
|
31
|
+
# AudioHardware.h
|
32
|
+
# OSStatus AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
33
|
+
# const AudioObjectPropertyAddress* inAddress,
|
34
|
+
# UInt32 inQualifierDataSize,
|
35
|
+
# const void* inQualifierData,
|
36
|
+
# UInt32* outDataSize)
|
37
|
+
attach_function :AudioObjectGetPropertyDataSize, [AudioObject::ObjectID, AudioObject::PropertyAddress.by_ref, :uint32, :pointer, :pointer], :OSStatus
|
38
|
+
|
39
|
+
# OSStatus AudioObjectGetPropertyData(AudioObjectID inObjectID,
|
40
|
+
# const AudioObjectPropertyAddress* inAddress,
|
41
|
+
# UInt32 inQualifierDataSize,
|
42
|
+
# const void* inQualifierData,
|
43
|
+
# UInt32* ioDataSize,
|
44
|
+
# void* outData)
|
45
|
+
attach_function :AudioObjectGetPropertyData, [AudioObject::ObjectID, AudioObject::PropertyAddress.by_ref, :uint32, :pointer, :pointer, :pointer], :OSStatus
|
46
|
+
|
47
|
+
# @return [Array<AudioObject>] the list of available audio devices
|
48
|
+
def self.devices
|
49
|
+
address = AudioObject::PropertyAddress.global_master(AudioHardware::PropertyDevices)
|
50
|
+
buffer = AudioObject.system.get_property(address)
|
51
|
+
device_IDs = buffer.get_array_of_int32(0, buffer.size/4)
|
52
|
+
device_IDs.map {|id| AudioDevice.new(id)}
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'audio_object'
|
2
|
+
|
3
|
+
module CoreAudio
|
4
|
+
AudioDeviceIOProcID = FFI::Pointer
|
5
|
+
typedef :pointer, :AudioDeviceIOProcID
|
6
|
+
|
7
|
+
# @group AudioHardware.h
|
8
|
+
|
9
|
+
# typedef OSStatus (*AudioDeviceIOProc)(AudioObjectID inDevice,
|
10
|
+
# const AudioTimeStamp* inNow,
|
11
|
+
# const AudioBufferList* inInputData,
|
12
|
+
# const AudioTimeStamp* inInputTime,
|
13
|
+
# AudioBufferList* outOutputData,
|
14
|
+
# const AudioTimeStamp* inOutputTime,
|
15
|
+
# void* inClientData);
|
16
|
+
callback :AudioDeviceIOProc, [AudioObject::ObjectID, :pointer, AudioBufferList.by_ref, :pointer, AudioBufferList.by_ref, :pointer, :pointer], :OSStatus
|
17
|
+
|
18
|
+
# OSStatus AudioDeviceCreateIOProcID(AudioObjectID inDevice,
|
19
|
+
# AudioDeviceIOProc inProc,
|
20
|
+
# void* inClientData,
|
21
|
+
# AudioDeviceIOProcID* outIOProcID)
|
22
|
+
attach_function :AudioDeviceCreateIOProcID, [AudioObject::ObjectID, :AudioDeviceIOProc, :pointer, :pointer], :OSStatus
|
23
|
+
|
24
|
+
# OSStatus AudioDeviceDestroyIOProcID(AudioObjectID inDevice,
|
25
|
+
# AudioDeviceIOProcID inIOProcID
|
26
|
+
attach_function :AudioDeviceDestroyIOProcID, [AudioObject::ObjectID, :AudioDeviceIOProc], :OSStatus
|
27
|
+
|
28
|
+
# OSStatus AudioDeviceStart(AudioObjectID inDevice,
|
29
|
+
# AudioDeviceIOProcID inProcID)
|
30
|
+
attach_function :AudioDeviceStart, [AudioObject::ObjectID, :AudioDeviceIOProcID], :OSStatus
|
31
|
+
|
32
|
+
# OSStatus AudioDeviceStop(AudioObjectID inDevice,
|
33
|
+
# AudioDeviceIOProcID inProcID)
|
34
|
+
attach_function :AudioDeviceStop, [AudioObject::ObjectID, :AudioDeviceIOProcID], :OSStatus
|
35
|
+
|
36
|
+
# @endgroup
|
37
|
+
|
38
|
+
class AudioDevice < AudioObject
|
39
|
+
|
40
|
+
# @group AudioHardware.h: AudioDevice Properties
|
41
|
+
PropertyPlugIn = 'plug'
|
42
|
+
PropertyDeviceHasChanged = 'diff'
|
43
|
+
PropertyDeviceIsRunningSomewhere = 'gone'
|
44
|
+
ProcessorOverload = 'over'
|
45
|
+
PropertyIOStoppedAbnormally = 'stpd'
|
46
|
+
PropertyHogMode = 'oink'
|
47
|
+
PropertyBufferFrameSize = 'fsiz'
|
48
|
+
PropertyBufferFrameSizeRange = 'fsz#'
|
49
|
+
PropertyUsesVariableBufferFrameSizes = 'vfsz'
|
50
|
+
PropertyIOCycleUsage = 'ncyc'
|
51
|
+
PropertyStreamConfiguration = 'slay'
|
52
|
+
PropertyIOProcStreamUsage = 'suse'
|
53
|
+
PropertyActualSampleRate = 'asrt'
|
54
|
+
# @endgroup
|
55
|
+
|
56
|
+
# @group Properties
|
57
|
+
def actual_sample_rate
|
58
|
+
address = PropertyAddress.global_master(PropertyActualSampleRate)
|
59
|
+
get_property(address).get_float64(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
def buffer_frame_size
|
63
|
+
address = PropertyAddress.global_master(PropertyBufferFrameSize)
|
64
|
+
get_property(address).get_uint32(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
def running_somewhere?
|
68
|
+
address = PropertyAddress.global_master(PropertyDeviceIsRunningSomewhere)
|
69
|
+
0 != get_property(address).get_uint32(0)
|
70
|
+
end
|
71
|
+
# @endgroup
|
72
|
+
|
73
|
+
# Start the AudioDevice
|
74
|
+
# If a block is provided, register it as a callback before starting the device
|
75
|
+
# @note The device will continue to run until `stop` is called
|
76
|
+
def start(&block)
|
77
|
+
if block_given?
|
78
|
+
io_proc_id = FFI::MemoryPointer.new(:pointer)
|
79
|
+
status = CoreAudio.AudioDeviceCreateIOProcID(id, block, nil, io_proc_id)
|
80
|
+
|
81
|
+
raise "Couldn't create an IO Proc #{status} => '#{[status].pack('L').reverse}'" unless status.zero? # && !@proc_id.nil?
|
82
|
+
|
83
|
+
@proc_id = io_proc_id.get_pointer(0)
|
84
|
+
end
|
85
|
+
|
86
|
+
Thread.start do
|
87
|
+
CoreAudio.AudioDeviceStart(id, @proc_id)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Stop the AudioDevice and delete any registered callbacks
|
92
|
+
def stop
|
93
|
+
CoreAudio.AudioDeviceStop(id, @proc_id)
|
94
|
+
CoreAudio.AudioDeviceDestroyIOProcID(id, @proc_id)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require_relative '../core_foundation'
|
2
|
+
|
3
|
+
module CoreAudio
|
4
|
+
extend FFI::Library
|
5
|
+
|
6
|
+
module AudioHardwareBase
|
7
|
+
# AudioHardwareBase.h: AudioDevice Properties
|
8
|
+
AudioDevicePropertyConfigurationApplication = 'capp'
|
9
|
+
AudioDevicePropertyDeviceUID = 'uid '
|
10
|
+
AudioDevicePropertyModelUID = 'muid'
|
11
|
+
AudioDevicePropertyTransportType = 'tran'
|
12
|
+
AudioDevicePropertyRelatedDevices = 'akin'
|
13
|
+
AudioDevicePropertyClockDomain = 'clkd'
|
14
|
+
AudioDevicePropertyDeviceIsAlive = 'livn'
|
15
|
+
AudioDevicePropertyDeviceIsRunning = 'goin'
|
16
|
+
AudioDevicePropertyDeviceCanBeDefaultDevice = 'dflt'
|
17
|
+
AudioDevicePropertyDeviceCanBeDefaultSystemDevice = 'sflt'
|
18
|
+
AudioDevicePropertyLatency = 'ltnc'
|
19
|
+
AudioDevicePropertyStreams = 'stm#'
|
20
|
+
AudioObjectPropertyControlList = 'ctrl'
|
21
|
+
AudioDevicePropertySafetyOffset = 'saft'
|
22
|
+
AudioDevicePropertyNominalSampleRate = 'nsrt'
|
23
|
+
AudioDevicePropertyAvailableNominalSampleRates = 'nsr#'
|
24
|
+
AudioDevicePropertyIcon = 'icon'
|
25
|
+
AudioDevicePropertyIsHidden = 'hidn'
|
26
|
+
AudioDevicePropertyPreferredChannelsForStereo = 'dch2'
|
27
|
+
AudioDevicePropertyPreferredChannelLayout = 'srnd'
|
28
|
+
end
|
29
|
+
|
30
|
+
module AudioHardware
|
31
|
+
# AudioHardware.h: AudioSystemObject Properties
|
32
|
+
PropertyDevices = 'dev#'
|
33
|
+
PropertyDefaultInputDevice = 'dIn '
|
34
|
+
PropertyDefaultOutputDevice = 'dOut'
|
35
|
+
PropertyDefaultSystemOutputDevice = 'sOut'
|
36
|
+
PropertyTranslateUIDToDevice = 'uidd'
|
37
|
+
PropertyMixStereoToMono = 'stmo'
|
38
|
+
PropertyPlugInList = 'plg#'
|
39
|
+
PropertyTranslateBundleIDToPlugIn = 'bidp'
|
40
|
+
PropertyTransportManagerList = 'tmg#'
|
41
|
+
PropertyTranslateBundleIDToTransportManager = 'tmbi'
|
42
|
+
PropertyBoxList = 'box#'
|
43
|
+
PropertyTranslateUIDToBox = 'uidb'
|
44
|
+
PropertyProcessIsMaster = 'mast'
|
45
|
+
PropertyIsInitingOrExiting = 'inot'
|
46
|
+
PropertyUserIDChanged = 'euid'
|
47
|
+
PropertyProcessIsAudible = 'pmut'
|
48
|
+
PropertySleepingIsAllowed = 'slep'
|
49
|
+
PropertyUnloadingIsAllowed = 'unld'
|
50
|
+
PropertyHogModeIsAllowed = 'hogr'
|
51
|
+
PropertyUserSessionIsActiveOrHeadless = 'user'
|
52
|
+
PropertyServiceRestarted = 'srst'
|
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
|
+
end
|
70
|
+
|
71
|
+
class AudioObject
|
72
|
+
attr_reader :id
|
73
|
+
|
74
|
+
ObjectID = FFI::Type::UINT32
|
75
|
+
PropertyElement = FFI::Type::UINT32
|
76
|
+
PropertyScope = FFI::Type::UINT32
|
77
|
+
PropertySelector = FFI::Type::UINT32
|
78
|
+
|
79
|
+
# AudioHardwareBase.h: Basic Constants
|
80
|
+
PropertyScopeGlobal = 'glob'
|
81
|
+
PropertyScopeInput = 'inpt'
|
82
|
+
PropertyScopeOutput = 'outp'
|
83
|
+
PropertyScopePlayThrough = 'ptru'
|
84
|
+
PropertyElementMaster = 0
|
85
|
+
|
86
|
+
# AudioHardwareBase.h: AudioObject Properties
|
87
|
+
PropertyBaseClass = 'bcls'
|
88
|
+
PropertyClass = 'clas'
|
89
|
+
PropertyOwner = 'stdv'
|
90
|
+
PropertyName = 'lnam'
|
91
|
+
PropertyModelName = 'lmod'
|
92
|
+
PropertyManufacturer = 'lmak'
|
93
|
+
PropertyElementName = 'lchn'
|
94
|
+
PropertyElementCategoryName = 'lccn'
|
95
|
+
PropertyElementNumberName = 'lcnn'
|
96
|
+
PropertyOwnedObjects = 'ownd'
|
97
|
+
PropertyIdentify = 'iden'
|
98
|
+
PropertySerialNumber = 'snum'
|
99
|
+
PropertyFirmwareVersion = 'fwvn'
|
100
|
+
|
101
|
+
# AudioHardware.h: Basic Constants
|
102
|
+
SystemObject = 1
|
103
|
+
|
104
|
+
def self.system
|
105
|
+
new(SystemObject)
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(id)
|
109
|
+
@id = id
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [FFI::MemoryPointer]
|
113
|
+
def get_property(address)
|
114
|
+
buffer_size = FFI::MemoryPointer.new(:uint32)
|
115
|
+
status = CoreAudio.AudioObjectGetPropertyDataSize(id, address, 0, nil, buffer_size)
|
116
|
+
raise('Could not get audio property size') unless 0 == status
|
117
|
+
|
118
|
+
# buffer_size is now the size of the buffer to be passed to AudioObjectGetPropertyData()
|
119
|
+
buffer = FFI::MemoryPointer.new(1, buffer_size.get_int32(0))
|
120
|
+
status = CoreAudio.AudioObjectGetPropertyData(id, address, 0, nil, buffer_size, buffer)
|
121
|
+
raise('Could not get the audio property data') unless 0 == status
|
122
|
+
|
123
|
+
buffer
|
124
|
+
end
|
125
|
+
|
126
|
+
# @group Convenience Attributes
|
127
|
+
def external?
|
128
|
+
not internal?
|
129
|
+
end
|
130
|
+
|
131
|
+
def internal?
|
132
|
+
transport_type == 'bltn'
|
133
|
+
end
|
134
|
+
# @endgroup
|
135
|
+
|
136
|
+
# @return [String] the name of the device
|
137
|
+
def device_name
|
138
|
+
address = PropertyAddress.global_master(PropertyName)
|
139
|
+
get_string(address)
|
140
|
+
end
|
141
|
+
|
142
|
+
def device_uid
|
143
|
+
address = PropertyAddress.global_master(AudioHardwareBase::AudioDevicePropertyDeviceUID)
|
144
|
+
get_string(address)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [String] a persistent identifier for the model of an AudioDevice
|
148
|
+
def model_uid
|
149
|
+
address = PropertyAddress.global_master(AudioHardwareBase::AudioDevicePropertyModelUID)
|
150
|
+
get_string(address)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [String] the 4-character transport type identifier
|
154
|
+
def transport_type
|
155
|
+
address = PropertyAddress.global_master(AudioHardwareBase::AudioDevicePropertyTransportType)
|
156
|
+
buffer = get_property(address)
|
157
|
+
buffer.get_bytes(0, buffer.size).reverse
|
158
|
+
end
|
159
|
+
|
160
|
+
class PropertyAddress < FFI::Struct
|
161
|
+
layout :mSelector, AudioObject::PropertySelector,
|
162
|
+
:mScope, AudioObject::PropertyScope,
|
163
|
+
:mElement, AudioObject::PropertyElement
|
164
|
+
|
165
|
+
def self.make(selector, scope, element)
|
166
|
+
element, scope, selector = [element, scope, selector].map {|a| a.is_a?(String) ? a.reverse.unpack('L').first : a }
|
167
|
+
new.tap do |address|
|
168
|
+
address[:mSelector] = selector
|
169
|
+
address[:mScope] = scope
|
170
|
+
address[:mElement] = element
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.global_master(selector)
|
175
|
+
make(selector, PropertyScopeGlobal, PropertyElementMaster)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# @return [String] the String for the addressed property
|
182
|
+
def get_string(address)
|
183
|
+
buffer = get_property(address)
|
184
|
+
cf_string_ref = buffer.get_pointer(0)
|
185
|
+
CoreFoundation::CFStringRef.new(cf_string_ref).to_s
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module CoreFoundation
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation'
|
6
|
+
|
7
|
+
typedef :pointer, :CFStringRef
|
8
|
+
|
9
|
+
if FFI::Platform::ARCH == 'x86_64'
|
10
|
+
CFIndex = FFI::Type::LONG_LONG
|
11
|
+
else
|
12
|
+
CFIndex = FFI::Type::LONG
|
13
|
+
end
|
14
|
+
|
15
|
+
# CFString.h
|
16
|
+
CFStringEncoding = enum :uint32,
|
17
|
+
:MacRoman, 0,
|
18
|
+
:WindowsLatin1, 0x0500, # ANSI codepage 1252
|
19
|
+
:ISOLatin1, 0x0201, # ISO 8859-1
|
20
|
+
:NextStepLatin, 0x0B01, # NextStep encoding
|
21
|
+
:ASCII, 0x0600, # 0..127 (in creating CFString, values greater than 0x7F are treated as corresponding Unicode value)
|
22
|
+
:Unicode, 0x0100, # kTextEncodingUnicodeDefault + kTextEncodingDefaultFormat (aka kUnicode16BitFormat)
|
23
|
+
:UTF8, 0x08000100, # kTextEncodingUnicodeDefault + kUnicodeUTF8Format
|
24
|
+
:NonLossyASCII, 0x0BFF, # 7bit Unicode variants used by Cocoa & Java
|
25
|
+
:UTF16, 0x0100, # kTextEncodingUnicodeDefault + kUnicodeUTF16Format (alias of kCFStringEncodingUnicode)
|
26
|
+
:UTF16BE, 0x10000100, # kTextEncodingUnicodeDefault + kUnicodeUTF16BEFormat
|
27
|
+
:UTF16LE, 0x14000100, # kTextEncodingUnicodeDefault + kUnicodeUTF16LEFormat
|
28
|
+
:UTF32, 0x0c000100, # kTextEncodingUnicodeDefault + kUnicodeUTF32Format
|
29
|
+
:UTF32BE, 0x18000100, # kTextEncodingUnicodeDefault + kUnicodeUTF32BEFormat
|
30
|
+
:UTF32LE, 0x1c000100, # kTextEncodingUnicodeDefault + kUnicodeUTF32LEFormat
|
31
|
+
:Invalid, 0xffffffff # Invalid Encoding
|
32
|
+
|
33
|
+
class CFRange < FFI::Struct
|
34
|
+
layout :location, CFIndex,
|
35
|
+
:length, CFIndex
|
36
|
+
|
37
|
+
def self.make(location:0, length:0)
|
38
|
+
new.tap do |range|
|
39
|
+
range[:location] = location
|
40
|
+
range[:length] = length
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class CFStringRef < FFI::Pointer
|
46
|
+
# @return [CFIndex] the length of the referenced CFString
|
47
|
+
def length
|
48
|
+
CoreFoundation.CFStringGetLength(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [CFIndex] the maximum size of the buffer that will hold the string
|
52
|
+
def max_size
|
53
|
+
CoreFoundation.CFStringGetMaximumSizeForEncoding(length, CFStringEncoding[:UTF8])
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String] the CFString, converted to a UTF-8 string
|
57
|
+
def to_s
|
58
|
+
buffer = FFI::MemoryPointer.new(:char, max_size)
|
59
|
+
used_bytes = FFI::MemoryPointer.new(CFIndex)
|
60
|
+
CoreFoundation.CFStringGetBytes(self,
|
61
|
+
CFRange.make(location:0, length:length),
|
62
|
+
CFStringEncoding[:UTF8],
|
63
|
+
0,
|
64
|
+
false,
|
65
|
+
buffer,
|
66
|
+
buffer.size,
|
67
|
+
used_bytes)
|
68
|
+
|
69
|
+
used_bytes = if CFIndex == CoreFoundation.find_type(:long_long)
|
70
|
+
used_bytes.read_long_long
|
71
|
+
else
|
72
|
+
used_bytes.read_long
|
73
|
+
end
|
74
|
+
|
75
|
+
buffer.read_string(used_bytes).force_encoding(Encoding::UTF_8)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# CFIndex CFStringGetBytes(CFStringRef theString, CFRange range, CFStringEncoding encoding, UInt8 lossByte, Boolean isExternalRepresentation, UInt8 *buffer, CFIndex maxBufLen, CFIndex *usedBufLen)
|
80
|
+
attach_function :CFStringGetBytes, [:CFStringRef, CFRange.by_value, CFStringEncoding, :uint8, :bool, :buffer_out, CFIndex, :buffer_out], CFIndex
|
81
|
+
|
82
|
+
# CFIndex CFStringGetLength(CFStringRef theString)
|
83
|
+
attach_function :CFStringGetLength, [:CFStringRef], CFIndex
|
84
|
+
|
85
|
+
# CFIndex CFStringGetMaximumSizeForEncoding(CFIndex length, CFStringEncoding encoding)
|
86
|
+
attach_function :CFStringGetMaximumSizeForEncoding, [CFIndex, CFStringEncoding], CFIndex
|
87
|
+
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.1'
|
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-02-
|
11
|
+
date: 2015-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -51,6 +51,10 @@ files:
|
|
51
51
|
- Rakefile
|
52
52
|
- audio.gemspec
|
53
53
|
- lib/audio.rb
|
54
|
+
- lib/core_audio.rb
|
55
|
+
- lib/core_audio/audio_device.rb
|
56
|
+
- lib/core_audio/audio_object.rb
|
57
|
+
- lib/core_foundation.rb
|
54
58
|
homepage: http://github.com/bfoz/audio-ruby
|
55
59
|
licenses:
|
56
60
|
- BSD
|