fmod 0.9.0

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