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,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
+