Australian Toolbook User Group
Synchronising graphics and sound, in most cases is pretty simple. If you need strict synchronisation, you should consider creating creating and playing an AVI or Quicktime video.
How do I play a sound clip and display several image files one after another? Solution by Stephen Hunter
Question: How do I play a sound clip and display several image files one after another while the sound is playing? Can I control the timing and duration of the images on the screen? I am sorry to ask this but the Ref manuals have little or no examples
Answer: Regarding "How do I play a sound clip": use the mmplay command as usual. Then in an idle handler structure, test the mmposition property of the clip, e.g. if you want to show an image after 4 seconds for 1 second duration
if mmposition of clip myClip > 4000 visible of picture "mypic1" = false visible of picture "mypic2" = true if mmposition of clip myClip > 5000 visible of picture "mypic" = false
Text and Sound Synchronisation Solution by John Petrikovitsch
Question: I am a novice. I got MTB30 a few weeks ago. I am basically asking how to go about doing the following. What I want to do is display the sentence "This is my first try" one word at a time and play a wav file recording of the same entence as it plays the recorded words in sync with the displaying words. I have tried the following 2 scenarios withminimum success.
1. Created a single text field with the sentence and used a script to select each word and change the stroke color with a pause after each word selection. This >handles the display. I then tried to mmplay each words particular range in the wave file as each word displayed.this was quite time consumming and I never could get the entire sentence to play smoothly.
2. Made the sentence "This is my first try" 5 separate text fields and made 5 separate wav files 1 for each word.I then made a script to highlight and play each wav for eachword. As you can imagine this was even worse.I know this can be done I just do not know how yet. Any suggestions would be appreciated.
Answer:
I have tried something similar with an automated presentation which highlights bullets as a sound clip of the instructor is playing.
I have a variable (clipReference) which contains the name of the sound clip, the starting time, the time when I want the highlight to change and the ending time of the clip - all in ms. (note: the itemcount of this list the number of points (or words in your case) + 2 for the clip name and ending time).
I then have the following script in the field:
notifybefore idle
system clipReference
get mmposition of clip \
(item 1 of clipReference)
--item 1 is name
set pointnumber to \
itemcount(clipReference)-2
set i to 0
while it>(item i+2 of clipReference) \
AND i<pointnumber
increment i
end
if i>0 AND i<>my currentpoint then
send highlightpoint i
end if
end
to handle highlightpoint i
... (you must use your own code here)
In the highlightpoint section, you must change the
strokecolor of word i
to the highlight color, and change the
stroke color of word i-1
back to the original color if i<>1. Also when you use mmplay to start the sound file, you must notify the field when the clip is done so it can un-highlight the last word.
This works great, because it does not interrupt the playing of the soundclip.
Wavefile Control of Animations Solution by Jeff Parkes
I wanted to make animations in Toolbook that synchronised with wave files. To use the following technique, you need to precisely determine the start and end points of a portion of sound in a wave file portion, so as to put these values in the script together with animation control statements (see below).
-- THE LearnTech
-- IDLE HANDLER STUFF FOR
-- WAVEFILE CONTROL OF ANIMATIONS
-- by Jeff Parkes
to handle idle
forward
system playnow
if playnow is yes
set playnow to no
get tbkMCIchk("play waveFile from" && \
0 && "to" && 19100,"",1)
get yieldApp()
end
--this plays the wavefile for this page once
--it is completely drawn and ready
--to go
--this finds out where the wavefile is up to and...
get tbkMCI("status waveFile position","")
--the conditions determine when the
--various thingies get shown
conditions
when it > 2800 and it < 3300
hide group "music"
--by specifying a "window of opportunity"
--this wide, it's reasonably certain
--that even a very slow computer will
--catch the wavefile position within one
--loop of the idle handler, while a super
--fast one is unlikely to try to do it
--twice. Showing something twice
--wouldn't have been a problem here but in
--some circumstances it could mean things
--remain on screen when they
--shouldn't. Also, where the idle handler
--changes the bounds of an object, it
--could keep going right off the screen.
when it > 9500 and it < 11000
hide field "brew"
show group "bucket"
when it > 13700 and it < 14200
show group "driller"
when it > 17400 and it < 17900
hide group "driller"
show ellipse "grommet"
when it > 18500 and it < 18900
show group "bubbler"
when it > 18800
send showbutts
--shows the navigation button to continue
--the "exit" button is always available
end
--this makes the chuck of the drill appear to turn
set transparent of line "w1" to false
set transparent of line "w1" to true
set transparent of line "w2" to false
set transparent of line "w2" to true
set transparent of line "w3" to false
set transparent of line "w3" to true
end
As a voice over plays a wave file, the animation keeps in perfect synch with the audio.
An alternative to the wavefile gadget
Use the clip editor to define the clips into the wave file - then read the values that define those clips out of the clip manager. Put these values in your code.
Playing sound with bitmaps at given intervals Solution by John R. Hall
Question: Does anybody have some script code to play a wave file and at the same time play some bitmaps at specified times. I also need to be able to show no bitmaps at certain points, i.e., the screen has to be blanck during certain parts of the voice over when no graphics are needed.
-- Also see openscript help about mmNotify, which is more appropriate if you have separate clips
-- rather than one long clip.
-- Here's a script I use to display
-- bitmaps timed to a long clip. You
-- might be able to modify to accomplish
-- what you want. It will
-- also work with video playback
-- with slight modification.
-- In page script, put info needed
-- for whole page. Something like:
to handle enterpage
system stack popList
system string ClipName
forward
-- create a list of audio frame numbers
-- where you want events to happen.
-- You can get these numbers by playing the
-- clip in the clip
-- manager and writing down the current
-- frame when you stop the
-- clip at the point you want to display
-- the next bitmap.
-- popList is then created with the form
-- FrameNumber,ObjectToDisplay
poplist = "1,FieldOne,1200,FieldTwo"
-- identify the name of the clip for the page
ClipName = "myWave"
-- identify the format you're going to use
mmTimeFormat of clip "myWave" = "milliseconds"
end
-- Now, you use the following generic
-- handler for every page.
-- The script can be located in a
-- button, page, whatever.
to handle PlayTheSegs -- or FirstIdle or buttonclick
system stack popList
system string ClipName
mmplay clip ClipName
-- pop list of clips and times to show from stack
do
pop popList
StartTime = it
pop popList
ObjectToShow = it
get ShowObject(StartTime,ObjectToShow)
until popList = null
-- wait for last section of clip to finish
do
if keyState(sysMediaBreakKey) is "up"
get flushMessageQueue()
mmyield
end
until mmstatus of clip ClipName <> "playing"
-- continue processing for the page
request "I'm done"
end
to get ShowObject StartTime,ObjectToShow
system string ClipName
do
if keyState(sysMediaBreakKey) is "up"
get flushMessageQueue()
end
mmyield
until mmposition of clip ClipName > StartTime
-- if you use other object types, modify next line
-- or include object info in original
-- stack named popList
show field ObjectToShow
return "OK"
end
Karaoke - like book
Question: I'm doing a book that needs to synchronize CD audio with the lyrics of the current track played. How do I do this ?
Why not begin with a list of time changes:
to handle enterPage
system TimeChanges, Lyrics, Current
set Current to 1
put item 1 of Lyrics into text \
of field "Lyrics"
--play your cd here
TimerStart(single,item 1 of TimeChanges, \
200, self)
end
Start your audio, show your first lot of lyrics in your one field and grab the first time change with which you set a timer which in turn will clear the old text and put the next lot of lyrics into your field, then set the timer for the next change.
to handle TimerNotify
system TimeChanges, Lyrics, Current
set text of field "Lyrics" to \
item current of Lyrics
increment Current
TimerStart(single,item Current \
of TimeChanges,200,self)
end
If the user wants to leave the page just send a timerstop and kill the current lyrics before you leave.
I've not done this with cd audio, but I've synchronized scrolling a text field to match a video. I think, whether you use clips, or straight MCI commands, the key is to realize that you can find out the position of the audio (or video) as it is being played. With audio, you probably will use milliseconds. So, what you'll need to do is decide how you are presenting the lyrics. You could, simply, create a number of different pages, with the lyrics spread across these pages. Dividing up the lyrics would be based on the natural phrasing of the songs. Then, you would manually go through the audio cd and obtain the necessary information for the start of each new phrase. What I mean is this: if you're using milliseconds as the timing basis, then you would determine that phrase 1 should be shown while the position of the audio is between 0 and 20 ms; phrase 2: between 21 and 40, etc.
Another approach (much more efficient): store the text to be displayed in an array and then display this text in a single field on a single page as the audio is played. You would use a timer to periodically check the status of the cdaudio's position.
Using a timer in animation Solution by Michael McKinnon.
In OpenScript, Asymetrix has managed to incorporate some commands, which although they appear to be OpenScript commands, are actually functions from the Windows system. The commands which I will demonstrate here are the timerStart and timerStop functions. These functions allow you to either start a virtual alarm clock which can go off after a preset amount of time, or have a metronome effect, triggering an event periodically.
When it comes to cel animation, most people are inclined to use a step loop to control the hiding and showing of the separate cels, but this has a number of disadvantages as follows:
The speed of the animation will change on different types of machines. i.e. a Pentium vs. an older 386 machine. Even on the same machine, the time it takes for a step loop to complete may change dependent on the resource loads at the time.
A step loop uses valuable CPU time, which could be employed doing other tasks, such as waiting for the user to stop the animation.
It can take a considerable amount of time trying to guess exactly how many times the loop must iterate. This often means switching between author and reader mode several times, just to change one number in a script.
When you start to use Windows timers, people will start calling you a real Windows programmer. The timer will be precise on almost any machine, it doesnt waste valuable CPU time, and you need only make a few adjustments to know what the results will be.
So, now that youre convinced that the Windows timer is the way to go, maybe you should check out your "Multimedia ToolBook User Manual and OpenScript Reference" under section 10-48 and 10-49, or consult the Help file by searching for timerStart.
As you can see, the commands are reasonably straight forward. The only other thing you must know is that the notify object which receives the timer message intercepts it with a "timerNotify" handler, and this is where the action happens.
First youll need Some animation cels. Usually in the form of imported bitmaps. They should all be the same size, named in sequence PaintObject "1","2","3" etc. and then grouped. You should make sure that they are all aligned to sit on top of each other (use the Tools menu to align them easily).
Using my script, youll need to set two user properties on the group, by using the Property browser. Call the first property CurrentFrame and give it a value of 1, then call the second property FrameCount and give it a value equal to the number of cel PaintObjects you have in the group and add one. (So, if you have six cels, give the FrameCount property a value of seven.)
Next, youll need a script on the group to control the animation. You can place the script higher in the book if you have animations on several pages, and in Multimedia ToolBook 4.0 you might even like to use the SharedScript feature.
Next, youll want to test the animation to see if it works. Lets assume that I called the group of cels logo. To start the animation, all I have to do (from the command Window in author mode, or in script at runtime) is to issue the command send animate "start" to group "logo".
When the animation has started, it will continue to run, regardless of whether you switch between reader and author mode. Do you know why? Its because the Windows timer event which we have started is doing all the work for us!
As you can clearly see, there are many advantages to running animations in this way. You can use the Windows timer for a number of tasks, and these are limited only by your imagination. Here are some of my suggestions:
A splash screen at the start of your application. By using a timer, you could show a viewer with some credits or disclaimer information for, say 2 seconds.
On a kiosk application, you might want to perform a demonstration routine if the kiosk hasnt been touched for, say 5 minutes. All you would do is catch the first idle and start a timer for 5 minutes. If someone comes back before then, you stop the timer and start it again when you catch the first idle. When the timer does elapse your application can start its demonstration.
For CBT applications you can easily use a single timer to lock out a question if it hasnt been answered in the correct amount of time.
If you produce any applications that provide network functionality, you can use the timer to keep polling the other application to test if it is still alive. The periodic nature of the timer makes it an ideal heartbeat for all types of tasks.
-- NOTE: The cel animation that I use
-- consists of the real frames, plus the last one
-- which is always on the background image,
-- and which can't be shown or hidden.
-- I call it the "dummy" frame.
to handle TimerNotify
-- variable declaration
local INT vFrameCount, vCurrentFrame, vNewFrame
-- number of paintObjects
vFrameCount = my FrameCount
-- current frame number
vCurrentFrame = my CurrentFrame
-- calculate newframe
vNewFrame = vCurrentFrame mod vFrameCount + 1
-- lock the screen
syslockscreen = true
if vNewFrame <> vFrameCount then
-- show next frame (if not dummy)
show paintobject vNewFrame of self
end if
if vCurrentFrame <> vFrameCount then
-- hide last frame (cant hide dummy)
hide paintobject vCurrentFrame of self
end if
-- unlock the screen
syslockscreen = false
-- update current frame for next time
my CurrentFrame = vNewFrame
end
------------------------------------------
-- ANIMATE (START OR STOP) MJM 01/02/96
------------------------------------------
-- This handler is called when the cel
-- animation in this group is requested to
-- start. it then starts a Windows
-- timer to initialise the animation event. The
-- timerNotify handler will control the rest,
-- as long as the timer is going.
--
to handle animate switch
-- Check for the switch passed to the handler
if switch = "start" then
-- If the object already has a
-- TimerID, dont start!
if my TimerID is NULL then
-- Set my TimerID by starting a Windows Timer
my TimerID = \
timerStart(periodic,100,1000,target)
-- Check for a problem with the TimerStart command.
if my TimerID is NULL then
-- Show what went wrong.
request "Timer Start"&CRLF&"ERROR:"& \
syserror
end if
else
-- If this group object already had a TimerID, then
-- it is preferable to stop it, if for some reason
-- we have tried to start another one.
-- This is a safeguard
-- when used in a MouseEnter/Leave
-- situation where you
-- might be switching between reader/author
-- mode, and where
-- the MouseLeave may not be sent.
send animate "stop" to self
end if
else
- "Stop" (or something else) has been called.
if my TimerID is not NULL
get TimerStop(my TimerID)
if it is NULL then
-- Tell the user why we couldnt stop the thing.
request "Timer Stop"&CRLF&"ERROR:" & \
syserror
else
-- get rid of the TimerID user property
clear my TimerID
-- reset all the animation cels to the start point
send resetcels to self
end if
end if
end if
end
-------------------------------------
-- SHOWCELS MJM 01/02/96
-------------------------------------
-- I keep this in the script so I can
-- call it to show all the cels if I
-- need to move any of them around.
--
to handle showcels
step i from 1 to \
ItemCount(my objects)
show (item i of my objects)
end step
end
------------------------------------------
-- RESETCELS MJM 01/02/96
------------------------------------------
-- The animation must be reset so as
-- to hide all the
-- cels and only leave the background ("dummy")
-- image present. In theory, I am simply hiding the
-- last current frame and showing the
-- "dummy" frame.
--
to handle resetcels
-- variable declaration
local vCurrentFrame, vFrameCount
vFrameCount = my FrameCount -- number of frames
vCurrentFrame = my CurrentFrame -- current frame
-- check if we are already
-- showing the "dummy" frame.
if vCurrentFrame <> vFrameCount then
-- hide last frame (not dummy)
hide paintobject vCurrentFrame of target
end if
-- reset currentFrame to dummy
my CurrentFrame = vFrameCount
end
Using the Toolbook freeware Synchronization Plug-In Utility Solution by Tony Domigan
As a developer, you invariably write nifty routines to make programming tasks easier. These utilities are rarely shared with others because of the effort required to make them bug-free and intuitive for others to use.
At the last meeting I discussed a utility from Slade Mitchell called the Synchronization Plug-In. I was surprised that this utility was virtually unknown so I thought I might describe this utility here as I believe that it can provide a useful and simple interface for handling timer functions.
The SYNCH system book was written by Slade Mitchell (smitchel@wat.hookup.net) and can be obtained from
http://www.hookup.net/~smitchel
From his site you can download a version for MTBCBT3, 4 and 5. Slade invites you to register on his web site so that you can be informed of changes.
The SYNCH package contains:
SYNCHXX.SPB the widget editor which you move to your MTBCBT widget directory
SYNCHXX.SBK the widget handler which you push onto sysbooks on enterApplication
SYNCHXX.TBK an example toolbook and a sample wave file used by the toolbook.
Additionally, you can download documentation from Slades web site.
The SYNCH system book supports the management of synchronization widgets. To begin to use the utility you push the sbk onto sysbooks in your enterApplication handler (you do not add it to your preferences sysbooks).
Once you have started the toolbook with the SYNCH sbk loaded you can choose any object from the tools palette and from the command prompt issue the synch_make handler to define the object as a synchronization controller widget. E.g.
send WID_Synch_MakeWidget to \ OBJECT "objectname" of this page
If you examine the new widget using the property editor you will see that there have been new widget user properties added as follows:
The synch sbk will also make changes to the script of the object to add a basic handling template which you customise to invoke your routine when the timing function is triggered.
Having defined the object as a widget you can then invoke the CBT widget properties editor for the new widget to establish the synchronization type and parameters to use for the widget.
There are 4 synchronization (timer) types handled by the widget:
You first select which of these modes you want to use. The first three modes all use trigger points measured in milliseconds. The mediaClip mode also allows you to choose frames if you are trying to synchronize to a video clip.
Once you have defined the type and parameters for the synchronization controller you modify the trigger handler template which was automatically added to the script of the object.
to handle WID_Synch_EventTriggered pTriggerIndex
conditions
when pTriggerIndex = 1
-- add an action
show field "prompt1"
end conditions
end WID_Synch_EventTriggered
The widget is controlled by 2 messages:
WID_Synch_Start WID_Synch_Stop
All that needs to be done now is to start the trigger in your program e.g.
send WID_Synch_Start to OBJECT "objectname"
When the timer completes the trigger will be sent to the object EventTriggered handler where your custom actions will be activated.
Timers are relatively easy to use but you do need to be vigilant when using them as a mistake could lead to a fatal Windows error. Slades Synchronization plug-in provides a simple to use and robust interface to timer functions.
The Synchronization Plug-In system books (SYNCH30.SBK, SYNCH40.SBK, SYNCH50.SBK) can be freely used and distributed by anyone as part of ToolBook development projects as long as the following acknowledgement appears in the book script of your application:
"Portions of this application are the copyright of Interactive Software Solutions". "Portions of the code are the copyright of Asymetrix Corporation"
I recommend you give this plug-in a try.
To access thousands more tips offline - download Toolbook Knowledge Nuggets