Metronome: Difference between revisions
Line 154: | Line 154: | ||
=={{header|Factor}}== |
=={{header|Factor}}== |
||
<lang factor>USING: accessors calendar circular colors.constants colors.hsv |
<lang factor>USING: accessors calendar circular colors.constants colors.hsv |
||
command-line continuations io kernel math math.parser namespaces |
|||
openal.example sequences system timers ui ui.gadgets |
|||
ui.pens.solid ; |
|||
IN: rosetta-code.metronome |
IN: rosetta-code.metronome |
||
Line 169: | Line 170: | ||
[ blink-gadget ] [ 0.3 play-sine blank-gadget ] 2bi ; |
[ blink-gadget ] [ 0.3 play-sine blank-gadget ] 2bi ; |
||
: |
: metronome-iteration ( gadget circular -- ) |
||
[ first play-note ] [ rotate-circular ] bi ; |
|||
gadget new { 200 200 } >>pref-dim |
|||
dup "Metronome" open-window yield ; |
|||
: metronome- |
TUPLE: metronome-gadget < gadget bpm notes timer ; |
||
[ |
|||
acquire [ play-note ] [ drop find-world handle>> ] 2bi |
|||
] curry with circular-loop ; |
|||
: |
: <metronome-gadget> ( bpm notes -- gadget ) |
||
\ metronome-gadget new swap >>notes swap >>bpm ; |
|||
: |
: metronome-quot ( gadget -- quot ) |
||
dup notes>> <circular> [ metronome-iteration ] 2curry ; |
|||
: |
: metronome-timer ( gadget -- timer ) |
||
[ |
[ metronome-quot ] [ bpm>> bpm>duration ] bi every ; |
||
: metronome |
M: metronome-gadget graft* ( gadget -- ) |
||
[ |
[ metronome-timer ] keep timer<< ; |
||
[ run-metronome ] 2curry [ stop-timer ] [ ] cleanup ; |
|||
: metronome- |
M: metronome-gadget ungraft* |
||
timer>> stop-timer ; |
|||
M: metronome-gadget pref-dim* drop { 200 200 } ; |
|||
: metronome-defaults ( -- bpm notes ) 60 { 440 220 330 } ; |
|||
: metronome-ui ( bpm notes -- ) <metronome-gadget> "Metronome" open-window ; |
|||
: metronome-example ( -- ) metronome-defaults metronome-ui ; |
|||
: validate-args ( int-args -- ) |
|||
[ length 2 < ] [ [ 0 <= ] any? ] bi or [ "args error" throw ] when ; |
|||
: (metronome-cmdline) ( args -- bpm notes ) |
|||
[ string>number ] map dup validate-args |
|||
unclip swap ; |
|||
: metronome-cmdline ( -- bpm notes ) |
|||
command-line get [ metronome-defaults ] [ (metronome-cmdline) ] if-empty ; |
|||
: print-defaults ( -- ) |
|||
metronome-defaults swap prefix |
|||
[ " " write ] [ number>string write ] interleave nl ; |
|||
: metronome-usage ( -- ) |
|||
"Usage: metronome [BPM FREQUENCIES...]" print |
|||
"Arguments must be non-zero" print |
|||
"Example: metronome " write print-defaults flush ; |
|||
: metronome-main ( -- ) |
|||
[ [ metronome-cmdline metronome-ui ] [ drop metronome-usage 1 exit ] recover ] with-ui ; |
|||
MAIN: metronome-main</lang> |
|||
=={{header|Haskell}}== |
=={{header|Haskell}}== |
Revision as of 12:04, 19 October 2013
You are encouraged to solve this task according to the task description, using any language you may know.
The task is to implement a metronome. The metronome should be capable of producing high and low audio beats, accompanied by a visual beat indicator, and the beat pattern and tempo should be configurable.
For the purpose of this task, it is acceptable to play sound files for production of the beat notes, and an external player may be used. However, the playing of the sounds should not interfere with the timing of the metronome.
The visual indicator can simply be a blinking red or green area of the screen (depending on whether a high or low beat is being produced), and the metronome can be implemented using a terminal display, or optionally, a graphical display, depending on the language capabilities. If the language has no facility to output sound, then it is permissible for this to implemented using just the visual indicator.
AutoHotkey
Rather basic implementation, but meets the requirements and is reasonably accurate. <lang AHK>bpm = 120 ; Beats per minute pattern = 4/4 ; duration = 100 ; Milliseconds beats = 0 ; internal counter
Gui -Caption
StringSplit, p, pattern, /
Start := A_TickCount
Loop { Gui Color, 0xFF0000 Gui Show, w200 h200 Na SoundBeep 750, duration beats++ Sleep 1000 * 60 / bpm - duration Loop % p1 -1 { Gui Color, 0x00FF00 Gui Show, w200 h200 Na SoundBeep, , duration beats++ Sleep 1000 * 60 / bpm - duration } }
Esc:: MsgBox % "Metronome beeped " beats " beats, over " (A_TickCount-Start)/1000 " seconds. " ExitApp</lang>
BBC BASIC
<lang bbcbasic> BeatPattern$ = "HLLL"
Tempo% = 100 *font Arial,36 REPEAT FOR beat% = 1 TO LEN(BeatPattern$) IF MID$(BeatPattern$, beat%, 1) = "H" THEN SOUND 1,-15,148,1 ELSE SOUND 1,-15,100,1 ENDIF VDU 30 COLOUR 2 PRINT LEFT$(BeatPattern$,beat%-1); COLOUR 9 PRINT MID$(BeatPattern$,beat%,1); COLOUR 2 PRINT MID$(BeatPattern$,beat%+1); WAIT 6000/Tempo% NEXT UNTIL FALSE</lang>
C
Using usleep
with self correcting delays. Audio is the bell character, which will definitely drive one insane (but I'm ok: my computer doesn't have the bell device). Invoke with ./a.out [beats_per_minute]
, default to 60.
<lang c>#include <stdio.h>
- include <stdlib.h>
- include <unistd.h>
- include <stdint.h>
- include <signal.h>
- include <time.h>
- include <sys/time.h>
struct timeval start, last;
inline int64_t tv_to_u(struct timeval s) { return s.tv_sec * 1000000 + s.tv_usec; }
inline struct timeval u_to_tv(int64_t x) { struct timeval s; s.tv_sec = x / 1000000; s.tv_usec = x % 1000000; return s; }
void draw(int dir, int64_t period, int64_t cur, int64_t next) { int len = 40 * (next - cur) / period; int s, i;
if (len > 20) len = 40 - len; s = 20 + (dir ? len : -len);
printf("\033[H"); for (i = 0; i <= 40; i++) putchar(i == 20 ? '|': i == s ? '#' : '-'); }
void beat(int delay) { struct timeval tv = start; int dir = 0; int64_t d = 0, corr = 0, slp, cur, next = tv_to_u(start) + delay; int64_t draw_interval = 20000; printf("\033[H\033[J"); while (1) { gettimeofday(&tv, 0); slp = next - tv_to_u(tv) - corr; usleep(slp); gettimeofday(&tv, 0);
putchar(7); /* bell */ fflush(stdout);
printf("\033[5;1Hdrift: %d compensate: %d (usec) ", (int)d, (int)corr); dir = !dir;
cur = tv_to_u(tv); d = cur - next; corr = (corr + d) / 2; next += delay;
while (cur + d + draw_interval < next) { usleep(draw_interval); gettimeofday(&tv, 0); cur = tv_to_u(tv); draw(dir, delay, cur, next); fflush(stdout); } } }
int main(int c, char**v) { int bpm;
if (c < 2 || (bpm = atoi(v[1])) <= 0) bpm = 60; if (bpm > 600) { fprintf(stderr, "frequency %d too high\n", bpm); exit(1); }
gettimeofday(&start, 0); last = start; beat(60 * 1000000 / bpm);
return 0; }</lang>
Factor
<lang factor>USING: accessors calendar circular colors.constants colors.hsv command-line continuations io kernel math math.parser namespaces openal.example sequences system timers ui ui.gadgets ui.pens.solid ; IN: rosetta-code.metronome
- bpm>duration ( bpm -- duration ) 60 swap / seconds ;
- blink-gadget ( gadget freq -- )
1.0 1.0 1.0 <hsva> <solid> >>interior relayout-1 ;
- blank-gadget ( gadget -- )
COLOR: white <solid> >>interior relayout-1 ;
- play-note ( gadget freq -- )
[ blink-gadget ] [ 0.3 play-sine blank-gadget ] 2bi ;
- metronome-iteration ( gadget circular -- )
[ first play-note ] [ rotate-circular ] bi ;
TUPLE: metronome-gadget < gadget bpm notes timer ;
- <metronome-gadget> ( bpm notes -- gadget )
\ metronome-gadget new swap >>notes swap >>bpm ;
- metronome-quot ( gadget -- quot )
dup notes>> <circular> [ metronome-iteration ] 2curry ;
- metronome-timer ( gadget -- timer )
[ metronome-quot ] [ bpm>> bpm>duration ] bi every ;
M: metronome-gadget graft* ( gadget -- )
[ metronome-timer ] keep timer<< ;
M: metronome-gadget ungraft*
timer>> stop-timer ;
M: metronome-gadget pref-dim* drop { 200 200 } ;
- metronome-defaults ( -- bpm notes ) 60 { 440 220 330 } ;
- metronome-ui ( bpm notes -- ) <metronome-gadget> "Metronome" open-window ;
- metronome-example ( -- ) metronome-defaults metronome-ui ;
- validate-args ( int-args -- )
[ length 2 < ] [ [ 0 <= ] any? ] bi or [ "args error" throw ] when ;
- (metronome-cmdline) ( args -- bpm notes )
[ string>number ] map dup validate-args unclip swap ;
- metronome-cmdline ( -- bpm notes )
command-line get [ metronome-defaults ] [ (metronome-cmdline) ] if-empty ;
- print-defaults ( -- )
metronome-defaults swap prefix [ " " write ] [ number>string write ] interleave nl ;
- metronome-usage ( -- )
"Usage: metronome [BPM FREQUENCIES...]" print "Arguments must be non-zero" print "Example: metronome " write print-defaults flush ;
- metronome-main ( -- )
[ [ metronome-cmdline metronome-ui ] [ drop metronome-usage 1 exit ] recover ] with-ui ;
MAIN: metronome-main</lang>
Haskell
<lang Haskell>import Control.Concurrent import Control.Concurrent.MVar import System.Process (runCommand)
-- This program works only on the GHC compiler because of the use of -- threadDelay
data Beep = Stop | Hi | Low
type Pattern = [Beep]
type BeatsPerMinute = Int
minute = 60000000 -- 1 minute = 60,000,000 microseconds
-- give one of the following example patterns to the metronome function
pattern4_4 = [Hi, Low, Low, Low] pattern2_4 = [Hi, Low] pattern3_4 = [Hi, Low, Low] pattern6_8 = [Hi, Low, Low, Low, Low, Low]
-- use this version if you can't play audio, use Windows or don't -- have audio files to play -- beep :: Beep -> IO () -- beep Stop = return () -- beep Hi = putChar 'H' -- beep Low = putChar 'L'
-- use this version if you can and want to play audio on Linux using -- Alsa. Change the name of the files to those of your choice
beep Stop = return () beep Hi = putChar 'H' >> runCommand "aplay hi.wav &> /dev/null" >> return () beep Low = putChar 'L' >> runCommand "aplay low.wav &> /dev/null" >> return ()
tick :: MVar Pattern -> BeatsPerMinute -> IO () tick b i = do
t <- readMVar b case t of [Stop] -> return () x -> do mapM_ (\v -> forkIO (beep v) >> threadDelay (minute `div` i)) t tick b i
metronome :: Pattern -> BeatsPerMinute -> IO () metronome p i = do
putStrLn "Press any key to stop the metronome." b <- newMVar p _ <- forkIO $ tick b i _ <- getChar putMVar b [Stop]</lang>
Liberty BASIC
Requires two supplied wav files for accentuated & standard sounds. <lang lb> WindowWidth =230
WindowHeight =220
button #w.b1 "Start", [start], LR, 110, 90, 55, 20 button #w.b2 "Tempo", [tempo], LR, 180, 90, 55, 20 button #w.b3 "Pattern", [pattern], LR, 40, 90, 55, 20
open "Metronome" for graphics_nsb_nf as #w
#w "trapclose quit" #w "down" #w "fill darkblue ; backcolor darkblue ; color white"
tempo = 60 ' per minute interval =1000 /(tempo /60) ' timer works in ms tickCount = 0 ' cycle counter running = 1 ' flag for state bar$ = "HLLL" ' initially strong-weak-weak-weak count = len( bar$)
wait
sub quit w$
close #w$ end
end sub
[start]
if running =1 then running =0 #w.b1 "Stop" #w.b2 "!disable" #w.b3 "!disable" else running =1 #w.b1 "Start" #w.b2 "!enable" #w.b3 "!enable" end if if running =0 then timer interval, [tick] else timer 0 wait
[tempo]
prompt "New tempo 30...360"; tempo$ tempo =val( tempo$) tempo =min( tempo, 360) tempo =max( tempo, 30) interval =int( 1000 /(tempo /60)) wait
[pattern]
prompt "New Pattern, eg 'HLLL' "; bar$ count =len( bar$) if count <2 or count >8 then goto [pattern]
wait
[tick]
'beep and flash #w "place 115 40"
if mid$( bar$, tickCount +1, 1) ="H" then playwave "mHi.wav", async #w "backcolor blue ; color white ; circlefilled "; 20 -tickCount *2 else playwave "mLo.wav", async #w "backcolor cyan ; circlefilled "; 20 -tickCount *2 end if
#w "place 50 140 ; backcolor darkblue ; color white" #w "\ "; tempo; " beats /min." #w "place 85 160" #w "\"; bar$
#w "place 85 120" #w "\Beat # "; tickCount +1
#w "place 115 40" #w "color darkblue"
tickCount =( tickCount +1) mod count
#w "flush"
wait</lang>
Perl 6
This code only uses textual output, but any noise-generating commands may be substituted; as long as they are executed synchronously, and do not run longer than the specified duration, the timing loop will compensate, since the sequence operator is determining a list of absolute times for each sleep to target. <lang perl6>sub MAIN ($beats-per-minute = 72, $beats-per-bar = 4) {
my $duration = 60 / $beats-per-minute; my $base-time = now + $duration; my $i;
for $base-time, $base-time + $duration ... * -> $next-time { if $i++ %% $beats-per-bar { print "\nTICK"; } else { print " tick"; } sleep $next-time - now; }
}</lang> Sample run:
$ metronome 120 6 TICK tick tick tick tick tick TICK tick tick tick tick tick TICK tick tick tick tick tick TICK tick tick tick tick tick TICK tick tick^C
PicoLisp
A short beep (440 Hz, 40 msec) is produced in a child process, while a "pendulum" is swinging left and right. Hitting any key will stop it. <lang PicoLisp>(de metronome (Bpm)
(if (fork) (let Pid @ (for Pendulum '(" /" . ("^H^H\\ " "^H^H /" .)) (tell Pid 'call "/usr/bin/beep" "-f" 440 "-l" 40) (prin Pendulum) (T (key (*/ 30000 Bpm)) (tell Pid 'bye)) ) (prinl) ) (wait) ) )</lang>
Test: <lang PicoLisp>: (metronome 60)
/
-> NIL # A key was hit</lang>
PureBasic
Metronome features:
- A periodic graphical-metronomimic image.
- The wav file is included within the resulting executable as raw data.
- A milliseconds between each click field in order to assess accuracy.
- Fine tuning of the click-rate judged via the milliseconds between each click field.
- Volumn controls for when you just can't stand it anymore!
<lang PureBasic>
Structure METRONOMEs string_mil.i string_err.i string_bmp.i volumn.i image_metronome.i EndStructure
Enumeration
- STRING_MIL
- STRING_ERR
- STRING_BMP
- BUTTON_ERRM
- BUTTON_ERRP
- BUTTON_VOLM
- BUTTON_VOLP
- TEXT_TUNE
- TEXT_MIL
- TEXT_BMP
- BUTTON_START
- BUTTON_STOP
- WINDOW
- IMAGE_METRONOME
EndEnumeration
If Not InitSound()
MessageRequester("Error", "Sound system is not available", 0)
End
EndIf
- the wav file saved as raw data
- sClick
- [?sClick < address of label sClick:]
- Data.a $52,$49,$46,... [?eClick-?sClick the # of bytes]
- eClick
- [?eClick < address of label eClick:]
If Not CatchSound(0,?sClick,?eClick-?sClick) MessageRequester("Error", "Could not CatchSound", 0) End EndIf
If LoadFont(0,"tahoma",9,#PB_Font_HighQuality|#PB_Font_Bold) SetGadgetFont(#PB_Default, FontID(0)) EndIf
Procedure.i Metronome(*m.METRONOMEs) Protected j Protected iw =360 Protected ih =360 Protected radius =100 Protected originX =iw/2 Protected originY =ih/2 Protected BeatsPerMinute=*m\string_bmp.i Protected msError.i =*m\string_err.i Protected Milliseconds =int((60*1000)/BeatsPerMinute) Protected msDword.i =Milliseconds*2/12
If CreateImage(*m\image_metronome.i,iw,ih)
Repeat
- [sAngleA
- < prepare to read starting at address of label]
Restore sAngleA
st1=ElapsedMilliseconds()
- 1 | 90
; 2 6 | 75 75 ; 3 5 | 60 60 ; 4 | 45
- 90, 75, 60, 45, 60, 75 ; click
For j=1 to 6 st2=ElapsedMilliseconds() Read.f Angle.f If StartDrawing(ImageOutput(*m\image_metronome.i))
StartTime = ElapsedMilliseconds() Box(0,0,iw,ih,RGB(0,0,0)) CircleX=int(radius*cos(Radian(Angle))) CircleY=int(radius*sin(Radian(Angle))) LineXY(originX,originY, originX+CircleX,originY-CircleY,RGB(255,255,0)) Circle(originX+CircleX,originY-CircleY,10,RGB(255,0,0)) StopDrawing() ; don't forget to: StopDrawing() ! SetGadgetState(*m\image_metronome.i,ImageID(*m\image_metronome.i)) Endif
Delay(msDword-msError) Next While ElapsedMilliseconds()-st1<Milliseconds-msError.i Wend
SetGadgetText(*m\string_mil.i,str(ElapsedMilliseconds()-st1)) PlaySound(0) ; click
st3=ElapsedMilliseconds():err2=0
; 1 | 090 ; 2 6 | 105 105 ; 3 5 | 120 120
- 4 | 135
; 90,105,120,135,120,105 ; click
For j=1 to 6 st4=ElapsedMilliseconds() Read.f Angle.f If StartDrawing(ImageOutput(*m\image_metronome.i))
StartTime = ElapsedMilliseconds() Box(0,0,iw,ih,RGB(0,0,0)) CircleX=int(radius*cos(Radian(Angle))) CircleY=int(radius*sin(Radian(Angle))) LineXY(originX,originY, originX+CircleX,originY-CircleY,RGB(255,255,0)) Circle(originX+CircleX,originY-CircleY,10,RGB(255,0,0)) StopDrawing() ; don't forget to: StopDrawing() ! SetGadgetState(*m\image_metronome.i,ImageID(*m\image_metronome.i)) Endif
Delay(msDword-msError) Next While ElapsedMilliseconds()-st3<Milliseconds-msError.i Wend
SetGadgetText(*m\string_mil.i,str(ElapsedMilliseconds()-st3)) PlaySound(0) ; click
ForEver Endif ProcedureReturn i.i EndProcedure
- width of image
- height of image
- allocate memory for our structure
- the StringGadget number for milliseconds between clicks
- the ImageGadget number for the metronome
- intial value for the volumn [30 %]
- intial value adjustment for time accuracy adjustment
w =360 h =360
- m.METRONOMEs =Allocatememory(SizeOf(METRONOMEs))
- m\string_mil.i =#STRING_MIL
- m\image_metronome.i =#IMAGE_METRONOME
- m\volumn.i =30
DelayVal =5
SoundVolume(0,*m\volumn.i)
radius =100 originX =w/2 originY =h/2 Angle.f =90
- draw our initial metronome at 90 degrees
; * ; | ; | ; |
If CreateImage(#IMAGE_METRONOME,w,h) If StartDrawing(ImageOutput(#IMAGE_METRONOME))
Box(0,0,w,h,RGB(0,0,0)) CircleX=int(radius*cos(Radian(Angle))) CircleY=int(radius*sin(Radian(Angle))) LineXY(originX,originY, originX+CircleX,originY-CircleY,RGB(255,255,0)) Circle(originX+CircleX,originY-CircleY,10,RGB(255,0,0)) StopDrawing() ; don't forget to: StopDrawing() it's time for dinner Endif
Endif
- window style
WINDOW_STYLE=#PB_Window_SystemMenu WINDOW_STYLE|#PB_Window_Screencentered WINDOW_STYLE|#PB_Window_MinimizeGadget
If Not OpenWindow(#WINDOW,0,0,w+200+12,h+4,"Metronome",WINDOW_STYLE) MessageRequester("Error", "Not OpenWindow", 0) End EndIf SetWindowColor(#Window,$505050)
TEXT_STYLE=#PB_Text_Center|#PB_Text_Border
- data strings
i=2:wp=18:gh=24 i+1:TextGadget(#TEXT_TUNE ,w+wp-10 ,gh*(i-1),200,gh,"Fine tuning",TEXT_STYLE) i+1 i+1:StringGadget(#STRING_ERR,w+wp+100,gh*(i-1),90,gh,"-5",#PB_String_ReadOnly) i+1 i+1:StringGadget(#STRING_MIL,w+wp+100,gh*(i-1),90,gh,"0",#PB_String_ReadOnly) i+1 i+1:StringGadget(#STRING_BMP,w+wp+100,gh*(i-1),90,gh,"120",#PB_String_Numeric)
- control buttons
i=2:wp=10:gh=24 i+1 i+1 i+1:ButtonGadget(#BUTTON_ERRM ,w+wp,gh*(i-1),50,gh,"-") i+0:ButtonGadget(#BUTTON_ERRP ,w+wp+50,gh*(i-1),50,gh,"+") i+1 i+1:TextGadget(#TEXT_MIL ,w+wp,gh*(i-1),100,gh,"MilliSeconds ",TEXT_STYLE) i+1 i+1:TextGadget(#TEXT_BMP ,w+wp,gh*(i-1),100,gh,"BeatsPerMin " ,TEXT_STYLE) i+1 i+1:ButtonGadget(#BUTTON_START,w+wp,gh*(i-1),200,gh,"Start",#PB_Button_Toggle) i+1 i+1:ButtonGadget(#BUTTON_VOLM ,w+wp ,gh*(i-1),100,gh,"-Volumn") i+0:ButtonGadget(#BUTTON_VOLP ,w+wp+100,gh*(i-1),100,gh,"+Volumn")
- the metronome image
IMG_STYLE=#PB_Image_Border ImageGadget(#IMAGE_METRONOME,0,0,360,360,ImageID(#IMAGE_METRONOME),IMG_STYLE) SetGadgetState(#IMAGE_METRONOME,ImageID(#IMAGE_METRONOME))
Repeat
- the control loop for our application [not all the values are used
- but this makes it easy to add further controls and functionality]
msg= WaitWindowEvent () wid= EventWindow () mid= EventMenu () gid= EventGadget () etp= EventType () ewp= EventwParam () elp= EventlParam () :If msg=#PB_Event_CloseWindow : End : EndIf
- Esc kills application regardless of window focus
If GetAsyncKeyState_(#VK_ESCAPE) : End : EndIf
Select msg
case #PB_Event_Gadget
Select gid
Case #BUTTON_VOLP ; +volumn If *m\volumn.i<100:*m\volumn.i+10 SoundVolume(0,*m\volumn.i) EndIf
Case #BUTTON_VOLM ; -volumn
If *m\volumn.i>0 :*m\volumn.i-10 SoundVolume(0,*m\volumn.i) EndIf
Case #BUTTON_ERRP ; time accuracy adjustment [faster]
DelayVal-1 *m\string_err.i =DelayVal SetGadgetText(#STRING_ERR,str(0-DelayVal)) If GetGadgetState(#BUTTON_START)=1 GoSub Stop GoSub Start EndIf
Case #BUTTON_ERRM ; time accuracy adjustment [slower]
DelayVal+1 *m\string_err.i =DelayVal SetGadgetText(#STRING_ERR,str(0-DelayVal)) If GetGadgetState(#BUTTON_START)=1 GoSub Stop GoSub Start EndIf
Case #BUTTON_START ; the toggle button for start/stop
Select GetGadgetState(#BUTTON_START) Case 1 :GoSub Stop : GoSub Start :SetGadgetText(#BUTTON_START,"Stop") Case 0 :GoSub Stop :SetGadgetText(#BUTTON_START,"Start") EndSelect
EndSelect EndSelect ForEver End
Start: ; start up the thread with new values
- m\string_err.i =DelayVal
- m\string_bmp.i =Val(GetGadgetText(#STRING_BMP))
If *m\string_bmp.i
MetronomeThread=CreateThread(@Metronome(),*m) EndIf
Return
Stop: ; if the thread is running: stop it If IsThread(MetronomeThread)
KillThread(MetronomeThread) EndIf
Return
DataSection ; an array of angles to be read by: MetronomeThread sAngleA: Data.f 90, 75, 60, 45, 60, 75 ; click Data.f 90,105,120,135,120,105 ; click eAngleA: EndDataSection
DataSection ; a small wav file saved as raw data sClick: Data.a $52,$49,$46,$46,$2E,$08,$00,$00,$57,$41,$56,$45,$66,$6D,$74,$20 Data.a $10,$00,$00,$00,$01,$00,$01,$00,$44,$AC,$00,$00,$44,$AC,$00,$00 Data.a $01,$00,$08,$00,$64,$61,$74,$61,$02,$06,$00,$00,$83,$84,$84,$84 Data.a $85,$85,$86,$86,$88,$89,$8A,$8B,$8E,$91,$95,$9C,$A2,$A9,$B3,$C0 Data.a $CF,$CF,$D0,$CE,$D3,$9B,$47,$31,$31,$33,$32,$32,$32,$32,$33,$32 Data.a $33,$31,$42,$A1,$C9,$C4,$AE,$BD,$D4,$CD,$D1,$CF,$D0,$CF,$CF,$D0 Data.a $CB,$A9,$70,$37,$33,$32,$32,$32,$32,$32,$33,$32,$33,$31,$34,$2E Data.a $53,$AF,$CF,$CF,$CA,$CF,$D0,$CF,$D0,$CF,$D0,$CE,$D3,$AA,$83,$97 Data.a $A1,$8A,$44,$32,$33,$31,$33,$32,$33,$32,$32,$33,$32,$33,$30,$44 Data.a $83,$94,$7E,$7D,$AE,$CF,$D0,$CF,$D0,$CE,$D1,$BF,$B8,$C3,$B9,$B7 Data.a $98,$68,$47,$37,$30,$31,$33,$32,$33,$32,$33,$32,$3D,$49,$48,$3E Data.a $38,$43,$5C,$77,$87,$91,$95,$85,$78,$79,$7A,$80,$8D,$8F,$8A,$89 Data.a $8D,$8D,$88,$81,$83,$89,$7F,$73,$77,$7A,$71,$64,$55,$43,$31,$31 Data.a $34,$32,$34,$3A,$41,$36,$2F,$33,$37,$4D,$5C,$69,$73,$78,$7B,$82 Data.a $8A,$8F,$90,$91,$91,$94,$9B,$9C,$94,$86,$75,$67,$5A,$51,$50,$4D Data.a $45,$42,$41,$41,$43,$48,$51,$54,$59,$65,$75,$82,$87,$86,$83,$7B Data.a $6E,$67,$65,$63,$61,$5F,$5D,$56,$4E,$4B,$50,$57,$5F,$67,$71,$78 Data.a $7B,$7C,$7E,$82,$83,$80,$7C,$79,$76,$71,$6D,$6C,$68,$5E,$52,$4D Data.a $4A,$46,$43,$43,$47,$4B,$4B,$4D,$53,$59,$5D,$65,$6F,$79,$82,$8B Data.a $92,$93,$90,$8B,$88,$83,$7C,$7B,$7B,$76,$6F,$66,$5E,$59,$53,$51 Data.a $53,$55,$57,$59,$5B,$5B,$5A,$5A,$5B,$5E,$61,$67,$6D,$6E,$6C,$69 Data.a $6B,$6F,$72,$73,$75,$77,$79,$78,$79,$7B,$7C,$79,$76,$73,$72,$71 Data.a $6C,$64,$59,$51,$50,$52,$53,$50,$4A,$41,$3B,$3C,$46,$53,$61,$6B Data.a $6E,$6D,$70,$79,$83,$90,$9B,$A0,$9C,$94,$8C,$85,$7E,$7A,$76,$6F Data.a $62,$56,$4E,$48,$45,$48,$4D,$4D,$4C,$4E,$54,$5B,$64,$6F,$79,$80 Data.a $83,$82,$82,$85,$88,$88,$87,$84,$7D,$73,$69,$63,$60,$5D,$5A,$55 Data.a $51,$4E,$4E,$52,$58,$5F,$66,$6A,$6D,$73,$7D,$86,$8B,$8F,$8E,$87 Data.a $81,$80,$7F,$79,$72,$6B,$60,$54,$4D,$4C,$4E,$4E,$4F,$50,$52,$58 Data.a $5F,$67,$6F,$75,$79,$7A,$7B,$7E,$82,$81,$7F,$7C,$75,$6F,$6C,$6B Data.a $6C,$6E,$6F,$6C,$67,$65,$68,$6D,$73,$77,$77,$74,$70,$6C,$6B,$6E Data.a $72,$73,$6F,$67,$60,$5D,$5E,$61,$63,$64,$63,$61,$60,$63,$69,$70 Data.a $76,$79,$79,$7A,$7C,$7F,$81,$81,$80,$7E,$79,$72,$6E,$6B,$67,$65 Data.a $62,$5F,$5D,$5D,$5E,$5F,$62,$65,$6A,$6E,$71,$75,$78,$7B,$7C,$7D Data.a $7E,$7F,$7F,$7E,$7B,$77,$74,$6F,$6A,$67,$63,$5D,$56,$50,$49,$45 Data.a $43,$43,$46,$4B,$50,$55,$5A,$60,$69,$74,$81,$8E,$97,$9D,$9F,$9E Data.a $9C,$9B,$9A,$98,$93,$8A,$7D,$6E,$61,$58,$50,$4C,$49,$46,$45,$44 Data.a $46,$4C,$53,$5C,$66,$6F,$75,$7B,$80,$85,$88,$88,$87,$85,$82,$7E Data.a $7A,$75,$70,$6B,$66,$61,$5D,$5C,$5E,$61,$65,$69,$6B,$6B,$6B,$6A Data.a $6B,$6F,$73,$76,$76,$71,$6B,$64,$61,$62,$67,$6D,$71,$72,$72,$72 Data.a $74,$7A,$81,$85,$88,$87,$85,$82,$80,$7D,$79,$72,$6A,$63,$5F,$5F Data.a $60,$60,$5E,$5A,$58,$58,$5D,$64,$6D,$74,$77,$78,$77,$79,$7D,$82 Data.a $85,$86,$84,$80,$7C,$79,$78,$78,$75,$70,$6B,$66,$64,$66,$68,$6A Data.a $6B,$68,$64,$63,$66,$6C,$72,$76,$78,$78,$78,$78,$78,$7B,$7E,$80 Data.a $7F,$7D,$7B,$79,$74,$6F,$6A,$65,$63,$61,$5F,$5D,$5C,$5A,$58,$59 Data.a $5E,$66,$6D,$74,$79,$7C,$80,$84,$89,$8B,$8C,$8B,$87,$81,$7C,$77 Data.a $72,$6B,$63,$5B,$55,$52,$53,$55,$57,$59,$59,$5C,$62,$6B,$77,$82 Data.a $8B,$91,$93,$94,$97,$9A,$9B,$98,$92,$8A,$81,$77,$72,$6E,$6A,$65 Data.a $5E,$58,$56,$56,$57,$57,$54,$53,$53,$57,$5E,$67,$6D,$6F,$6E,$6E Data.a $74,$7F,$8D,$96,$98,$93,$8C,$89,$8B,$91,$97,$96,$8C,$7E,$71,$69 Data.a $67,$67,$65,$5D,$4F,$41,$39,$3B,$45,$51,$59,$5A,$56,$56,$5B,$69 Data.a $7B,$89,$90,$8F,$8A,$87,$86,$89,$8C,$8A,$84,$7B,$75,$75,$78,$7A Data.a $76,$6D,$63,$5E,$60,$68,$71,$74,$6F,$63,$57,$52,$58,$65,$73,$7B Data.a $7C,$77,$72,$72,$77,$81,$89,$8C,$88,$81,$7A,$76,$76,$76,$75,$70 Data.a $69,$62,$5F,$62,$68,$6D,$6D,$6A,$68,$68,$6C,$74,$7D,$84,$87,$86 Data.a $84,$84,$86,$89,$8A,$88,$85,$84,$83,$80,$7B,$74,$6B,$61,$5A,$58 Data.a $5A,$5C,$5C,$59,$55,$53,$55,$5B,$65,$72,$7D,$84,$86,$87,$89,$8C Data.a $92,$98,$9A,$99,$96,$90,$8C,$88,$83,$7F,$79,$72,$6C,$66,$63,$61 Data.a $5E,$59,$52,$4C,$4B,$4E,$55,$5E,$63,$66,$67,$69,$71,$7C,$88,$91 Data.a $93,$91,$8C,$88,$88,$8A,$8E,$8E,$88,$7E,$73,$6A,$67,$66,$66,$65 Data.a $62,$5B,$55,$52,$53,$58,$5F,$65,$69,$6B,$6F,$75,$7C,$83,$88,$8B Data.a $8B,$89,$8B,$8E,$8E,$8B,$85,$7E,$74,$6E,$6C,$6D,$6D,$6B,$67,$62 Data.a $5F,$60,$64,$67,$6A,$6B,$6C,$6C,$6F,$73,$75,$77,$78,$78,$79,$7C Data.a $82,$89,$8D,$8D,$8C,$8C,$8C,$8E,$91,$90,$8C,$85,$7C,$74,$6D,$68 Data.a $65,$62,$5E,$5B,$58,$58,$5C,$62,$68,$6C,$70,$75,$7C,$83,$8A,$90 Data.a $96,$98,$98,$96,$93,$8E,$8A,$84,$7F,$7A,$75,$6E,$67,$60,$59,$54 Data.a $52,$53,$57,$5B,$5D,$5E,$60,$65,$6C,$74,$7C,$87,$90,$98,$9D,$A1 Data.a $A2,$A1,$9F,$9C,$9A,$97,$92,$89,$7E,$70,$64,$5C,$57,$55,$53,$52 Data.a $50,$4F,$51,$56,$5D,$66,$6D,$73,$79,$7E,$82,$86,$89,$8B,$8D,$8D Data.a $8C,$89,$84,$7F,$7B,$79,$77,$77,$76,$73,$6E,$6A,$6A,$6F,$74,$78 Data.a $79,$78,$74,$72,$73,$76,$79,$7B,$7A,$75,$6F,$6A,$68,$69,$6B,$6C Data.a $6D,$6C,$6C,$6D,$6F,$74,$78,$7C,$80,$82,$85,$88,$8B,$8C,$8B,$88 Data.a $84,$81,$7F,$7D,$7B,$79,$75,$6F,$6A,$67,$65,$65,$68,$6B,$6D,$6F Data.a $70,$72,$74,$79,$7D,$81,$85,$87,$88,$89,$8A,$8C,$8D,$8D,$8B,$86 Data.a $82,$7F,$7D,$7C,$7A,$77,$73,$6F,$6A,$66,$65,$64,$65,$68,$6B,$6F Data.a $72,$74,$75,$76,$79,$7D,$83,$8A,$8D,$8C,$88,$82,$7D,$7A,$78,$76 Data.a $73,$6F,$69,$64,$60,$60,$62,$65,$68,$6B,$6F,$75,$7C,$84,$8A,$8E Data.a $90,$91,$92,$94,$94,$93,$90,$8D,$88,$80,$78,$6F,$68,$64,$62,$60 Data.a $5F,$5D,$5A,$58,$58,$5D,$68,$73,$7D,$83,$87,$89,$8C,$91,$96,$9B Data.a $9D,$9B,$95,$8C,$83,$7B,$76,$71,$6D,$68,$63,$5D,$58,$56,$57,$5B Data.a $62,$69,$6F,$75,$7B,$81,$86,$8C,$91,$95,$97,$97,$95,$93,$8F,$88 Data.a $80,$77,$6F,$69,$66,$63,$60,$5C,$58,$54,$53,$59,$62,$6D,$75,$7A Data.a $7C,$7E,$83,$8D,$98,$A0,$A2,$9E,$97,$90,$8E,$8F,$8F,$8D,$85,$79 Data.a $6E,$66,$65,$68,$6D,$6E,$6B,$65,$60,$60,$65,$6C,$74,$79,$7A,$79 Data.a $78,$79,$7C,$7E,$7F,$7F,$7F,$7F,$80,$82,$84,$84,$83,$82,$81,$83 Data.a $88,$8D,$91,$92,$8E,$88,$82,$7F,$7F,$7F,$7F,$7C,$75,$6A,$60,$59 Data.a $56,$56,$58,$59,$5A,$5A,$5E,$65,$6E,$79,$82,$8A,$91,$97,$9F,$A5 Data.a $AA,$AB,$A6,$9C,$93,$8A,$84,$7E,$76,$6B,$5E,$51,$48,$45,$45,$48 Data.a $4B,$4E,$51,$57,$60,$6E,$7C,$8A,$94,$9B,$A0,$A4,$A8,$AB,$AC,$A9 Data.a $A3,$98,$8C,$7F,$73,$6A,$62,$5A,$54,$4E,$4A,$48,$49,$4D,$55,$60 Data.a $6B,$77,$81,$8A,$92,$9A,$A1,$A9,$AD,$AF,$AD,$A7,$9F,$96,$8C,$84 Data.a $7B,$71,$67,$5C,$52,$4C,$49,$4B,$4F,$54,$5A,$60,$67,$71,$7D,$8A Data.a $96,$9F,$A4,$A6,$A6,$A5,$A4,$A2,$9C,$94,$89,$7F,$77,$70,$69,$62 Data.a $5A,$54,$4F,$50,$56,$5E,$65,$6B,$6E,$73,$79,$83,$8F,$98,$9F,$A1 Data.a $9F,$9C,$9A,$98,$96,$92,$8C,$84,$7A,$71,$6B,$67,$65,$64,$62,$61 Data.a $61,$62,$67,$6D,$73,$79,$7E,$80,$83,$85,$88,$8A,$8C,$8B,$89,$86 Data.a $84,$84,$84,$84,$82,$7E,$7A,$79,$7A,$7C,$7E,$7E,$7C,$7A,$77,$77 Data.a $79,$7C,$7E,$7D,$7B,$79,$79,$79,$7A,$7B,$7C,$7C,$7B,$7B,$7C,$7D Data.a $7D,$7C,$7B,$7A,$7B,$7C,$7D,$7D,$7B,$79,$77,$76,$77,$78,$79,$79 Data.a $78,$76,$75,$76,$79,$7A,$7A,$78,$76,$75,$77,$7C,$81,$80,$53,$41 Data.a $55,$52,$00,$02,$00,$00,$31,$2C,$20,$30,$2C,$20,$36,$2C,$20,$30 eClick: EndDataSection </lang>
Racket
<lang Racket>
- lang racket
(require racket/gui)
(define msec 500) (define sounds '("hi.wav" "lo.wav")) (define colors '("red" "green"))
(define f
(new frame% [label "Metronome"] [width 200] [height 200]))
(define c
(new (class canvas% (define brushes (map (λ(c) (new brush% [color c] [style 'solid])) colors)) (define cur 0) (define/override (on-paint) (send* (send this get-dc) (clear) (set-brush (list-ref brushes cur)) (draw-rectangle 0 0 200 200))) (define/public (flip!) (set! cur (modulo (add1 cur) (length sounds))) (play-sound (list-ref sounds cur) #f) (on-paint)) (super-new)) [parent f]))
(define (flip)
(define init (current-inexact-milliseconds)) (define next (+ msec init)) (define ticks 1) (let loop () (when (> (current-inexact-milliseconds) next) (set! ticks (add1 ticks)) (set! next (+ init (* msec ticks))) (queue-callback (λ() (send c flip!)))) (sleep 0.01) (loop)))
(send* f (center) (show #t)) (void (thread flip)) </lang>
REXX
These REXX program examples are modeled after the Perl 6 example.
textual visual, no sound
<lang rexx>/*REXX program simulates a visual (textual) metronome (with no sound).*/ parse arg bpm bpb dur . if bpm== | bpm==',' then bpm=72 /*number of beats per minute. */ if bpb== | bpb==',' then bpb= 4 /*number of beats per bar. */ if dur== | dur==',' then dur= 5 /*duration of run in seconds. */ call time 'R' /*reset the REXX elapsed timer. */ bt=1/bpb /*calculate a tock-time interval.*/
do until et>=dur; et=time('E') /*process tick-tocks for duration*/ say; call charout ,'TICK' /*show the first tick for period.*/ es=et+1 /*bump the elapsed time limiter. */ ee=et+bt do until time('E')>=es; e=time('E') if e<ee then iterate /*time for tock?*/ call charout ,' tock' /*show a "tock".*/ ee=ee+bt /*bump tock time*/ end /*until time('E')≥es*/ end /*until et≥dur*/</lang>
with sound, REGINA only
<lang rexx>/*REXX program simulates a metronome (with sound), Regina REXX only. */ parse arg bpm bpb dur tockf tockd tickf tickd . if bpm== | bpm==',' then bpm=72 /*number of beats per minute. */ if bpb== | bpb==',' then bpb= 4 /*number of beats per bar. */ if dur== | dur==',' then dur= 5 /*duration of run in seconds. */ if tockf==|tockf==',' then tockf=400 /*frequency of tock sound in HZ. */ if tockd==|tockd==',' then tockd= 20 /*duration of tock sound in msec*/ if tickf==|tickf==',' then tickf=600 /*frequency of tick sound in HZ. */ if tickd==|tickd==',' then tickd= 10 /*duration of tick sound in msec*/ call time 'R' /*reset the REXX elapsed timer. */ bt=1/bpb /*calculate a tock-time interval.*/
do until et>=dur; et=time('E') /*process tick-tocks for duration*/ call beep tockf,tockd /*sound a beep for the "TOCK". */ es=et+1 /*bump the elapsed time limiter. */ ee=et+bt do until time('E')>=es; e=time('E') if e<ee then iterate /*time for tock?*/ call beep tickf,tickd /*sound a tick.*/ ee=ee+bt /*bump tock time*/ end /*until time('E')≥es*/ end /*until et≥dur*/
</lang>
with sound, PC/REXX only
<lang rexx>/*REXX program simulates a metronome (with sound), PC/REXX only. */ parse arg bpm bpb dur tockf tockd tickf tickd . if bpm== | bpm==',' then bpm=72 /*number of beats per minute. */ if bpb== | bpb==',' then bpb= 4 /*number of beats per bar. */ if dur== | dur==',' then dur= 5 /*duration of run in seconds. */ if tockf==|tockf==',' then tockf=400 /*frequency of tock sound in HZ. */ if tockd==|tockd==',' then tockd=.02 /*duration of tock sound in secs*/ if tickf==|tickf==',' then tickf=600 /*frequency of tick sound in HZ. */ if tickd==|tickd==',' then tickd=.01 /*duration of tick sound in secs*/ call time 'R' /*reset the REXX elapsed timer. */ bt=1/bpb /*calculate a tock-time interval.*/
do until et>=dur; et=time('E') /*process tick-tocks for duration*/ call sound tockf,tockd /*sound a beep for the "TOCK". */ es=et+1 /*bump the elapsed time limiter. */ ee=et+bt do until time('E')>=es; e=time('E') if e<ee then iterate /*time for tock?*/ call sound tickf,tickd /*sound a tick.*/ ee=ee+bt /*bump tock time*/ end /*until time('E')≥es*/ end /*until et≥dur*/</lang>
Tcl
This code only rings the bell on the high beat, which occurs at the start of the bar. <lang tcl>package require Tcl 8.5
lassign $argv bpm bpb if {$argc < 2} {set bpb 4} if {$argc < 1} {set bpm 60}
fconfigure stdout -buffering none set intervalMS [expr {round(60000.0 / $bpm)}] set ctr 0
proc beat {} {
global intervalMS ctr bpb after $intervalMS beat ;# Reschedule first, to encourage minimal drift if {[incr ctr] == 1} {
puts -nonewline "\r\a[string repeat { } [expr {$bpb+4}]]\rTICK"
} else {
puts -nonewline "\rtick[string repeat . [expr {$ctr-1}]]"
} if {$ctr >= $bpb} {
set ctr 0
}
}
- Run the metronome until the user uses Ctrl+C...
beat vwait forever</lang> It might be executed like this: <lang bash>tclsh8.5 metronome.tcl 90 4</lang>