Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: f1122192fd
Fetching contributors…

Cannot retrieve contributors at this time

634 lines (449 sloc) 19.51 kb

Sound and Music in SDL are handled by the Audio and SDL_Mixer components. Enabling Audio devices is provided with the Core SDL Library and only supports wav files. SDL_Mixer supports more audio file formats and has additional features that we need for sound in Game Development.

Similarly to video in SDL, there are several way for perl developers to access the Sound components of SDL. For the plain Audio component the SDL::Audio and related modules are available. SDL_Mixer is supported with th SDL::Mixer module. There is currently a SDLx::Sound module in the work, but not completed at the time of writing this manual. For that reason this chapter will use SDL::Audio and SDL::Mixer.

Simple Sound Script

To begin using sound we must enable and open an audiospec:

   use strict;
   use warnings;
   use SDL;
   use Carp;
   use SDL::Audio;
   use SDL::Mixer;
   
   SDL::init(SDL_INIT_AUDIO);
   
   unless( SDL::Mixer::open_audio( 44100, AUDIO_S16SYS, 2, 4096 ) == 0 )
   {
      Carp::croak "Cannot open audio: ".SDL::get_error(); 
   }

open_audio will open an audio device with frequency at 44100 Mhz, audio format AUDIO_S16SYS (Note: This is currently the most portable format, however there are others), 2 channels and a chunk size of 4096. Fiddle with these values if you are comfortable with sound terminology and techniques.

Loading Samples

Next we will load sound samples that generally used for sound effects and the like. Currently SDL_Mixer reserves samples for .WAV, .AIFF, .RIFF .OGG, and .VOC formats.

Samples run on one of the 2 channels that we opened up, while the other channel will be reserved for multiple plays of the sample. To load samples we will be doing the following:

   +use SDL::Mixer::Samples;

   +#Brillant Lazer Sound from U<http://www.freesound.org/samplesViewSingle.php?id=30935>
   +my $sample = SDL::Mixer::Samples::load_WAV('data/sample.wav');

   +unless($sample)
   +{
   +   Carp::croak "Cannot load file data/sample.wav: ".SDL::get_error();   
   +}

Playing the sample and closing audio

Now we can play that sample on any open channel looping forever:

    use SDL::Mixer::Samples;
   +use SDL::Mixer::Channels;
   
   my $sample =  SDL::Mixer::Samples::load_WAV('data/sample.wav');
   unless( $sample)
   {
      Carp::croak "Cannot load file data/sample.wav: ".SDL::get_error(); 
   }

   +my $playing_channel = SDL::Mixer::Channels::play_channel( -1, $sample, 0 );

play_channel allows us to assign a sample to the channel -1 which indicates any open channel. 0 indicates we want to play the sample only once.

Note that since the sound will be playing in an external process we will need to keep the perl script running. In a game this is no problem but for a single script like this we can just use a simple sleep function. Once we are done we can go ahead and close the audio device.

   +sleep(1);
   +SDL::Mixer::close_audio();

Streaming Music

Next we will use SDL::Mixer::Music to add a background music to our script here.

    use SDL::Mixer::Channels;
    +use SDL::Mixer::Music;

    +#Load our awesome music from U<http://8bitcollective.com>
    +my $background_music = 
        +         SDL::Mixer::Music::load_MUS('data/music/01-PC-Speaker-Sorrow.ogg');


    +unless( $background_music )
    +{
    +   Carp::croak "Cannot load music file data/music/01-PC-Speaker-Sorrow.ogg: ".SDL::get_error() ;
    +}

Music types in SDL::Mixer run in a separate channel from our samples which allows us to have sound effects (like jump, or lasers etc) to play at the same time.

   +SDL::Mixer::Music::play_music($background_music,0); 

play_music also takes a parameter for how many loops you would like to play the song for, where 0 is 1.

To stop the music we can call halt_music.

    sleep(2);
   +SDL::Mixer::Music::halt_music();
    SDL::Mixer::close_audio();

Code so far

Sound Applications

Now that we know how to prepare and play simple sounds we will apply it to an SDLx::App.

SDLx::App Audio Initialization

SDLx::App will initialize everything normally for us. However for a stream line application it is recommend to initialize only the things we need. In this case that is SDL_INIT_VIDEO and SDL_INIT_AUDIO.

    use strict;
    use warnings;
    use SDL;
    use Carp;
    use SDLx::App;
    use SDL::Audio;
    use SDL::Mixer;
    use SDL::Event;
    use SDL::Events;
    use SDL::Mixer::Music;
    use SDL::Mixer::Samples;
    use SDL::Mixer::Channels;

    my $app = SDLx::App->new(
        init  => SDL_INIT_AUDIO | SDL_INIT_VIDEO,
        width => 250,
        height => 75,
        title => "Sound Event Demo",
        eoq   => 1
  
  );

Loading Resources

It is highly recommended to perform all resource allocations before a SDLx::App::run() method is called.

    # Initialize the Audio
    unless ( SDL::Mixer::open_audio( 44100, AUDIO_S16SYS, 2, 4096 ) == 0 ) {
        Carp::croak "Cannot open audio: " . SDL::get_error();
    }

    #Something to show while we play music and sounds
    my $channel_volume = 100;
    my $music_volume   = 100;
    my $laser_status   = 'none';
    my $music_status   = 'not playing';

    # Load our sound resources
    my $laser = SDL::Mixer::Samples::load_WAV('data/sample.wav');
    unless ($laser) {
        Carp::croak "Cannot load sound: " . SDL::get_error();
    }

    my $background_music =
    SDL::Mixer::Music::load_MUS('data/music/01-PC-Speaker-Sorrow.ogg');
    unless ($background_music) {
        Carp::croak "Cannot load music: " . SDL::get_error();
    }

The Show Handler

For the purposes of describing the current state of the music lets draw text to the screen in a show_handler.

    $app->add_show_handler(
    sub { 

        $app->draw_rect([0,0,$app->w,$app->h], 0 );

        $app->draw_gfx_text( [10,10], [255,0,0,255], "Channel Volume : $channel_volume" );
        $app->draw_gfx_text( [10,25], [255,0,0,255], "Music Volume   : $music_volume" );
        $app->draw_gfx_text( [10,40], [255,0,0,255], "Laser Status   : $laser_status" );
        $app->draw_gfx_text( [10,55], [255,0,0,255], "Music Status   : $music_status" );

        $app->update();

    }
    );

This will draw the channel volume of our samples, and the volume of the music. It will also print the status of our two sounds in the application.

The Event Handler

Finally our event handler will do the actual leg work and trigger the music and sound as we need it.

    $app->add_event_handler(
        sub {
            my $event = shift;

            if ( $event->type == SDL_KEYDOWN ) {
                my $keysym  = $event->key_sym;
                my $keyname = SDL::Events::get_key_name($keysym);

                if ( $keyname eq 'space' ) {

                    $laser_status = 'PEW!';
                    #fire lasers!
                    SDL::Mixer::Channels::play_channel( -1, $laser, 0 );

                }
                elsif ( $keyname eq 'up' ) {
                    $channel_volume += 5 unless $channel_volume == 100;
                }
                elsif ( $keyname eq 'down' ) {
                    $channel_volume -= 5 unless $channel_volume == 0;
                }
                elsif ( $keyname eq 'right' ) {
                    $music_volume += 5 unless $music_volume == 100;
                }
                elsif ( $keyname eq 'left' ) {
                    $music_volume -= 5 unless $music_volume == 0;
                }
                elsif ( $keyname eq 'return' ) {
                    my $playing = SDL::Mixer::Music::playing_music();
                    my $paused  = SDL::Mixer::Music::paused_music();

                    if ( $playing == 0 && $paused == 0 ) {
                        SDL::Mixer::Music::play_music( $background_music, 1 );
                        $music_status = 'playing';
                    }
                    elsif ( $playing && !$paused ) {
                        SDL::Mixer::Music::pause_music();
                        $music_status = 'paused'
                    }
                    elsif ( $playing && $paused ) {
                        SDL::Mixer::Music::resume_music();
                        $music_status = 'resumed playing';
                    }

                }

                SDL::Mixer::Channels::volume( -1, $channel_volume );
                SDL::Mixer::Music::volume_music($music_volume);

            }

          }

    );

The above event handler fires the laser on pressing the 'Space' key. Go ahead and press it multiple times as if you are firing a gun in a game! You will notice that depending on how fast you fire the laser the application will still manage to overlap the sounds as needed. The sample overlapping is accomplished by requiring multiple channels in the open_audio call. If your game has lots of samples that may play at the same time you may need more channels allocated. Additionally you can see that the volume control is easily managed both on the channels and the music with just incrementing or decrementing a value and calling the appropriate function.

Finally it is worth noticing the various state the background music can be in.

Lets run this application and the make sure to clean up the audio on the way out. $app->run(); SDL::Mixer::Music::halt_music(); SDL::Mixer::close_audio;

Completed Code

Music Visualizer

The final example in this section will be making a music visualizer that will take real time sound data and showing the wave form on our screen. It will look something like:

The Code and Comments

        use strict;
        use warnings;

        use Cwd;
        use Carp;
        use File::Spec;

        use threads;
        use threads::shared;

        use SDL;
        use SDL::Event;
        use SDL::Events;

        use SDL::Audio;
        use SDL::Mixer;
        use SDL::Mixer::Music;
        use SDL::Mixer::Effects;

        use SDLx::App;

Start the application with audio and video support.

        my $app = SDLx::App->new(
                init   => SDL_INIT_AUDIO | SDL_INIT_VIDEO,
                width  => 800,
                height => 600,
                depth  => 32,
                title  => "Sound Event Demo",
                eoq    => 1,
                dt     => 0.2,
        );

Initializing the Audio with a specific format that we know. Using AUDIO_S16 provides us with a 16 bit signed integer array for the stream data.

        # Initialize the Audio
        unless ( SDL::Mixer::open_audio( 44100, AUDIO_S16, 2, 1024 ) == 0 ) {
                Carp::croak "Cannot open audio: " . SDL::get_error();
        }

We will load all *.ogg music files from our data directory.

        # Load our music files
        my $data_dir = '.';
        my @songs = glob 'data/music/*.ogg';

Next we will make a music effect to read our stream data, we need to use a shared lock and array.

        my $stream_data :shared = '';
        my $stream_lock :shared = 0;

        # Music Effect to pull Stream Data 
        sub music_data {
                my $channel = shift;
                my $samples = shift;
                my $position = shift;
                my @stream = @_;

                if( $stream_lock != 0 ) 
                {
                        $stream_data = join ',', @stream;
                        $stream_lock = 0;
                }
                return @stream;
        }

We hook the callback into SDL::Mixer::Effects.

        my $music_data_effect_id = 
                  SDL::Mixer::Effects::register( MIX_CHANNEL_POST, "main::music_data",
                        "main::done_music_data", 0 );

The drawing callback for the SDLx::App when a song is playing. We read the stream data and show it on the screen as a wave form.

The number of lines to show in our visualizer.

    my $lines = $ARGV[0];
       $lines = 50 unless $lines;

        # Music Playing Callbacks 
        my $current_song = 0;

        my $current_music_callback = sub { 
                my( $delta, $app ) = @_;

                
                if( $stream_lock == 0  ){

                $app->draw_rect([ 0, 0, $app->w(), $app->h()], 0x000000FF );
                $app->draw_gfx_text([5,$app->h()-10], [255,0,0,255], "Playing Song: ".$songs[$current_song-1]);

                                my @stream = split( ',', $stream_data );
                                $stream_data = '';
                                my @left;
                                my @right;
                                my $cut =  $#stream/$lines;
                                my @x;
                                                   
                                my $l_wdt= ( $app->w() / $lines) /2;
                                
                                
                                for ( my $i = 0 ; $i < $#stream ; $i += $cut ) {

                                        my $left  = $stream[$i];
                                        my $right = $stream[ $i + 1 ];

                                        my $point_y   = ( ( ($left) ) * $app->h()/4 / 32000 ) + ($app->h/2);
                                        my $point_y_r = ( ( ($right) ) * $app->h()/4 / 32000 )+ ($app->h/2);
                                        my $point_x   = ( $i / $#stream ) * $app->w;
                                        
                           
                                        SDL::GFX::Primitives::box_RGBA(  $app, $point_x-$l_wdt, 300,
                                                                                                         $point_x+$l_wdt, $point_y, 
                                                                                                         40, 0, 255, 128 );

                                        SDL::GFX::Primitives::box_RGBA(  $app, $point_x-$l_wdt, 300, 
                                                                                                         $point_x+$l_wdt, $point_y_r , 
                                                                                                         255, 0, 40, 128 );

                                }
                        
                
                        $stream_lock = 1;
                }
                $app->flip();
        };
When a song is finished playing we remove the show handler and hook on another callback to switch to the
next song gracefully. 

        my $cms_move_callback_id;
        my $pns_move_callback_id; 

        sub music_finished_playing { 

                SDL::Mixer::Music::halt_music();
                $pns_move_callback_id = $app->add_move_handler( $play_next_song_callback ); 
                $app->remove_show_handler($cms_move_callback_id); 

        }

        my $play_next_song_callback = sub {

                $app->stop() if $current_song > $#songs;
                my $song = SDL::Mixer::Music::load_MUS($songs[$current_song++]);
                
                SDL::Mixer::Music::hook_music_finished('main::music_finished_playing');
                SDL::Mixer::Music::play_music($song, 0 );

                $app->remove_move_handler( $pns_move_callback_id ) if defined $pns_move_callback_id;
                $cms_move_callback_id = $app->add_show_handler( $current_music_callback );
        };
Add the first callback to get our first song.

        $pns_move_callback_id= $app->add_move_handler( $play_next_song_callback);

Add a event handler allows you to move through the songs.

        $app->add_event_handler(
        sub {
                my ($event, $app) = @_;

                if( $event->type == SDL_KEYDOWN && $event->key_sym == SDLK_DOWN)
                {       
                        #Indicate that we are done playing the music_finished_playing
                        music_finished_playing();
                }

        }
        );

        $app->run();

Gracefully stop SDL::Mixer.

        SDL::Mixer::Effects::unregister( MIX_CHANNEL_POST, $music_data_effect_id );
        SDL::Mixer::Music::hook_music_finished();
        SDL::Mixer::Music::halt_music();
        SDL::Mixer::close_audio();

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 1:

Unknown directive: =head0

Jump to Line
Something went wrong with that request. Please try again.