Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

Re^2: Playing sounds

by haukex (Abbot)
on Nov 09, 2017 at 22:09 UTC ( #1203085=note: print w/replies, xml ) Need Help??

in reply to Re: Playing sounds
in thread Playing sounds

I'm not sure which node you are replying to (there are per-node "reply" links next to each post), but I'll reply anyway.

But to have any hope of running my program on other machines, never mind other systems, I'd rather avoid that.

AFAIK sound is always going to be an OS-dependent thing, so any Perl code would at some level have to access an external OS-dependent library or tool. Yes, running an external program just to play a sound is a bit wasteful, but you said this was for a game, and unless you're playing hundreds of sounds a second, or you need high precision in playback (precise timing down to the millisecond of when the sounds are played), or this is going to be run on very low-end machines, then IMHO using an external program is probably fine, especially for background music. (Update: On the other hand, see Discipulus's and zentara's comments about SDL here.)

If it's running an external process anyway, I might as well do it with afplay, right?

Personally I'd look at it differently: If I'm going to be running an external process anyway, I'd like to use the same one no matter which OS, then I will only have to handle one interface (command line arguments etc.). Then, the "only" issues I'd have to worry about are possibly pathname issues, which can be handled with a cross-platform tool like Path::Class or at least the core File::Spec, and making it as easy as possible for a user of my program to install that external tool. Perhaps you can try installing the previously mentioned mpg123 or SoX on different machines to see how easily their installers handle, how easy it is for your Perl script to call those external programs after their installation (potential PATH issues etc.), and so on.

You might just simply document for your users, "if you want sound support, install package X and set configuration option Y to the installation path", and have your program gracefully handle the case of the sound playback tool not being installed (if having no sound is an option for you). Or, if your program has an installer, and you wanted to go this far, the installers of the audio software may support a "quiet" installation, which you can run during your program's installation, and depending on the license terms you might even distribute the audio package contained within yours.

As for your code, I don't quite understand why you want to go through an external file - is that for interprocess communication, or will your user be editing that file? Note that both aforementioned players can play multiple files one after the other if given on the command line, and play a single file multiple times (e.g. play sound.mp3 repeat 2 and mpg123 --loop 3 sound.mp3 both play the same file three times). If you want to monitor the playback and re-start after it is finished, then yes, one way to do that would be to fork a child and have it block until the sound stops playing, but on the other hand, since I assume your code is probably based on an event loop, then regularly polling the state of the playback of the music and re-starting if necessary is also an option (that's what I do in my code below). Anyway, as a fun little project I whipped up an OO package based on the code I linked to earlier (only tested on Linux so far):

{ # ---8<--- put this in file if you like ---8<--- package Sound; use warnings; use strict; use Carp; use IPC::Run qw/start/; use Scalar::Util qw/weaken/; our $DEFAULT_PLAYER = ['play','-q']; # SoX my @all_sounds; my %NEW_KNOWN_ARGS = map {$_=>1} qw/file player/; sub new { my $class = shift; my %self = (player => $DEFAULT_PLAYER); $self{file} = shift if @_%2; my %args = @_; $NEW_KNOWN_ARGS{$_} or croak "new: unknown arg '$_'" for keys %args; carp "new: file specified twice (or odd number of arguments)" if defined $self{file} && defined $args{file}; defined $args{$_} and $self{$_} = $args{$_} for keys %args; croak "new: player must be a nonempty arrayref" if ref $self{player} ne 'ARRAY' || @{$self{player}}<1; croak "new: no file specified" unless defined $self{file}; my $self = bless \%self, $class; push @all_sounds, $self; weaken $all_sounds[-1]; return $self; } sub stop_all_Sounds { # package function, stops *all* Sounds globally! defined and $_->stop for @all_sounds; } sub play { my $self = shift; $self->stop if $self->is_playing; $self->{harness} = start [ @{$self->{player}}, ref $self->{file} eq 'ARRAY' ? @{$self->{file}} : $self->{file} ]; return $self; } sub is_playing { my $h = shift->{harness}; return unless defined $h; return 1 if $h->pumpable; $h->finish; return; } sub wait { my $h = shift->{harness}; return unless defined $h; return $h->finish; } sub stop { my $h = shift->{harness}; return unless defined $h; $h->signal('INT'); return $h->finish; } sub DESTROY { shift->stop; # take this opportunity to clean up the array a bit @all_sounds = grep {defined} @all_sounds; # need to re-weaken as per Scalar::Util docs weaken $_ for @all_sounds; } 1; } # --->8--- end package Sound --->8--- BEGIN { $INC{''}++ } # ignore; just for inlining the package # ----- ----- Demo ----- ----- use warnings; use strict; use Sound; # Note: my "short" sound is ~2.3s long my $snd_short = Sound->new('/tmp/short.mp3'); my $snd_long = Sound->new('/tmp/long.mp3'); local $SIG{INT} = sub { print "Caught SIGINT, stopping and exiting...\n"; Sound->stop_all_Sounds(); exit }; print "Playing long sound in background, and sleeping 10s...\n"; $snd_long->play; sleep 10; print "Playing short sound, blocking until done...\n"; $snd_short->play->wait; print "Sleeping 3s...\n"; sleep 3; print "Playing short sound in background, not sleeping...\n"; $snd_short->play; print "Re-starting playback of long sound...\n"; $snd_long->play; print "Waiting for playback of short sound to finish...\n"; $snd_short->wait; print "Sleeping 5s (long sound playback will continue)...\n"; sleep 5; # $snd_long will stop playing when the variable goes out of scope # (in this case, when the script ends) my $snd_two = Sound->new( # multiple files played one after another file => ['/tmp/short.mp3', '/tmp/short.mp3'], # player can be specified with absolute path, or no path # if the binary is in the PATH environment variable player => ['/usr/bin/mpg123','-q'] ); print "Playing second short sound in background...\n"; $snd_two->play; for (1..10) { # loop for roughly 10 seconds print "Monitoring playback (loop $_, sleeping)...\n"; sleep 1; if (not $snd_two->is_playing) { print "Playback ended, re-starting with long sound...\n"; $snd_two = Sound->new('/tmp/long.mp3')->play; } } print "Stopping second sound...\n"; $snd_two->stop; print "Script done.\n";

(BTW, yet another option for commandline playback is from the powerful FFmpeg or libav packages, the command line tools ffplay/avplay can be used like so: avplay -vn -nodisp -autoexit -nostats -loglevel quiet sound.mp3, I believe the options are identical for ffplay.)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1203085]
What's the matter? Cat got your tongue?...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (9)
As of 2018-07-17 18:04 GMT
Find Nodes?
    Voting Booth?
    It has been suggested to rename Perl 6 in order to boost its marketing potential. Which name would you prefer?

    Results (374 votes). Check out past polls.