7

Part of my code goes like this:

while(1){
        my $winmm = new Win32::MediaPlayer;   
        $winmm->load('1.mp3'); $winmm->play; $winmm->volume(100);
        Do Some Stuff;
        last if some condition is met;
    }

Problem is: I want the music to be always on when I'm in the Do Some Stuff stage in the while loop. But the length of the music is so short that it will come to a full stop before I go to the next stage, so I want the music to repeat itself, but the Win32::Mediaplayer module does not seem to have a repeat mode, so I'm thinking of doing an infinite loop for the music playing part. Like this:

while(1){
 my $winmm = new Win32::MediaPlayer;   
 $winmm->load('1.mp3'); $winmm->play; $winmm->volume(100);
}
while(2){
Do some stuff;
last if some condition is met
}

But based on my current Perl knowledge if I'm in the while(1) part, I can never go to the while(2) part. Even if it comes to a nested loop, I have to do something to break out of the inside loop before going to the other part of the outside loop.

The answer to my question "Can we run two simultaneous non-nested loops in Perl?" may be a NO, but I assume there is some way of handling such situation. Correct me if I'm wrong.

Thanks as always for any comments/suggestions :)

UPDATE

I really appreciate the help from everyone. Thanks :) So the answer to my question is a YES, not a NO. I'm happy that I've learned how to use fork() and threads to solve a real problem :)

Mike
  • 1,841
  • 5
  • 24
  • 34

3 Answers3

10

You can run the two different processes in separate threads.

Something along the lines of:

use strict;
use warnings;
use threads;
use threads::shared;
use Win32::MediaPlayer;


my $killAudio :shared = undef;
my $audio = threads->create(\&playAudio);
my $condition = threads->create(\&doSomething,$audio);
$condition->join();

sub playAudio {

    my $winmm = new Win32::MediaPlayer;
    $winmm->load('1.mp3') or die 'Could not load file: $!';
    $winmm->volume(100);
    $winmm->play until $killAudio;
}

sub doSomething {
    my $thread = shift;
    my $conditionMet = undef;

    while (1) {
        ($conditionMet,$killAudio) = doSomeStuff();    # set doSomeStuff() to
                                                       # return only when 
                                                       # conditions are met

        $thread->join() if $killAudio;        # This line will terminate $audio
        last if $conditionMet;
    }
}

UPDATE

Based on Mike's comment below, the playAudio() subroutine can be rewritten as:

sub playAudio {
    my $winmm = new Win32::MediaPlayer;
    $winmm->load('1.mp3') or die 'Could not load file: $!';
    while (1) {
        $winmm->play;
        $winmm->volume(100);
        sleep($winmm->length/1000);
        last if $killAudio;
    }
}
Zaid
  • 36,680
  • 16
  • 86
  • 155
  • @Zaid, this also looks like a very promising solution to my problem. Thanks a lot. I'll try this threading thing too. Thanks :) – Mike Mar 11 '10 at 08:14
  • @Mike: Also, pay attention to the `playAudio()` sub, the creation of new `$winmm`s is unnecessary for every iteration of the while loop. I've updated the code to reflect that. – Zaid Mar 11 '10 at 08:34
  • 1
    @Zaid, thanks to your answer, I've now learned how to use threads to solve a real problem. Had some trouble making the playAudio sub working though. To make the music play, I changed the sub to this: sub playAudio { my $winmm = new Win32::MediaPlayer; $winmm->load('1.mp3') or die 'Could not load file: $!'; while(1){ $winmm->play; $winmm->volume(100); sleep(4); last if $killAudio; } } In the test, the music lasts about 4 seconds. Otherwise, the music simply refuses to play. Anyway I'm happy that I've solved the problem. – Mike Mar 12 '10 at 03:30
  • 1
    @Mike: You're welcome! I've just taken a look at the `Win32::MediaPlayer` module documentation on CPAN. You could use `sleep($winmm->length/1000);` to allow the code to pause for long enough to play the entire audio file. (And `use Time::HiRes;` + `usleep($winmm->length);` would have the same result) – Zaid Mar 12 '10 at 07:03
  • The combination of use Time::HiRes qw(usleep); and usleep($winmm->length); somehow does not work as expected. – Mike Mar 12 '10 at 07:40
  • Just checked perldoc. Looks like usleep($winmm->length);" works on microseconds. – Mike Mar 12 '10 at 07:47
  • And $length = $winmm->length; # Return the length in micro second integer. Weird. Why it isn't working – Mike Mar 12 '10 at 07:48
  • @Mike: Try `usleep` with a constant like 4000, to check it – Zaid Mar 12 '10 at 08:28
  • @Zaid, I've figured out why. 4000 doesn't work. 4,000,000 works. – Mike Mar 12 '10 at 09:08
  • @Mike: Ah yes, of course, μ is 1E-6, not 1E-3. So that should actually be `usleep(1000*$winmm->length);`, which isn't much better than the solution I posted. Anyway... – Zaid Mar 12 '10 at 10:10
  • @Zaid - +1 (not like this excellent answer needs another +1, but consider it a fee for prompting me to fix-up the Getopt::Long answer >=) – DVK Jun 09 '10 at 14:38
  • @DVK : Thanks a lot, this one's been aching for a 'Nice Answer' badge for quite some time now ;) – Zaid Jun 09 '10 at 14:46
  • @Zaid - heh... I must admit that consideration did indeed figure in my picking which of your Perl asnwers to upvote :) – DVK Jun 09 '10 at 14:48
6

You may try to fork, like this:

So you will create 2 threads, one of which will play your music and second one will do all your other stuff.

You should also think on terminating your music thread by some condition

    my @child_pids = ();
    my $pid = fork();
    if ($pid)
    {
        # parent
        push(@child_pids, $pid);
        Do some stuff;
        last if some condition is met
    }
    elsif ($pid == 0)
    {
        # child
        while(1){
         my $winmm = new Win32::MediaPlayer;   
         $winmm->load('1.mp3'); $winmm->play; $winmm->volume(100);
        }
        exit(0);
    }
    else
    {
        die "couldn’t fork: $!\n";
    }
Miollnyr
  • 269
  • 2
  • 8
  • 1
    You are welcome. I have slightly updated code: now you can use saved pids to kill child process in parent when necessary. – Miollnyr Mar 11 '10 at 08:13
  • @user278023, Thanks to your answer, I'm glad that I've learned how to use fork() to solve a real problem. I had a little problem with this Win32::Mediaplayer module though. To make the music play repeatedly, I have to code in the while(1) loop like this: $winmm->load('1.mp3'); $winmm->play; $winmm->volume(100);sleep(4); The music lasts around 4 seconds in the test, without this sleep(4) line, the music simply refuses to play. Well, I'm glad that I've finally got the script working :) Thanks. – Mike Mar 12 '10 at 03:24
2

If your doSomething() stage fits naturally within a looping structure, you can simply check the status of the song periodically and seek back to the beginning if the song has ended -- admittedly a hack, but easy.

use strict;
use warnings;

use Win32::MediaPlayer;
my $winmm = Win32::MediaPlayer->new;

$winmm->load($ARGV[0]) or die $!;
$winmm->play;
my $end_of_song = $winmm->length;

# doSomething
for (1 .. 1000){
    # Perform difficult computations...
    sleep 2;

    # Has song ended?
    $winmm->seek(0) if $winmm->pos >= $end_of_song;
}
FMc
  • 41,963
  • 13
  • 79
  • 132
  • +1 for a solution without threads, no need to add more complexity than is needed – Eric Strom Mar 11 '10 at 14:01
  • @FM, on second thoughts, no. it doesn't fit the bill. the song ends before difficult computations ends. I want the song to be on before difficult computations ends. – Mike Mar 12 '10 at 03:40