--*by Jim Andrews, vispo.com, jim@vispo.com, August 2002


--*Sound as object, meaning as constructed, sound poetry as generative music,

--application as machine made out of words.

--*A two-frame movie.

--*The below is the one and only script, a frame script.

--*Source code available below.

--*If you use this code, credit where credit is due, please.


--*Comments are this color. Properties start with p. Globals start with g.

--*Variables of local scope start with 'the'.



global gWaveForm, gCurrentArea, gBar  --Spritenums of the wave form, currently playing area, and bar.

property pDuration    --Length of the sound in milliseconds.

property pWaveFormLength  --Length in pixels of the waveform graphic.

property pConstant   --Conversion factor of time to length  (used to synch the sonic and visual).

property pConstantReciprocal --The reciprocal of pConstant. Used to convert length to time.

--Maya must be cleverly synchronized but the world just is. Digital media objects are independent

--of one another, whereas material objects cause sound. Except via imagining. You are currently

--located somewhere between the two.

property pAnchorTime --Total milliseconds during which pFrameCounter frames played.

property pFrameCounter --Counts the number of frames the movie has processed during pAnchorTime.

global pBarIncrement --This is the number of pixels to move the 'sliver of now' each frame.

property pBarPosition --Horizontal position of the sliver of now.

property pPlayerHasSelected  --Set to TRUE when the player clicks. Set to FALSE after the player's click is processed.

property pStartTime  --The millisecond in the sound where play begins.



on beginsprite me

  --This runs once and only once.

  gWaveForm = 1

  gCurrentArea = 2

  gBar = 3 

  sprite(gCurrentArea).locV = sprite(gWaveForm).top

  sprite(gBar).locV = sprite(gWaveForm).top

  --The above two lines position the current area and the sliver of now vertically. Note that for these lines to

  --work, these sprites must exist when this code is run. Which is why, if you look at the source code, the

  --gWaveForm, gCurrentArea, and gBar sprites exist in frame 1, but this script is in frame 2. Before this

  --script is run for the first time, gWaveForm, gCurrentArea, and gBar need a frame to come into existence.

  pDuration = member("meaning").duration

  --If you want to use a different sound and graphic, just change the code in three ways: change "meaning"

  --in the above line to the name of the sound you want to use, and change "waveForm" in the below line

  --to the name of the wave form graphic representation of the sound. Drag a copy of your wave form

  --graphic onto the stage. Also, make sure that the wave graphic is sprite 1, or change gWaveForm.

  pWaveFormLength = member("waveForm").width --The length of the wave graphic in pixels.

  pConstant = pWaveFormLength/float(pDuration)  --SelectedLengthInPixels = pConstant * DurationOfSelectedPortionOfSound

  pConstantReciprocal = 1/pConstant  --DurationOfSelectedPortionOfSound = pConstantReciprocal * SelectedLengthInPixels

  pAnchorTime = float(the milliseconds)

  pFramecounter = 0

  pBarIncrement = 1.5 --This value is updated in calculatepBarIncrement.

  pPlayerHasSelected = FALSE  --Determines whether the player or computer sets pStartTime.

  member("selected").rect = rect(0, 0, 0, member("waveForm").height)

  member("sliver of now").rect = rect(0, 0, 1, member("waveForm").height)

  --The above lines change the height of the selected area and the sliver of now to be the same as the height

  --of the wave form graphic if you download the code and use a different wave form graphic and sound,

  --which I encourage you to do to discover the music of different types of sounds. Burroughs said that when

  --you cut tape, the future leaks out. Same with cutting into digital sound. The size of your graphic and the

  --length of your sound can be different from the source code's graphic and sound.

  dummy1 = timeOut("playAndUpdate").new(VOID, #playAndUpdate, me)

  dummy2 = timeOut("updateBar").new(VOID, #updateBar, me)

  --The "playAndUpdate" timer is used to play sounds and move the graphic of the currently selected area to

  --its proper spot and width. The "updateBar" timer is used to move the sliver of now back to the beginning

  --at the end of each loop. If you want more info on timers in Director, click the link at the bottom of this page.

  playAndUpdate me --Start the sound.

end beginsprite



on exitFrame me

  --Updates the position of the bar. The 'if' statement means: "If the position of the bar is not going to end up to

  --the right of the selected area, then move the bar. " Now, we should not need that 'if' if  pBarIncrement were

  --calculated with total precision. But if you set a Director movie to x fps, it may run faster than that, it may run

  --slower, depending on the speed of the machine and the amount of background processing. And

  --pBarIncrement depends on the framerate. So I calculate the effective frame rate, as you can see in

  --the calculatepBarIncrement handler at the bottom of the page.

  if pBarPosition + pBarIncrement <= sprite(gCurrentArea).right  then   

    pBarPosition = pBarPosition + pBarIncrement

    sprite(gBar).locH = pBarPosition  

  end if

  pFrameCounter = pFrameCounter + 1

  go to the frame

end exitFrame



on playAndUpdate me

  --This is called when the "playAndUpdate" timer times out, which happens when a sound finishes, after

  --theLoopCount repetitions of the sound. This handler is also called if the player clicks. This handler is

  --the brains of the show. First it recalculates pBarIncrement. Then it sets a startTime, if the user hasn't

  --clicked to establish it, and a random endTime, and a loop count. Then it does other nefarious things,

  --as below, including playing the sound. Finally, it sets itself to time out when the next sound ends.

  calculatepBarIncrement me

  --Randomly select a new current playing area and play the sound.

  --If the player has clicked, use their pStartTime.

  if not  pPlayerHasSelected then

    pStartTime = random(pDuration-250)

  end if

  theRemainder = pDuration - pStartTime

  theEndTime = random(theRemainder) + pStartTime

  theLoopCount = random(6)

  sound(1).play([#member: member("meaning"), #startTime: pStartTime, #endTime: theEndTime, #loopCount: theLoopCount]) 

  --Adjust the visible width and location of the current playing area.

  sprite(gCurrentArea).width = (theEndTime - pStartTime) * pConstant

  sprite(gCurrentArea).locH = sprite(gWaveForm).left + pConstant * pStartTime

  --Set the bar.

  pBarPosition = sprite(gCurrentArea).locH

  sprite(gBar).locH = pBarPosition

  --Set the timer alarm to when the current sound ends.

  timeOut("playAndUpdate").period = (theEndTime - pStartTime) * theLoopCount

  --This timer updates the bar at the end of each loop.

  timeOut("updateBar").period = theEndTime - pStartTime

  pPlayerHasSelected = FALSE

end playAndUpdate



on updateBar me

  --Position the bar at the beginning of the selected current area.

  pBarPosition = sprite(gCurrentArea).locH

  sprite(gBar).locH = pBarPosition 

end updateBar



on mousedown me

  --If the player clicks, they establish the new startTime.

  theMousieLocH = the mouseloc.locH

  if (theMousieLocH >= sprite(gWaveForm).left) and (theMousieLocH < (sprite(gWaveForm).right  - 250 * pConstant)) then

    --If the player clicked further than 250 ms from the end of the sound.

    pPlayerHasSelected = TRUE

    pStartTime = (theMousieLocH - sprite(gWaveForm).left) * pConstantReciprocal

    playAndUpdate me

  end if

end mousedown



on calculatepBarIncrement me

  --This handler recalculates pBarIncrement periodically. pBarIncrement is the number

  --of pixels to move the bar each frame. This number depends on how fast the machine

  --is running. The idea is to check and see how fast the machine is running and calculate

  --pBarIncrement accordingly.

  if pFrameCounter >10 then

    --Then we have some data to work with.

    effectiveFrameRate = pFrameCounter / (the milliseconds - pAnchorTime) --frames / ms

    pBarIncrement = pWaveFormLength / (pDuration * effectiveFrameRate) --pixels / frame


    pBarIncrement = 1.5 --This value is quickly replaced with a more accurate one.

  end if

  if  pFrameCounter > 500 then

    --Time to reset pFrameCounter and pAnchorTime to keep tabs on the current frame rate.

    --Although in the source code the tempo is set at 20 frames per second, an empirical

    --measure of the frameRate I did measured the effective frameRate at 53 fps on my

    --P2 450 MHz machine. So, if we say that the framerate is roughly 50 fps, then it takes

    --ten seconds before this code is executed, ie, we reset pFrameCounter and

    --pAnchorTime once every 10 seconds or so.  That will vary between machines,

    --but that's OK. The main thing is to keep accurate tabs on the current frame rate,

    --which will vary throughout the playing of the piece, owing to the machine doing

    --other things in the background. Never assume you know what the frame rate

    --of a movie is: measure it periodically. Otherwise, your piece may run fine on your

    --machine but not likely very well on other machines.

    pAnchorTime = float(the milliseconds)

    pFrameCounter = 0

  end if

end calculatepBarIncrement




See also:

Enigma n2 source code--887kb (requires Macromedia Director 8.5.1+)
Enigma n
Timers in Director
Other downloadable Lingo code