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.
@@ -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
+ }