>this version generates a wav file of the whole text and plays it through the speakers.
Now THAT is *VERY* cool! Nice work!
MCM
PS:
You might want to post that in the Source Code Forum with a 'searchable' title, eg "Generate and Play Morse Code WAV files from text input"
Announcement
Collapse
No announcement yet.
Thread loop speed with Sleep
Collapse
X
-
> thought this thread was about "SLEEP" not Morse Code and only used it as an example
Isn't it amazing that when you describe your application instead of/in addition to the function or statement you are currently try to deploy in that application, you end up with more and better suggestions?
Leave a comment:
-
Michael,
this version generates a wav file of the whole text and plays it through the speakers.
Code:'PBCC5 program #DIM ALL #BREAK ON #INCLUDE "win32api.inc" FUNCTION PBMAIN() MorseCode("SOS SOS Hello from Mister Morse SOS", 80,1000,50) 'What to send, dot duration, frequency and % volume PRINT "press key to end." WAITKEY$ END FUNCTION 'define types TYPE WAVEheader rID AS STRING*4 ' Contains the characters "RIFF" rLen AS LONG ' The length of the data in the next chunk wID AS STRING*4 ' Contains the characters "WAVE" fId AS STRING*4 ' Contains the characters "fmt " fLen AS LONG ' Length of data in the format chunk wFormatTag AS INTEGER ' specifies the wave format, eg 1 = Pulse Code Modulation nChannels AS INTEGER ' Number of channels, 1=mono, 2=stereo nSamplesPerSec AS LONG ' Playback frequency nAvgBytesPerSec AS LONG ' Indicates the average number of bytes a second the data should be ' transferred at = nChannels * nSamplesPerSec * (nBitsPerSample / 8) nBlockAlign AS INTEGER ' Indicates the block alignment of the data in the data chunk. Software ' needs to process a multiplt of nBlockAlign at a time. ' nBlockAlign = nChannels * (nBitsPerSample / 8) wBitsPerSample AS INTEGER ' Format specific data area dId AS STRING*4 ' Contains the characters "data" dLen AS LONG ' Length of data in the dData field END TYPE TYPE twominwav wv AS waveheader dta AS ASCIIZ *1000000 END TYPE SUB MorseCode(Phrase AS STRING, DitDuration AS LONG, Freq AS LONG, Volume AS LONG) STATIC sound AS twominwav LOCAL ShortGap, LetterGap, WordGap, r, k AS LONG LOCAL DitSamples, DahSamples, TotalSamples, Char AS LONG LOCAL SCALE AS EXT LOCAL p AS BYTE PTR LOCAL symbol, letter, Morse AS STRING IF DitDuration > 330 THEN DitDuration = 330 'limit of 1 second for a dah DitSamples = 8000/1000 * DitDuration '8000 samples/sec, 1000 msec/sec DahSamples = 3 * DitSamples ShortGap = DitSamples LetterGap = 2 * DitSamples 'should be 3*DitSamples but dot or dash always ends in a 1 Dit gap so only 2 more to add WordGap = 4 * DitSamples 'should be 7*DitSamples but the letter always ends in a 3 Dit gap so only 4 more to add SCALE=2*3.14159265358979323#/8000 p=VARPTR(sound.dta) FOR char = 1 TO LEN(Phrase) letter = LCASE$(MID$(Phrase,char,1)) SELECT CASE letter CASE "a" TO "z" symbol = READ$(ASC(letter)-ASC("a")+1) CASE "0" TO "9" symbol = READ$(ASC(letter)-ASC("0")+27) CASE ELSE symbol = " " END SELECT FOR k = 1 TO LEN (symbol) 'generate the tone samples SELECT CASE MID$(symbol,k,1) CASE "." FOR r = 0 TO DitSamples-1 @p[r]=volume*(SIN(r * SCALE * Freq) ) +128 NEXT p = p + DitSamples TotalSamples = TotalSamples + DitSamples FOR r = 0 TO ShortGap-1 @p[r]= 128 'silence NEXT p = p + ShortGap TotalSamples = TotalSamples + ShortGap CASE "-" FOR r = 0 TO DahSamples-1 @p[r]=volume*(SIN(r * SCALE * Freq) ) +128 NEXT p = p + DahSamples TotalSamples = TotalSamples + DahSamples FOR r = 0 TO ShortGap-1 @p[r]= 128 'silence NEXT p = p + DitSamples TotalSamples = TotalSamples + ShortGap CASE " " FOR r = 0 TO WordGap-1 @p[r]=128 'silence NEXT p = p + WordGap TotalSamples = TotalSamples + WordGap END SELECT NEXT FOR r = 0 TO LetterGap-1 @p[r]=128 'silence NEXT p = p + LetterGap TotalSamples = TotalSamples + LetterGap NEXT 'sort out the wav header sound.wv.rID = "RIFF" sound.wv.rLen = 36+TotalSamples sound.wv.wID = "WAVE" sound.wv.fId = "fmt " sound.wv.fLen = 16 sound.wv.wFormatTag = 1 sound.wv.nChannels = 1 'nChannels sound.wv.nSamplesPerSec = 8000 'nSamplesPerSec sound.wv.nAvgBytesPerSec = 8000 '44100 '88200 'nChannels * nSamplesPerSec * (wBitsPerSample / 8) sound.wv.nBlockAlign = 1 ' nChannels * (wBitsPerSample / 8) sound.wv.wBitsPerSample = 8 'wBitsPerSample sound.wv.dId = "data" sound.wv.dLen = TotalSamples playsound( BYVAL VARPTR(sound),0 ,%SND_MEMORY OR %SND_ASYNC ) 'a-m DATA .-,-...,-.-.,-..,.,..-.,--.,....,..,.---,-.-,.-..,-- 'n-z DATA -.,---,.--.,--.-,.-.,...,-,..-,...-,.--,-..-,-.--,--.. '0-9 DATA -----,.----,..---,...--,....-,.....,-....,--...,---..,----. END SUB
Leave a comment:
-
I thought this thread was about "SLEEP" not Morse Code and only used it as an example. However not to be outdone I will have a look at your code Paul and make up my own mind after trying it out. Such variables as speed (from 25 to 250 letters per minute), ratio (varying the 3 dits to 1 dah timing) etc must come into it. Sound does not come into it as it must switch a radio transmitters output on and off for each dit or dah using either a COM port or a PARALLEL port. The arrangement I have works exceedingly well changing a character such as "a" to the necessary dit/dah combination. But I try out your code and see whether it gives any additional benefit.
73 Denys
Leave a comment:
-
Just because you can't guarantee the speed of the thread, does not mean that using a thread won't work for what you need.
The first question is:
"What task does the thread need to accomplish" ?
Second, how accurate a time interval is required ?
In many instances, as long as the thread runs at a reasonable pace, there may be no need to have absolute control of the speed.
For example, lets say that one needs to poll a device or something at least 10 times a second. Thats 100 milliseconds.
A thread would work reasonably well.
The thread could run a continuous loop and use a QueryPerformanceCounter to get an accurate count for the frame rate (each iteration) to see how fast the routine is running (no matter how Windows handles time slices).
Calculate how much time it took for the current iteration, since the last one using high performance timers and then determine whether the current iteration has taken took long or whether it has finished faster than what is needed. If the current iteration has taken longer than was is required, then simply don't call SLEEP and your thread will run as fast as is possible to keep up. If there is time left for the current iteration, then call SLEEP based on the calculation of how much time is left for the iteration (frame) so you can reliquish the time slice to other threads/processes.
If the time slice for each iteration is not enough to keep up with the speed you need then you need to have some kind of skip iteration built in to compensate and then flag to the user and the program that the thread can not keep up.
This is how it is done with video I think. With video in the old days if the thread could not keep up with the requested frame rate, the software would drop frames.
Now if you can't keep up to the required frame rate, then you may need to increase the priority of the thread so Windows gives it more time for each time slice while it decreases the time given to other threads.
This is why Windows allows you to set thread priority levels.
Each process is given a specific thread priority and each thread within the process is given a thread priority. The SetThreadPriority API function for example lets you add or subtract from the current priority of a thread.
There are very high priorities left for things like device drivers and hardware in the rare instance you need absolute control over the speed but that is beyond the scope of my knowledge and requires some special programming skills.
Leave a comment:
-
I went away to do a little programming, and now with the recent posts above, this is all old hat.It doesn't make real letters and words, but it sounds kinda kewl and seems pretty even timing-wise.
Code:#COMPILE EXE #DIM ALL DECLARE FUNCTION sndPlaySound LIB "WINMM.DLL" ALIAS "sndPlaySoundA" (lpszSoundName AS ASCIIZ, BYVAL uFlags AS DWORD) AS LONG DECLARE FUNCTION GetTickCount LIB "KERNEL32.DLL" ALIAS "GetTickCount" () AS DWORD FUNCTION PBMAIN () AS LONG LOCAL daOrDit AS STRING, ii AS LONG FOR ii = 1 TO 100 IF RND < .5 THEN daOrDit = "dah.wav" ELSE daOrDit = "dit.wav" sndPlaySound BYCOPY daOrDit, 0 IF RND < .05 THEN millisecDelay(150) NEXT END FUNCTION FUNCTION millisecDelay(x AS LONG) AS LONG LOCAL y AS LONG y = getTickCount DO IF getTickCount - y >= x THEN EXIT FUNCTION LOOP END FUNCTION
Attached Files
Leave a comment:
-
A better sounding way would be to create some .WAV files of dits and dahs and silence and string them together to play but that's more complicated to program.
Maybe even create WAVs for each letter of the alphabet plus combined letters of the common words used in telegrams eg "STOP". The the user might be able to type and have the sounds generated in real time, without learning Morse code at all. (I learned as a Cub Scout. I have forgotten).
You'd think there must be some Windows functions to "Build WAV or other sound file by specifying a table of frequency and duration" or something like that, wouldn't you?
[LATER]
Hmm, thank you, Google(r)....
1. WinMorse 2.0 (winmorse.com)
is a FREE text to Morse Code conversion application for Windows. WinMorse converts text into Morse Code, using various Output Formats. Here are some ideas for what you can do with the files created by WinMorse: Use for Morse Code practice/training/testing Accessibility tool for the vision impaired Create Morse Code…
Leave a comment:
-
> [SLEEP] makes the thread sleep for that many milliseconds max.
I believe that should be "that many milliseconds MINIMUM."
However, the help for SLEEP() doesn't:
Pause the current thread of the application for a specified number of milliseconds (mSec), allowing other processes (or threads) to continue.
>OK so show me how it should really be done.
If you mean "precision timing" as a general proposition, it ain't SLEEP, or even a waitable timer, or a LOOP querying TIX(). The preemptive multitasking of Windows is downright incompatible with precision timing.
But if you mean running a background thread with a GUI, I think this is a pretty good starter demo:
GUI + Worker Thread + Abort Demo 11-24-07
MCM
Leave a comment:
-
Thanks Paul, the link you posted was exactly the explanation I needed.
Leave a comment:
-
Denys,
I'm sure there are better ways of sending Morse code but here's one simple method. It assumes you have a system speaker in your computer:
Code:'PBCC5.01 program $INCLUDE "WIN32API.INC" FUNCTION WINMAIN (BYVAL hInstance AS LONG, _ BYVAL hPrevInstance AS LONG, _ BYVAL lpCmdLine AS ASCIIZ PTR, _ BYVAL iCmdShow AS LONG) AS LONG SendMorse("Hello from Mister Morse") END FUNCTION SUB SendMorse(m AS STRING) LOCAL r AS LONG LOCAL symbol, letter, Morse AS STRING LOCAL DitDuration, DahDuration AS LONG DitDuration=50 'milliseconds DahDuration = 3 * DitDuration 'a-m DATA .-,-...,-.-.,-..,.,..-.,--.,....,..,.---,-.-,.-..,-- 'n-z DATA -.,---,.--.,--.-,.-.,...,-,..-,...-,.--,-..-,-.--,--.. '0-9 DATA -----,.----,..---,...--,....-,.....,-....,--...,---..,----. FOR r = 1 TO LEN(m) letter = LCASE$(MID$(m,r,1)) SELECT CASE letter CASE "a" TO "z" symbol = READ$(ASC(letter)-ASC("a")+1) CASE "0" TO "9" symbol = READ$(ASC(letter)-ASC("0")+27) CASE ELSE symbol = " " END SELECT Morse = Morse + " " + symbol PRINT r,letter,symbol NEXT FOR r& = 1 TO LEN(Morse) SELECT CASE MID$(Morse,r,1) CASE "." winbeep 500,DitDuration winbeep 20000,DitDuration CASE "-" winbeep 500,DahDuration winbeep 20000,DitDuration CASE " " winbeep 20000,DahDuration END SELECT NEXT END SUB
If you're trying to trigger an outside device then the the multimedia timer demonstrated in the previous post will probably be accurate enough for Morse Code. It's not exact but is probably closer in timing than a human could get.
Paul.
Leave a comment:
-
Rory,
perhaps this explanation might help:
If you want quicker response times you can try using multimedia timers which give a resloution of about 1ms:
Code:'PBWin9.01 program $INCLUDE "WIN32API.INC" DECLARE FUNCTION test( BYVAL uID AS LONG, BYVAL uMsg AS LONG, _ BYVAL dwUser AS LONG, BYVAL dw1 AS LONG, BYVAL dw2 AS LONG) AS LONG GLOBAL msec, CloseFlag AS LONG GLOBAL Freq AS QUAD GLOBAL Result AS STRING %UpButton=500 %DownButton=501 %CloseButton=502 %OutputText=503 %TargetText=504 CALLBACK FUNCTION UpButtonClick () IF CB.MSG = %WM_COMMAND AND CB.CTLMSG = %BN_CLICKED THEN DECR msec FUNCTION = 1 END IF END FUNCTION CALLBACK FUNCTION DownButtonClick () IF CB.MSG = %WM_COMMAND AND CB.CTLMSG = %BN_CLICKED THEN INCR msec FUNCTION = 1 END IF END FUNCTION CALLBACK FUNCTION CloseButtonClick () IF CB.MSG = %WM_COMMAND AND CB.CTLMSG = %BN_CLICKED THEN CloseFlag=1 FUNCTION = 1 END IF END FUNCTION FUNCTION WINMAIN (BYVAL hInstance AS LONG, _ BYVAL hPrevInstance AS LONG, _ BYVAL lpCmdLine AS ASCIIZ PTR, _ BYVAL iCmdShow AS LONG) AS LONG LOCAL count0,count1 AS QUAD LOCAL hDlg, TimerHandle AS LONG 'start the timer '1=milliseconds between triggers, 0=maximum timer resolution, test=the routine to call TimerHandle = timeSetEvent ( BYVAL 1, BYVAL 0, CODEPTR(test), BYVAL 0&, BYVAL %TIME_PERIODIC) msec=100' start off aiming for 100msec (10Hz) QueryPerformanceFrequency freq QueryPerformanceCounter count0 DIALOG NEW 0,"Test",,,100,100 TO hDlg CONTROL ADD BUTTON, hDlg, %UpButton,"Up",10,10,30,20 CALL UpButtonClick CONTROL ADD BUTTON, hDlg, %Downbutton,"Down",10,40,30,20 CALL DownButtonClick CONTROL ADD BUTTON, hDlg, %CloseButton,"Close",10,70,30,20 CALL CloseButtonClick CONTROL ADD LABEL, hDlg, %OutputText,"Freq = ####Hz",50,70,40,20 CONTROL ADD LABEL, hDlg, %TargetText,"",50,10,40,20 DIALOG SHOW MODELESS hDlg DO DIALOG DOEVENTS ' sleep 10 CONTROL SET TEXT hDlg, %OutputText, Result CONTROL SET TEXT hDlg, %TargetText, "Target= "+FORMAT$((1000/msec),"###.000")+"Hz" LOOP UNTIL CloseFlag 'stop the timer timeKillEvent TimerHandle DIALOG END hDlg END FUNCTION FUNCTION test ( BYVAL uID AS LONG, BYVAL uMsg AS LONG, _ BYVAL dwUser AS LONG, BYVAL dw1 AS LONG, BYVAL dw2 AS LONG) AS LONG 'this is the routine that is run everytime the timer triggers STATIC COUNT AS LONG STATIC count1, count2 AS QUAD INCR COUNT IF COUNT >= msec THEN COUNT=0 QueryPerformanceCounter count2 Result = "Freq = "+FORMAT$((freq/(count2-count1)),"0000.000")+"Hz" count1 = count2 END IF END FUNCTION
Leave a comment:
-
This might be useful:
Code:Sub Sleepy(milliseconds As Long) 'a better sleep Dim x As Long, Counter As Long Counter = (milliseconds \ 11) + 1 'minimum 1 loop (10 milliseconds) For x = 1 To Counter DIALOG DoEvents Sleep 10 Next End Sub
Leave a comment:
-
Anyway, here's the code.... can anyone else replicate?
I get similar results with my PC (Athlon 64 X2 WinXP SP2).
I see about 64hz which rises to around 512 whem windows media player is active (playing a DVD etc) with Sleep 1.
The speed rises to 2000+ if there is no Sleep or Dialog DoEvent in the threaded function.
I also noted that if I include a msg pump ahead of the MsgBox..
Code:Do Until IsWin(hDlg) = 0 Dialog DoEvents Loop
I can't explain any of this except to say that Chris is right in that you can't really predict how many time slices the OS will give your application. From the above it looks as if ActiveX muscles in on the act and also that the OS allows more time slices to your app when it knows a drag operation is going on. All of which makes running time critical tasks a bit awkward.
Leave a comment:
-
My density is pretty high regarding multiple threads as well, and I haven't had much need for 'em yet. I do know that you can't have all the processor time, and you can't predict just how much you can have. Your loop runs about 4khz on my Athlon64 using SLEEP 0. You can also use a DIALOG DOEVENTS 0 instead, with about the same results. I've run some very fast loops by not updating GUI displays on every pass. You could update every hundred passes and probably gain a lot.
Leave a comment:
-
I guess I should just give up and admit I'm too stupid to do this. All my apps have to be single-threaded cause I'm just too thick to use threading properly.
The point was not to try to control the loop frequency with the sleep, the sleep was just to keep the loop from hogging processor time. That the loop frequency fell close to what the sleep period would create was incidental. I KNOW BETTER THAN TO USE SLEEP IN TIMING LOOPS DAGNABBIT! However I AM concerned with how fast I can get a loop to run because time constraints talking to the outside world.
The point I am trying to understand is what is causing the loop to bog down when there are only "hidden" processes and why does it suddenly stop bogging when other processes are running.
OK so show me how it should really be done.Last edited by Rory Davis; 27 Jun 2009, 11:06 PM.
Leave a comment:
-
Sorry Rory. Chris is right. I have a program which sends morse code which requires correct timing to get the correct length of each "dit" and "dah" else the code ends up all jumbled. Using DOS as a platform it is easy to keep correct timing but with Windows I have to use a seperate piece of equipment to make the morse code readable. Don't worry, I spent a lot of time before admitting defeat.
Leave a comment:
-
SLEEP is not some kind of timing control function. You can't use SLEEP with a specific value and expect a consistant timing of the thread.
All sleep does is relinquish the current threads time slice to the next thread which Windows chooses to be next. A value of zero relinquishes whats left of the current threads time slice and a non-zero value makes the thread sleep for that many milliseconds max.
What you don't see is how other threads are being executed by Windows.
Especially with Windows XP and later, there may be dozens of processes and threads running, even if you are not running a single program. Just take a look at how many services the PC is running at the time and you will see how much Windows is doing. Depending upon the software installed, the hardware, drivers, etc. each PC is uniquely different in how many processes or threads may be running at any given time.
To make things even more complex, processes and threads have different priorities. For example some hardware drivers may have a very high priority while other threads/processes have a lower priority. Windows is determining how much time each process or thread is given for its time slice and then it continuously moves from thread/process to thread/process. It never stops.
A clue to all of this is in the description of Windows as a "multi-tasking" operating, meaning it is doing many, many different things seemly all at the same time (actually it is only doing one thing at a time, but keeps switching threads so fast, they appear to be running simultaneously.
Do not think you can somehow get exact control of all of this with the Sleep command. You have no control of all of this timing. You can change a threads priority to get more time. You can make a thread sleep to relinquish some of its time. Yet you can't control the exact amount of time being used, since Windows has all control of this and it depends upon how many processes/threads are running at any given time, what each process/thread is doing and their priority settings.
Now add to this how much memory the PC has, which can also affect this, since when time slices are switched (a context switch) Windows has to do some fancy footwork to make each process think it is all alone and has its own memory blocks (including the settings on the CPU registers). The problem is that what makes all of this work is that a certain amount of memory is needed and if the memory is below that threshhold, Windows must do page swapping to the harddrive. So depending upon how much page swapping is going on, things can either be slowed down or speed up.
Simply put, there are too many factors involved here to think you can somehow control exact timing with the little old SLEEP command.
To make things worse, your code is calling a GUI command within the worker thread.
ie.
Code:DO SLEEP 2 'compare with sleep 1, sleep 0, dialog doevents DIALOG SET TEXT x, display_loopfreq() LOOP UNTIL gThreadrun = 0
DIALOG SET TEXT calls API's which will force Windows to so a context switch (switch threads) and jump from the worker thread to the primary GUI thread. This context switch adds a lot of over head and slows things down. You are simply just adding one more problem to the mix. You call SLEEP (which could possibly switch to the next thread which may be the primary thread) and immediately afterwards force a context switch again by calling DIALOG SET TEXT.
One must fully understand how threads really work before using them and also the ramifications of calls to things like SLEEP or GUI commands.
It is easy to create and use threads, but it is much more difficult to calculate the effects of the code you put into that thread and how it effects timing.Last edited by Chris Boss; 27 Jun 2009, 09:57 PM.
Leave a comment:
-
Thread loop speed with Sleep
I ran into a phenomenon that I don't understand.
Basically it goes like this. If I run this code, it displays the frequency the loop cycles. It is a very high frequency if the SLEEP is 0 or is omitted. If it is SLEEP 1 (ms), it should be nominal 1000Hz while SLEEP 2 it's 500Hz.
However, the frequency varies from machine to machine. On my vista laptop with dual core, it runs at 64Hz nominal until I run Firefox on a page that has graphics - it then runs at the predicted frequency. Closing Firefox causes it to return to 64Hz. MSIE however doesn't have the same effect, nor does opening Firefox to a local jpg file. I also found if I open and run my DVD player or other prog that uses DirectX video, the speed goes to where it should.
On my wife's XP laptop it runs at 171 Hz regardless of other apps running. On another machine with no other progs running it shows predicted speed.
Any ideas? Virus infection? Spyware?
Anyway, here's the code.... can anyone else replicate?
Code:#COMPILE EXE #DIM ALL #IF NOT %DEF(%WINAPI) #INCLUDE "WIN32API.INC" #ENDIF GLOBAL qFreq AS QUAD GLOBAL qStartCount AS QUAD GLOBAL qEndCount AS QUAD FUNCTION InitHiresTimer() AS DWORD CALL QueryPerformanceFrequency(qFreq) END FUNCTION MACRO startHiresTimer CALL QueryPerformanceCounter(qStartCount) END MACRO FUNCTION display_loopfreq() AS STRING QueryPerformanceCounter(qEndCount) FUNCTION = FORMAT$(qFreq/(qEndCount-QStartCount),"#.0")+" Hz" QueryPerformanceCounter(qStartCount) END FUNCTION 'THREAD FUNCTION loopthread(BYVAL x AS DWORD) AS DWORD 'pbwin9 FUNCTION loopthread(BYVAL x AS DWORD) AS DWORD initHirestimer() startHiresTimer() DO SLEEP 2 'compare with sleep 1, sleep 0, dialog doevents DIALOG SET TEXT x, display_loopfreq() LOOP UNTIL gThreadrun = 0 END FUNCTION FUNCTION PBMAIN () AS LONG LOCAL x AS DWORD LOCAL hdlg AS DWORD LOCAL hThread AS DWORD DIALOG NEW %HWND_DESKTOP, "", 0,0, 100,0 TO hdlg DIALOG SHOW MODELESS hDlg GLOBAL gThreadrun AS DWORD gThreadrun = 1 THREAD CREATE loopthread(hdlg) TO hThread THREAD CLOSE hThread TO hThread MSGBOX "Quit?",,"Loopspeed" 'This holds loop running until msgbox closes gThreadrun = 0 END FUNCTION
Tags: None
Leave a comment: