rubygame 2.2.0 → 2.3.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.
- data/NEWS +55 -4
- data/README +23 -39
- data/ROADMAP +85 -19
- data/Rakefile +49 -20
- data/doc/windows_install.rdoc +1 -1
- data/ext/rubygame/rubygame_image.c +145 -1
- data/ext/rubygame/rubygame_main.h +6 -3
- data/ext/rubygame/rubygame_mixer.c +323 -63
- data/ext/rubygame/rubygame_mixer.h +5 -31
- data/ext/rubygame/rubygame_music.c +1017 -0
- data/ext/rubygame/rubygame_music.h +29 -0
- data/ext/rubygame/rubygame_shared.c +63 -0
- data/ext/rubygame/rubygame_shared.h +8 -0
- data/ext/rubygame/rubygame_sound.c +863 -0
- data/ext/rubygame/rubygame_sound.h +29 -0
- data/ext/rubygame/rubygame_surface.c +4 -0
- data/lib/rubygame/color/models/base.rb +5 -0
- data/lib/rubygame/named_resource.rb +254 -0
- data/samples/chimp.rb +52 -63
- data/samples/demo_gl.rb +0 -0
- data/samples/demo_gl_tex.rb +0 -0
- data/samples/demo_music.rb +10 -8
- data/samples/demo_rubygame.rb +15 -3
- data/samples/demo_sfont.rb +0 -0
- data/samples/demo_ttf.rb +0 -0
- data/samples/demo_utf8.rb +0 -0
- metadata +55 -45
- data/ext/rubygame/MANIFEST +0 -25
- data/lib/rubygame/MANIFEST +0 -12
@@ -0,0 +1,863 @@
|
|
1
|
+
/*
|
2
|
+
* Interface to SDL_mixer sound playback and mixing.
|
3
|
+
*--
|
4
|
+
* Rubygame -- Ruby code and bindings to SDL to facilitate game creation
|
5
|
+
* Copyright (C) 2004-2008 John Croisant
|
6
|
+
*
|
7
|
+
* This library is free software; you can redistribute it and/or
|
8
|
+
* modify it under the terms of the GNU Lesser General Public
|
9
|
+
* License as published by the Free Software Foundation; either
|
10
|
+
* version 2.1 of the License, or (at your option) any later version.
|
11
|
+
*
|
12
|
+
* This library is distributed in the hope that it will be useful,
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15
|
+
* Lesser General Public License for more details.
|
16
|
+
*
|
17
|
+
* You should have received a copy of the GNU Lesser General Public
|
18
|
+
* License along with this library; if not, write to the Free Software
|
19
|
+
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
20
|
+
*++
|
21
|
+
*/
|
22
|
+
|
23
|
+
#include "SDL_mixer.h"
|
24
|
+
#include "rubygame_shared.h"
|
25
|
+
#include "rubygame_mixer.h"
|
26
|
+
|
27
|
+
VALUE cSound;
|
28
|
+
|
29
|
+
/* A pointer to a Mix_Chunk, with a reference count.
|
30
|
+
* Allows re-use of sound data, then freeing memory when
|
31
|
+
* there are no references to it.
|
32
|
+
*/
|
33
|
+
typedef struct RG_WrapChunk {
|
34
|
+
/* 'private' */
|
35
|
+
Mix_Chunk *chunk;
|
36
|
+
int ref_count;
|
37
|
+
} RG_WrapChunk;
|
38
|
+
|
39
|
+
|
40
|
+
/* The struct that the Sound class wraps. Stores a
|
41
|
+
* pointer to a RG_WrapChunk and important attributes
|
42
|
+
* of the Sound like its volume and channel it's
|
43
|
+
* playing on.
|
44
|
+
*/
|
45
|
+
typedef struct RG_Sound {
|
46
|
+
/* 'public' */
|
47
|
+
float volume;
|
48
|
+
|
49
|
+
/* 'private' */
|
50
|
+
RG_WrapChunk *wrap;
|
51
|
+
int channel;
|
52
|
+
} RG_Sound;
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
/* Allocate/initialize the memory for a RG_WrapChunk and return a pointer. */
|
58
|
+
static RG_WrapChunk* _rg_wrapchunk_alloc()
|
59
|
+
{
|
60
|
+
RG_WrapChunk *wrap;
|
61
|
+
wrap = ALLOC(RG_WrapChunk);
|
62
|
+
|
63
|
+
wrap->chunk = NULL;
|
64
|
+
wrap->ref_count = 0;
|
65
|
+
|
66
|
+
return wrap;
|
67
|
+
}
|
68
|
+
|
69
|
+
/* Load a Mix_Chunk from a file and assign it to the RG_WrapChunk. */
|
70
|
+
static int _rg_wrapchunk_load( RG_WrapChunk *wrap, char *file )
|
71
|
+
{
|
72
|
+
/* Open audio if it's not already. Return -1 if it failed. */
|
73
|
+
if( ensure_open_audio() != 0 )
|
74
|
+
{
|
75
|
+
return -1;
|
76
|
+
}
|
77
|
+
|
78
|
+
wrap->chunk = Mix_LoadWAV( file );
|
79
|
+
|
80
|
+
if( !(wrap->chunk) )
|
81
|
+
return -1;
|
82
|
+
else
|
83
|
+
return 0;
|
84
|
+
}
|
85
|
+
|
86
|
+
/* Make a copy of the other's Mix_Chunk audio data, and assign
|
87
|
+
* it to the RG_WrapChunk.
|
88
|
+
*/
|
89
|
+
static void _rg_wrapchunk_deepcopy( RG_WrapChunk *wrap, RG_WrapChunk *other )
|
90
|
+
{
|
91
|
+
*(wrap->chunk) = *(other->chunk);
|
92
|
+
}
|
93
|
+
|
94
|
+
|
95
|
+
/* Free the memory used by the RG_WrapChunk */
|
96
|
+
static void _rg_wrapchunk_free( RG_WrapChunk *wrap )
|
97
|
+
{
|
98
|
+
Mix_FreeChunk( wrap->chunk );
|
99
|
+
wrap->chunk = NULL;
|
100
|
+
free(wrap);
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
/* Associate a RG_WrapChunk with a RG_Sound. Handles reference counts. */
|
107
|
+
static inline void _rg_sound_associate( RG_Sound *sound, RG_WrapChunk *wrap )
|
108
|
+
{
|
109
|
+
sound->wrap = wrap;
|
110
|
+
sound->wrap->ref_count += 1;
|
111
|
+
}
|
112
|
+
|
113
|
+
/* Deassociate the RG_Sound's WrapChunk. Handles reference counts. */
|
114
|
+
static inline void _rg_sound_deassociate( RG_Sound *sound )
|
115
|
+
{
|
116
|
+
sound->wrap->ref_count -= 1;
|
117
|
+
sound->wrap = NULL;
|
118
|
+
}
|
119
|
+
|
120
|
+
/* Allocate/initialize the memory for a RG_Sound return a pointer. */
|
121
|
+
static RG_Sound* _rg_sound_alloc()
|
122
|
+
{
|
123
|
+
RG_Sound *sound;
|
124
|
+
sound = ALLOC(RG_Sound);
|
125
|
+
|
126
|
+
sound->wrap = NULL;
|
127
|
+
sound->volume = 1.f;
|
128
|
+
sound->channel = -1;
|
129
|
+
|
130
|
+
return sound;
|
131
|
+
}
|
132
|
+
|
133
|
+
|
134
|
+
/* Free the memory used by the Sound, and possibly the memory
|
135
|
+
* used by the WrapChunk it refers to.
|
136
|
+
*/
|
137
|
+
static void _rg_sound_free( RG_Sound *sound )
|
138
|
+
{
|
139
|
+
RG_WrapChunk *wrap = sound->wrap;
|
140
|
+
|
141
|
+
_rg_sound_deassociate( sound );
|
142
|
+
|
143
|
+
free(sound);
|
144
|
+
|
145
|
+
/* If the WrapChunk has no more referrers, free it too. */
|
146
|
+
if( wrap->ref_count <= 0 )
|
147
|
+
{
|
148
|
+
_rg_wrapchunk_free( wrap );
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
|
153
|
+
/* Load a new Sound from a file. */
|
154
|
+
static int _rg_sound_load( RG_Sound *sound, char *file )
|
155
|
+
{
|
156
|
+
RG_WrapChunk *wrap = _rg_wrapchunk_alloc();
|
157
|
+
|
158
|
+
int result = _rg_wrapchunk_load( wrap, file );
|
159
|
+
|
160
|
+
_rg_sound_associate( sound, wrap );
|
161
|
+
|
162
|
+
return result;
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
/* Make a shallow copy of the given Sound; the new Sound points to
|
167
|
+
* the same audio data in memory as the old one. Also copies
|
168
|
+
* user-visible attributes (e.g. volume).
|
169
|
+
*/
|
170
|
+
static void _rg_sound_copy( RG_Sound *sound, RG_Sound *other )
|
171
|
+
{
|
172
|
+
_rg_sound_associate( sound, other->wrap );
|
173
|
+
|
174
|
+
sound->volume = other->volume;
|
175
|
+
sound->channel = -1;
|
176
|
+
}
|
177
|
+
|
178
|
+
|
179
|
+
/* Make a new Sound with a copy of the audio from an existing Sound */
|
180
|
+
static void _rg_sound_deepcopy( RG_Sound *sound, RG_Sound *other )
|
181
|
+
{
|
182
|
+
RG_WrapChunk *wrap = _rg_wrapchunk_alloc();
|
183
|
+
_rg_wrapchunk_deepcopy( wrap, other->wrap );
|
184
|
+
|
185
|
+
_rg_sound_associate( sound, wrap );
|
186
|
+
|
187
|
+
sound->volume = other->volume;
|
188
|
+
sound->channel = -1;
|
189
|
+
}
|
190
|
+
|
191
|
+
|
192
|
+
/* Check that the given channel is (still) loaded with the given chunk. */
|
193
|
+
static int _rg_sound_channel_check( RG_Sound *sound )
|
194
|
+
{
|
195
|
+
/* channel is unset, so it doesn't belong. */
|
196
|
+
if( sound->channel == -1 )
|
197
|
+
{
|
198
|
+
return 0;
|
199
|
+
}
|
200
|
+
|
201
|
+
Mix_Chunk *chan_chunk = Mix_GetChunk(sound->channel);
|
202
|
+
|
203
|
+
/* Check that the channel chunk is the same as the given one */
|
204
|
+
return ( sound->wrap->chunk == chan_chunk );
|
205
|
+
}
|
206
|
+
|
207
|
+
|
208
|
+
/* Play the sound, fading in, repeating, and stopping as specified.
|
209
|
+
* fade_in and stop_after are in milliseconds!
|
210
|
+
*/
|
211
|
+
static int _rg_sound_play( RG_Sound *sound,
|
212
|
+
int fade_in, int repeats, int stop_after )
|
213
|
+
{
|
214
|
+
|
215
|
+
/* Open audio if it's not already. Return -1 if it failed. */
|
216
|
+
if( ensure_open_audio() != 0 )
|
217
|
+
{
|
218
|
+
return -1;
|
219
|
+
}
|
220
|
+
|
221
|
+
/* If it's already playing on a channel, stop it first. */
|
222
|
+
if( _rg_sound_channel_check(sound) )
|
223
|
+
{
|
224
|
+
Mix_HaltChannel( sound->channel );
|
225
|
+
}
|
226
|
+
|
227
|
+
/* Find first available channel */
|
228
|
+
sound->channel = Mix_GroupAvailable(-1);
|
229
|
+
|
230
|
+
if( sound->channel == -1 )
|
231
|
+
{
|
232
|
+
/* No channels were available, so make one more than there are now.
|
233
|
+
* (Mix_AllocateChannels(-1) returns the current number of channels)
|
234
|
+
*/
|
235
|
+
Mix_AllocateChannels( Mix_AllocateChannels(-1) + 1 );
|
236
|
+
|
237
|
+
/* Try again. */
|
238
|
+
sound->channel = Mix_GroupAvailable(-1);
|
239
|
+
}
|
240
|
+
|
241
|
+
/* Set its volume before we play */
|
242
|
+
Mix_Volume( sound->channel, (int)(MIX_MAX_VOLUME * sound->volume) );
|
243
|
+
|
244
|
+
|
245
|
+
if( fade_in <= 0 )
|
246
|
+
{
|
247
|
+
/* Play sound without fading in */
|
248
|
+
return Mix_PlayChannelTimed( sound->channel, sound->wrap->chunk,
|
249
|
+
repeats, stop_after );
|
250
|
+
}
|
251
|
+
else
|
252
|
+
{
|
253
|
+
/* Play sound with fading in */
|
254
|
+
return Mix_FadeInChannelTimed( sound->channel, sound->wrap->chunk,
|
255
|
+
repeats, fade_in, stop_after );
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
|
260
|
+
/* Ruby allocation function. */
|
261
|
+
static VALUE rg_sound_alloc( VALUE klass )
|
262
|
+
{
|
263
|
+
RG_Sound *sound = _rg_sound_alloc();
|
264
|
+
return Data_Wrap_Struct(klass, 0, _rg_sound_free, sound);
|
265
|
+
}
|
266
|
+
|
267
|
+
|
268
|
+
|
269
|
+
/*
|
270
|
+
* call-seq:
|
271
|
+
* load( filename ) -> sound
|
272
|
+
*
|
273
|
+
* Load the given audio file.
|
274
|
+
* Supported file formats are WAV, AIFF, RIFF, OGG (Ogg Vorbis), and
|
275
|
+
* VOC (SoundBlaster).
|
276
|
+
*
|
277
|
+
* filename:: Full or relative path to the file. (String, required)
|
278
|
+
*
|
279
|
+
* Returns:: The new Sound instance. (Sound)
|
280
|
+
* May raise:: SDLError, if the sound file could not be loaded.
|
281
|
+
*
|
282
|
+
*/
|
283
|
+
static VALUE rg_sound_load( VALUE klass, VALUE filename )
|
284
|
+
{
|
285
|
+
RG_Sound *sound;
|
286
|
+
|
287
|
+
VALUE s = rg_sound_alloc( cSound );
|
288
|
+
|
289
|
+
Data_Get_Struct( s, RG_Sound, sound );
|
290
|
+
|
291
|
+
char *file = StringValuePtr( filename );
|
292
|
+
|
293
|
+
int result = _rg_sound_load( sound, file );
|
294
|
+
|
295
|
+
if( result == -1 )
|
296
|
+
{
|
297
|
+
rb_raise(eSDLError, "Could not load Sound file '%s': %s",
|
298
|
+
file, Mix_GetError());
|
299
|
+
}
|
300
|
+
|
301
|
+
return s;
|
302
|
+
}
|
303
|
+
|
304
|
+
|
305
|
+
/*
|
306
|
+
* call-seq:
|
307
|
+
* Sound.autoload( filename ) -> Surface or nil
|
308
|
+
*
|
309
|
+
* Searches each directory in Sound.autoload_dirs for a file with
|
310
|
+
* the given filename. If it finds that file, loads it and returns
|
311
|
+
* a Sound instance. If it doesn't find the file, returns nil.
|
312
|
+
*
|
313
|
+
* See Rubygame::NamedResource for more information about this
|
314
|
+
* functionality.
|
315
|
+
*
|
316
|
+
*/
|
317
|
+
VALUE rg_sound_autoload( VALUE klass, VALUE namev )
|
318
|
+
{
|
319
|
+
VALUE pathv = rb_funcall( klass, rb_intern("find_file"), 1, namev );
|
320
|
+
|
321
|
+
if( RTEST(pathv) )
|
322
|
+
{
|
323
|
+
return rg_sound_load( klass, pathv );
|
324
|
+
}
|
325
|
+
else
|
326
|
+
{
|
327
|
+
return Qnil;
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
331
|
+
|
332
|
+
/*
|
333
|
+
*
|
334
|
+
* call-seq:
|
335
|
+
* new
|
336
|
+
*
|
337
|
+
* **NOTE**: Don't use this method. Use Sound.load.
|
338
|
+
*
|
339
|
+
* This method is not implemented. (In the future, it might be
|
340
|
+
* used to create a new Sound with blank audio data.)
|
341
|
+
*
|
342
|
+
* Raises NotImplementedError.
|
343
|
+
*
|
344
|
+
*/
|
345
|
+
static VALUE rg_sound_new( int argc, VALUE *ARGV, VALUE self )
|
346
|
+
{
|
347
|
+
rb_raise(rb_eNotImpError, "Sound.new is not implemented yet. Use Sound.load to load a sound file.");
|
348
|
+
}
|
349
|
+
|
350
|
+
|
351
|
+
/*
|
352
|
+
* call-seq:
|
353
|
+
* clone( other ) -> sound
|
354
|
+
* dup( other ) -> sound
|
355
|
+
*
|
356
|
+
* Create a copy of the given Sound instance. Much more memory-efficient
|
357
|
+
* than using #load to load the sound file again.
|
358
|
+
*
|
359
|
+
* other:: An existing Sound instance. (Sound, required)
|
360
|
+
*
|
361
|
+
* Returns:: The new Sound instance. (Sound)
|
362
|
+
*
|
363
|
+
* **NOTE**: #clone and #dup do slightly different things; #clone will copy
|
364
|
+
* the 'frozen' state of the object, while #dup will create a fresh, un-frozen
|
365
|
+
* object.
|
366
|
+
*
|
367
|
+
*/
|
368
|
+
static VALUE rg_sound_initialize_copy( VALUE self, VALUE other )
|
369
|
+
{
|
370
|
+
RG_Sound *soundA, *soundB;
|
371
|
+
Data_Get_Struct(self, RG_Sound, soundA);
|
372
|
+
Data_Get_Struct(other, RG_Sound, soundB);
|
373
|
+
|
374
|
+
_rg_sound_copy( soundA, soundB );
|
375
|
+
|
376
|
+
return self;
|
377
|
+
}
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
|
382
|
+
/*
|
383
|
+
* call-seq:
|
384
|
+
* play( options={} ) -> self
|
385
|
+
*
|
386
|
+
* Play the Sound, optionally fading in, repeating a certain number of
|
387
|
+
* times (or forever), and/or stopping automatically after a certain time.
|
388
|
+
*
|
389
|
+
* See also #pause and #stop.
|
390
|
+
*
|
391
|
+
* options:: Hash of options, listed below. (Hash, required)
|
392
|
+
*
|
393
|
+
* :fade_in:: Fade in from silence over the given number of seconds.
|
394
|
+
* (Numeric)
|
395
|
+
* :repeats:: Repeat the sound the given number of times, or forever
|
396
|
+
* (or until stopped) if -1. (Integer)
|
397
|
+
* :stop_after:: Automatically stop playing after playing for the given
|
398
|
+
* number of seconds. (Numeric)
|
399
|
+
*
|
400
|
+
* Returns:: The receiver (self).
|
401
|
+
* May raise:: SDLError, if the sound file could not be played.
|
402
|
+
*
|
403
|
+
* **NOTE**: If the sound is already playing (or paused), it will be stopped
|
404
|
+
* and played again from the beginning.
|
405
|
+
*
|
406
|
+
* Example:
|
407
|
+
* # Fade in over 2 seconds, play 4 times (1 + 3 repeats),
|
408
|
+
* # but stop playing after 5 seconds.
|
409
|
+
* sound.play( :fade_in => 2, :repeats => 3, :stop_after => 5 );
|
410
|
+
*
|
411
|
+
*/
|
412
|
+
static VALUE rg_sound_play( int argc, VALUE *argv, VALUE self )
|
413
|
+
{
|
414
|
+
RG_Sound *sound;
|
415
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
416
|
+
|
417
|
+
VALUE options;
|
418
|
+
rb_scan_args(argc, argv, "01", &options);
|
419
|
+
|
420
|
+
int fade_in = 0;
|
421
|
+
int repeats = 0;
|
422
|
+
int stop_after = -1;
|
423
|
+
|
424
|
+
/* If we got some options */
|
425
|
+
if( RTEST(options) )
|
426
|
+
{
|
427
|
+
/* Make sure options is a Hash table */
|
428
|
+
if( TYPE(options) != T_HASH )
|
429
|
+
{
|
430
|
+
rb_raise(rb_eTypeError, "wrong argument type %s (expected Hash)",
|
431
|
+
rb_obj_classname(options));
|
432
|
+
}
|
433
|
+
|
434
|
+
VALUE temp;
|
435
|
+
|
436
|
+
temp = rb_hash_aref(options, make_symbol("fade_in"));
|
437
|
+
if( RTEST(temp) )
|
438
|
+
{
|
439
|
+
fade_in = (int)(1000 * NUM2DBL( temp ));
|
440
|
+
}
|
441
|
+
|
442
|
+
temp = rb_hash_aref(options, make_symbol("repeats"));
|
443
|
+
if( RTEST(temp) )
|
444
|
+
{
|
445
|
+
repeats = NUM2INT(temp);
|
446
|
+
}
|
447
|
+
|
448
|
+
temp = rb_hash_aref(options, make_symbol("stop_after"));
|
449
|
+
if( RTEST(temp) )
|
450
|
+
{
|
451
|
+
stop_after = (int)(1000 * NUM2DBL( temp ));
|
452
|
+
}
|
453
|
+
|
454
|
+
}
|
455
|
+
|
456
|
+
int result = _rg_sound_play( sound, fade_in, repeats, stop_after );
|
457
|
+
|
458
|
+
if( result == -1 )
|
459
|
+
{
|
460
|
+
rb_raise(eSDLError, "Could not play Sound: %s", Mix_GetError());
|
461
|
+
}
|
462
|
+
|
463
|
+
return self;
|
464
|
+
}
|
465
|
+
|
466
|
+
|
467
|
+
/*
|
468
|
+
* call-seq:
|
469
|
+
* playing? -> true or false
|
470
|
+
*
|
471
|
+
* True if the Sound is currently playing (not paused or stopped).
|
472
|
+
* See also #paused? and #stopped?.
|
473
|
+
*
|
474
|
+
*/
|
475
|
+
static VALUE rg_sound_playingp( VALUE self )
|
476
|
+
{
|
477
|
+
RG_Sound *sound;
|
478
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
479
|
+
|
480
|
+
int channel = sound->channel;
|
481
|
+
|
482
|
+
/* Make sure the sound actually belongs to the channel */
|
483
|
+
if( _rg_sound_channel_check(sound) )
|
484
|
+
{
|
485
|
+
/* Return true if it's playing, but not paused. */
|
486
|
+
if( Mix_Playing(channel) && !Mix_Paused(channel) )
|
487
|
+
{
|
488
|
+
return Qtrue;
|
489
|
+
}
|
490
|
+
else
|
491
|
+
{
|
492
|
+
return Qfalse;
|
493
|
+
}
|
494
|
+
}
|
495
|
+
else
|
496
|
+
{
|
497
|
+
return Qfalse;
|
498
|
+
}
|
499
|
+
}
|
500
|
+
|
501
|
+
|
502
|
+
|
503
|
+
/*
|
504
|
+
* call-seq:
|
505
|
+
* pause -> self
|
506
|
+
*
|
507
|
+
* Pause the Sound. Unlike #stop, it can be unpaused later to resume
|
508
|
+
* from where it was paused. See also #unpause and #paused?.
|
509
|
+
*
|
510
|
+
* Returns:: The receiver (self).
|
511
|
+
*
|
512
|
+
* **NOTE**: Does nothing if the sound is not currently playing.
|
513
|
+
*
|
514
|
+
*/
|
515
|
+
static VALUE rg_sound_pause( VALUE self )
|
516
|
+
{
|
517
|
+
RG_Sound *sound;
|
518
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
519
|
+
|
520
|
+
int channel = sound->channel;
|
521
|
+
|
522
|
+
/* Make sure the sound actually belongs to the channel */
|
523
|
+
if( _rg_sound_channel_check(sound) )
|
524
|
+
{
|
525
|
+
Mix_Pause( channel );
|
526
|
+
}
|
527
|
+
|
528
|
+
return self;
|
529
|
+
}
|
530
|
+
|
531
|
+
|
532
|
+
/*
|
533
|
+
* call-seq:
|
534
|
+
* unpause -> self
|
535
|
+
*
|
536
|
+
* Unpause the Sound, if it is currently paused. Resumes from
|
537
|
+
* where it was paused. See also #pause and #paused?.
|
538
|
+
*
|
539
|
+
* Returns:: The receiver (self).
|
540
|
+
*
|
541
|
+
* **NOTE**: Does nothing if the sound is not currently paused.
|
542
|
+
*
|
543
|
+
*/
|
544
|
+
static VALUE rg_sound_unpause( VALUE self )
|
545
|
+
{
|
546
|
+
RG_Sound *sound;
|
547
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
548
|
+
|
549
|
+
int channel = sound->channel;
|
550
|
+
|
551
|
+
/* Make sure the sound actually belongs to the channel */
|
552
|
+
if( _rg_sound_channel_check(sound) )
|
553
|
+
{
|
554
|
+
Mix_Resume( channel );
|
555
|
+
}
|
556
|
+
|
557
|
+
return self;
|
558
|
+
}
|
559
|
+
|
560
|
+
|
561
|
+
/*
|
562
|
+
* call-seq:
|
563
|
+
* paused? -> true or false
|
564
|
+
*
|
565
|
+
* True if the Sound is currently paused (not playing or stopped).
|
566
|
+
* See also #playing? and #stopped?.
|
567
|
+
*
|
568
|
+
*/
|
569
|
+
static VALUE rg_sound_pausedp( VALUE self )
|
570
|
+
{
|
571
|
+
RG_Sound *sound;
|
572
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
573
|
+
|
574
|
+
int channel = sound->channel;
|
575
|
+
|
576
|
+
/* Make sure the sound actually belongs to the channel */
|
577
|
+
if( _rg_sound_channel_check(sound) )
|
578
|
+
{
|
579
|
+
/* Return true if it's "playing" (not stopped), as well as paused. */
|
580
|
+
if( Mix_Playing(channel) && Mix_Paused(channel) )
|
581
|
+
{
|
582
|
+
return Qtrue;
|
583
|
+
}
|
584
|
+
else
|
585
|
+
{
|
586
|
+
return Qfalse;
|
587
|
+
}
|
588
|
+
}
|
589
|
+
else
|
590
|
+
{
|
591
|
+
return Qfalse;
|
592
|
+
}
|
593
|
+
}
|
594
|
+
|
595
|
+
|
596
|
+
|
597
|
+
/*
|
598
|
+
* call-seq:
|
599
|
+
* stop -> self
|
600
|
+
*
|
601
|
+
* Stop the Sound. Unlike #pause, the sound must be played again from
|
602
|
+
* the beginning, it cannot be resumed from it was stopped.
|
603
|
+
*
|
604
|
+
* Returns:: The receiver (self).
|
605
|
+
*
|
606
|
+
* **NOTE**: Does nothing if the sound is not currently playing or paused.
|
607
|
+
*
|
608
|
+
*/
|
609
|
+
static VALUE rg_sound_stop( VALUE self )
|
610
|
+
{
|
611
|
+
RG_Sound *sound;
|
612
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
613
|
+
|
614
|
+
int channel = sound->channel;
|
615
|
+
|
616
|
+
/* Make sure the sound actually belongs to the channel */
|
617
|
+
if( _rg_sound_channel_check(sound) )
|
618
|
+
{
|
619
|
+
Mix_HaltChannel( channel );
|
620
|
+
}
|
621
|
+
|
622
|
+
return self;
|
623
|
+
}
|
624
|
+
|
625
|
+
|
626
|
+
/*
|
627
|
+
* call-seq:
|
628
|
+
* stopped? -> true or false
|
629
|
+
*
|
630
|
+
* True if the Sound is currently stopped (not playing or paused).
|
631
|
+
* See also #playing? and #paused?.
|
632
|
+
*
|
633
|
+
*/
|
634
|
+
static VALUE rg_sound_stoppedp( VALUE self )
|
635
|
+
{
|
636
|
+
RG_Sound *sound;
|
637
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
638
|
+
|
639
|
+
int channel = sound->channel;
|
640
|
+
|
641
|
+
/* Make sure the sound actually belongs to the channel */
|
642
|
+
if( _rg_sound_channel_check(sound) )
|
643
|
+
{
|
644
|
+
/* Return true if it's not playing. */
|
645
|
+
if( !Mix_Playing(channel) )
|
646
|
+
{
|
647
|
+
return Qtrue;
|
648
|
+
}
|
649
|
+
else
|
650
|
+
{
|
651
|
+
return Qfalse;
|
652
|
+
}
|
653
|
+
}
|
654
|
+
else
|
655
|
+
{
|
656
|
+
/* If it's not on a channel... it can't be playing! */
|
657
|
+
return Qtrue;
|
658
|
+
}
|
659
|
+
}
|
660
|
+
|
661
|
+
|
662
|
+
/*
|
663
|
+
* call-seq:
|
664
|
+
* fade_out( fade_time ) -> self
|
665
|
+
*
|
666
|
+
* Fade out to silence over the given number of seconds. Once the sound
|
667
|
+
* is silent, it is automatically stopped.
|
668
|
+
*
|
669
|
+
* Returns:: The receiver (self).
|
670
|
+
*
|
671
|
+
* **NOTE**: If the sound is currently paused, the fade will start,
|
672
|
+
* but you won't be able to hear it happening unless you #unpause during
|
673
|
+
* the fade. Does nothing if the sound is currently stopped.
|
674
|
+
*
|
675
|
+
*/
|
676
|
+
static VALUE rg_sound_fadeout( VALUE self, VALUE fade_time )
|
677
|
+
{
|
678
|
+
RG_Sound *sound;
|
679
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
680
|
+
|
681
|
+
int channel = sound->channel;
|
682
|
+
int fade_ms = (int)(1000 * NUM2DBL(fade_time));
|
683
|
+
|
684
|
+
/* Make sure the sound actually belongs to the channel */
|
685
|
+
if( _rg_sound_channel_check(sound) )
|
686
|
+
{
|
687
|
+
Mix_FadeOutChannel( channel, fade_ms );
|
688
|
+
}
|
689
|
+
|
690
|
+
return self;
|
691
|
+
}
|
692
|
+
|
693
|
+
|
694
|
+
/*
|
695
|
+
* call-seq:
|
696
|
+
* fading?( direction=:either ) -> true or false
|
697
|
+
*
|
698
|
+
* True if the Sound is currently fading in or out.
|
699
|
+
* See also #play and #fade_out.
|
700
|
+
*
|
701
|
+
* direction:: Check if it is fading :in, :out, or :either.
|
702
|
+
* (Symbol, required)
|
703
|
+
*
|
704
|
+
*/
|
705
|
+
static VALUE rg_sound_fadingp( int argc, VALUE *argv, VALUE self )
|
706
|
+
{
|
707
|
+
RG_Sound *sound;
|
708
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
709
|
+
|
710
|
+
VALUE vdirection;
|
711
|
+
rb_scan_args(argc, argv, "01", &vdirection);
|
712
|
+
|
713
|
+
int direction;
|
714
|
+
int channel = sound->channel;
|
715
|
+
|
716
|
+
/* If it's not actually on a channel, return false right away. */
|
717
|
+
if( !(_rg_sound_channel_check(sound)) )
|
718
|
+
{
|
719
|
+
return Qfalse;
|
720
|
+
}
|
721
|
+
|
722
|
+
if( RTEST(vdirection) )
|
723
|
+
{
|
724
|
+
if( make_symbol("in") == vdirection )
|
725
|
+
{
|
726
|
+
return ( (Mix_FadingChannel(channel) == MIX_FADING_IN) ? Qtrue : Qfalse );
|
727
|
+
}
|
728
|
+
|
729
|
+
else if( make_symbol("out") == vdirection )
|
730
|
+
{
|
731
|
+
return ( (Mix_FadingChannel(channel) == MIX_FADING_OUT) ? Qtrue : Qfalse );
|
732
|
+
}
|
733
|
+
|
734
|
+
else if( make_symbol("either") == vdirection )
|
735
|
+
{
|
736
|
+
return ( (Mix_FadingChannel(channel) != MIX_NO_FADING) ? Qtrue : Qfalse );
|
737
|
+
}
|
738
|
+
}
|
739
|
+
|
740
|
+
/* default */
|
741
|
+
return ( (Mix_FadingChannel(channel) != MIX_NO_FADING) ? Qtrue : Qfalse );
|
742
|
+
}
|
743
|
+
|
744
|
+
|
745
|
+
|
746
|
+
/*
|
747
|
+
* call-seq:
|
748
|
+
* volume -> vol
|
749
|
+
*
|
750
|
+
* Return the volume level of the sound.
|
751
|
+
* 0.0 is totally silent, 1.0 is full volume.
|
752
|
+
*
|
753
|
+
* **NOTE**: Ignores fading in or out.
|
754
|
+
*
|
755
|
+
*/
|
756
|
+
static VALUE rg_sound_getvolume( VALUE self )
|
757
|
+
{
|
758
|
+
RG_Sound *sound;
|
759
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
760
|
+
|
761
|
+
return rb_float_new(sound->volume);
|
762
|
+
}
|
763
|
+
|
764
|
+
|
765
|
+
/*
|
766
|
+
* call-seq:
|
767
|
+
* volume = new_vol
|
768
|
+
*
|
769
|
+
* Set the new #volume level of the sound.
|
770
|
+
* 0.0 is totally silent, 1.0 is full volume.
|
771
|
+
*
|
772
|
+
* Volume cannot be set while the sound is fading in or out.
|
773
|
+
* Be sure to check #fading? or rescue from SDLError when
|
774
|
+
* using this method.
|
775
|
+
*
|
776
|
+
* May raise:: SDLError if the sound is fading in or out.
|
777
|
+
*
|
778
|
+
*/
|
779
|
+
static VALUE rg_sound_setvolume( VALUE self, VALUE volume )
|
780
|
+
{
|
781
|
+
RG_Sound *sound;
|
782
|
+
Data_Get_Struct(self, RG_Sound, sound);
|
783
|
+
|
784
|
+
/* Change channel volume if Sound is currently assigned to a channel */
|
785
|
+
if( _rg_sound_channel_check(sound) )
|
786
|
+
{
|
787
|
+
/* But only if it's not fading right now. */
|
788
|
+
if( Mix_FadingChannel(sound->channel) == MIX_NO_FADING )
|
789
|
+
{
|
790
|
+
sound->volume = NUM2DBL(volume);
|
791
|
+
Mix_Volume( sound->channel, (int)(MIX_MAX_VOLUME * sound->volume) );
|
792
|
+
}
|
793
|
+
else
|
794
|
+
{
|
795
|
+
rb_raise(eSDLError, "cannot set Sound volume while fading");
|
796
|
+
}
|
797
|
+
}
|
798
|
+
else
|
799
|
+
{
|
800
|
+
/* Save it for later. */
|
801
|
+
sound->volume = NUM2DBL(volume);
|
802
|
+
}
|
803
|
+
|
804
|
+
return volume;
|
805
|
+
}
|
806
|
+
|
807
|
+
|
808
|
+
void Rubygame_Init_Sound()
|
809
|
+
{
|
810
|
+
#if 0
|
811
|
+
mRubygame = rb_define_module("Rubygame");
|
812
|
+
#endif
|
813
|
+
|
814
|
+
/*
|
815
|
+
* **IMPORTANT**: Sound is only available if Rubygame was compiled
|
816
|
+
* with SDL_mixer support!
|
817
|
+
*
|
818
|
+
* Sound holds a sound effect, loaded from an audio file (see #load for
|
819
|
+
* supported formats).
|
820
|
+
*
|
821
|
+
* Sound can #play, #pause/#unpause, #stop, adjust #volume,
|
822
|
+
* and #fade_out (you can fade in by passing an option to #play).
|
823
|
+
*
|
824
|
+
* Sound can create duplicates (with #dup or #clone) in a memory-efficient
|
825
|
+
* way -- the new Sound instance refers back to the same audio data,
|
826
|
+
* so having 100 duplicates of a sound uses only slightly more memory
|
827
|
+
* than having the first sound. Duplicates can different volume levels,
|
828
|
+
* too!
|
829
|
+
*
|
830
|
+
* Sound includes the Rubygame::NamedResource mixin module, which
|
831
|
+
* can perform autoloading of sounds on demand, among other things.
|
832
|
+
*
|
833
|
+
*/
|
834
|
+
cSound = rb_define_class_under(mRubygame,"Sound",rb_cObject);
|
835
|
+
|
836
|
+
/* Include the Rubygame::NamedResource mixin module. */
|
837
|
+
rg_include_named_resource(cSound);
|
838
|
+
|
839
|
+
rb_define_alloc_func( cSound, rg_sound_alloc );
|
840
|
+
|
841
|
+
rb_define_singleton_method( cSound, "new", rg_sound_new, -1 );
|
842
|
+
rb_define_singleton_method( cSound, "load", rg_sound_load, 1 );
|
843
|
+
|
844
|
+
rb_define_singleton_method( cSound, "autoload", rg_sound_autoload, 1 );
|
845
|
+
|
846
|
+
rb_define_method( cSound, "initialize_copy", rg_sound_initialize_copy, 1 );
|
847
|
+
|
848
|
+
rb_define_method( cSound, "play", rg_sound_play, -1 );
|
849
|
+
rb_define_method( cSound, "playing?", rg_sound_playingp, 0 );
|
850
|
+
|
851
|
+
rb_define_method( cSound, "pause", rg_sound_pause, 0 );
|
852
|
+
rb_define_method( cSound, "unpause", rg_sound_unpause, 0 );
|
853
|
+
rb_define_method( cSound, "paused?", rg_sound_pausedp, 0 );
|
854
|
+
|
855
|
+
rb_define_method( cSound, "stop", rg_sound_stop, 0 );
|
856
|
+
rb_define_method( cSound, "stopped?", rg_sound_stoppedp, 0 );
|
857
|
+
|
858
|
+
rb_define_method( cSound, "fade_out", rg_sound_fadeout, 1 );
|
859
|
+
rb_define_method( cSound, "fading?", rg_sound_fadingp, -1 );
|
860
|
+
|
861
|
+
rb_define_method( cSound, "volume", rg_sound_getvolume, 0 );
|
862
|
+
rb_define_method( cSound, "volume=", rg_sound_setvolume, 1 );
|
863
|
+
}
|