Who Am I?

Toney, Alabama, United States
Software Engineer, Systems Analyst, XML/X3D/VRML97 Designer, Consultant, Musician, Composer, Writer

Wednesday, April 25, 2007

AJAXing The X3D Sequencer

In this tutorial, we will take the X3D sequencer to the next logical steps of development.

  • We will add an HTML dashboard that enables the listener/viewer to play, pause, stop and set the audio volume of the sequence.


  • We will create and enable information displays such as the state of the sequencer (playing, paused, stopped), which song is currently loaded, and a display of information about the sequence such as the performer, songwriter, title, album, length of the song as well as a link to the performer’s web page and the contact information.


  • We will begin installing and configuring a select control to enable them to select different sequences to load and play. We will come back to this control in a later tutorial to complete it with other AJAX techniques that use XMLhttp objects to get information for each sequence.


  • The following figure illustrates the page as it will look when loaded and the sequence is playing.



    What is AJAX?



    There are plenty of sites on the web that describe AJAX in its full geeky glory. There is just enough information here for you to say, ‘yeah, got it’ and move on to the running code.

    In The Beginning the web was a dark world of dull gray pages with simple text hyperlinks. Since those days of plainitude, it has evolved to a distributed system of clients and database servers that can be queried and updated. A modern web page is now a fully-interactive client/server system. While once the average Jane or Joe could write a web page with a hand full of links and get $100 an hour for that, those days are long gone. It’s Real Computer Science now. Not to be afraid though, because I AM a rocket scientist and I’m going to put this into a Shake-and-Bake bag that you can use. I might even mix up a glass of Tang to go with it. Put down that Teflon-coated frying pan you are threatening me with, sit down on your bean bag, and pay attention.

    MEGO Danger: a geek-filled paragraph follows.

    The current phase of web application evolution revolves around basic techniques for getting and sending information in formats such as XML or JSON (Javascript Object Notation) between the client and the local file system or an occasionally connected server using the HTTP protocol. The client-side or server-side process the information using scripting languages such as Javascript or Jscript at the client and other languages on the server.

    The term, AJAX, originally meant Asynchronous Javascript and XML but has drifted into meaning any combination of these technologies that enables distributed client/server processing on the web with fast hidden processing of the data.

    Asynchronous: no waiting. One process does not wait for another to complete. The other process can work in the background and the first process will get notification when it is ready to give it something.

    Web 2.0 technology is not a revolution except perhaps in thinking about the use of the web. The technical concepts have been applied since Web 1.0 and even before the web. They are some of the oldest hypermedia approaches there are. Using client side hidden fields to hold information from database servers to increase client speed has been a common practice since the beginning of form-enabled graphical user interfaces.

    The common problem of web browsers has been the need to refresh the entire page every time the application made a request to the server for information. Hiding this refresh cycle to make a thin web client appear to behave like its more powerful fat client cousin does has been a goal since the earliest days of thin HTML web clients.

    Early web page authors used techniques such as hidden frames and fields to get and store information asynchronously to speed up refresh time. Many authors still use these techniques. The XMLhttp object takes the place of the hidden frame with hidden fields. With the Microsoft development of the XMLHttp object that gets XML from the server and persists the object in the client as a sort of mini-document database, the evolution to a Document Object Model (DOM) API and XML data-driven client was complete.

    • The XML object takes the place of the hidden frame and fields for storing as much information as is needed for the client for some set of operations.

    • The XMLHttp request object takes the place of plain HTTP requests

    • The DOM API becomes the API between the XML object and the HTML client.



    The beauty of the approach is that for the most part, all one needs to develop a fully-interactive client is the browser and an ASCII editor plus a mastery of the HTML language, the scripting language (typically Javascript) and the XML structures one will be using. Compared to earlier systems, this is extremely CHEAP; so, well within the means of anyone with access to the web.

    Why am I doing this part by hand when I insist on interactive tools for creating the actual 3D models?

    Building a 3D model by hand is very painful. A good one consists of hundreds even thousands of lines of strings of numbers (vectors) and the odds you can do that well and quickly are essentially zero. X3D looks like HTML but that resemblance is superficial.

    The Big Secret: An X3D model is not really a document and certainly not a document model for consumption by a document object model. We heisted the XML syntax and transcoded VRML97 into it with a few nicks here and there. We talk about X3D DOMs, but it’s a con to save us from the XML zealots. X3D and the SAI use a scene-graph object model. Shhhh!

    On the other hand, you can write HTML by hand reasonably quickly. Understanding it is vital to understanding how interfaces to other APIs such as the X3D Scene-graph Object Model SAI work. Tools for building integrated X3D/HTML applications don’t exist because, we’re making them up just now in a bathtub out back. When you come back, if you say the right word to the man behind the peephole, we might let you in to our geekEasy.

    Interactive tools for building these are good to have and productive, but strictly speaking, they aren’t required for integration and in fact can hide things from a developer they are better off taking the time to learn in detail. Worse, third party frameworks can introduce reliance on client-side and server-side objects that the end user must get and install. The less of this you use, the better the reach of your page and the cheaper it is.

    Remember every black box you install comes not only with the costs of getting it and putting it on every machine/client/site that must use it, but also the costs of certifying it and maintaining it in the face of changes among ALL of the other components. This hidden cost to bottom up development by top down designs is well known but often underestimated. Don’t let the wagging tongues of the K.I.S.S. gods keep you from washing their feet every morning.




    Great Karmic Lies of Engineering: I convinced our new owners I am keeping customers happy by adding features while simultaneously reducing costs. So now they want to know how many of you I can fire.


    That bit of preaching aside, the question, is how does this AJAX approach work with the X3D browser, itself, a third party object framework? What kind of complexity does it add and how do you keep that to a minimum?

    What is the Scene Application Interface (SAI)?



    The simple answer is standards. In the third stage of developing standards for 3D On The Web, the members of the Web3D Consortium and the VRML community chose five dependent goals as part of the major effort to create the next generation 3D system for the web:


    1. To create an XML-encoding for the next generation of VRML

    2. To find and fix as many sources of incompatibility and ambiguity in the object model for standard 3D web browsers

    3. To add objects and utilities that experience with VRML show are useful to authors and necessary to meet the other goals.

    4. To create a better external and internal interface to the objects such that manipulation of the 3D content is easier and more reliable.
    5. To make the 3D interface model as similar as possible to the Document Object Model popular in HTML web browsers so manipulation of content in any web page that embeds a 3D browser is easy and familiar.



    In short: to enable 3D scenes that are easy to build and can talk to and listen to an HTML document or a network of other 3D scenes.

    The outcome of goal 1 is the X3D XML-encoding. The outcome of goal 2 is a much improved object model for the X3D standard. The outcomes of goal 3 are better 3D objects such as the extended audio node and the event utilities that enable easier time-based behaviors without requiring scripts but that work with scripts. The outcome of goals 4 and 5 is the Scene Application Interface that replaces the External Application Interface of VRML97. In combination, these and other goals resulted in the next generation X3D language and object model.

    The SAI is the means to send and get events from the X3D scene-graph object model to any environment such as an HTML browser. A future improvement could add XML support in the form of XML parsing and will include a Network Sensor to enable easy author-access to network resources. These improvements can create a true 3D Web of interconnected 3D clients with or without a single grid infrastructure such as one finds in hosted online game systems and 3D social spaces such as There.com and Second Life. It is even thinkable to not use the HTML browser at all.

    On the other hand, HTML pages can be used for all of the interface controls that you don’t want cluttering up your scene such as publishing information, scene controls, Digg This, favorite pages, well-formatted text and the other nine necessary yards of Social Media Optimization (SMO) links. It’s a tradeoff of utility and aesthetics.

    For the purpose of this tutorial, the X3D object is embedded in an HTML page and the HTML Javascript is used to access the X3D scene-graph object model and the HTML Document Object Model. The resulting application can be diagrammed as shown in the following illustration.



    In this tutorial, we are creating a page using the innermost block of this diagram: an HTML page with a contained X3D object. Communication between the HTML page controls and the X3D controls is mediated by Javascript through the DOM and SAI.

    To create this we need:

    • An X3D scene with named triggering nodes that issue events the DOM can listen to.

    • An HTML page to contain the HTML content and the embedded browser object with Javascript for receiving and dispatching events to HTML and X3D

    • A Cascading Style Sheet for creating a better looking and better maintained HTML. This isn’t strictly necessary for the tutorial, but it is for the HTML, so why not?



    Edit the X3D Sequencer Scene



    First we will add the X3D elements and attributes that we need for the sequencer to send and receive events. At this point, we are also losing VRML97 compatibility in that we are adding X3D elements and attributes that do not have corresponding VRML97 nodes and fields. We are crossing the 3D On the Web Rubicon, so to speak because a VRML97 scene graph cannot do what we are going to do here.

    Create the Metadata Nodes





    The X3D Meta element enables the author to add annotation information to an X3D scene. The author fills the name field with the values used to reference these tags and puts the annotative information in the content field. I am using these elements to store and transmit publication information similar to what one finds on sound recordings as part of standard industrial practice. For example, the play length of a song is used by radio programmers to create play lists. Copyright information is required. This is also a good way to send contact information such as web page and email addresses.

    If you were using the sequencer page as a means to aggregate and display sequences from other songwriters and bands, this is a good way to ensure that credit is given, rights are asserted, and the direct contacts are advertised. You could put other information here that supports other social media constructs. Because you want the owner of the sequencer to be responsible for providing that information to your page, putting them in the meta elements and enabling your page to pull these out of the X3D and put them in the HTML display is a time-saver and ensures the sequence provider is responsible for maintaining the information.


    <head>
    <meta name='Application' content='X3D Band In A Box' />
    <meta name='SongTitle' content='Taking My Time'/>
    <meta name='AlbumTitle' content='Will Work For Food'/>
    <meta name='Performer' content='Ground Level Sound'/>
    <meta name='Songwriter' content='Len Bullard'/>
    <meta name='Label' content='Blind Dillo Records'/>
    <meta name='Publisher' content='Landru Publishing'/>
    <meta name='Copyright' content='1993 All Rights Reserved'/>
    <meta name='Length' content='3:32'/>
    <meta name='PerformanceRightsOrg' content='BMI'/>
    <meta name='WebPage' content='3donthewebcheap.blogspot.com'/>
    <meta name='Email' content='cbullard@hiwaay.net'/>
    </head>


    DEF Name the URI Resources



    To use the LoadSensor element as demonstrated later, you need to give all of the URI resources in your scene DEF names. This includes image textures, movie textures and audio nodes as well as any inline scenes. Be aware that Background elements cannot be tracked by a LoadSensor because the multiple URIs in the element would be ambiguous and the sensor would not be able to determine which named resource is loaded and when.


    <ImageTexture DEF="Kamala"
    containerField='texture'
    url='
    "texture/kamala01.jpg"'/>


    If you reuse the same resource, such as a texture multiple times, you only need to include it in the LoadSensor once. A USE attribute will be more efficient for reusing the resource.


    <ImageTexture USE="Kamala " />


    Even if for some other purpose you need to give the resource a different DEF name, pick one DEF named element containing that resource and include it in the sensor.

    Create the SAI Triggers



    To interact with the HTML page, a set of X3D triggers is created to receive and send events through the SAI. In some cases, you could use triggers that you already have in the scene, and in others, you want exclusive triggers. This depends on the effects and side effects. For example, if you have a vital piece of animation that depends on an audio node isActive event you don’t want to disable that event prematurely by setting the audio enabled externally but you may want to be able to turn the sound up and down through setting the audio intensity field.

    We’ll look at how each of these triggers is used when we cover the HTML script. Otherwise they behave as any internal X3D trigger does. Note that for that reason, they are in their own group. Otherwise, if they are not parented, the browser will assume that the TouchSensors are exposed to the entire scene and if the user clicks anywhere in the scene display, they will fire. That might be bad juju so parent them in a Group node.

    This element set introduces an X3D node not found in VRML97: the TimeTrigger. The TimeTrigger is part of the new Event Utilities of X3D that enable you to create complex timing and event routing sequences without the use of scripting. It might be possible to recreate our sequencer using the Event Utilities exclusively, but it might not be easier if you are already comfortable with Javascript. A Time Trigger accepts an SFBool event and sends an SFTime. It can be used with Booleans such as isActive sent from an audio node to trigger other events at the time the isActive goes to true or false.


    <Group DEF="SAITriggers">
    <TouchSensor DEF='setButtons' />
    <TouchSensor DEF='stopAudio' />
    <TimeTrigger DEF='stopAudioTrigger'/>
    <TouchSensor DEF='startAudio' />
    <TimeTrigger DEF='startAudioTrigger'/>
    <TouchSensor DEF='volUp' />
    <TimeTrigger DEF='volUpTrigger'/>
    <TimeTrigger DEF='volDownTrigger'/>
    <TouchSensor DEF='volDown' />
    <TouchSensor DEF='pause' />
    <TimeTrigger DEF='pauseTrigger'/>
    <TimeSensor DEF="FaderTS" startTime="-1" loop="FALSE" cycleInterval="3" />
    <ScalarInterpolator DEF="FadeScalar" key="0 1" keyValue=".785 1" />
    <TouchSensor DEF='setButtons' />
    <TimeSensor DEF='setVolumeUp'
    cycleInterval='0.2'
    loop='FALSE'
    startTime='-1'/>
    <TimeSensor DEF='setVolumeDown'
    cycleInterval='0.2'
    loop='FALSE'
    startTime='-1'/>
    </Group>


    Script Internal to External Events: From X3D SAI to HTML DOM



    These scripts are used by the SAI to set states of buttons and displays in the HTML page. The first script is already in the scene. It is the script used to make the flaming texture vanish at the end of the song. When picking events for different animation effects, one considers the timing of the event and this is just as true for sending events to the HTML DOM. Particularly, one wants to set the states of the buttons so that the correct buttons are available at the right time, Play when the song is stopped, and Pause and Stop when the song is playing. The Select box for selecting new songs should only be active when the scene loads or when the song is stopped. Allowing DOM to SAI events at other times can create conflicts that cause the web browser to hang. For example, one doesn’t want any of the controls to become enabled until the scene has completed loading.

    So part of the design of any page that integrates real time events with external controls is to work out which objects provide the right events at the right time. Fortunately with a linear sequence, this is reasonably straightforward. With a complex non-linear scene, it is harder but doable. In the following examples, I modify existing scripts and use functions or add functions to control the button states through the SAI. For the LoadSensor, I add a script used for SAI events and to control the LoadSensor display in the scene. There is no one right way to do this.

    Modify the FLAMESCRIPT



    The modifications to the FLAMESCRIPT are as follows:

    Add a field, setBtns to the script to get access to the setButtons TouchSensor in the SAI Group.


    <Script DEF='FLAMESCRIPT' directOutput='TRUE' mustEvaluate='FALSE'>
    <field name='vanish' accessType='inputOnly' type='SFBool'/>
    <!-- Add this field -->

    <field name='setBtns' accessType='initializeOnly' type='SFNode'>
    <TouchSensor USE='setButtons'/>
    </field>



    Add internal events to the vanish function to send Boolean events to the TouchSensor when the Boolean event received by the script is evaluated.


    function vanish (value) {
    if (value == FALSE) {
    mat.transparency = 1;
    light.on = FALSE;
    // add this event
    setBtns.isActive = TRUE;
    }
    else if (value == TRUE) {
    mat.transparency = 0;
    mat2.transparency = 0;
    light.on = TRUE;
    view.set_bind = TRUE;
    // add this event
    setBtns.isActive = FALSE;
    }
    }


    Use the X3D AudioClip



    The sequencer illustrates one of the improvements of X3D over VRML97: the AudioClip node includes new fields for pausing and resuming audio. In VRML97, startTime and stopTime fields could receive SFTime events to start and stop audio, but could not pause and resume at the point of the pause. An X3D AudioClip can do this. Here is the complete specification for an X3D AudioClip.


    AudioClip : X3DSoundSourceNode, X3DUrlObject {
    SFString [in,out] description ""
    SFBool [in,out] loop FALSE
    SFNode [in,out] metadata NULL [X3DMetadataObject]
    SFTime [in,out] pauseTime 0 (-?,?)
    SFFloat [in,out] pitch 1.0 (0,?)
    SFTime [in,out] resumeTime 0 (-?,?)
    SFTime [in,out] startTime 0 (-?,?)
    SFTime [in,out] stopTime 0 (-?,?)
    MFString [in,out] url [] [urn]
    SFTime [out] duration_changed
    SFTime [out] elapsedTime
    SFBool [out] isActive
    SFBool [out] isPaused
    }


    Stupid Pet Trick # 6: Route a ScalarInterpolator with rising and falling values to the pitch and intensity fields as an approaching object with an AudioClip approaches and passes a Viewpoint currently bound. If you want to go further, route the Scalar through a Script that modulates its values making them more random, convert them to SFVec3F events and and route those to the Viewpoint position. Now look carefully at the screen shot opening this tutorial at the Ground Level Sound CD cover with the blind armadillo about to be run down by that truck. Oh yeah…

    Modify the Kareoke Sequencer Script



    The sequencer script is the logical script to control most of the SAI trigger events for starting and stopping the sound and animation. For convenience, the volume triggers are also included in this script.

    First we add the field elements for the nodes and properties we need.


    <Script DEF='Kareoke' directOutput='TRUE' mustEvaluate='FALSE'>

    <field name='pause' accessType='inputOnly' type='SFTime'/>
    <field name='pauseState' accessType='inputOutput' type='SFInt32' value='0'/>
    <field name='volumeUp' accessType='inputOnly' type='SFTime'/>
    <field name='volumeDown' accessType='inputOnly' type='SFTime'/>
    <field name='song' accessType='initializeOnly' type='SFNode'>
    <TimeSensor USE='Song_Sound'/>
    </field>
    <field name='kareokeTS' accessType='initializeOnly' type='SFNode'>
    <TimeSensor USE='Kareoke_Timer'/>
    </field>
    <field name='audio' accessType='initializeOnly' type='SFNode'>
    <AudioClip USE='myAudio'/>
    </field>
    <field name='mat' accessType='initializeOnly' type='SFNode'>
    <Material USE='FlameMat'/>
    </field>


    Volume Functions


    The volume functions are simple. When the Volume up (+) button is pressed, it sends an event to the SAI that starts a Timer that runs once. When the function receives the Timer value, the function adds one-tenth to the AudioClip intensity field value. Using dedicated timers to set events is a reasonable way to work with the SAI just as with internal X3D processing.


    function volumeUp (value) {
    if (song.intensity < 1.0) {
    song.intensity = song.intensity + 0.1;
    }
    }


    When the Volume down (-) button is pressed, the reverse happens.


    function volumeDown (value) {
    if (song.intensity > 0.0) {
    song.intensity = song.intensity - 0.1;
    }
    }


    The Volume down button can lower the volume but not all the way to silence.

    Pause Functions


    The Pause function is a little more complex. It stores the current pause state, pauses the audio by sending it the timestamp which is a standard argument for any function in an X3D script because events are always passed with a timestamp. It also stops and starts the animation by stopping the master kareoke timer. The Pause button both stops and starts the audio and the Timer, but only that audio restarts where it was paused. The Timer restarts at zero. Because the kareoke timer is sending events at intervals of one second, this technique appears to work, but if you press the button and leave it off, then restart, you will notice that the animation and the sound are desynchronized. Re-synchronizing event timers would require more complicated script than shown here.


    function pause (value, timestamp){
    if (pauseState == 0) {
    kareokeTS.enabled = FALSE;
    audio.pauseTime = timestamp;
    pauseState = 1;
    }
    else if (pauseState == 1) {
    kareokeTS.enabled = TRUE;
    kareokeTS.startTime = timestamp;
    audio.resumeTime = timestamp;
    pauseState = 0;
    }
    }


    Create a Load Sensor



    The LoadSensor is a new X3D node. It enables you to track and report on the loading status of the URI referenced resources in your scenes such as images, movies, audio clips and inlines. As explained earlier, it can’t track a Background node. For that reason, events that rely on the LoadSensor completion for timing other events should have a fudge factor added for the background images if any.

    The LoadSensor is obviously useful for driving a display that gives the end user the status of the time it takes to load the scene and when the loading is complete or if it stalls because a resource is not available. Thus, it also can be used to find out if your resources are where you thought you put them and if your syntax is right.

    Syntax? Isn’t that what XML does?

    A little side excursion into a problem you might have with specifying pathnames. Because of the X3D XML-encoding is a transcoding of the original VRML97 syntax, URI values are stored in XML attributes. In VRML97, the syntax is object-oriented. In objects, fields can contain other objects. The transcoding attempts to map objects to XML elements and object properties, aka, fields, to XML attributes. However, in object structures, fields can contain objects but in elements, attributes cannot contain elements. This is sometime referred to as the object-to-XML impedance mismatch. XML is for data; objects are for data and methods. This is not a marriage made in heaven.

    As a result, to keep the transcoding as one-to-one as possible, it was decided to map multiple URI declarations for URI objects into XML attributes by a little trick called microparsing. The designer adds a character to the strings in the attribute to delimit them, and the application is assumed to sort these out after the XML parser has completed its run.

    This trick is controversial in XML design circles. It hides the object/XML mismatch but also introduces a potential failure mode in the application syntax which the XML parser will not catch. The application has to catch it. If not, it passes silently into the application where it can introduce side effects such as causing other functions to fail without giving good error messages if any at all.

    Programmers are obsessed with size and speed when perhaps they ought to be more obsessed with the customer, and maybe that explains their love lives. Finding these kinds of post-XML syntax mistakes introduces costs to the application, introduces noisy data to the web, and is a PIA for the average page builder who can’t afford the special tools made to locate these tiny mistakes if the browser doesn’t.

    Since X3D browser vendors and customers originally used a VRML97 processing model that was much more fault tolerant of author errors, there is no love there for causing the application to halt-and-catch-fire (aka Draconian parsing) as there is in XML circles. X3D browsers vary considerably in their forgiveness although they do have options that can be set for stricter error checking.

    Let’s look at what happens with the LoadSensor. In a URI field value, the field values are separated by double quotes. The attribute value as you recall is surrounded by single quotes. Here is what will happen if you have to rely on micro-parsing or app-specific loading checks:

    Author creates two calls for the same texture:

    <ImageTexture
    containerField='texture'
    url='
    "texture/kamala01.jpg"'/>


    and


    <ImageTexture DEF="Kamala"
    containerField='texture'
    url='
    ""texture/kamala01.jpg"'/>


    Note that the second declaration has an extra left double quote. When copying and pasting, this is an easy error to introduce and nearly impossible in a large file to spot by eye. Because the extra double quote is inside the paired single quotes, the XML parser considers it well-formed or syntactically correct. In other words, this is not an error in XML so XML’s well-known halt-and-catch-fire rule will not protect you.

    Console reports:

    Loading file:///F:/VRML/generalAudio/TakingMyTime/texture/kamala01.jpg...
    Done

    Why? Because the extra declaration means the file did load so the LoadSensor watchlist value is satisfied. The author will notice on rendering, a single a white figure in the scene because this is a prominent texture. The author will look at the X3D browser console. All is well there. The only indication of a problem is that the author put a LoadSensor and LoadSensor display in with the DEF pointing to the XML-well-formed but micro-malformed field delimiter syntax and the LoadSensor fails to reach 1 (loading complete). It gets to 98% and stops.

    Second case:


    <ImageTexture DEF="Kamala"
    containerField='texture'
    url='
    "texture/kamala01.jpg"'/>


    The LoadSensor completes normally because the DEFed node is well-formed and micro-well-formed. The LoadSensor ONLY checks DEFed nodes.

    If an author is relying on the LoadSensor, they have to DEF every thing it checks or know that to rely on the design, they have to USE copies. They may or may not. The only way they will notice is if the resource is prominent in the scene OR if they built and used the LoadSensor to catch every URL resource OR to know that relying on a single report from the console won't be reliable in the edge cases as shown.

    On the other hand, we can live with this. This explanation is included to show you one way a LoadSensor might fail to complete even though you are sure all of the resources are there and did load. If you are counting on the completion events to trigger a behavior and it fails but your scene looks fine, check for this. If the LoadSensor completes, yet you notice a missing texture, check for this. If you don’t notice, the texture may not be that important. Choose wisely.

    Create a Load Sensor Display



    This part of the sequencer is based on code written by Peter Gerstman at Ohio State University and put in a public proto library. I adapted and simplified the function and the display code. I added a TimeSensor/ScalarInterpolator combination to fade the display out of the scene when the load is complete.

    The display function takes the LoadSensor.progress event out which is an SFFloat ranging between 0 and 1 with 1 representing the completion of the loading. The floating point value is converted to a percentage. The percentage is assigned to a Text node array value where it displays percentage done. It is added to the x dimension scale value of a Transform containing an indexed face set. The results are a sliding bar that grows as the sensor executes and a numeric value that is displayed. The following figure shows this.



    Notice that the images are not completely loaded such as the Rose and the flashing star on the bass drumhead. Others are part of the sequence displayed during the song play so aren’t noticeable here but are checked by the LoadSensor. After the load completes, the display shows ‘Done’ and fades from the view.



    The button states are still disabled because the fading event timer triggers the states of the buttons and sets a Song Title display in the left column of the HTML page. This adds a little more fudge factor to enable the audio node to stream more completely.

    Note that while the audio node loading is tracked by a LoadSensor, when it gets the first frame/chunk of streaming audio, it considers it done. The effect is not noticeable on broadband where speeds are sufficient to stay ahead of the animation, but on dial-up, the audio can’t keep up and will stop and start. This will cause the audio to get out of sync with the animation. The recourse is to let the audio play through, then restart the scene.

    This is an example of using an SAI event listener to trigger an HTML display function to change the state of the HTML page text. The same HTML text is used by HTML DOM functions in the HTML page when the Select button in the dashboard is activated, and when the Pause and Stop buttons are used. This is an example of the interaction possible between the X3D scene graph and the HTML DOM via the SAI. While simple enough to create, this dynamic display of information is a key use of the X3D SAI and HTML DOM when integrated. We will use this technique again to get the contents of the X3D meta nodes and display them on the HTML page when a song sequence is selected and played.



    Now the Play button and the Select Songs box are active. The Pause button is disabled, and the Song title display shows the value of the default song selected in the control. In the HTML DOM code examples that follow, I will show you how to do this in response to the SAI events in combination with the HTML DOM events.

    Here is the code for the LoadSensor display.


    <Transform translation='.6 2 -.1'
    containerField='children'
    scale='.5 .5 .5'>
    <Transform DEF='PROG'
    containerField='children'
    translation='0 0 .025'
    scale='.001 .1 1'
    center='-1.4 0 0'>
    <Shape DEF='c'
    containerField='children'>
    <Appearance DEF='A'
    containerField='appearance'>
    <Material DEF='material0'
    containerField='material'
    ambientIntensity='0.200'
    shininess='0.200'
    transparency='0.0'
    diffuseColor='1 0 0'/>
    </Appearance>
    <IndexedFaceSet DEF='B_Geo'
    containerField='geometry'
    creaseAngle='0.524'
    coordIndex='
    0 1 2 -1
    0 2 3 -1
    0 3 0 -1'>
    <Coordinate DEF='B_Coord'
    containerField='coord'
    point='
    -1.4 -.2 0
    1.4 -.2 0
    1.4 .2 0
    -1.4 .2 0'/>
    </IndexedFaceSet>
    </Shape>
    </Transform>
    <Transform
    containerField='children'
    translation='-.75 -.145 -.025'>
    <Shape
    containerField='children'>
    <Appearance
    containerField='appearance'>
    <Material DEF='material1'
    containerField='material'
    ambientIntensity='0.200'
    shininess='0.200'
    transparency='0.300'
    diffuseColor='0 0 1'/>
    </Appearance>
    <Text DEF='MSG'
    containerField='geometry'
    string='"Loading..."'
    maxExtent='0.000'>
    <FontStyle
    containerField='fontStyle'
    family='SERIF'
    style='PLAIN'
    justify='"BEGIN"
    "BEGIN"'
    size='0.3000'
    spacing='1.000'/>
    </Text>
    </Shape>
    </Transform>
    </Transform>


    Create Load Sensor Node



    The LoadSensor is shown below. I’ve truncated the list in the actual sequencer code for brevity. The content of the LoadSensor is a watchList. The watchList is a set of elements to be checked. They are identified by their element type and a USE reference to the actual node in the scene. Note that LoadSensor will not display the nodes it contains. For that reason, each element is referenced with a USE attribute. As noted above, you only need to reference a particular resource once if it is used multiple times in a scene.

    HINT: To make the load faster, make liberal use of the USE attributes in the scene so the browser will only load each texture or audio file once.


    <LoadSensor DEF='LoadExternal' timeOut='0' >
    <ImageTexture USE="Kamala" containerField='watchList'/>
    <ImageTexture USE="DilloLogo" containerField='watchList'/>
    <MovieTexture USE='Lightning' containerField='watchList'/>
    <AudioClip USE='myAudio' containerField='watchList'/>
    </LoadSensor>


    Note that where you are using X3D Inline elements to pull pieces of the scene into the world, as I do in River of Life for the larger scene components such as the Temples and Kamala, a LoadSensor can also include and check references to these. It is one of the reasons for upgrading ROL to the X3D standard later this year.

    The timeOut attribute specifies an amount of time in seconds until the sensor stops if the load does not complete. If the load fails after some time, the sensor can send a message and the program can take some action. It is set to zero meaning it never times out. Optimistic? Sure.

    Create Load Sensor Script



    This is the script for the load sensor. It has a field for the progress events output by the LoadSensor and it includes the element needed to set the load bar display, the load text and to fade the display when done. Note that the timestamp sent to the fader is increased by two seconds. Again, this is a fudge factor added to the fade time.


    <Script DEF='DisplayLoadProgress' directOutput='TRUE' mustEvaluate='FALSE'>
    <field name='progress' type='SFFloat' accessType='inputOnly'/>
    <field name='load' type='SFNode' accessType='initializeOnly'>
    <LoadSensor USE='LoadExternal' />
    </field>
    <field name='prog' type='SFNode' accessType='initializeOnly'>
    <Text USE='PROG' />
    </field>
    <field name='msg' type='SFNode' accessType='initializeOnly'>
    <Transform USE='MSG' />
    </field>
    <field name='fader' type='SFNode' accessType='initializeOnly'>
    <Transform USE='FaderTS' />
    </field>

    <![CDATA[javascript:


    The LoadSensor works automatically. If the value of the progress event is 1, the function sets the text string in the display to “Loading Done!”. Otherwise, the function multiplies and rounds the floating point value of the progress event to integer values using a standard Javascript math library routine. It adds the percentage symbol to the text. The next statement outside the if clause is sending the SFFloat value to the x value of the scale attribute of the indexed face set. Thus as the load increases, the bar grows longer in the x axis.

    This is a clever piece of simple code doing a job cleanly and easily. Send Peter Gerstman mail and say “Thanks!”


    function progress (p, timestamp) {

    if(p >= 1) {
    msg.string[0] = 'Loading Done!';
    fader.startTime = timestamp + 2;
    }
    else {
    msg.string[0] = 'Loading..' + Math.round(p*100) + '%';
    }
    prog.set_scale = new SFVec3f(p, .5, 1);
    }
    ]]>
    </Script>


    Hook Up The X3D Events



    Listed below are the ROUTE statements that hook all of this up. They are self-explanatory and included here so you can follow events through the sequencer code.


    <ROUTE fromNode='LoadExternal' fromField='progress' toNode='DisplayLoadProgress' toField='progress'/>

    <ROUTE fromNode='FaderTS' fromField='fraction_changed' toNode='FadeScalar' toField='set_fraction'/>
    <ROUTE fromNode='FadeScalar' fromField='value_changed' toNode='material0' toField='set_transparency'/>
    <ROUTE fromNode='FadeScalar' fromField='value_changed' toNode='material1' toField='set_transparency'/>
    <ROUTE fromNode='stopAudio' fromField='isActive' toNode='stopAudioTrigger' toField='set_boolean' />
    <ROUTE fromNode='stopAudioTrigger' fromField='triggerTime' toNode='myAudio' toField='stopTime'/>
    <ROUTE fromNode='stopAudioTrigger' fromField='triggerTime' toNode='Main_Timer' toField='stopTime'/>
    <ROUTE fromNode='stopAudioTrigger' fromField='triggerTime' toNode='Kareoke_Timer' toField='stopTime'/>
    <ROUTE fromNode='startAudio' fromField='isActive' toNode='startAudioTrigger' toField='set_boolean' />
    <ROUTE fromNode='startAudioTrigger' fromField='triggerTime' toNode='myAudio' toField='startTime'/>
    <ROUTE fromNode='startAudioTrigger' fromField='triggerTime' toNode='Main_Timer' toField='startTime'/>
    <ROUTE fromNode='startAudioTrigger' fromField='triggerTime' toNode='Kareoke_Timer' toField='startTime'/>
    <ROUTE fromNode='startAudioTrigger' fromField='triggerTime' toNode='Kareoke' toField='setCycle'/>
    <ROUTE fromNode='volUp' fromField='isActive' toNode='volUpTrigger' toField='set_boolean' />
    <ROUTE fromNode='volUpTrigger' fromField='triggerTime' toNode='Kareoke' toField='volumeUp'/>
    <ROUTE fromNode='volDown' fromField='isActive' toNode='volDownTrigger' toField='set_boolean' />
    <ROUTE fromNode='volDownTrigger' fromField='triggerTime' toNode='Kareoke' toField='volumeDown'/>
    <ROUTE fromNode='pause' fromField='isActive' toNode='pauseTrigger' toField='set_boolean' />
    <ROUTE fromNode='pauseTrigger' fromField='triggerTime' toNode='Kareoke' toField='pause'/>


    The HTML Web Page



    This section is possibly a harder part of this tutorial. I am assuming that you know how HTML works and structure. I have included all of the code for the HTML but I have also deliberately kept it simple for this tutorial. Any skilled HTML web page designer can improve upon it. By leaving out necessary HTML design elements such as absolute positioning, more powerful CSS and sticking to a vanilla table-formatted page, I can focus on the DOM scripting that sets up the SAI communications and coordinates with HTML object events on the page.

    NOTE: My productivity improved when I remembered that Internet Explorer has a Script debugger built into it. By default it is turned off, but if you go to the Internet Options and select the Advanced button, there is a checkbox to turn it on. It will save you ENORMOUS amounts of time.

    I am informed that the Firefox browser has a similar debugger called FireBug that is even better. Regardless of which you use, do use one. On the other hand, remember if you go surfing the web, 99 pages out a hundred have scripting errors. If you forget to turn the debugger off, IT WILL REMIND YOU.

    Build A Cascading Style Sheet (CSS)



    I am using a CSS style sheet for consistency in the dashboard elements. I include the CSS here for completeness. You can look at the HTML class attributes to see where these are used. An orange and maroon design turned out to be cosmically appropriate the week I was coding the page, but was also quite unintentional. Serendipity scales to universal forces. Ponder that a while then move on to the code. I send best wishes to my friends at VT anyway.


    .control {
    font-family: "arial", "sans-serif"; "helvetica";
    font-size: 12pt;
    background-color: #880000;
    color: #FFFFFF;
    font-weight: bold;
    text-align:left;
    align: left;
    valign: top;
    }

    .metadata {
    font-family: "arial", "sans-serif"; "helvetica";
    font-size: 10pt;
    color: #0000FF;
    text-align:left;
    align: left;
    valign: top;
    }

    .playTime {
    font-family: "arial", "sans-serif"; "helvetica";
    font-size: 10pt;
    color: #00FFFF;
    text-align:left;
    align: left;
    valign: top;
    }

    legend {
    font-size: 13pt;
    color: #00FFFF;
    font-weight: bold;
    text-align:left;
    align: left;
    valign: top;
    }


    Create the HTML



    For this HTML page, I included the Javascript inline. Once I have a page running, I pull the script out and put it in an external file, but while coding and debugging I find it easier to leave it inline. For that reason, the script URI is commented out.

    The code in this section is based on the examples for AJAX X3D created by Tony Parisi of Media Machines for the
    Ajax3D Organization and examples created by
    Joe Williams.

    Have you turned the browser script debugger on?

    Create the HEAD Element


    First we have the obligatory head and title tags. Note that I am not including an XML doctype that would enable XML validation of (x)HTML. This is a simplification in the example and is not recommended for common practice. It will work in most modern HTML browsers but degrades reuse for other applications.


    <html>
    <head>
    <title>X3D Band In A Box Sequencing Demo With AJAX</title>
    <style type="text/css">@import "style/text01.css";</style>
    <!-- src="scripts/sequencer.js" -->


    All of the functions used in this page are in one script tag except for one line of scripting code used in the body to load the X3D object (the Flux viewer) in position in the page.


    <script type="text/javascript" >


    Set up the SAI (SAI to HTML) Functions


    The next lines initialize global variables that will be used later in the page. The paused variable is set to zero to indicate that when the page loads, the song is not paused (it ain’t played yet, sucka). The current song variable is blank.


    var paused = 0;
    var curSong = "";


    The following initialize variables for objects that will be needed to set up the connections to the X3D browser through the SAI.


    var web3Dbrowser = null;
    var web3Dbrowsercontext = null;
    var listener = null;
    var listenersSetup = false;


    The next line calls the main function to initialize the page.


    window.onload = initX3Dbrowser;


    The main function called by the DOM onload function creates the running environment for the X3D object embedded in the page and sets the states of the HTML controls. Joe improved the original examples to do more error checking and to ensure the browser is initialized prior to loading the X3D scene.

    In the initX3Dbrowser function, note the statement:


    web3Dbrowser = document.objx3d.getBrowser();


    This identifies the X3D viewer using an HTML DOM call to an object in the DOM tree by name and a method supported by the object of that name, in this case the Flux viewer. If you look below, you will note that when the Flux viewer is referenced by the DOM object element, the name of the DOM element is ‘objx3d’. This method call relies on that name matching the statement above. You can call it anything you like, but be sure they match or all of the references by the name web3Dbrowser will fail with an error message something like “referenced object not found or is null”.

    SAI support depends on supporting the getBrowser method. Subsequent calls to the SAI will use the variable name for this object. Keep this distinction in mind: calls prepended by ‘document’ are HTML DOM calls. Calls prepended by the browser variable, in this example, ‘web3Dbrowser’ are calls to the X3D SAI.

    Once these objects are created and initialized, the DOM listeners are assigned to the X3D SAI objects using the variable name given in the assignment statement above. After that, it is a straightforward process of creating new DOM listeners for each SAI event you need and context variables for X3D properties you want to set from the DOM. Through this code, the DOM and the SAI can now communicate. This is the heart of the AJAX client-side design.

    initX3Dbrowser Function


    This function that initializes the X3D browser. It declares a variable for storing the browser as an HTML DOM document object and loads the target X3D world, in this example, the sequence for the song “Taking My Time” by Ground Level Sound. Again, note the name of the object tag in the HTML for the 3D browser object and ensure it matches the name in the document getBrowser() function call. This name is declared in the LoadObjx3d() function. The initX3Dbrowser function then sets the initial enable/disable states of the dashboard buttons.


    function initX3Dbrowser()
    {

    // Connect host DOM to X3D Browser SAI
    web3Dbrowser = null;

    // connect host DOM to X3D Browser SAI
    web3Dbrowser = document.objx3d.getBrowser();
    if (web3Dbrowser == null) {
    alert("Couldn't get X3D Browser object!");
    return;
    }

    // Connect SAI browserChanged event to DOM
    listener = new Object();
    listener.browserChanged = web3DbrowserChanged;

    // assign DOM browserChanged() handler
    web3Dbrowser.addBrowserListener(listener);

    // Load target X3D scene
    // NOTE: To support the select box, this function uses the songs function to ensure
    // the selected option matches this call, and to enable the user to select a different song.
    // This isn’t included in this installment of the tutorial.

    web3Dbrowser.loadUrlS('TakingMyTime.x3d');

    // NOTE: loadUrlS is an artifact of the Flux implementation. The spec standard is
    // loadUrl.

    // disable buttons until file is loaded.

    document.getElementById('play').disabled = true;
    document.getElementById('stop').disabled = true;
    document.getElementById('pause').disabled = true;
    document.getElementById('songs').disabled = true;
    }


    web3DbrowserChanged(evt) Function


    This function checks to ensure that the 3D browser has been initialized with the target scene, then creates the Execution Context object. The Execution Context object is the X3D SAI means of setting and getting events from the X3D object. If it fails, the page loading procedure fails. If it succeeds, the function processes normally and calls a function (setupListeners) that creates the listener objects for each object in the X3D scene that the HTML DOM must pay attention to.


    function web3DbrowserChanged(evt)
    {
    web3Dbrowsercontext = null;

    // test to see if the target scene is loaded and running

    if (evt != 0) // BROWSER_INITIALIZED = 0
    {
    alert("browserChanged(evt) != 0");
    return;
    }

    // The next statement gets the ExecutionContext of the X3D browser and names it.
    // Subsequent references to the context object are by this name.

    web3Dbrowsercontext = web3Dbrowser.getExecutionContext();

    // Test immediately that the context is set. Otherwise, give an error alert and stop
    // processing now.

    if (web3Dbrowsercontext == null)
    {
    alert("Couldn't get executionContext object!");
    return;
    }

    // This is a call to the function that gathers the various listeners needed
    // into a single function call.

    setupListeners();
    }


    setListenerObserver Function


    This is a helper function that abstracts the details of setting up a listener into a single function that is then used repeatedly in the setupListeners() function. You should not modify this function.


    function setListenerObserver(nodeName,fieldName,callback)
    {
    var node = null;
    var field = null;

    node = web3Dbrowsercontext.getNode(nodeName);
    if (node)
    {
    field = node.getField(fieldName);
    if (field)
    {
    var observer = new Object;
    observer.readableFieldChanged = callback;
    observer.field = field;
    field.addFieldEventListener(observer);
    field = null;
    return observer;
    }
    }
    }


    Declare the Listener Global Variables


    The following statements declare variables that will contain the references to the scene components to listen to (SAI to DOM).


    var faderTS = null;
    var setbuttons = null;


    setupListeners Function


    This function creates the listeners. As a safety check, the context is checked again. If not provided, an error alert is issued and the processing returns to the calling function. If it is provided, the nodes for the X3D elements needed are retrieved by their DEF names in the X3D file using the context’s getNode methods. These are then passed to the setListenerObserver function to do the actual work of creating the listeners and giving them a name of the function that is listening and reacts to events. In this case, the Javascript methods in the HTML file are audioDone1 and loadDone.

    In this example, the listeners determine when the X3D fader for the LoadSensor completes thus adding the fudge factor mentioned earlier, and determine when the audio completes playing. Both X3D events are used to set the enable/disable states of the dashboard buttons.

    Of course, quite a bit more can be done with the same SAI functions, but this is all we need for now. In a complex simulation, one can use the SAI for many kind of feedback mediated control such as sending messages to other components or users.


    function setupListeners()
    {
    // Set up the observers that detect events

    if (web3Dbrowsercontext == null) {
    alert("web3Dbrowsercontext is null!");
    return;
    }
    else {

    setbuttons = web3Dbrowsercontext.getNode("setButtons");
    faderTS = web3Dbrowsercontext.getNode("FaderTS");

    setListenerObserver("setButtons", "isActive", audioDone1);
    setListenerObserver("FaderTS", "isActive", loadDone);
    }
    }


    audioDone1 Function


    The audioDone1 function listens to the SAI to determine is the audio has finished playing. If so, it enables the Play button and the Song Select and disables the Pause and Stop buttons. Since the event will fire twice, it counts and sets on the second event, then resets the count to zero for subsequent plays.


    var countAudio = 0;

    function audioDone1(f, t)
    {
    if (countAudio > 0) {
    // Set HTML Status text
    document.getElementById('play').disabled = false;
    document.getElementById('stop').disabled = true;
    document.getElementById('pause').disabled = true;
    document.getElementById('songs').disabled = false;
    if (curSong.length > 0) {
    document.getElementById('playlist').innerHTML = "Current Selection: " +curSong;
    }
    countAudio = 0;
    return;
    }
    else {
    countAudio++;
    }
    }


    loadDone Function


    The loadDone function only needs to know that the event it is interested in is received. That means if the loadDone function is called, it proceeds without further tests. Like audioDone1, it resets the buttons to the Play condition.

    The function also does one other thing which is to get the value of the default song selection in the select box as assigned by the author and puts that information in the status HTML paragraph on the left side of the page. In a future version, this will go away and the default selection will be provided in xmlHTTP-called configuration files or in the X3D metadata. However the function provided is worth studying as an exercise in walking nodes in a DOM tree to get information from one part of the tree (curOpt) and using it to set information in another part of the tree (curTitle). It’s ugly and perhaps will be replaced by XQuery calls someday.


    function loadDone(f, t) {

    // Set HTML Status text
    document.getElementById('playlist').innerHTML = "Select Song";
    document.getElementById('play').disabled = false;
    document.getElementById('stop').disabled = true;
    document.getElementById('pause').disabled = true;
    document.getElementById('songs').disabled = false;

    // initialize song display field in HTML with default selected value on songs select list

    var songSelect = document.getElementById('songs');

    for(i = 0; i < document.getElementById('songs').childNodes.length; i++) {
    if (songSelect.getElementsByTagName('option')[i]){
    curOpt = songSelect.getElementsByTagName('option')[i];
    for( var x = 0; x < curOpt.attributes.length; x++ ) {
    if (curOpt.attributes[x].nodeName.toLowerCase() == 'value'){
    curTitle = curOpt.attributes[x].nodeValue;
    }
    if((curOpt.attributes[x].nodeName.toLowerCase() == 'selected' ) &&
    (curOpt.attributes[x].nodeValue == true)){
    songTitle = getSongTitle(curTitle);
    document.getElementById('playlist').innerHTML = "Song: " +songTitle;
    }
    }
    }
    }
    }


    Set up the DOM To SAI Functions)



    In this section, we setup the controls needed on the HTML page and provide their DOM event functions.

    Get the Right X3D Player for the User’s Browser: loadObjx3d Function


    The following code from Joe Williams is used by a script within the HTML body to create the markup for loading the X3D object. Its purpose is to detect if the user is using Microsoft Internet Explorer. It does this by testing for the ActiveX object needed to support an IE plug-in. Otherwise, it defaults to the Firefox browser version of the Flux player. This technique of browser sniffing is necessary to cope with browser non-interoperability.


    function loadObjx3d() {
    if (window.ActiveXObject)
    {
    // browser supports ActiveX/IE API and specifies FluxPlayer
    document.write('<object id="objx3d" width="640" height="480" class="x3dscene" type="model/x3d+xml"');
    document.write('classid="clsid:918B202D-8E8F-4649-A70B-E9B178FEDC58"');
    document.write('codebase="http://mediamachines.com/download/SetupFluxPlayer.exe">');
    document.write('<param name="DASHBOARD" value="0">');
    document.write('<param name="BGCOLOR" value="0xaaaaaa">');
    document.write('</object>');
    }
    else
    {
    // browser supports Firefox/Netscape Plugin API.
    document.write('<object id="objx3d" width="640" height="480" class="x3dscene" type="model/x3d+xml"');
    document.write('codebase="http://mediamachines.com/download/SetupFluxPlayer.exe">');
    document.write('<param name="DASHBOARD" value="0">');
    document.write('<param name="BGCOLOR" value="0xaaaaaa">');
    document.write('</object>');
    }
    }


    Create the Event Handlers for 3D Sequencer HTML Dashboard Controls




    Now we will look at the functions for 3D Sequencer Dashboard controls. In these, the DOM is querying the X3D scene-graph through the SAI to get and set properties. The web3Dbrowsercontext object created above and the getNode() method are used extensively. To remind you, getNode gets the reference to the element in the X3D scene using the DEF name of the element/node.

    Some of the functions in the X3D Javascript and the special elements described above and grouped were created to ensure the X3D scene is accessible to the HTML DOM functions. As you review this code, you may want to look back at the X3D to see how they relate.

    Note that the Play All checkbox is not enabled in this tutorial. That’s for later when we have more sequences to play.

    play Function


    This is the onClick event for the Play button. It gets the nodes in the X3D scene graph to get and set their properties. Then, it uses the SAI getMetadata method to get the content of the X3D metadata nodes described earlier. This information is then displayed in the HTML column to the left beneath the song status display when the user presses the Play button. It also replicates the play length information in the dashboard itself.

    It then tests for the audio context and if provided, uses the startSound isActive value acquired from the X3D scene trigger to start the audio and animation and set the initial states of objects in the scene.


    function play() {

    // get all nodes needed for operation

    startanimation = web3Dbrowsercontext.getNode("KareokeStart");
    startsound = web3Dbrowsercontext.getNode("startAudio");
    vizxTS = web3Dbrowsercontext.getNode("vizx_init");
    kareokeTS = web3Dbrowsercontext.getNode("Kareoke_Timer");
    mainTS = web3Dbrowsercontext.getNode("Main_Timer");
    colorTS = web3Dbrowsercontext.getNode("Color_Timer");
    lyric = web3Dbrowsercontext.getNode("TakingMyTimePoem");
    lyricImg = web3Dbrowsercontext.getNode("TakingMyTimeImage");
    flameback = web3Dbrowsercontext.getNode("FlameMat");
    bckgrnd = web3Dbrowsercontext.getNode("Background1");
    font1 = web3Dbrowsercontext.getNode("WhiteFont");
    imgMat = web3Dbrowsercontext.getNode("ImgMat");
    light = web3Dbrowsercontext.getNode("OMLight");

    // set displays for metadata from X3D head

    if (web3Dbrowsercontext.getMetadata("AlbumTitle") != null) {
    album = web3Dbrowsercontext.getMetadata("AlbumTitle");
    document.getElementById('albTitle').innerHTML = "Album: " +album;
    }

    if (web3Dbrowsercontext.getMetadata("Performer") != null) {
    performer = web3Dbrowsercontext.getMetadata("Performer");
    document.getElementById('performedBy').innerHTML = "Performed By: "
    +performer;
    }

    if (web3Dbrowsercontext.getMetadata("Songwriter") != null) {
    writer = web3Dbrowsercontext.getMetadata("Songwriter");
    document.getElementById('songwriter').innerHTML = "Songwriter: " +writer;
    }

    if (web3Dbrowsercontext.getMetadata("Label") != null) {
    recordLabel = web3Dbrowsercontext.getMetadata("Label");
    document.getElementById('label').innerHTML = "Record Label: " +recordLabel;
    }

    if (web3Dbrowsercontext.getMetadata("Publisher") != null) {
    pub = web3Dbrowsercontext.getMetadata("Publisher");
    document.getElementById('publisher').innerHTML = "Copyright " +pub;
    }

    if (web3Dbrowsercontext.getMetadata("Copyright") != null) {
    copy = web3Dbrowsercontext.getMetadata("Copyright");
    document.getElementById('copyright').innerHTML = "©  " +copy;
    }

    if (web3Dbrowsercontext.getMetadata("Length") != null) {
    songlength = web3Dbrowsercontext.getMetadata("Length");
    document.getElementById('length').innerHTML = "Playing Time: " +songlength;
    document.getElementById('pTime').innerHTML = " " +songlength;
    }

    if (web3Dbrowsercontext.getMetadata("PerformanceRightsOrg") != null) {
    pro = web3Dbrowsercontext.getMetadata("PerformanceRightsOrg");
    document.getElementById('rights').innerHTML = " " +pro;
    }

    if (web3Dbrowsercontext.getMetadata("WebPage") != null) {
    page = web3Dbrowsercontext.getMetadata("WebPage");
    document.getElementById('webPage').innerHTML = "<a href='http://"+page +"'>Web Page</a>";
    }

    if (web3Dbrowsercontext.getMetadata("Email") != null) {
    mail = web3Dbrowsercontext.getMetadata("Email");
    document.getElementById('email').innerHTML = "<a href='mailto://"+mail +"'>Contact Performer</a>";
    }

    // if acquired startSound timer, play music and set node properties and button states

    if (startsound != null) {
    startsound.isActive = true;

    lyric.whichChoice = 0;
    lyricImg.whichChoice = 0;
    kareokeTS.enabled = true;
    vizxTS.enabled = true;
    mainTS.enabled = true;
    colorTS.enabled = true;
    light.on = true;
    flameback.transparency = 0;
    font1.transparency = 0;
    bckgrnd.set_bind = false;
    imgMat.transparency = 0;
    startanimation.isActive = true;

    document.getElementById('play').disabled=true;
    document.getElementById('pause').disabled=false;
    document.getElementById('stop').disabled=false;
    document.getElementById('songs').disabled=true;
    if (curSong.length > 0) {
    document.getElementById('playlist').innerHTML = "Playing: " +curSong;
    }
    }
    }


    stopSong Function


    The Stop button onClick event handler simply gets the contexts and tests to see if the audio stop trigger is provided. If so, it stops the animation and audio, resets the animation conditions to their starting state, and resets the dashboard buttons to their Play states.

    NOTE: because the button states occur in groups that are used more than once, consider isolating them into their own function and call them from each function that uses them using a named group and a Javascript Switch. As a dashboard gets more complex or any GUI for that matter, this is a good design practice.


    function stopSong() {

    stopsound = web3Dbrowsercontext.getNode("stopAudio");
    vizxTS = web3Dbrowsercontext.getNode("vizx_init");
    kareokeTS = web3Dbrowsercontext.getNode("Kareoke_Timer");
    mainTS = web3Dbrowsercontext.getNode("Main_Timer");
    colorTS = web3Dbrowsercontext.getNode("Color_Timer");
    lyric = web3Dbrowsercontext.getNode("TakingMyTimePoem");
    lyricImg = web3Dbrowsercontext.getNode("TakingMyTimeImage");
    flameback = web3Dbrowsercontext.getNode("FlameMat");
    bckgrnd = web3Dbrowsercontext.getNode("Background1");
    font1 = web3Dbrowsercontext.getNode("WhiteFont");
    imgMat = web3Dbrowsercontext.getNode("ImgMat");
    light = web3Dbrowsercontext.getNode("OMLight");

    if (stopsound != null) {
    stopsound.isActive = true;
    document.getElementById('playlist').innerHTML = "Song Stopped";
    document.getElementById('play').disabled=false;
    document.getElementById('pause').disabled=true;
    document.getElementById('stop').disabled=true;
    document.getElementById('songs').disabled=false;

    kareokeTS.enabled = false;
    vizxTS.enabled = false;
    mainTS.enabled = false;
    colorTS.enabled = false;
    light.on = false;
    flameback.transparency = 1;
    font1.transparency = 1;
    bckgrnd.set_bind = true;
    lyric.whichChoice = 0;
    lyricImg.whichChoice = 0;
    imgMat.transparency = 0;
    }
    }


    pause Function


    The Pause onClick function is basic because the pause functionality occurs in the X3D scene-graph. The Pause button signals the event to the SAI, then sets the button states. Since the Pause button toggles the state on and off, it keeps up with its own state and resets it appropriate. The paused is a Javascript global variable declared above.


    function pause() {

    pauseSound = web3Dbrowsercontext.getNode("pause");
    pauseSound.isActive = true;

    if (paused == 0){
    paused = 1;
    document.getElementById('songs').disabled=true;
    document.getElementById('play').disabled=true;
    document.getElementById('stop').disabled=true;
    document.getElementById('playlist').innerHTML = "Paused: " +curSong;
    }
    else if (paused == 1) {
    paused = 0;
    document.getElementById('play').disabled=true;
    document.getElementById('stop').disabled=false;
    document.getElementById('songs').disabled=true;
    document.getElementById('playlist').innerHTML = "Playing: " +curSong;
    }
    }


    setVolUp and setVolDown Functions


    The Volume Up and Down onClick button functions are also simple because the functionality is in the X3D scene and the buttons only signal the button press event to the SAI. It sends the event every time it is pressed. The scene functions increment or decrement the volume by one tenth. No, it won’t go to 11.


    // set volume up
    function setVolUp () {

    setVUp = web3Dbrowsercontext.getNode("volUp");

    if (setVUp) {
    setVUp.isActive = true;
    }
    else {
    alert("Got No Vol UP!");
    }
    }

    // set volume down
    function setVolDown () {
    setVDwn = web3Dbrowsercontext.getNode("volDown");
    if (setVDwn) {
    setVDwn.isActive = true;
    }
    else {
    alert("Got No Vol DOWN!");
    }
    }


    getSongTitle Function


    Finally, we have a helper function for the HTML song status control. It gets song titles from the option button values in the HTML select control and puts these in the Song status display in the left column. The titles in the option values are numbered in order of the original album play list. The function uses the Javascript split function which takes a string and splits it at the delimiter declared in the split, then stores the values into an array. In this case, the option value is of the form


    value="2:Taking My Time"


    The function uses the colon (:) character delimiter and assigns the title, the second value in the array, to the status line.


    function getSongTitle(song) {

    var songlist = song.split(":",2);
    if (songlist[0] && songlist[1]) {
    songNum = songlist[0];
    songTitle = songlist[1];
    }
    curSong = songTitle;
    return songTitle;
    }
    </script>
    </head>




    Until Next Time



    We are done with this installment of the tutorial.

    Many thanks to the people who provide examples to enable me to code the SAI interface particularly Tony Parisi at Media Machines, Joe Williams and the students and staff of the Naval Postgraduate School (NPS) at Monterey.

    As they say on the mail lists, I hope this helps. Namaste!

    len

    Appendix – The HTML Body



    This appendix contains the HTML body elements and attributes used in this tutorial. Look through these to see the controls that use the functions described so far to communicate with the X3D scene.


    <body background="texture/653120.jpg">

    <h1>X3D Band In A Box: Sequencing Demo With AJAX</h1>

    <table>
    <tr>
    <td valign=top >
    <p>This X3D Scene is a tutorial example for <a
    href="http://3donthewebcheap.blogspot.com/2007/04/songs-in-3d-making-simple-x3d-sequencer_12.html">
    X3D sequencing techniques</a> and AJAX Web 2.0 technology.</p>
    <p>This model uses the <em>AudioSensor</em>, an X3D extension node
    provided by Media Machines in Flux Viewer. <strong>This model only runs
    correctly in Flux Viewer.</strong>
    <a href='http://www.mediamachines.com:80/downloadplayerty.php?inst=-279607152&src=codes'
    target='_blank'>Download the Flux Player</a> to see 3D. No spyware or viruses.</p>
    <p>The band model for this demo is provided courtesy of <a
    href="http://www.mediamachines.com">Media Machines Inc.</a> Thanks to Tony Parisi and Keith Victor for the use of the band model created by <em>Mauro Turri, aka MrGB</em>. It has been modified from the original for
    this presentation. See original at the Media Machines <a href="http://www.mediamachines.com">Virtual Gallery</a>.</p>
    <p id="playlist" class="control"></p>
    <div id="performedBy" class="metadata"></div>
    <div id="songwriter" class="metadata"></div>
    <div id="albTitle" class="metadata"></div>
    <div id="songwriter" class="metadata"></div>
    <div id="label" class="metadata"></div>
    <div>
    <span id="publisher" class="metadata"></span> 
    <span id="copyright" class="metadata"></span> 
    <span id='rights' class="metadata"></span>
    </div>
    <div id="length" class="metadata"></div>
    <div id="webPage" class="metadata"></div>
    <div id="email" class="metadata"></div>
    </td>
    <td id="fcell">
    <p>
    <script type="text/javascript">
    loadObjx3d();
    </script>
    </p>
    </td>
    </tr>
    <tr>
    <td></td>
    <td>
    <fieldset id='dashboard' class="control">
    <legend>3D Band Controls</legend>
    <table>
    <tr>
    <th>  
    <label class="control" FOR=name >Volume</label>
    </th>
    <th>     
    <label class="control" FOR=name >Play</label>
    </th>
    <th>  
    <label class="control" FOR=name >Pause</label>
    </th>
    <th>  
    <label class="control" FOR=name >Stop</label>
    </th>
    <th>  
    <label class="control" FOR=name >Play All</label>
    </th>
    <th>     
    <label class="control" FOR=name >Select Songs</label>
    </th>
    <th>     
    <label class="playTime" FOR=name >Length</label>
    </th>
    </tr>
    <tr>
    <td>  
    <input id="vDwn" type="button" value="  -  " onClick="setVolDown();" />
    <input id="vUp" type="button" value=" + " onClick="setVolUp();" />
    </td>
    <td>     
    <input id="play" type="button" value="Play" onClick="play();" />
    </td>
    <td>  
    <input id="pause" type="button" value="Pause" onClick="pause();" />
    </td>
    <td>  
    <input id="stop" type="button" value="Stop" onClick="stopSong();" />
    </td>
    <td>
      <input id="allplay" type="checkbox" value="no"/>
    </td>
    <td>     
    <select name="songs" multiple size=2 onchange="getSongTitle(this.value)">
    <option value="1:Baby Can You Be With Me">Baby Can You Be With Me</option>
    <option value="2:Taking My Time" selected="selected">Taking My Time</option>
    <option value="3:Forest Lady">Forest Lady</option>
    <option value="4:Who Ya Gonna Love Today">Who Ya Gonna Love Today</option>
    <option value="5:Get It While You Can">Get It While You Can</option>
    <option value="6:The Reefs Of Contentment">The Reefs Of Contentment</option>
    <option value="7:Perfect Love">Perfect Love</option>
    <option value="8:Just A Memory">Just A Memory</option>
    <option value="9:I Need You">I Need You</option>
    <option value="10:On The Run">On The Run</option>
    </select>
    </td>
    <td><CENTER><label id='pTime' class="playTime" ></label></CENTER></td>
    </tr>
    </table>
    </fieldset>
    </td>
    </tr>
    </table>
    </body>
    </html>

    4 comments:

    Rob Meyers said...

    Wow, this is a great post. I was worried when I saw X3D coming down the mountain that the EAI had been dropped. It seems like instead its been absorbed into the SAI? The application you produced in and of itself is pretty snazzy.

    I have worked with building multiuser vrml servers in the past. Worked on the VRSpace team, and recently built my own server AstralX. VRSpace is an applet that loads up with the VRML plugin to provide all interactions with the server. I abandoned the web browser entirely with AstralX and made it a standalone app. This example prog you put up here made me think of building a multi-user client based off of AJAX.

    But that reminded me of ABNet, a chat server built way back when, that mysteriously got around the need for using IE Java, and was actually able to run with the Sun Java plugin. I had a couple conversations with the writer, and he mentioned things about having the javascript on the page poll the server for updates and talk to the VRML browser. Sounds familiar.

    So I did a little search and found this interesting post talking about how ABNet does and does not relate to AJAX.

    As a side note, would be nice if you put a link to your original post of the app at the top of this one. I hadn't been following the thread and had to search around for the demo page.

    Len Bullard said...

    Good to hear from you, Rob. Yeah, now is a good time to brush up all of the old 3D chops and pursue a project with one of the X3D-aligned communities. There are lots of server, object framework and content projects happening around the language again. The original 3DOnTheWeb community has a way of staying alive in the face of all of the different work going on, so it's a stable and growing place to put some mindErgs.

    I'll come back around to the open server projects after I get through the next phase. I like ABnet but they also need to learn to build the standalone worlds first. There is sort of world business type I want to get to with this project. Then finding servers should be a reaching out campaign where various people who understand how to work with the simple constructs shown start on their own projects. Do this one block or compoenent at a time and at the end, it all just works.

    Great to hear from you. Chime in with stuff! Please! The more the merrier.

    Tessa said...

    I recently came accross your blog and have been reading along. I thought I would leave my first comment. I don't know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.


    Ruth

    http://muffinsnow.com

    Anonymous said...

    I want to quote your post in my blog. It can?
    And you et an account on Twitter?