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,151 @@
1
+
2
+ module FMOD
3
+ class Channel < ChannelControl
4
+
5
+ ##
6
+ # @!attribute frequency
7
+ # This value can also be negative to play the sound backwards (negative
8
+ # frequencies allowed with non-stream sounds only).
9
+ #
10
+ # When a sound is played, it plays at the default frequency of the sound
11
+ # which can be set by {Sound.default_frequency}.
12
+ #
13
+ # For most file formats, the default frequency is determined by the audio
14
+ # format.
15
+ #
16
+ # @return [Float] the channel frequency or playback rate, in Hz.
17
+ float_reader(:frequency, :Channel_GetFrequency)
18
+ float_writer(:frequency=, :Channel_SetFrequency)
19
+
20
+ ##
21
+ # @!attribute [r] index
22
+ # @return [Integer] the internal channel index for a channel.
23
+ integer_reader(:index, :Channel_GetIndex)
24
+
25
+ ##
26
+ # @!attribute priority
27
+ # The channel priority.
28
+ #
29
+ # When more channels than available are played the virtual channel system
30
+ # will choose existing channels to steal. Lower priority sounds will always
31
+ # be stolen before higher priority sounds. For channels of equal priority,
32
+ # that with the quietest {ChannelControl.audibility} value will be stolen.
33
+ # * *Minimum:* 0
34
+ # * *Maximum:* 256
35
+ # * *Default:* 128
36
+ # @return [Integer] the current priority.
37
+ integer_reader(:priority, :Channel_GetPriority)
38
+ integer_writer(:priority=, :Channel_SetPriority, 0, 256)
39
+
40
+ ##
41
+ # @!attribute loop_count
42
+ # Sets a sound, by default, to loop a specified number of times before
43
+ # stopping if its mode is set to {Mode::LOOP_NORMAL} or {Mode::LOOP_BIDI}.
44
+ # @return [Integer] the number of times to loop a sound before stopping.
45
+ integer_reader(:loop_count, :Channel_GetLoopCount)
46
+ integer_writer(:loop_count=, :Channel_SetLoopCount, -1)
47
+
48
+ ##
49
+ # @!attribute [r] current_sound
50
+ # @return [Sound, nil] the currently playing sound for this channel, or
51
+ # +nil+ if no sound is playing.
52
+ def current_sound
53
+ FMOD.invoke(:Channel_GetCurrentSound, self, sound = int_ptr)
54
+ sound.unpack1('J').zero? ? nil : Sound.new(sound)
55
+ end
56
+
57
+ ##
58
+ # @!method virtual?
59
+ # Retrieves whether the channel is virtual (emulated) or not due to the
60
+ # virtual channel management system
61
+ # @return [Boolean] the current virtual state.
62
+ # * *true:* Inaudible and currently being emulated at no CPU cost
63
+ # * *false:* Real voice that should be audible.
64
+ bool_reader(:virtual?, :Channel_IsVirtual)
65
+
66
+ ##
67
+ # Returns the current playback position.
68
+ # @param unit [Integer] Time unit to retrieve into the position in.
69
+ # @see TimeUnit
70
+ # @return [Integer] the current playback position.
71
+ def position(unit = TimeUnit::MS)
72
+ buffer = "\0" * SIZEOF_INT
73
+ FMOD.invoke(:Channel_SetPosition, self, buffer, unit)
74
+ buffer.unpack1('L')
75
+ end
76
+
77
+ ##
78
+ # Sets the playback position for the currently playing sound to the
79
+ # specified offset.
80
+ # @param position [Integer] Position of the channel to set in specified
81
+ # units.
82
+ # @param unit [Integer] Time unit to set the channel position by.
83
+ # @see TimeUnit
84
+ # @return [self]
85
+ def seek(position, unit = TimeUnit::MS)
86
+ position = 0 if position < 0
87
+ FMOD.invoke(:Channel_SetPosition, self, position, unit)
88
+ self
89
+ end
90
+
91
+ ##
92
+ # @!attribute group
93
+ # @return [ChannelGroup] the currently assigned channel group for this
94
+ # {Channel}.
95
+
96
+ def group
97
+ FMOD.invoke(:Channel_GetChannelGroup, self, group = int_ptr)
98
+ ChannelGroup.new(group)
99
+ end
100
+
101
+ def group=(channel_group)
102
+ FMOD.type?(channel_group, ChannelGroup)
103
+ FMOD.invoke(:Channel_SetChannelGroup, self, channel_group)
104
+ end
105
+
106
+ ##
107
+ # Retrieves the loop points for a sound.
108
+ # @param start_unit [Integer] The time format used for the returned loop
109
+ # start point.
110
+ # @see TimeUnit
111
+ # @param end_unit [Integer] The time format used for the returned loop end
112
+ # point.
113
+ # @see TimeUnit
114
+ # @return [Array(Integer, Integer)] the loop points in an array where the
115
+ # first element is the start loop point, and second element is the end
116
+ # loop point in the requested time units.
117
+ def loop_points(start_unit = TimeUnit::MS, end_unit = TimeUnit::MS)
118
+ loop_start, loop_end = "\0" * SIZEOF_INT, "\0" * SIZEOF_INT
119
+ FMOD.invoke(:Channel_GetLoopPoints, self, loop_start,
120
+ start_unit, loop_end, end_unit)
121
+ [loop_start.unpack1('L'), loop_end.unpack1('L')]
122
+ end
123
+
124
+ ##
125
+ # Sets the loop points within a sound
126
+ #
127
+ # If a sound was 44100 samples long and you wanted to loop the whole sound,
128
+ # _loop_start_ would be 0, and _loop_end_ would be 44099, not 44100. You
129
+ # wouldn't use milliseconds in this case because they are not sample
130
+ # accurate.
131
+ #
132
+ # If loop end is smaller or equal to loop start, it will result in an error.
133
+ #
134
+ # If loop start or loop end is larger than the length of the sound, it will
135
+ # result in an error
136
+ #
137
+ # @param loop_start [Integer] The loop start point. This point in time is
138
+ # played, so it is inclusive.
139
+ # @param loop_end [Integer] The loop end point. This point in time is
140
+ # played, so it is inclusive
141
+ # @param start_unit [Integer] The time format used for the loop start point.
142
+ # @see TimeUnit
143
+ # @param end_unit [Integer] The time format used for the loop end point.
144
+ # @see TimeUnit
145
+ def set_loop(loop_start, loop_end, start_unit = TimeUnit::MS, end_unit = TimeUnit::MS)
146
+ FMOD.invoke(:Channel_SetLoopPoints, self, loop_start,
147
+ start_unit, loop_end, end_unit)
148
+ self
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,821 @@
1
+
2
+ module FMOD
3
+ class ChannelControl < Handle
4
+
5
+ ChannelDelay = Struct.new(:start, :end, :stop)
6
+
7
+ DistanceFilter = Struct.new(:custom, :level, :frequency)
8
+
9
+ include Fiddle
10
+ include FMOD::Core
11
+
12
+ ##
13
+ # @!attribute volume
14
+ # Gets or sets the linear volume level.
15
+ #
16
+ # Volume level can be below 0.0 to invert a signal and above 1.0 to
17
+ # amplify the signal. Note that increasing the signal level too far may
18
+ # cause audible distortion.
19
+ #
20
+ # @return [Float]
21
+ float_reader(:volume, :ChannelGroup_GetVolume)
22
+ float_writer(:volume=, :ChannelGroup_SetVolume)
23
+
24
+ ##
25
+ # @!attribute volume_ramp
26
+ # Gets or sets flag indicating whether the channel automatically ramps
27
+ # when setting volumes.
28
+ #
29
+ # When changing volumes on a non-paused channel, FMOD normally adds a
30
+ # small ramp to avoid a pop sound. This function allows that setting to be
31
+ # overridden and volume changes to be applied immediately.
32
+ #
33
+ # @return [Boolean]
34
+ bool_reader(:volume_ramp, :ChannelGroup_GetVolumeRamp)
35
+ bool_writer(:volume_ramp=, :ChannelGroup_SetVolumeRamp)
36
+
37
+ ##
38
+ # @!attribute pitch
39
+ # Sets the pitch value.
40
+ #
41
+ # This function scales existing frequency values by the pitch.
42
+ # * *0.5:* One octave lower
43
+ # * *2.0:* One octave higher
44
+ # * *1.0:* Normal pitch
45
+ # @return [Float]
46
+ float_reader(:pitch, :ChannelGroup_GetPitch)
47
+ float_writer(:pitch=, :ChannelGroup_SetPitch)
48
+
49
+ ##
50
+ # @!attribute mode
51
+ # Changes some attributes for a {ChannelControl} based on the mode passed in.
52
+ #
53
+ # Supported flags:
54
+ # * {Mode::LOOP_OFF}
55
+ # * {Mode::LOOP_NORMAL}
56
+ # * {Mode::LOOP_BIDI}
57
+ # * {Mode::TWO_D}
58
+ # * {Mode::THREE_D}
59
+ # * {Mode::HEAD_RELATIVE_3D}
60
+ # * {Mode::WORLD_RELATIVE_3D}
61
+ # * {Mode::INVERSE_ROLLOFF_3D}
62
+ # * {Mode::LINEAR_ROLLOFF_3D}
63
+ # * {Mode::LINEAR_SQUARE_ROLLOFF_3D}
64
+ # * {Mode::CUSTOM_ROLLOFF_3D}
65
+ # * {Mode::IGNORE_GEOMETRY_3D}
66
+ # * {Mode::VIRTUAL_PLAY_FROM_START}
67
+ #
68
+ # When changing the loop mode, sounds created with {Mode::CREATE_STREAM}
69
+ # may have already been pre-buffered and executed their loop logic ahead
70
+ # of time before this call was even made. This is dependant on the size of
71
+ # the sound versus the size of the stream decode buffer. If this happens,
72
+ # you may need to re-flush the stream buffer by calling {Channel.seek}.
73
+ # Note this will usually only happen if you have sounds or loop points
74
+ # that are smaller than the stream decode buffer size.
75
+ #
76
+ # @return [Integer] Mode bits.
77
+ integer_reader(:mode, :ChannelGroup_GetMode)
78
+ integer_writer(:mode=, :ChannelGroup_SetMode)
79
+
80
+ ##
81
+ # @!method playing?
82
+ # @return [Boolean] the playing state.
83
+ bool_reader(:playing?, :ChannelGroup_IsPlaying)
84
+
85
+ ##
86
+ # @!method paused?
87
+ # @return [Boolean] the paused state.
88
+ bool_reader(:paused?, :ChannelGroup_GetPaused)
89
+
90
+ ##
91
+ # @!method muted?
92
+ # @return [Boolean] the mute state.
93
+ bool_reader(:muted?, :ChannelGroup_GetMute)
94
+
95
+ ##
96
+ # @!attribute [r] audibility
97
+ # The combined volume after 3D spatialization and geometry occlusion
98
+ # calculations including any volumes set via the API.
99
+ #
100
+ # This does not represent the waveform, just the calculated result of all
101
+ # volume modifiers. This value is used by the virtual channel system to
102
+ # order its channels between real and virtual.
103
+ #
104
+ # @return [Float] the combined volume after 3D spatialization and geometry
105
+ # occlusion.
106
+ float_reader(:audibility, :ChannelGroup_GetAudibility)
107
+
108
+ float_reader(:low_pass_gain, :ChannelGroup_GetLowPassGain)
109
+ float_writer(:low_pass_gain=, :ChannelGroup_SetLowPassGain, 0.0, 1.0)
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+ float_reader(:level3D, :ChannelGroup_Get3DLevel)
144
+ float_writer(:level3D=, :ChannelGroup_Set3DLevel, 0.0, 1.0)
145
+
146
+ float_reader(:spread3D, :ChannelGroup_Get3DSpread)
147
+ float_writer(:spread3D, :ChannelGroup_Set3DSpread, 0.0, 360.0)
148
+
149
+ float_reader(:doppler3D, :ChannelGroup_Get3DDopplerLevel)
150
+ float_writer(:doppler3D=, :ChannelGroup_Set3DDopplerLevel, 0.0, 5.0)
151
+
152
+ def direct_occlusion
153
+ direct = "\0" * SIZEOF_FLOAT
154
+ FMOD.invoke(:ChannelGroup_Get3DOcclusion, self, direct, nil)
155
+ direct.unpack1('f')
156
+ end
157
+
158
+ def direct_occlusion=(direct)
159
+ direct = direct.clamp(0.0, 1.0)
160
+ reverb = reverb_occlusion
161
+ FMOD.invoke(:ChannelGroup_Set3DOcclusion, self, direct, reverb)
162
+ direct
163
+ end
164
+
165
+ def reverb_occlusion
166
+ reverb = "\0" * SIZEOF_FLOAT
167
+ FMOD.invoke(:ChannelGroup_Get3DOcclusion, self, nil, reverb)
168
+ reverb.unpack1('f')
169
+ end
170
+
171
+ def reverb_occlusion=(reverb)
172
+ direct = direct_occlusion
173
+ reverb = reverb.clamp(0.0, 1.0)
174
+ FMOD.invoke(:ChannelGroup_Set3DOcclusion, self, direct, reverb)
175
+ reverb
176
+ end
177
+
178
+ ##
179
+ # Add a volume point to fade from or towards, using a clock offset and 0.0
180
+ # to 1.0 volume level.
181
+ # @overload add_fade(fade_point)
182
+ # @param fade_point [FadePoint] Fade point structure defining the values.
183
+ # @overload add_fade(clock, volume)
184
+ # @param clock [Integer] DSP clock of the parent channel group to set the
185
+ # fade point volume.
186
+ # @param volume [Float] Volume level where 0.0 is silent and 1.0 is normal
187
+ # volume. Amplification is supported.
188
+ # @return [self]
189
+ def add_fade(*args)
190
+ args = args[0].values if args.size == 1 && args[0].is_a?(FadePoint)
191
+ FMOD.invoke(:ChannelGroup_AddFadePoint, self, *args)
192
+ self
193
+ end
194
+
195
+ ##
196
+ # Retrieves the number of fade points set within the {ChannelControl}.
197
+ # @return [Integer] The number of fade points.
198
+ def fade_point_count
199
+ count = "\0" * SIZEOF_INT
200
+ FMOD.invoke(:ChannelGroup_GetFadePoints, self, count, nil, nil)
201
+ count.unpack1('l')
202
+ end
203
+
204
+ ##
205
+ # Retrieve information about fade points stored within a {ChannelControl}.
206
+ # @return [Array<FadePoint>] An array of {FadePoint} objects, or an empty
207
+ # array if no fade points are present.
208
+ def fade_points
209
+ count = fade_point_count
210
+ return [] if count.zero?
211
+ clocks = "\0" * (count * SIZEOF_LONG_LONG)
212
+ volumes = "\0" * (count * SIZEOF_FLOAT)
213
+ FMOD.invoke(:ChannelGroup_GetFadePoints, self, int_ptr, clocks, volumes)
214
+ args = clocks.unpack('Q*').zip(volumes.unpack('f*'))
215
+ args.map { |values| FadePoint.new(*values) }
216
+ end
217
+
218
+ ##
219
+ # Remove volume fade points on the time-line. This function will remove
220
+ # multiple fade points with a single call if the points lay between the 2
221
+ # specified clock values (inclusive).
222
+ # @param clock_start [Integer] DSP clock of the parent channel group to
223
+ # start removing fade points from.
224
+ # @param clock_end [Integer] DSP clock of the parent channel group to start
225
+ # removing fade points to.
226
+ # @return [self]
227
+ def remove_fade_points(clock_start, clock_end)
228
+ FMOD.invoke(:ChannelGroup_RemoveFadePoints, self, clock_start, clock_end)
229
+ self
230
+ end
231
+
232
+ def position3D
233
+ position = Vector.zero
234
+ FMOD.invoke(:ChannelGroup_Get3DAttributes, self, position, nil, nil)
235
+ position
236
+ end
237
+
238
+ def position3D=(vector)
239
+ FMOD.type?(vector, Vector)
240
+ FMOD.invoke(:ChannelGroup_Set3DAttributes, self, vector, nil, nil)
241
+ vector
242
+ end
243
+
244
+ def velocity3D
245
+ velocity = Vector.zero
246
+ FMOD.invoke(:ChannelGroup_Get3DAttributes, self, nil, velocity, nil)
247
+ velocity
248
+ end
249
+
250
+ def velocity3D=(vector)
251
+ FMOD.type?(vector, Vector)
252
+ FMOD.invoke(:ChannelGroup_Set3DAttributes, self, nil, vector, nil)
253
+ vector
254
+ end
255
+
256
+ def distance_filter
257
+ args = ["\0" * SIZEOF_INT, "\0" * SIZEOF_FLOAT, "\0" * SIZEOF_FLOAT]
258
+ FMOD.invoke(:ChannelGroup_Get3DDistanceFilter, self, *args)
259
+ args = args.join.unpack('lff')
260
+ args[0] = args[0] != 0
261
+ DistanceFilter.new(*args)
262
+ end
263
+
264
+ def distance_filter=(filter)
265
+ FMOD.type?(filter, DistanceFilter)
266
+ args = filter.values
267
+ args[0] = args[0].to_i
268
+ FMOD.invoke(:ChannelGroup_Set3DDistanceFilter, self, *args)
269
+ end
270
+
271
+ ##
272
+ # @!attribute custom_rolloff
273
+ # A custom rolloff curve to define how audio will attenuate over distance.
274
+ #
275
+ # Must be used in conjunction with {Mode::CUSTOM_ROLLOFF_3D} flag to be
276
+ # activated.
277
+ #
278
+ # <b>Points must be sorted by distance! Passing an unsorted list to FMOD
279
+ # will result in an error.</b>
280
+ # @return [Array<Vector>] the rolloff curve.
281
+
282
+ def custom_rolloff
283
+ count = "\0" * SIZEOF_INT
284
+ FMOD.invoke(:ChannelGroup_Get3DCustomRolloff, self, nil, count)
285
+ count = count.unpack1('l')
286
+ return [] if count.zero?
287
+ size = SIZEOF_FLOAT * 3
288
+ FMOD.invoke(:ChannelGroup_Get3DCustomRolloff, self, ptr = int_ptr, nil)
289
+ buffer = Pointer.new(ptr.unpack1('J'), count * size).to_str
290
+ (0...count).map { |i| Vector.new(*buffer[i * size, size].unpack('fff')) }
291
+ end
292
+
293
+ def custom_rolloff=(rolloff)
294
+ FMOD.type?(rolloff, Array)
295
+ vectors = rolloff.map { |vector| vector.to_str }.join
296
+ FMOD.invoke(:ChannelGroup_Set3DCustomRolloff, self, vectors, rolloff.size)
297
+ rolloff
298
+ end
299
+
300
+ ##
301
+ # Sets the speaker volume levels for each speaker individually, this is a
302
+ # helper to avoid having to set the mix matrix.
303
+ #
304
+ # Levels can be below 0 to invert a signal and above 1 to amplify the
305
+ # signal. Note that increasing the signal level too far may cause audible
306
+ # distortion. Speakers specified that don't exist will simply be ignored.
307
+ # For more advanced speaker control, including sending the different
308
+ # channels of a stereo sound to arbitrary speakers, see {#matrix}.
309
+ #
310
+ # @note This function overwrites any pan/mix-level by overwriting the
311
+ # {ChannelControl}'s matrix.
312
+ #
313
+ # @param fl [Float] Volume level for the front left speaker of a
314
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
315
+ # @param fr [Float] Volume level for the front right speaker of a
316
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
317
+ # @param center [Float] Volume level for the center speaker of a
318
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
319
+ # @param lfe [Float] Volume level for the sub-woofer speaker of a
320
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
321
+ # @param sl [Float] Volume level for the surround left speaker of a
322
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
323
+ # @param sr [Float] Volume level for the surround right speaker of a
324
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
325
+ # @param bl [Float] Volume level for the back left speaker of a multichannel
326
+ # speaker setup, 0.0 (silent), 1.0 (normal volume).
327
+ # @param br [Float] Volume level for the back right speaker of a
328
+ # multichannel speaker setup, 0.0 (silent), 1.0 (normal volume).
329
+ # @return [self]
330
+ def output_mix(fl, fr, center, lfe, sl, sr, bl, br)
331
+ FMOD.invoke(:ChannelGroup_SetMixLevelsOutput, self, fl,
332
+ fr, center, lfe, sl, sr, bl, br)
333
+ self
334
+ end
335
+
336
+ ##
337
+ # Sets the incoming volume level for each channel of a multi-channel sound.
338
+ # This is a helper to avoid calling {#matrix}.
339
+ #
340
+ # A multi-channel sound is a single sound that contains from 1 to 32
341
+ # channels of sound data, in an interleaved fashion. If in the extreme case,
342
+ # a 32 channel wave file was used, an array of 32 floating point numbers
343
+ # denoting their volume levels would be passed in to the levels parameter.
344
+ #
345
+ # @param levels [Array<Float>] Array of volume levels for each incoming
346
+ # channel.
347
+ # @return [self]
348
+ def input_mix(*levels)
349
+ count = levels.size
350
+ binary = levels.pack('f*')
351
+ FMOD.invoke(:ChannelGroup_SetMixLevelsInput, self, binary, count)
352
+ self
353
+ end
354
+
355
+ ##
356
+ # @!attribute min_distance
357
+ # @return [Float] Minimum volume distance in "units". (Default: 1.0)
358
+ # @see max_distance
359
+ # @see min_max_distance
360
+
361
+ def min_distance
362
+ min = "\0" * SIZEOF_FLOAT
363
+ FMOD.invoke(:ChannelGroup_Get3DMinMaxDistance, self, min, nil)
364
+ min.unpack1('f')
365
+ end
366
+
367
+ def min_distance=(distance)
368
+ min_max_distance(distance, max_distance)
369
+ end
370
+
371
+ ##
372
+ # @!attribute max_distance
373
+ # @return [Float] Maximum volume distance in "units". (Default: 10000.0)
374
+ # @see min_distance
375
+ # @see in_max_distance
376
+
377
+ def max_distance
378
+ max = "\0" * SIZEOF_FLOAT
379
+ FMOD.invoke(:ChannelGroup_Get3DMinMaxDistance, self, nil, max)
380
+ max.unpack1('f')
381
+ end
382
+
383
+ def max_distance=(distance)
384
+ min_max_distance(min_distance, distance)
385
+ end
386
+
387
+ ##
388
+ # Sets the minimum and maximum audible distance.
389
+ #
390
+ # When the listener is in-between the minimum distance and the sound source
391
+ # the volume will be at its maximum. As the listener moves from the minimum
392
+ # distance to the maximum distance the sound will attenuate following the
393
+ # rolloff curve set. When outside the maximum distance the sound will no
394
+ # longer attenuate.
395
+ #
396
+ # Minimum distance is useful to give the impression that the sound is loud
397
+ # or soft in 3D space. An example of this is a small quiet object, such as a
398
+ # bumblebee, which you could set a small minimum distance such as 0.1. This
399
+ # would cause it to attenuate quickly and disappear when only a few meters
400
+ # away from the listener. Another example is a jumbo jet, which you could
401
+ # set to a minimum distance of 100.0 causing the volume to stay at its
402
+ # loudest until the listener was 100 meters away, then it would be hundreds
403
+ # of meters more before it would fade out.
404
+ #
405
+ # Maximum distance is effectively obsolete unless you need the sound to stop
406
+ # fading out at a certain point. Do not adjust this from the default if you
407
+ # dont need to. Some people have the confusion that maximum distance is the
408
+ # point the sound will fade out to zero, this is not the case.
409
+ #
410
+ # @param min [Float] Minimum volume distance in "units".
411
+ # * *Default:* 1.0
412
+ # @param max [Float] Maximum volume distance in "units".
413
+ # * *Default:* 10000.0
414
+ #
415
+ # @see min_distance
416
+ # @see max_distance
417
+ def min_max_distance(min, max)
418
+ FMOD.invoke(:ChannelGroup_Set3DMinMaxDistance, self, min, max)
419
+ end
420
+
421
+
422
+ ##
423
+ # @!attribute matrix
424
+ # A 2D pan matrix that maps input channels (columns) to output speakers
425
+ # (rows).
426
+ #
427
+ # Levels can be below 0 to invert a signal and above 1 to amplify the
428
+ # signal. Note that increasing the signal level too far may cause audible
429
+ # distortion.
430
+ #
431
+ # The matrix size will generally be the size of the number of channels in
432
+ # the current speaker mode. Use {System.software_format }to determine this.
433
+ #
434
+ # If a matrix already exists then the matrix passed in will applied over the
435
+ # top of it. The input matrix can be smaller than the existing matrix.
436
+ #
437
+ # A "unit" matrix allows a signal to pass through unchanged. For example for
438
+ # a 5.1 matrix a unit matrix would look like this:
439
+ # [[ 1, 0, 0, 0, 0, 0 ]
440
+ # [ 0, 1, 0, 0, 0, 0 ]
441
+ # [ 0, 0, 1, 0, 0, 0 ]
442
+ # [ 0, 0, 0, 1, 0, 0 ]
443
+ # [ 0, 0, 0, 0, 1, 0 ]
444
+ # [ 0, 0, 0, 0, 0, 1 ]]
445
+ #
446
+ # @return [Array<Array<Float>>] a 2-dimensional array of volume levels in
447
+ # row-major order. Each row represents an output speaker, each column
448
+ # represents an input channel.
449
+ def matrix
450
+ o, i = "\0" * SIZEOF_INT, "\0" * SIZEOF_INT
451
+ FMOD.invoke(:ChannelGroup_GetMixMatrix, self, nil, o, i, 0)
452
+ o, i = o.unpack1('l'), i.unpack1('l')
453
+ return [] if o.zero? || i.zero?
454
+ buffer = "\0" * (SIZEOF_FLOAT * o * i)
455
+ FMOD.invoke(:ChannelGroup_GetMixMatrix, self, buffer, int_ptr, int_ptr, 0)
456
+ buffer.unpack('f*').each_slice(i).to_a
457
+ end
458
+
459
+ def matrix=(matrix)
460
+ out_count, in_count = matrix.size, matrix.first.size
461
+ unless matrix.all? { |ary| ary.size == in_count }
462
+ raise Error, "Matrix contains unequal length input channels."
463
+ end
464
+ data = matrix.flatten.pack('f*')
465
+ FMOD.invoke(:ChannelGroup_SetMixMatrix, self, data,
466
+ out_count, in_count, 0)
467
+ end
468
+
469
+
470
+ def pan(pan)
471
+ FMOD.invoke(:ChannelGroup_SetPan, self, pan.clamp(-1.0, 1.0))
472
+ self
473
+ end
474
+
475
+ def stop
476
+ FMOD.invoke(:ChannelGroup_Stop, self)
477
+ self
478
+ end
479
+
480
+ def pause
481
+ FMOD.invoke(:ChannelGroup_SetPaused, self, 1)
482
+ self
483
+ end
484
+
485
+ def resume
486
+ FMOD.invoke(:ChannelGroup_SetPaused, self, 0)
487
+ self
488
+ end
489
+
490
+ def mute
491
+ FMOD.invoke(:ChannelGroup_SetMute, self, 1)
492
+ end
493
+
494
+ def unmute
495
+ FMOD.invoke(:ChannelGroup_SetMute, self, 0)
496
+ end
497
+
498
+ def dsps
499
+ DspChain.send(:new, self)
500
+ end
501
+
502
+ def parent
503
+ FMOD.invoke(:ChannelGroup_GetSystemObject, self, system = int_ptr)
504
+ System.new(system)
505
+ end
506
+
507
+ def fade_ramp(clock, volume)
508
+ FMOD.invoke(:ChannelGroup_SetFadePointRamp, self, clock, volume)
509
+ end
510
+
511
+ def dsp_clock
512
+ buffer = "\0" * SIZEOF_LONG_LONG
513
+ FMOD.invoke(:ChannelGroup_GetDSPClock, self, buffer, nil)
514
+ buffer.unpack1('Q')
515
+ end
516
+
517
+ def parent_clock
518
+ buffer = "\0" * SIZEOF_LONG_LONG
519
+ FMOD.invoke(:ChannelGroup_GetDSPClock, self, nil, buffer)
520
+ buffer.unpack1('Q')
521
+ end
522
+
523
+ def get_reverb_level(index)
524
+ wet = "\0" * SIZEOF_FLOAT
525
+ FMOD.invoke(:ChannelGroup_GetReverbProperties, self, index, wet)
526
+ wet.unpack1('f')
527
+ end
528
+
529
+ def set_reverb_level(index, wet_level)
530
+ wet = wet_level.clamp(0.0, 1.0)
531
+ FMOD.invoke(:ChannelGroup_SetReverbProperties, self, index, wet)
532
+ wet
533
+ end
534
+
535
+ def delay
536
+ clock_start = "\0" * SIZEOF_LONG_LONG
537
+ clock_end = "\0" * SIZEOF_LONG_LONG
538
+ stop = "\0" * SIZEOF_INT
539
+ FMOD.invoke(:ChannelGroup_GetDelay, self, clock_start, clock_end, stop)
540
+ stop = stop.unpack1('l') != 0
541
+ ChannelDelay.new(clock_start.unpack1('Q'), clock_end.unpack1('Q'), stop)
542
+ end
543
+
544
+ def delay=(delay)
545
+ FMOD.type?(delay, ChannelDelay)
546
+ set_delay(delay.start, delay.end, delay.stop)
547
+ delay
548
+ end
549
+
550
+ def set_delay(clock_start, clock_end, stop)
551
+ stop = stop.to_i
552
+ FMOD.invoke(:ChannelGroup_SetDelay, self, clock_start, clock_end, stop)
553
+ self
554
+ end
555
+
556
+ def cone_orientation
557
+ vector = FMOD::Core::Vector.new
558
+ FMOD.invoke(:ChannelGroup_Get3DConeOrientation, self, vector)
559
+ vector
560
+ end
561
+
562
+ def cone_orientation=(vector)
563
+ FMOD.type?(vector, Vector)
564
+ FMOD.invoke(:ChannelGroup_Set3DConeOrientation, self, vector)
565
+ vector
566
+ end
567
+
568
+ # @!group Callbacks
569
+
570
+ ##
571
+ # Binds the given block so that it will be invoked when the channel is
572
+ # stopped, either by {#stop} or when playback reaches an end.
573
+ # @example
574
+ # >> channel.on_stop do
575
+ # >> puts "Channel stop"
576
+ # >> end
577
+ # >> channel.stop
578
+ #
579
+ # "Channel stop"
580
+ # @param proc [Proc] Proc to call. Optional, must give block if nil.
581
+ # @yield The block to call when the {ChannelControl} is stopped.
582
+ # @yieldreturn [Channel] The {ChannelControl} receiving this callback.
583
+ # @return [self]
584
+ def on_stop(proc = nil, &block)
585
+ set_callback(0, &(block_given? ? block : proc))
586
+ end
587
+
588
+ ##
589
+ # Binds the given block so that it will be invoked when the a voice is
590
+ # swapped to or from emulated/real.
591
+ # @param proc [Proc] Proc to call. Optional, must give block if nil.
592
+ # @yield [emulated] The block to call when a voice is swapped, with flag
593
+ # indicating if voice is emulated passed to it.
594
+ # @yieldparam emulated [Boolean]
595
+ # * *true:* Swapped from real to emulated
596
+ # * *false:* Swapped from emulated to real
597
+ # @yieldreturn [Channel] The {ChannelControl} receiving this callback.
598
+ # @return [self]
599
+ def on_voice_swap(proc = nil, &block)
600
+ set_callback(1, &(block_given? ? block : proc))
601
+ end
602
+
603
+ ##
604
+ # Binds the given block so that it will be invoked when a sync-point is
605
+ # encountered.
606
+ # @param proc [Proc] Proc to call. Optional, must give block if nil.
607
+ # @yield [index] The block to call when a sync-point is encountered, with
608
+ # the index of the sync-point passed to it.
609
+ # @yieldparam index [Integer] The sync-point index.
610
+ # @yieldreturn [Channel] The {ChannelControl} receiving this callback.
611
+ # @return [self]
612
+ def on_sync_point(proc = nil, &block)
613
+ set_callback(2, &(block_given? ? block : proc))
614
+ end
615
+
616
+ ##
617
+ # Binds the given block so that it will be invoked when the occlusion is
618
+ # calculated.
619
+ # @param proc [Proc] Proc to call. Optional, must give block if nil.
620
+ # @yield [direct, reverb] The block to call when occlusion is calculated,
621
+ # with pointers to the direct and reverb occlusion values passed to it.
622
+ # @yieldparam direct [Pointer] A pointer to a floating point direct value
623
+ # that can be read (de-referenced) and modified after the geometry engine
624
+ # has calculated it for this channel.
625
+ # @yieldparam reverb [Pointer] A pointer to a floating point reverb value
626
+ # that can be read (de-referenced) and modified after the geometry engine
627
+ # has calculated it for this channel.
628
+ # @yieldreturn [Channel] The {ChannelControl} receiving this callback.
629
+ # @return [self]
630
+ def on_occlusion(proc = nil, &block)
631
+ set_callback(3, &(block_given? ? block : proc))
632
+ end
633
+
634
+ # @!endgroup
635
+
636
+ def initialize(address = nil)
637
+ super
638
+ @callbacks = {}
639
+ ret = TYPE_INT
640
+ sig = [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP, TYPE_VOIDP]
641
+ abi = FMOD::ABI
642
+ bc = Closure::BlockCaller.new(ret, sig, abi) do |_c, _t, cb_type, d1, d2|
643
+ if @callbacks[cb_type]
644
+ case cb_type
645
+ when 0 then @callbacks[0].each(&:call)
646
+ when 1
647
+ virtual = d1.to_s(SIZEOF_INT).unpack1('l') != 0
648
+ @callbacks[1].each { |cb| cb.call(virtual) }
649
+ when 2
650
+ index = d1.to_s(SIZEOF_INT).unpack1('l')
651
+ @callbacks[2].each { |cb| cb.call(index) }
652
+ when 3 then @callbacks[3].each { |cb| cb.call(d1, d2) }
653
+ else raise FMOD::Error, "Invalid channel callback type."
654
+ end
655
+ end
656
+ Result::OK
657
+ end
658
+ FMOD.invoke(:ChannelGroup_SetCallback, self, bc)
659
+ end
660
+
661
+ private
662
+
663
+ def set_callback(index, &block)
664
+ raise LocalJumpError, "No block given." unless block_given?
665
+ @callbacks[index] ||= []
666
+ @callbacks[index] << block
667
+ self
668
+ end
669
+
670
+ ##
671
+ # Emulates an Array-type container of a {ChannelControl}'s DSP chain.
672
+ class DspChain
673
+
674
+ include Enumerable
675
+
676
+ ##
677
+ # Creates a new instance of a {DspChain} for the specified
678
+ # {ChannelControl}.
679
+ #
680
+ # @param channel [ChannelControl] The channel or channel group to create
681
+ # the collection wrapper for.
682
+ def initialize(channel)
683
+ FMOD.type?(channel, ChannelControl)
684
+ @channel = channel
685
+ end
686
+
687
+ ##
688
+ # Retrieves the number of DSPs within the chain. This includes the
689
+ # built-in {FMOD::Effects::Fader} DSP.
690
+ # @return [Integer]
691
+ def count
692
+ buffer = "\0" * Fiddle::SIZEOF_INT
693
+ FMOD.invoke(:ChannelGroup_GetNumDSPs, @channel, buffer)
694
+ buffer.unpack1('l')
695
+ end
696
+
697
+ ##
698
+ # @overload each(&block)
699
+ # If called with a block, passes each DSP in turn before returning self.
700
+ # @yield [dsp] Yields a DSP instance to the block.
701
+ # @yieldparam dsp [Dsp] The DSP instance.
702
+ # @return [self]
703
+ # @overload each
704
+ # Returns an enumerator for the {DspChain} if no block is given.
705
+ # @return [Enumerator]
706
+ def each
707
+ return to_enum(:each) unless block_given?
708
+ (0...count).each { |i| yield self[i] }
709
+ self
710
+ end
711
+
712
+ ##
713
+ # Element reference. Returns the element at index.
714
+ # @param index [Integer] The index into the {DspChain} to retrieve.
715
+ # @return [Dsp|nil] The DSP at the specified index, or +nil+ if index is
716
+ # out of range.
717
+ def [](index)
718
+ return nil unless index.between?(-2, count)
719
+ dsp = "\0" * Fiddle::SIZEOF_INTPTR_T
720
+ FMOD.invoke(:ChannelGroup_GetDSP, @channel, index, dsp)
721
+ Dsp.from_handle(dsp)
722
+ end
723
+
724
+ ##
725
+ # Element assignment. Sets the element at the specified index.
726
+ # @param index [Integer] The index into the {DspChain} to set.
727
+ # @param dsp [Dsp] A DSP instance.
728
+ # @return [Dsp] The given DSP instance.
729
+ def []=(index, dsp)
730
+ FMOD.type?(dsp, Dsp)
731
+ FMOD.invoke(:ChannelGroup_AddDSP, @channel, index, dsp)
732
+ dsp
733
+ end
734
+
735
+ ##
736
+ # Appends or pushes the given object(s) on to the end of this {DspChain}. This
737
+ # expression returns +self+, so several appends may be chained together.
738
+ # @param dsp [Dsp] One or more DSP instance(s).
739
+ # @return [self]
740
+ def add(*dsp)
741
+ dsp.each { |d| self[DspIndex::TAIL] = d }
742
+ self
743
+ end
744
+
745
+ ##
746
+ # Prepends objects to the front of +self+, moving other elements upwards.
747
+ # @param dsp [Dsp] A DSP instance.
748
+ # @return [self]
749
+ def unshift(dsp)
750
+ self[DspIndex::HEAD] = dsp
751
+ self
752
+ end
753
+
754
+ ##
755
+ # Removes the last element from +self+ and returns it, or +nil+ if the
756
+ # {DspChain} is empty.
757
+ # @return [Dsp|nil]
758
+ def pop
759
+ dsp = self[DspIndex::TAIL]
760
+ remove(dsp)
761
+ dsp
762
+ end
763
+
764
+ ##
765
+ # Returns the first element of +self+ and removes it (shifting all other
766
+ # elements down by one). Returns +nil+ if the array is empty.
767
+ # @return [Dsp|nil]
768
+ def shift
769
+ dsp = self[DspIndex::HEAD]
770
+ remove(dsp)
771
+ dsp
772
+ end
773
+
774
+ ##
775
+ # Deletes the specified DSP from this DSP chain. This does not release ot
776
+ # dispose the DSP unit, only removes from this {DspChain}, as a DSP unit
777
+ # can be shared.
778
+ # @param dsp [Dsp] The DSP to remove.
779
+ # @return [self]
780
+ def remove(dsp)
781
+ return unless dsp.is_a?(Dsp)
782
+ FMOD.invoke(:ChannelGroup_RemoveDSP, @channel, dsp)
783
+ self
784
+ end
785
+
786
+ ##
787
+ # Returns the index of the specified DSP.
788
+ # @param dsp [Dsp] The DSP to retrieve the index of.
789
+ # @return [Integer] The index of the DSP.
790
+ def index(dsp)
791
+ FMOD.type?(dsp, Dsp)
792
+ buffer = "\0" * Fiddle::SIZEOF_INT
793
+ FMOD.invoke(:ChannelGroup_GetDSPIndex, @channel, dsp, buffer)
794
+ buffer.unpack1('l')
795
+ end
796
+
797
+ ##
798
+ # Moves a DSP unit that exists in this {DspChain} to a new index.
799
+ # @param dsp [Dsp] The DSP instance to move, must exist within this
800
+ # {DspChain}.
801
+ # @param index [Integer] The new index to place the specified DSP.
802
+ # @return [self]
803
+ def move(dsp, index)
804
+ FMOD.type?(dsp, Dsp)
805
+ FMOD.invoke(:ChannelGroup_SetDSPIndex, @channel, dsp, index)
806
+ self
807
+ end
808
+
809
+ alias_method :size, :count
810
+ alias_method :length, :count
811
+ alias_method :length, :count
812
+ alias_method :delete, :remove
813
+ alias_method :push, :add
814
+ alias_method :<<, :add
815
+
816
+ private_class_method :new
817
+ end
818
+ end
819
+ end
820
+
821
+