fmod 0.9.0

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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +5 -0
  4. data/.yardopts +2 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +96 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +28 -0
  11. data/bin/setup +8 -0
  12. data/ext/fmod.dll +0 -0
  13. data/ext/fmod64.dll +0 -0
  14. data/ext/libfmod.dylib +0 -0
  15. data/ext/llbfmod.zip +0 -0
  16. data/extras/FMOD Studio Programmers API for Windows.chm +0 -0
  17. data/fmod.gemspec +58 -0
  18. data/lib/fmod.rb +564 -0
  19. data/lib/fmod/channel.rb +151 -0
  20. data/lib/fmod/channel_control.rb +821 -0
  21. data/lib/fmod/channel_group.rb +61 -0
  22. data/lib/fmod/core.rb +35 -0
  23. data/lib/fmod/core/bool_description.rb +18 -0
  24. data/lib/fmod/core/channel_mask.rb +24 -0
  25. data/lib/fmod/core/data_description.rb +14 -0
  26. data/lib/fmod/core/driver.rb +59 -0
  27. data/lib/fmod/core/dsp_description.rb +7 -0
  28. data/lib/fmod/core/dsp_index.rb +9 -0
  29. data/lib/fmod/core/dsp_type.rb +43 -0
  30. data/lib/fmod/core/extensions.rb +28 -0
  31. data/lib/fmod/core/file_system.rb +86 -0
  32. data/lib/fmod/core/filter_type.rb +19 -0
  33. data/lib/fmod/core/float_description.rb +16 -0
  34. data/lib/fmod/core/guid.rb +50 -0
  35. data/lib/fmod/core/init_flags.rb +19 -0
  36. data/lib/fmod/core/integer_description.rb +26 -0
  37. data/lib/fmod/core/mode.rb +36 -0
  38. data/lib/fmod/core/output_type.rb +30 -0
  39. data/lib/fmod/core/parameter_info.rb +41 -0
  40. data/lib/fmod/core/parameter_type.rb +10 -0
  41. data/lib/fmod/core/result.rb +88 -0
  42. data/lib/fmod/core/reverb.rb +217 -0
  43. data/lib/fmod/core/sound_ex_info.rb +7 -0
  44. data/lib/fmod/core/sound_format.rb +30 -0
  45. data/lib/fmod/core/sound_group_behavior.rb +9 -0
  46. data/lib/fmod/core/sound_type.rb +80 -0
  47. data/lib/fmod/core/speaker_index.rb +18 -0
  48. data/lib/fmod/core/speaker_mode.rb +16 -0
  49. data/lib/fmod/core/spectrum_data.rb +12 -0
  50. data/lib/fmod/core/structure.rb +23 -0
  51. data/lib/fmod/core/structures.rb +41 -0
  52. data/lib/fmod/core/tag.rb +51 -0
  53. data/lib/fmod/core/tag_data_type.rb +14 -0
  54. data/lib/fmod/core/time_unit.rb +40 -0
  55. data/lib/fmod/core/vector.rb +42 -0
  56. data/lib/fmod/core/window_type.rb +12 -0
  57. data/lib/fmod/dsp.rb +510 -0
  58. data/lib/fmod/dsp_connection.rb +113 -0
  59. data/lib/fmod/effects.rb +38 -0
  60. data/lib/fmod/effects/channel_mix.rb +101 -0
  61. data/lib/fmod/effects/chorus.rb +30 -0
  62. data/lib/fmod/effects/compressor.rb +52 -0
  63. data/lib/fmod/effects/convolution_reverb.rb +31 -0
  64. data/lib/fmod/effects/delay.rb +44 -0
  65. data/lib/fmod/effects/distortion.rb +16 -0
  66. data/lib/fmod/effects/dsps.rb +10 -0
  67. data/lib/fmod/effects/echo.rb +37 -0
  68. data/lib/fmod/effects/envelope_follower.rb +31 -0
  69. data/lib/fmod/effects/fader.rb +16 -0
  70. data/lib/fmod/effects/fft.rb +38 -0
  71. data/lib/fmod/effects/flange.rb +37 -0
  72. data/lib/fmod/effects/high_pass.rb +24 -0
  73. data/lib/fmod/effects/high_pass_simple.rb +25 -0
  74. data/lib/fmod/effects/it_echo.rb +56 -0
  75. data/lib/fmod/effects/it_lowpass.rb +36 -0
  76. data/lib/fmod/effects/ladspa_plugin.rb +14 -0
  77. data/lib/fmod/effects/limiter.rb +32 -0
  78. data/lib/fmod/effects/loudness_meter.rb +19 -0
  79. data/lib/fmod/effects/low_pass.rb +25 -0
  80. data/lib/fmod/effects/low_pass_simple.rb +26 -0
  81. data/lib/fmod/effects/mixer.rb +11 -0
  82. data/lib/fmod/effects/multiband_eq.rb +153 -0
  83. data/lib/fmod/effects/normalize.rb +47 -0
  84. data/lib/fmod/effects/object_pan.rb +62 -0
  85. data/lib/fmod/effects/oscillator.rb +52 -0
  86. data/lib/fmod/effects/pan.rb +166 -0
  87. data/lib/fmod/effects/param_eq.rb +36 -0
  88. data/lib/fmod/effects/pitch_shift.rb +47 -0
  89. data/lib/fmod/effects/return.rb +18 -0
  90. data/lib/fmod/effects/send.rb +21 -0
  91. data/lib/fmod/effects/sfx_reverb.rb +87 -0
  92. data/lib/fmod/effects/three_eq.rb +41 -0
  93. data/lib/fmod/effects/transceiver.rb +57 -0
  94. data/lib/fmod/effects/tremolo.rb +67 -0
  95. data/lib/fmod/effects/vst_plugin.rb +12 -0
  96. data/lib/fmod/effects/winamp_plugin.rb +12 -0
  97. data/lib/fmod/error.rb +108 -0
  98. data/lib/fmod/geometry.rb +380 -0
  99. data/lib/fmod/handle.rb +129 -0
  100. data/lib/fmod/reverb3D.rb +98 -0
  101. data/lib/fmod/sound.rb +810 -0
  102. data/lib/fmod/sound_group.rb +54 -0
  103. data/lib/fmod/system.rb +1242 -0
  104. data/lib/fmod/version.rb +3 -0
  105. metadata +220 -0
@@ -0,0 +1,129 @@
1
+ require 'fiddle'
2
+ require 'fiddle/import'
3
+
4
+ module FMOD
5
+ class Handle < Fiddle::Pointer
6
+
7
+ include Fiddle
8
+ include FMOD::Core
9
+
10
+ def initialize(address)
11
+ address = address.unpack1('J') if address.is_a?(String)
12
+ super(address.to_i)
13
+ end
14
+
15
+ def release
16
+ case self
17
+ when Sound then FMOD.invoke(:Sound_Release, self)
18
+ when Dsp then FMOD.invoke(:DSP_Release, self)
19
+ when ChannelGroup then FMOD.invoke(:ChannelGroup_Release, self)
20
+ when Geometry then FMOD.invoke(:Geometry_Release, self)
21
+ when Reverb3D then FMOD.invoke(:Reverb3D_Release, self)
22
+ when SoundGroup then FMOD.invoke(:SoundGroup_Release, self)
23
+ when System then FMOD.invoke(:System_Release, self)
24
+ end
25
+ end
26
+
27
+ alias_method :dispose, :release
28
+
29
+ def user_data
30
+ pointer = int_ptr
31
+ case self
32
+ when ChannelControl
33
+ FMOD.invoke(:ChannelGroup_GetUserData, self, pointer)
34
+ when Dsp
35
+ FMOD.invoke(:DSP_GetUserData, self, pointer)
36
+ when DspConnection
37
+ FMOD.invoke(:DSPConnection_GetUserData, self, pointer)
38
+ when Geometry
39
+ FMOD.invoke(:Geometry_GetUserData, self, pointer)
40
+ when Reverb3D
41
+ FMOD.invoke(:Reverb3D_GetUserData, self, pointer)
42
+ when Sound
43
+ FMOD.invoke(:Sound_GetUserData, self, pointer)
44
+ when SoundGroup
45
+ FMOD.invoke(:SoundGroup_GetUserData, self, pointer)
46
+ when System
47
+ FMOD.invoke(:System_GetUserData, self, pointer)
48
+ end
49
+ Pointer.new(pointer.unpack1('J'))
50
+ end
51
+
52
+ def user_data=(pointer)
53
+ case self
54
+ when ChannelControl
55
+ FMOD.invoke(:ChannelGroup_SetUserData, self, pointer)
56
+ when Dsp
57
+ FMOD.invoke(:DSP_SetUserData, self, pointer)
58
+ when DspConnection
59
+ FMOD.invoke(:DSPConnection_SetUserData, self, pointer)
60
+ when Geometry
61
+ FMOD.invoke(:Geometry_SetUserData, self, pointer)
62
+ when Reverb3D
63
+ FMOD.invoke(:Reverb3D_SetUserData, self, pointer)
64
+ when Sound
65
+ FMOD.invoke(:Sound_SetUserData, self, pointer)
66
+ when SoundGroup
67
+ FMOD.invoke(:SoundGroup_SetUserData, self, pointer)
68
+ when System
69
+ FMOD.invoke(:System_SetUserData, self, pointer)
70
+ end
71
+ pointer
72
+ end
73
+
74
+ def to_s
75
+ inspect # TODO
76
+ end
77
+
78
+ def int_ptr
79
+ "\0" * SIZEOF_INTPTR_T
80
+ end
81
+
82
+ private
83
+
84
+ def self.bool_reader(name, function)
85
+ self.send(:define_method, name) do
86
+ FMOD.invoke(function, self, buffer = "\0" * SIZEOF_INT)
87
+ buffer.unpack1('l') != 0
88
+ end
89
+ end
90
+
91
+ def self.bool_writer(name, function)
92
+ self.send(:define_method, name) do |bool|
93
+ FMOD.invoke(function, self, bool.to_i)
94
+ end
95
+ end
96
+
97
+ def self.integer_reader(name, function)
98
+ self.send(:define_method, name) do
99
+ FMOD.invoke(function, self, buffer = "\0" * SIZEOF_INT)
100
+ buffer.unpack1('l')
101
+ end
102
+ end
103
+
104
+ def self.integer_writer(name, function, min = nil, max = nil)
105
+ self.send(:define_method, name) do |int|
106
+ int = min if min && int < min
107
+ int = max if max && int > max
108
+ FMOD.invoke(function, self, int)
109
+ int
110
+ end
111
+ end
112
+
113
+ def self.float_reader(name, function)
114
+ self.send(:define_method, name) do
115
+ FMOD.invoke(function, self, buffer = "\0" * SIZEOF_FLOAT)
116
+ buffer.unpack1('f')
117
+ end
118
+ end
119
+
120
+ def self.float_writer(name, function, min = nil, max = nil)
121
+ self.send(:define_method, name) do |float|
122
+ float = min if min && float < min
123
+ float = max if max && float > max
124
+ FMOD.invoke(function, self, float)
125
+ float
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,98 @@
1
+
2
+ module FMOD
3
+
4
+ ##
5
+ # The 3D reverb object is a sphere having 3D attributes (position, minimum
6
+ # distance, maximum distance) and reverb properties.
7
+ #
8
+ # The properties and 3D attributes of all reverb objects collectively
9
+ # determine, along with the listener's position, the settings of and input
10
+ # gains into a single 3D reverb DSP.
11
+ #
12
+ # When the listener is within the sphere of effect of one or more 3D reverbs,
13
+ # the listener's 3D reverb properties are a weighted combination of such 3D
14
+ # reverbs. When the listener is outside all of the reverbs, the 3D reverb
15
+ # setting is set to the default ambient reverb setting.
16
+ class Reverb3D < Handle
17
+
18
+ ##
19
+ # @!attribute properties
20
+ # Gets or sets the reverb parameters for the current reverb object.
21
+ # @return [Reverb]
22
+
23
+ ##
24
+ # @!attribute position
25
+ # A {Vector} containing the 3D position of the center of the reverb in 3D
26
+ # space.
27
+ # * *Default:* {Vector.zero}
28
+ # @return [Vector]
29
+
30
+ ##
31
+ # @!attribute min_distance
32
+ # The distance from the center-point that the reverb will have full effect
33
+ # at.
34
+ # * *Default:* 0.0
35
+ # @return [Float]
36
+
37
+ ##
38
+ # @!attribute max_distance
39
+ # The distance from the center-point that the reverb will not have any
40
+ # effect.
41
+ # * *Default:* 0.0
42
+ # @return [Float]
43
+
44
+ ##
45
+ # @!attribute active
46
+ # Gets or sets the a state to disable or enable a reverb object so that it
47
+ # does or does not contribute to the 3D scene.
48
+ #
49
+ # @return [Boolean]
50
+ bool_reader(:active, :Reverb3D_GetActive)
51
+ bool_writer(:active=, :Reverb3D_SetActive)
52
+
53
+ def min_distance
54
+ buffer = "\0" * SIZEOF_FLOAT
55
+ FMOD.invoke(:Reverb3D_Get3DAttributes, self, nil, buffer, nil)
56
+ buffer.unpack1('f')
57
+ end
58
+
59
+ def min_distance=(distance)
60
+ FMOD.invoke(:Reverb3D_Set3DAttributes, self, position,
61
+ distance, max_distance )
62
+ end
63
+
64
+ def max_distance
65
+ buffer = "\0" * SIZEOF_FLOAT
66
+ FMOD.invoke(:Reverb3D_Get3DAttributes, self, nil, nil, buffer)
67
+ buffer.unpack1('f')
68
+ end
69
+
70
+ def max_distance=(distance)
71
+ FMOD.invoke(:Reverb3D_Set3DAttributes, self, position,
72
+ min_distance, distance )
73
+ end
74
+
75
+ def position
76
+ vector = Vector.zero
77
+ FMOD.invoke(:Reverb3D_Get3DAttributes, self, vector, nil, nil)
78
+ vector
79
+ end
80
+
81
+ def position=(vector)
82
+ FMOD.type?(vector, Vector)
83
+ FMOD.invoke(:Reverb3D_Set3DAttributes, self, vector,
84
+ min_distance, max_distance )
85
+ end
86
+
87
+ def properties
88
+ FMOD.invoke(:Reverb3D_GetProperties, self, reverb = Reverb.new)
89
+ reverb
90
+ end
91
+
92
+ def properties=(reverb)
93
+ FMOD.type?(reverb, Reverb)
94
+ FMOD.invoke(:Reverb3D_SetProperties, self, reverb)
95
+ reverb
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,810 @@
1
+
2
+ module FMOD
3
+ class Sound < Handle
4
+
5
+ ##
6
+ # Contains format information about the sound.
7
+ # @attr type [Integer] The type of sound.
8
+ # @see SoundType
9
+ # @attr format [Integer] The format of the sound.
10
+ # @see SoundFormat
11
+ # @attr channels [Integer] The number of channels for the sound.
12
+ # @attr bits [Integer] The number of bits per sample for the sound.
13
+ Format = Struct.new(:type, :format, :channels, :bits)
14
+
15
+ ##
16
+ # Contains the state a sound is in after {Mode::NON_BLOCKING} has been used
17
+ # to open it, or the state of the streaming buffer.
18
+ #
19
+ # When a sound is opened with {Mode::NON_BLOCKING}, it is opened and
20
+ # prepared in the background, or asynchronously.
21
+ # This allows the main application to execute without stalling on audio
22
+ # loads.
23
+ # This function will describe the state of the asynchronous load routine
24
+ # i.e. whether it has succeeded, failed or is still in progress.
25
+ #
26
+ # If {#starving?} is +true+, then you will most likely hear a
27
+ # stuttering/repeating sound as the decode buffer loops on itself and
28
+ # replays old data.
29
+ # You can detect buffer under-run and use something like
30
+ # {ChannelControl.mute} to keep it quiet until it is not starving any more.
31
+ class OpenState
32
+
33
+ private_class_method :new
34
+
35
+ ##
36
+ # Opened and ready to play.
37
+ READY = 0
38
+ ##
39
+ # Initial load in progress.
40
+ LOADING = 1
41
+ ##
42
+ # Failed to open - file not found, out of memory etc.
43
+ ERROR = 2
44
+ ##
45
+ # Connecting to remote host (internet sounds only).
46
+ CONNECTING = 3
47
+ ##
48
+ # Buffering data.
49
+ BUFFERING = 4
50
+ ##
51
+ # Seeking to subsound and re-flushing stream buffer.
52
+ SEEKING = 5
53
+ ##
54
+ # Ready and playing, but not possible to release at this time without
55
+ # stalling the main thread.
56
+ PLAYING = 6
57
+ ##
58
+ # Seeking within a stream to a different position.
59
+ SET_POSITION = 7
60
+
61
+ ##
62
+ # @return [Integer] the open state of a sound.
63
+ #
64
+ # Will be one of the following:
65
+ # * {READY}
66
+ # * {LOADING}
67
+ # * {ERROR}
68
+ # * {CONNECTING}
69
+ # * {BUFFERING}
70
+ # * {SEEKING}
71
+ # * {PLAYING}
72
+ # * {SET_POSITION}
73
+ attr_reader :state
74
+
75
+ ##
76
+ # @return [Float] the percentage of the file buffer filled progress of a
77
+ # stream.
78
+ attr_reader :buffered
79
+
80
+ ##
81
+ # The disk busy state of a sound.
82
+ # @return [Boolean] +true+ if disk is currently being accessed for the
83
+ # sound, otherwise +false+.
84
+ def busy?
85
+ @busy != 0
86
+ end
87
+
88
+ ##
89
+ # The starving state of a sound.
90
+ # @return [Boolean] +true+ f a stream has decoded more than the stream
91
+ # file buffer has ready for it, otherwise +false+.
92
+ def starving?
93
+ @starving != 0
94
+ end
95
+
96
+ # @api private
97
+ def initialize(state, buffered, busy, starving)
98
+ @state, @buffered, @busy, @starving = state, buffered, busy, starving
99
+ end
100
+ end
101
+
102
+ ##
103
+ # @!attribute mode
104
+ # Sets or alters the mode of a sound.
105
+ #
106
+ # When calling this function, note that it will only take effect when the
107
+ # sound is played again with {#play}. Consider this mode the "default mode"
108
+ # for when the sound plays, not a mode that will suddenly change all
109
+ # currently playing instances of this sound.
110
+ #
111
+ # Supported flags:
112
+ # * {Mode::LOOP_OFF}
113
+ # * {Mode::LOOP_NORMAL}
114
+ # * {Mode::LOOP_BIDI}
115
+ # * {Mode::TWO_D}
116
+ # * {Mode::THREE_D}
117
+ # * {Mode::HEAD_RELATIVE_3D}
118
+ # * {Mode::WORLD_RELATIVE_3D}
119
+ # * {Mode::INVERSE_ROLLOFF_3D}
120
+ # * {Mode::LINEAR_ROLLOFF_3D}
121
+ # * {Mode::LINEAR_SQUARE_ROLLOFF_3D}
122
+ # * {Mode::CUSTOM_ROLLOFF_3D}
123
+ # * {Mode::IGNORE_GEOMETRY_3D}
124
+ #
125
+ # @return [Integer]
126
+ integer_reader(:mode, :Sound_GetMode)
127
+ integer_writer(:mode=, :Sound_SetMode)
128
+
129
+ ##
130
+ # @!attribute loop_count
131
+ # Sets a sound, by default, to loop a specified number of times before
132
+ # stopping if its mode is set to {Mode::LOOP_NORMAL} or {Mode::LOOP_BIDI}.
133
+ # @return [Integer] the number of times to loop a sound before stopping.
134
+ integer_reader(:loop_count, :Sound_GetLoopCount)
135
+ integer_writer(:loop_count=, :Sound_SetLoopCount, -1)
136
+
137
+ ##
138
+ # @!attribute [r] subsound_count
139
+ # @return [Integer] the number of subsounds stored within a sound.
140
+ integer_reader(:subsound_count, :Sound_GetNumSubSounds)
141
+
142
+ ##
143
+ # @!attribute [r] syncpoint_count
144
+ # Retrieves the number of sync points stored within a sound. These points
145
+ # can be user generated or can come from a wav file with embedded markers.
146
+ # @return Retrieves the number of sync points stored within a sound.
147
+ integer_reader(:syncpoint_count, :Sound_GetNumSyncPoints)
148
+
149
+ ##
150
+ # @!attribute [r] name
151
+ # @return [String] the name of the sound.
152
+ def name
153
+ FMOD.invoke(:Sound_GetName, self, buffer = "\0" * 512, 512)
154
+ buffer.delete("\0")
155
+ end
156
+
157
+ ##
158
+ # @!attribute [r] tags
159
+ # @return [TagCollection] object containing the tags within the {Sound}.
160
+ def tags
161
+ TagCollection.send(:new, self)
162
+ end
163
+
164
+ ##
165
+ # @!attribute group
166
+ # @return [SoundGroup] the sound's current {SoundGroup}.
167
+ def group
168
+ FMOD.invoke(:Sound_GetSoundGroup, self, group = int_ptr)
169
+ SoundGroup.new(group)
170
+ end
171
+
172
+ def group=(group)
173
+ FMOD.type?(group, SoundGroup)
174
+ FMOD.invoke(:Sound_SetSoundGroup, self, group)
175
+ end
176
+
177
+ ##
178
+ # @!attribute [r] parent
179
+ # @return [System] the parent {System} object that was used to create this
180
+ # object.
181
+ def parent
182
+ FMOD.invoke(:Sound_GetSystemObject, self, system = int_ptr)
183
+ System.new(system)
184
+ end
185
+
186
+ ##
187
+ # Retrieves a handle to a {Sound} object that is contained within the parent
188
+ # sound.
189
+ # @param index [Integer] Index of the subsound to retrieve within this
190
+ # sound.
191
+ # @return [Sound, nil] the subsound, or +nil+
192
+ def subsound(index)
193
+ return nil unless index.between?(0, subsound_count - 1)
194
+ FMOD.invoke(:Sound_GetSubSound, self, index, sound = int_ptr)
195
+ Sound.new(sound)
196
+ end
197
+
198
+ ##
199
+ # @!attribute [r] subsounds
200
+ # @return [Array<Sound>] an array of the this sound's subsounds.
201
+ def subsounds
202
+ (0...subsound_count).map { |i| subsound(i) }
203
+ end
204
+
205
+ ##
206
+ # Enumerates each subsound within this {Sound}.
207
+ # @overload each_subsound
208
+ # When block is given, yields each subsound before returning self.
209
+ # @yield [sound] Yields a sound to the block.
210
+ # @yieldparam sound [Sound] The current sound being enumerated.
211
+ # @return [self]
212
+ # @overload each_subsound
213
+ # When no block is given, returns an enumerator for the subsounds.
214
+ # @return [Enumerator]
215
+ def each_subsound
216
+ return to_enum(:each_subsound) unless block_given?
217
+ (0...subsound_count).each { |i| yield subsound(i) }
218
+ self
219
+ end
220
+
221
+ ##
222
+ # @!attribute [r] parent_sound
223
+ # @return [Sound, nil] the parent {Sound} of this sound, or +nil+ if this
224
+ # sound is not a subsound.
225
+ def parent_sound
226
+ FMOD.invoke(:Sound_GetSubSoundParent, self, sound = "\0" * int_ptr)
227
+ address = sound.unpack1('J')
228
+ address.zero? ? nil : Sound.new(address)
229
+ end
230
+
231
+ ##
232
+ # Retrieve a handle to a sync point. These points can be user generated or
233
+ # can come from a wav file with embedded markers.
234
+ # @param index [Integer] Index of the sync point to retrieve.
235
+ # @return [Pointer] the sync point handle.
236
+ def syncpoint(index)
237
+ return nil unless index.between?(0, syncpoint_count - 1)
238
+ FMOD.invoke(:Sound_GetSyncPoint, self, index, sync = int_ptr)
239
+ Pointer.new(sync.unpack1('J'))
240
+ end
241
+
242
+ ##
243
+ # Adds a sync point at a specific time within the sound. These points can be
244
+ # user generated or can come from a wav file with embedded markers.
245
+ # @param name [String] A name character string to be stored with the sync
246
+ # point.
247
+ # @param offset [Integer] Offset to add the callback sync-point for a sound.
248
+ # @param unit [Integer] Offset type to describe the offset provided.
249
+ # @see TimeUnit
250
+ # @return [Pointer] The sync point handle.
251
+ def add_syncpoint(name, offset, unit = TimeUnit::MS)
252
+ sync = int_ptr
253
+ FMOD.invoke(:Sound_AddSyncPoint, self, offset, unit, name, sync)
254
+ Pointer.new(sync.unpack1('J'))
255
+ end
256
+
257
+ ##
258
+ # Deletes a syncpoint within the sound. These points can be user generated
259
+ # or can come from a wav file with embedded markers.
260
+ # @param sync_point [Pointer] A sync point handle.
261
+ # @return [void]
262
+ def delete_syncpoint(sync_point)
263
+ FMOD.type?(sync_point, Pointer)
264
+ FMOD.invoke(:Sound_DeleteSyncPoint, self, sync_point)
265
+ end
266
+
267
+ ##
268
+ # @!attribute [r] format
269
+ # @return [Format] the format information for the sound.
270
+ def format
271
+ arg = ["\0" * TYPE_INT, "\0" * TYPE_INT, "\0" * TYPE_INT, "\0" * TYPE_INT]
272
+ FMOD.invoke(:Sound_GetFormat, self, *arg)
273
+ arg.map! { |a| a.unpack1('l') }
274
+ Format.new(*arg)
275
+ end
276
+
277
+ # @!group Reading Sound Data
278
+
279
+ ##
280
+ # Returns a pointer to the beginning of the sample data for a sound.
281
+ #
282
+ # With this function you get access to the RAW audio data, for example 8,
283
+ # 16, 24 or 32-bit PCM data, mono or stereo data. You must take this into
284
+ # consideration when processing the data within the pointer.
285
+ #
286
+ # @overload lock(offset, length)
287
+ # If called with a block, yields the pointers to the first and second
288
+ # sections of locked data before unlocking and returning +self+.
289
+ # @yield [ptr1, ptr2] Yields two pointers to the block.
290
+ # @yieldparam ptr1 [Pointer] Pointer to the first part of the locked data,
291
+ # and its size set to the number of locked bytes.
292
+ # @yieldparam ptr2 [Pointer] The second pointer will point to the second
293
+ # part of the locked data. This will be {FMOD::NULL} if the data locked
294
+ # hasn't wrapped at the end of the buffer, and its size will be 0.
295
+ # @return [self]
296
+ # @overload lock(offset, length)
297
+ # If called without a block, returns the pointers in an array, and
298
+ # {#unlock} must be called.
299
+ # @return [Array(Pointer, Pointer)] An array containing two pointers.
300
+ #
301
+ # The first pointer will point to the first part of the locked data, and
302
+ # its size set to the number of locked bytes.
303
+ #
304
+ # The second pointer will point to the second part of the locked data.
305
+ # This will be {FMOD::NULL} if the data locked hasn't wrapped at the end
306
+ # of the buffer, and its size will be 0.
307
+ #
308
+ # @param offset [Integer] Offset in bytes to the position to lock in the
309
+ # sample buffer.
310
+ # @param length [Integer] Number of bytes you want to lock in the sample
311
+ # buffer.
312
+ #
313
+ # @see unlock
314
+ def lock(offset, length)
315
+ p1, p2, s1, s2 = int_ptr, int_ptr, "\0" * TYPE_INT, "\0" * TYPE_INT
316
+ FMOD.invoke(:Sound_Lock, self, offset, length, p1, p2, s1, s2)
317
+ ptr1 = Pointer.new(p1.unpack1('J'), s1.unpack1('L'))
318
+ ptr2 = Pointer.new(p2.unpack1('J'), s2.unpack1('L'))
319
+ if block_given?
320
+ yield ptr1, ptr2
321
+ FMOD.invoke(:Sound_Unlock, self, ptr1, ptr2, ptr1.size, ptr2.size)
322
+ return self
323
+ end
324
+ [ptr1, ptr2]
325
+ end
326
+
327
+ ##
328
+ # Releases previous sample data lock from {#lock}.
329
+ #
330
+ # @note This function should not be called if a block was passed to {#lock}.
331
+ #
332
+ # @param ptr1 [Pointer] Pointer to the first locked portion of sample data,
333
+ # from {#lock}.
334
+ # @param ptr2 [Pointer] Pointer to the second locked portion of sample data,
335
+ # from {#lock}.
336
+ # @see lock
337
+ # @return [void]
338
+ def unlock(ptr1, ptr2)
339
+ FMOD.invoke(:Sound_Unlock, self, ptr1, ptr2, ptr1.size, ptr2.size)
340
+ end
341
+
342
+ ##
343
+ # Reads data from an opened sound to a buffer, using the FMOD codec created
344
+ # internally, and returns it.
345
+ #
346
+ # This can be used for decoding data offline in small pieces (or big
347
+ # pieces), rather than playing and capturing it, or loading the whole file
348
+ # at once and having to {#lock} / {#unlock} the data. If too much data is
349
+ # read, it is possible an EOF error will be thrown, meaning it is out of
350
+ # data. The "read" parameter will reflect this by returning a smaller number
351
+ # of bytes read than was requested. To avoid an error, simply compare the
352
+ # size of the returned buffer against what was requested before calling
353
+ # again.
354
+ #
355
+ # As a sound already reads the whole file then closes it upon calling
356
+ # {System.create_sound} (unless {System.create_stream} or
357
+ # {Mode::CREATE_STREAM} is used), this function will not work because the
358
+ # file is no longer open.
359
+ #
360
+ # Note that opening a stream makes it read a chunk of data and this will
361
+ # advance the read cursor. You need to either use {Mode::OPEN_ONLY} to stop
362
+ # the stream pre-buffering or call {#seek_data} to reset the read cursor.
363
+ #
364
+ # If {Mode::OPEN_ONLY} flag is used when opening a sound, it will leave the
365
+ # file handle open, and FMOD will not read any data internally, so the read
366
+ # cursor will be at position 0. This will allow the user to read the data
367
+ # from the start.
368
+ #
369
+ # As noted previously, if a sound is opened as a stream and this function is
370
+ # called to read some data, then you will 'miss the start' of the sound.
371
+ #
372
+ # {Channel.position} will have the same result. These function will flush
373
+ # the stream buffer and read in a chunk of audio internally. This is why if
374
+ # you want to read from an absolute position you should use Sound::seekData
375
+ # and not the previously mentioned functions.
376
+ #
377
+ # Remember if you are calling readData and seekData on a stream it is up to
378
+ # you to cope with the side effects that may occur. Information functions
379
+ # such as {Channel.position} may give misleading results. Calling
380
+ # {Channel.position} will reset and flush the stream, leading to the time
381
+ # values returning to their correct position.
382
+ #
383
+ # @param size [Integer] The number of bytes to read into the buffer.
384
+ # @return [String] A binary string containing the buffer data. The data will
385
+ # be the size specified unless it has reached the end of the data, in
386
+ # which case it will be less, and trimmed to length.
387
+ # @see seek_data
388
+ def read_data(size)
389
+ buffer = "\0" * size
390
+ read = "\0" * SIZEOF_INT
391
+ FMOD.invoke(:Sound_ReadData, self, buffer, size, read)
392
+ read = read.unpack1('L')
393
+ read < size ? buffer.byteslice(0, read) : buffer
394
+ end
395
+
396
+ ##
397
+ # Seeks a sound for use with data reading.
398
+ #
399
+ # If a stream is opened and this function is called to read some data, then
400
+ # it will advance the internal file pointer, so data will be skipped if you
401
+ # play the stream. Also calling position / time information functions will
402
+ # lead to misleading results.
403
+ #
404
+ # A stream can be reset before playing by setting the position of the
405
+ # channel (ie using {Channel.position}), which will make it seek, reset and
406
+ # flush the stream buffer. This will make it sound correct again.
407
+ #
408
+ # Remember if you are calling {#read_data} and {#seek_data} on a stream it
409
+ # is up to you to cope with the side effects that may occur.
410
+ #
411
+ # @note This is not a function to "seek a sound" for normal use. This is for
412
+ # use in conjunction with {#read_data}.
413
+ # @param pcm [Integer] Offset to seek to in PCM samples.
414
+ # @return [void]
415
+ # @see read_data
416
+ def seek_data(pcm)
417
+ FMOD.invoke(:Sound_SeekData, self, pcm)
418
+ end
419
+
420
+ # @!endgroup
421
+
422
+ # @!group 3-D Sound
423
+
424
+ ##
425
+ # @!attribute cone_settings
426
+ # The angles that define the sound projection cone including the volume when
427
+ # outside the cone.
428
+ # @return [ConeSettings] the sound projection cone.
429
+ def cone_settings
430
+ args = ["\0" * SIZEOF_FLOAT, "\0" * SIZEOF_FLOAT, "\0" * SIZEOF_FLOAT]
431
+ FMOD.invoke(:Sound_Get3DConeSettings, self, *args)
432
+ ConeSettings.new(*args.map { |arg| arg.unpack1('f') } )
433
+ end
434
+
435
+ def cone_settings=(settings)
436
+ FMOD.type?(settings, ConeSettings)
437
+ set_cone(*settings.values)
438
+ settings
439
+ end
440
+
441
+ ##
442
+ # Sets the angles that define the sound projection cone including the volume
443
+ # when outside the cone.
444
+ # @param inside_angle [Float] Inside cone angle, in degrees. This is the
445
+ # angle within which the sound is at its normal volume.
446
+ # @param outside_angle [Float] Outside cone angle, in degrees. This is the
447
+ # angle outside of which the sound is at its outside volume.
448
+ # @param outside_volume [Float] Cone outside volume.
449
+ # @return [void]
450
+ def set_cone(inside_angle, outside_angle, outside_volume)
451
+ if outside_angle < inside_angle
452
+ raise Error, 'Outside angle must be greater than inside angle.'
453
+ end
454
+ FMOD.invoke(:Sound_Set3DConeSettings, self, inside_angle,
455
+ outside_angle, outside_volume)
456
+ self
457
+ end
458
+
459
+ ##
460
+ # @!attribute custom_rolloff
461
+ # A custom rolloff curve to define how audio will attenuate over distance.
462
+ #
463
+ # Must be used in conjunction with {Mode::CUSTOM_ROLLOFF_3D} flag to be
464
+ # activated.
465
+ #
466
+ # <b>Points must be sorted by distance! Passing an unsorted list to FMOD
467
+ # will result in an error.</b>
468
+ # @return [Array<Vector>] the rolloff curve.
469
+
470
+ def custom_rolloff
471
+ count = "\0" * SIZEOF_INT
472
+ FMOD.invoke(:Sound_Get3DCustomRolloff, self, nil, count)
473
+ count = count.unpack1('l')
474
+ return [] if count.zero?
475
+ size = SIZEOF_FLOAT * 3
476
+ FMOD.invoke(:Sound_Get3DCustomRolloff, self, ptr = int_ptr, nil)
477
+ buffer = Pointer.new(ptr.unpack1('J'), count * size).to_str
478
+ (0...count).map { |i| Vector.new(*buffer[i * size, size].unpack('fff')) }
479
+ end
480
+
481
+ def custom_rolloff=(rolloff)
482
+ FMOD.type?(rolloff, Array)
483
+ vectors = rolloff.map { |vector| vector.to_str }.join
484
+ FMOD.invoke(:Sound_Set3DCustomRolloff, self, vectors, rolloff.size)
485
+ rolloff
486
+ end
487
+
488
+ ##
489
+ # @!attribute min_distance
490
+ # @return [Float] Minimum volume distance in "units". (Default: 1.0)
491
+ # @see max_distance
492
+ # @see min_max_distance
493
+
494
+ def min_distance
495
+ min = "\0" * SIZEOF_FLOAT
496
+ FMOD.invoke(:Sound_Get3DMinMaxDistance, self, min, nil)
497
+ min.unpack1('f')
498
+ end
499
+
500
+ def min_distance=(distance)
501
+ min_max_distance(distance, max_distance)
502
+ end
503
+
504
+ ##
505
+ # @!attribute max_distance
506
+ # @return [Float] Maximum volume distance in "units". (Default: 10000.0)
507
+ # @see min_distance
508
+ # @see in_max_distance
509
+
510
+ def max_distance
511
+ max = "\0" * SIZEOF_FLOAT
512
+ FMOD.invoke(:Sound_Get3DMinMaxDistance, self, nil, max)
513
+ max.unpack1('f')
514
+ end
515
+
516
+ def max_distance=(distance)
517
+ min_max_distance(min_distance, distance)
518
+ end
519
+
520
+ ##
521
+ # Sets the minimum and maximum audible distance.
522
+ #
523
+ # When the listener is in-between the minimum distance and the sound source
524
+ # the volume will be at its maximum. As the listener moves from the minimum
525
+ # distance to the maximum distance the sound will attenuate following the
526
+ # rolloff curve set. When outside the maximum distance the sound will no
527
+ # longer attenuate.
528
+ #
529
+ # Minimum distance is useful to give the impression that the sound is loud
530
+ # or soft in 3D space. An example of this is a small quiet object, such as a
531
+ # bumblebee, which you could set a small minimum distance such as 0.1. This
532
+ # would cause it to attenuate quickly and disappear when only a few meters
533
+ # away from the listener. Another example is a jumbo jet, which you could
534
+ # set to a minimum distance of 100.0 causing the volume to stay at its
535
+ # loudest until the listener was 100 meters away, then it would be hundreds
536
+ # of meters more before it would fade out.
537
+ #
538
+ # Maximum distance is effectively obsolete unless you need the sound to stop
539
+ # fading out at a certain point. Do not adjust this from the default if you
540
+ # dont need to. Some people have the confusion that maximum distance is the
541
+ # point the sound will fade out to zero, this is not the case.
542
+ #
543
+ # @param min [Float] Minimum volume distance in "units".
544
+ # * *Default:* 1.0
545
+ # @param max [Float] Maximum volume distance in "units".
546
+ # * *Default:* 10000.0
547
+ #
548
+ # @see min_distance
549
+ # @see max_distance
550
+ # @return [void]
551
+ def min_max_distance(min, max)
552
+ FMOD.invoke(:Sound_Set3DMinMaxDistance, self, min, max)
553
+ end
554
+
555
+ # @!endgroup
556
+
557
+ ##
558
+ # @!attribute default_frequency
559
+ # The sounds's default frequency, so when it is played it uses this value
560
+ # without having to specify them later for each channel each time the sound
561
+ # is played.
562
+ # @return [Float] the default playback frequency, in hz. (ie 44100hz).
563
+
564
+ def default_frequency
565
+ value = "\0" * SIZEOF_FLOAT
566
+ FMOD.invoke(:Sound_GetDefaults, self, value, nil)
567
+ value.unpack1('f')
568
+ end
569
+
570
+ def default_frequency=(frequency)
571
+ FMOD.invoke(:Sound_SetDefaults, self, frequency, default_priority)
572
+ end
573
+
574
+ ##
575
+ # @!attribute default_priority
576
+ # The sounds's default priority, so when it is played it uses this value
577
+ # without having to specify them later for each channel each time the sound
578
+ # is played.
579
+ # @return [Integer] the default priority when played on a channel.
580
+ # * *Minimum:* 0 (most important)
581
+ # * *Maximum:* 256 (least important)
582
+ # * Default:* 128
583
+
584
+ def default_priority
585
+ value = "\0" * SIZEOF_INT
586
+ FMOD.invoke(:Sound_GetDefaults, self, nil, value)
587
+ value.unpack1('l')
588
+ end
589
+
590
+ def default_priority=(priority)
591
+ priority = priority.clamp(0, 256)
592
+ FMOD.invoke(:Sound_SetDefaults, self, default_frequency, priority)
593
+ end
594
+
595
+ ##
596
+ # Retrieves the length of the sound using the specified time unit.
597
+ # @param unit [Integer] Time unit retrieve into the length parameter.
598
+ # @see TimeUnit
599
+ # @return [Integer] the length in the requested units.
600
+ def length(unit = TimeUnit::MS)
601
+ value = "\0" * SIZEOF_INT
602
+ FMOD.invoke(:Sound_GetLength, self, value, unit)
603
+ value.unpack1('L')
604
+ end
605
+
606
+ ##
607
+ # Retrieves the loop points for a sound.
608
+ # @param start_unit [Integer] The time format used for the returned loop
609
+ # start point.
610
+ # @see TimeUnit
611
+ # @param end_unit [Integer] The time format used for the returned loop end
612
+ # point.
613
+ # @see TimeUnit
614
+ # @return [Array(Integer, Integer)] the loop points in an array where the
615
+ # first element is the start loop point, and second element is the end
616
+ # loop point in the requested time units.
617
+ def loop_points(start_unit = TimeUnit::MS, end_unit = TimeUnit::MS)
618
+ loop_start, loop_end = "\0" * SIZEOF_INT, "\0" * SIZEOF_INT
619
+ FMOD.invoke(:Sound_GetLoopPoints, self, loop_start,
620
+ start_unit, loop_end, end_unit)
621
+ [loop_start.unpack1('L'), loop_end.unpack1('L')]
622
+ end
623
+
624
+ ##
625
+ # Sets the loop points within a sound
626
+ #
627
+ # If a sound was 44100 samples long and you wanted to loop the whole sound,
628
+ # _loop_start_ would be 0, and _loop_end_ would be 44099, not 44100. You
629
+ # wouldn't use milliseconds in this case because they are not sample
630
+ # accurate.
631
+ #
632
+ # If loop end is smaller or equal to loop start, it will result in an error.
633
+ #
634
+ # If loop start or loop end is larger than the length of the sound, it will
635
+ # result in an error
636
+ #
637
+ # @param loop_start [Integer] The loop start point. This point in time is
638
+ # played, so it is inclusive.
639
+ # @param loop_end [Integer] The loop end point. This point in time is
640
+ # played, so it is inclusive
641
+ # @param start_unit [Integer] The time format used for the loop start point.
642
+ # @see TimeUnit
643
+ # @param end_unit [Integer] The time format used for the loop end point.
644
+ # @see TimeUnit
645
+ #
646
+ # @return [void]
647
+ def set_loop(loop_start, loop_end, start_unit = TimeUnit::MS, end_unit = TimeUnit::MS)
648
+ FMOD.invoke(:Sound_SetLoopPoints, self, loop_start,
649
+ start_unit, loop_end, end_unit)
650
+ end
651
+
652
+ # @!group MOD/S3M/XM/IT/MIDI
653
+
654
+ ##
655
+ # @!attribute music_speed
656
+ # The relative speed of MOD/S3M/XM/IT/MIDI music.
657
+ # * *Minimum:* 0.01
658
+ # * *Maximum:* 100.0
659
+ # * *Default:* 1.0
660
+ # 0.5 is half speed, 2.0 is double speed, etc.
661
+ # @return [Float] the relative speed of the song.
662
+ float_reader(:music_speed, :Sound_GetMusicSpeed)
663
+ float_writer(:music_speed=, :Sound_SetMusicSpeed)
664
+
665
+ ##
666
+ # Retrieves the volume of a MOD/S3M/XM/IT/MIDI music channel volume.
667
+ # @param channel [Integer] MOD/S3M/XM/IT/MIDI music sub-channel to retrieve
668
+ # the volume for.
669
+ # @return [Float] the volume of the channel from 0.0 to 1.0.
670
+ # * *Default:* 1.0
671
+ # @see set_music_volume
672
+ def music_volume(channel)
673
+ volume = "\0" * SIZEOF_FLOAT
674
+ FMOD.invoke(:Sound_GetMusicChannelVolume, self, channel, volume)
675
+ volume.unpack1('f')
676
+ end
677
+
678
+ ##
679
+ # Sets the volume of a MOD/S3M/XM/IT/MIDI music channel volume.
680
+ # @param channel [Integer] MOD/S3M/XM/IT/MIDI music sub-channel to set a
681
+ # linear volume for.
682
+ # @param volume [Float] Volume of the channel.
683
+ # * *Minimum:* 0.0
684
+ # * *Maximum:* 1.0
685
+ # * *Default:* 1.0
686
+ # @return [void]
687
+ def set_music_volume(channel, volume)
688
+ volume = volume.clamp(0.0, 1.0)
689
+ FMOD.invoke(:Sound_SetMusicChannelVolume, self, channel, volume)
690
+ end
691
+
692
+ ##
693
+ # @!attribute [r] music_channels
694
+ # @return [Integer] the number of channels inside a MOD/S3M/XM/IT/MIDI file.
695
+ integer_reader(:music_channels, :Sound_GetMusicNumChannels)
696
+
697
+ # @!endgroup
698
+
699
+ ##
700
+ # Retrieves the state a sound is in after {Mode::NON_BLOCKING} has been used
701
+ # to open it, or the state of the streaming buffer.
702
+ #
703
+ # @return [OpenState] the current state of the sound.
704
+ def open_state
705
+ args = ["\0" * SIZEOF_INT, "\0" * SIZEOF_INT, "\0" * SIZEOF_INT, "\0" * SIZEOF_INT]
706
+ FMOD.invoke(:Sound_GetOpenState, self, *args)
707
+ args = args.join.unpack('lLll')
708
+ OpenState.send(:new, *args)
709
+ end
710
+
711
+ ##
712
+ # Retrieves information on an embedded sync point. These points can be user
713
+ # generated or can come from a wav file with embedded markers.
714
+ # @param syncpoint [Pointer] A handle to a sync-point.
715
+ # @param time_unit [Integer] A {TimeUnit} parameter to determine a desired
716
+ # format for the offset parameter.
717
+ # @return [Array(String, Integer)] array containing the name of the
718
+ # sync-point and the offset in the requested time unit.
719
+ # @see TimeUnit
720
+ def syncpoint_info(syncpoint, time_unit = TimeUnit::MS)
721
+ name, offset = "\0" * 256, "\0" * SIZEOF_INT
722
+ FMOD.invoke(:Sound_GetSyncPointInfo, self, syncpoint,
723
+ name, 256, offset, time_unit)
724
+ [name.delete("\0"), offset.unpack1('L')]
725
+ end
726
+
727
+ ##
728
+ # Plays a sound object on a particular channel and {ChannelGroup}.
729
+ #
730
+ # When a sound is played, it will use the sound's default frequency and
731
+ # priority.
732
+ #
733
+ # A sound defined as {Mode::THREE_D} will by default play at the position of
734
+ # the listener.
735
+ #
736
+ # Channels are reference counted. If a channel is stolen by the FMOD
737
+ # priority system, then the handle to the stolen voice becomes invalid, and
738
+ # Channel based commands will not affect the new sound playing in its place.
739
+ # If all channels are currently full playing a sound, FMOD will steal a
740
+ # channel with the lowest priority sound. If more channels are playing than
741
+ # are currently available on the sound-card/sound device or software mixer,
742
+ # then FMOD will "virtualize" the channel. This type of channel is not
743
+ # heard, but it is updated as if it was playing. When its priority becomes
744
+ # high enough or another sound stops that was using a real hardware/software
745
+ # channel, it will start playing from where it should be. This technique
746
+ # saves CPU time (thousands of sounds can be played at once without actually
747
+ # being mixed or taking up resources), and also removes the need for the
748
+ # user to manage voices themselves. An example of virtual channel usage is a
749
+ # dungeon with 100 torches burning, all with a looping crackling sound, but
750
+ # with a sound-card that only supports 32 hardware voices. If the 3D
751
+ # positions and priorities for each torch are set correctly, FMOD will play
752
+ # all 100 sounds without any "out of channels" errors, and swap the real
753
+ # voices in and out according to which torches are closest in 3D space.
754
+ # Priority for virtual channels can be changed in the sound's defaults, or
755
+ # at runtime with {Channel.priority}.
756
+ #
757
+ # @param group [ChannelGroup] The {ChannelGroup} become a member of. This is
758
+ # more efficient than later setting with {Channel.group}, as it does it
759
+ # during the channel setup, rather than connecting to the master channel
760
+ # group, then later disconnecting and connecting to the new {ChannelGroup}
761
+ # when specified. Specify +nil+ to ignore (use master {ChannelGroup}).
762
+ # @return [Channel] the newly playing channel.
763
+ def play(group = nil)
764
+ parent.play_sound(self, group, false)
765
+ end
766
+
767
+
768
+ class TagCollection
769
+
770
+ include Enumerable
771
+
772
+ private_class_method :new
773
+
774
+ def initialize(sound)
775
+ @sound = sound
776
+ end
777
+
778
+ def each
779
+ return to_enum(:each) unless block_given?
780
+ (0...count).each { |i| yield self[i] }
781
+ self
782
+ end
783
+
784
+ def count
785
+ buffer = "\0" * Fiddle::SIZEOF_INT
786
+ FMOD.invoke(:Sound_GetNumTags, @sound, buffer, nil)
787
+ buffer.unpack1('l')
788
+ end
789
+
790
+ alias_method :size, :count
791
+
792
+ def updated_count
793
+ buffer = "\0" * Fiddle::SIZEOF_INT
794
+ FMOD.invoke(:Sound_GetNumTags, @sound, nil, buffer)
795
+ buffer.unpack1('l')
796
+ end
797
+
798
+ def [](index)
799
+ tag = FMOD::Core::Tag.new
800
+ if index.is_a?(Integer)
801
+ return nil unless index.between?(0, count - 1)
802
+ FMOD.invoke(:Sound_GetTag, @sound, nil, index, tag)
803
+ elsif tag.is_a?(String)
804
+ FMOD.invoke(:Sound_GetTag, @sound, index, 0, tag)
805
+ end
806
+ tag
807
+ end
808
+ end
809
+ end
810
+ end