fmod 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +5 -0
- data/.yardopts +2 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +96 -0
- data/Rakefile +1 -0
- data/bin/console +28 -0
- data/bin/setup +8 -0
- data/ext/fmod.dll +0 -0
- data/ext/fmod64.dll +0 -0
- data/ext/libfmod.dylib +0 -0
- data/ext/llbfmod.zip +0 -0
- data/extras/FMOD Studio Programmers API for Windows.chm +0 -0
- data/fmod.gemspec +58 -0
- data/lib/fmod.rb +564 -0
- data/lib/fmod/channel.rb +151 -0
- data/lib/fmod/channel_control.rb +821 -0
- data/lib/fmod/channel_group.rb +61 -0
- data/lib/fmod/core.rb +35 -0
- data/lib/fmod/core/bool_description.rb +18 -0
- data/lib/fmod/core/channel_mask.rb +24 -0
- data/lib/fmod/core/data_description.rb +14 -0
- data/lib/fmod/core/driver.rb +59 -0
- data/lib/fmod/core/dsp_description.rb +7 -0
- data/lib/fmod/core/dsp_index.rb +9 -0
- data/lib/fmod/core/dsp_type.rb +43 -0
- data/lib/fmod/core/extensions.rb +28 -0
- data/lib/fmod/core/file_system.rb +86 -0
- data/lib/fmod/core/filter_type.rb +19 -0
- data/lib/fmod/core/float_description.rb +16 -0
- data/lib/fmod/core/guid.rb +50 -0
- data/lib/fmod/core/init_flags.rb +19 -0
- data/lib/fmod/core/integer_description.rb +26 -0
- data/lib/fmod/core/mode.rb +36 -0
- data/lib/fmod/core/output_type.rb +30 -0
- data/lib/fmod/core/parameter_info.rb +41 -0
- data/lib/fmod/core/parameter_type.rb +10 -0
- data/lib/fmod/core/result.rb +88 -0
- data/lib/fmod/core/reverb.rb +217 -0
- data/lib/fmod/core/sound_ex_info.rb +7 -0
- data/lib/fmod/core/sound_format.rb +30 -0
- data/lib/fmod/core/sound_group_behavior.rb +9 -0
- data/lib/fmod/core/sound_type.rb +80 -0
- data/lib/fmod/core/speaker_index.rb +18 -0
- data/lib/fmod/core/speaker_mode.rb +16 -0
- data/lib/fmod/core/spectrum_data.rb +12 -0
- data/lib/fmod/core/structure.rb +23 -0
- data/lib/fmod/core/structures.rb +41 -0
- data/lib/fmod/core/tag.rb +51 -0
- data/lib/fmod/core/tag_data_type.rb +14 -0
- data/lib/fmod/core/time_unit.rb +40 -0
- data/lib/fmod/core/vector.rb +42 -0
- data/lib/fmod/core/window_type.rb +12 -0
- data/lib/fmod/dsp.rb +510 -0
- data/lib/fmod/dsp_connection.rb +113 -0
- data/lib/fmod/effects.rb +38 -0
- data/lib/fmod/effects/channel_mix.rb +101 -0
- data/lib/fmod/effects/chorus.rb +30 -0
- data/lib/fmod/effects/compressor.rb +52 -0
- data/lib/fmod/effects/convolution_reverb.rb +31 -0
- data/lib/fmod/effects/delay.rb +44 -0
- data/lib/fmod/effects/distortion.rb +16 -0
- data/lib/fmod/effects/dsps.rb +10 -0
- data/lib/fmod/effects/echo.rb +37 -0
- data/lib/fmod/effects/envelope_follower.rb +31 -0
- data/lib/fmod/effects/fader.rb +16 -0
- data/lib/fmod/effects/fft.rb +38 -0
- data/lib/fmod/effects/flange.rb +37 -0
- data/lib/fmod/effects/high_pass.rb +24 -0
- data/lib/fmod/effects/high_pass_simple.rb +25 -0
- data/lib/fmod/effects/it_echo.rb +56 -0
- data/lib/fmod/effects/it_lowpass.rb +36 -0
- data/lib/fmod/effects/ladspa_plugin.rb +14 -0
- data/lib/fmod/effects/limiter.rb +32 -0
- data/lib/fmod/effects/loudness_meter.rb +19 -0
- data/lib/fmod/effects/low_pass.rb +25 -0
- data/lib/fmod/effects/low_pass_simple.rb +26 -0
- data/lib/fmod/effects/mixer.rb +11 -0
- data/lib/fmod/effects/multiband_eq.rb +153 -0
- data/lib/fmod/effects/normalize.rb +47 -0
- data/lib/fmod/effects/object_pan.rb +62 -0
- data/lib/fmod/effects/oscillator.rb +52 -0
- data/lib/fmod/effects/pan.rb +166 -0
- data/lib/fmod/effects/param_eq.rb +36 -0
- data/lib/fmod/effects/pitch_shift.rb +47 -0
- data/lib/fmod/effects/return.rb +18 -0
- data/lib/fmod/effects/send.rb +21 -0
- data/lib/fmod/effects/sfx_reverb.rb +87 -0
- data/lib/fmod/effects/three_eq.rb +41 -0
- data/lib/fmod/effects/transceiver.rb +57 -0
- data/lib/fmod/effects/tremolo.rb +67 -0
- data/lib/fmod/effects/vst_plugin.rb +12 -0
- data/lib/fmod/effects/winamp_plugin.rb +12 -0
- data/lib/fmod/error.rb +108 -0
- data/lib/fmod/geometry.rb +380 -0
- data/lib/fmod/handle.rb +129 -0
- data/lib/fmod/reverb3D.rb +98 -0
- data/lib/fmod/sound.rb +810 -0
- data/lib/fmod/sound_group.rb +54 -0
- data/lib/fmod/system.rb +1242 -0
- data/lib/fmod/version.rb +3 -0
- metadata +220 -0
data/lib/fmod/handle.rb
ADDED
@@ -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
|
data/lib/fmod/sound.rb
ADDED
@@ -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
|