Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
O Monks,

I have a simple perl script I've written that acts as an accelerating musical metronome. The idea is that if I need to practice, say, a few bars out of a piece of music to be able to play it faster, the metronome clicks at faster and faster tempos, while I'm sitting away from the computer.

Currently I'm using code like this:

select undef,undef,undef,$t;
to wait the appropriate amount of time between clicks. (The sleep function didn't have high enough resolution.) Also, there's a little bit of processing time required in order to play the sound, which I estimate on the fly using calls to `date +%s` and `date +%N`. In fact, this processing time is normally very small on my system (less than a millisecond).

OK, so my software is working great, but there's one problem: when xscreensaver comes on, it starts gobbling up cpu like crazy. My on-the-fly correction detects this, and tries to compensate for it, but it only succeeds on the average, so the clicks become very uneven. The silly part of all this is that there's really no need for my script and xscreensaver to be competing like crazy for cpu, because my script is using less than 1% of the time-averaged cpu.

All I really want is to be able to make sure that a shell command ("play foo.wav") happens at some well-defined time in the future, even if a cpu-hog process starts butting in. Is there any way to do this? (I also need to get the return value back from the shell command, because when the user hits control-C during the execution of that child process, the signal goes to the child, not the parent.)

This is all on Linux, and I'm not particularly concerned about portability to non-Unix systems, although I'd like it to avoid gratuitous Linuxisms. (I'm also wondering if there's some more portable way to do the equivalent of `date +%s`, which is a GNU extension to the date command.)

Of course I can temporarily kill the xscreensaver daemon, but that's a hassle, and I'll forget to do it. Likewise I could log in as root and run my script as nice --18, but that's obviously a kludge.

TIA!

Since people have requested it, here's my code:

#!/usr/bin/perl # dependencies: # sox ("play" command) # festival (text to speech) use strict; use POSIX; use Time::HiRes; our $ogg_file = "/usr/share/apps/accelerando/sounds/metronome_click.og +g"; our $click_length = .0507; # seconds, the length of the ogg file our $decompressed_sound_file = POSIX::tmpnam().'.wav'; # ...temporary decompressed copy of the ogg file, for efficie +ncy; # cleaned up in the END block my $cmd = "sox $ogg_file $decompressed_sound_file"; system($cmd)==0 or die "Error executing this command: $cmd"; # The following only really works if a child process isn't running. If + it is, the child gets the signal, # and we just have to test the return value. $SIG{INT} = sub{ clean_up()}; $SIG{QUIT} = sub{ clean_up()}; my $player_delay = 0; # estimated time to play the wave file, in seconds; this will be upd +ated later, based on real-time # data about how fast we're actually going; on a modern system, putt +ing this at zero seems to # have no observable effect on the tempo my $initial = ask("Initial tempo",60); my $timesig = ask("Number of beats per bar",4); my $bars = ask("Number of bars before increasing the tempo",999999); my $add = ask("Amount to add to the tempo after the first time",0); my $between = ask("Beats between changes of tempo",1); if (1) { time_delay(7); # time for the musician to get ready, in seconds text_to_speech("ready")==0 or clean_up(); time_delay(1); text_to_speech("go")==0 or clean_up(); time_delay(1); } my $frac = 1+$add/$initial; print "The tempo will be increased by a factor of $frac each time.\n"; for (my $tempo=$initial; $tempo<500; $tempo*=$frac) { print int($tempo)." beats per minute\n"; text_to_speech(int($tempo))==0 or clean_up(); my $dt = 60/$tempo; my ($t,$last_t); for (my $bar=1; $bar<=$bars; $bar++) { for (my $beat=1; $beat<=$timesig; $beat++) { click()==0 or clean_up(); my $delay = $dt - $click_length - $player_delay; # seconds time_delay($delay); # because sleep() doesn't work for short tim +es $t = clock(); if (defined $last_t) { my $real_dt = $t-$last_t; # amount of time that actually elaps +ed #print "real_dt=$real_dt\n"; my $correction = $real_dt-$dt; # positive if we fell behind if ($correction>.05) {$correction=.05} # don't let it go crazy + if the cpu just got busy for a second $correction *= .5; # avoid undamped oscillations, etc. $player_delay += $correction; if ($player_delay<0) {$player_delay=0} #print "real_dt=$real_dt, player_delay=$player_delay, corr=$co +rrection, player_delay=$player_delay\n"; } $last_t = $t; } } time_delay($dt*$between); # seconds } exit; # automatically does a clean_up(), in END{} block #--------------------------------------------------------------------- +------------- sub time_delay { my $t = shift; # seconds #select undef,undef,undef,$t; # because sleep() doesn't work for sho +rt times Time::HiRes::usleep($t*1_000_000); } sub ask { my $prompt = shift; my $default = ''; my $show_default = ''; if (@_) {$default=shift; $show_default=" ($default)"} print "$prompt${show_default}?\n"; my $answer = <STDIN>; chomp $answer; if ($answer eq '') {$answer=$default} return $answer; } sub click { my $cmd = "play --silent $decompressed_sound_file"; # --silent means + not to print anything to stdout my $r= system($cmd); if ($r!=0) {print "Return code $r from $cmd\n"} return $r; } BEGIN { my $startup_time = seconds_since_epoch(); sub clock { my $s = seconds_since_epoch(); return sprintf "%d.%09d",($s-$startup_time),time_nanoseconds(); } sub seconds_since_epoch { # return `date +%s`; # GNU only my ($s,$usec) = Time::HiRes::gettimeofday(); return $s; } sub time_nanoseconds { # the nanoseconds part of the time since the +epoch # return `date +%N`; my ($s,$usec) = Time::HiRes::gettimeofday(); return $usec*1000; } } sub text_to_speech { my $text = shift; my $cmd = "echo '$text' | festival --tts"; my $r= system($cmd); if ($r!=0) {print "Return code $r from $cmd\n"} return $r; } sub clean_up { unlink $decompressed_sound_file; exit; } END { clean_up(); } 1;

In reply to making something happen in real time by bcrowell2

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (5)
As of 2024-04-19 14:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found