News
 

DLLBinder Example - Adjust system volume

Do you ever need to get or set the system volume? Or check / set the system mute?

By default, Director users can set the volume for different channels in the current movie but not the system volume (e.g. like when one clicks the volume control in the system tray). This example makes use of DLLBinder to get and set the system volume and system mute.

For more information about the use of this Win32 API, see the msdn Audio Mixers web pages .

Feel free to make use of this code in any way you want. If you make any great improvements, please send us a copy. Or if you have any other examples you would like to appear in these LIBRARY pages, please send it to info@the-mindseye.co.uk

Minds Eye Visualisation Services accepts no responibility for the reliability of any of the source code appearing in these pages. Use it at your own risk.

 

Requirements


DLLBinder or DLLBinder pro

 

  Download zip Download Zip file   (volume_example.zip - 14Kb)


------------------------------------------------------------------------  
-- Name           sctVolController  
-- Script Type    Parent   
-- Description    provides access to the volume slider and mute  
--                controls of the master audio line. 
-- Creation Date  20/12/2004  
-- Author         Minds Eye Visualisation  
------------------------------------------------------------------------  
------------------------------------------------------------------------  

--various consts we need to use -as defined in mmsystem.h   
property MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
property
 MIXER_GETLINECONTROLSF_ONEBYTYPE
property
 MIXER_GETLINEINFOF_COMPONENTTYPE
property
 MIXERCONTROL_CONTROLTYPE_VOLUME
property
 MIXERCONTROL_CONTROLTYPE_MUTE


--useful constants found in mmsystem.h -see below for definitions 
property MMSYSERR_NOERROR  


--property for the name Win32 Dll containing the wave volume functions  
property m_strDllName 


property
 m_hMixer
property
 m_dwLineID
property
 m_dwChannels
property
 m_dwMuteControlID
property
 m_dwVolumeControlID

------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: new 
-- 
-- PURPOSE: 
-- instance creation  
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on new me  
  
  --//////////////////////////////////////////////////  
  --// general error return values as defined in mmsystem.h  
  --//////////////////////////////////////////////////  
  --NB there are loads of others that really should be included here   
  --   but this'll do for now  
  MMSYSERR_NOERROR = 0  
  
  
  --//////////////////////////////////////////////////  
  --//various consts we need to use -as defined in mmsystem.h   
  --//////////////////////////////////////////////////  
  MIXERLINE_COMPONENTTYPE_DST_SPEAKERS    = 4
  MIXER_GETLINECONTROLSF_ONEBYTYPE        = 2

  MIXER_GETLINEINFOF_COMPONENTTYPE        = 3
 
  MIXERCONTROL_CONTROLTYPE_VOLUME         = 1342373889

  MIXERCONTROL_CONTROLTYPE_MUTE           = 536936450

  
  --//////////////////////////////////////////////////  
  --//the name of the Win32 dll containing our reqd API   
  --//constant across all instances of this script
  --//////////////////////////////////////////////////  
  m_strDllName="winmm.dll"
  
  me
.m_hMixer=0
  me
.m_dwLineID=0
  me
.m_dwChannels=0
  me
.m_dwMuteControlID=0
  me
.m_dwVolumeControlID=0
  
  return me
  
  
end
  

------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: Initialise 
-- 
-- PURPOSE: 
-- gets the volume slider and mute checkbox control IDs.
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on Initialise me  
  
  symRet=#
OK
  
  --1. open a mixer 
  if me.MixerOpen() = #CALL_FAILED then 
    
    symRet=#
MIXER_OPEN_FAIL
    
    --2. get master audio line ID  
  else if me.MixerGetLineInfo() = #CALL_FAILED then 
    
    symRet=#
GET_AUDIO_LINE_FAIL   
    
  else

    
    lstControlHandle=[]
    
    --3. get the mute control ID
    symRet = me.MixerGetLineControls(MIXERCONTROL_CONTROLTYPE_MUTE,\
                                     lstControlHandle)
    
    if
 symRet = #CALL_FAILED then 
      
      symRet=#
GET_MUTE_ID_FAIL 
      
    else

      
      me
.m_dwMuteControlID=lstControlHandle[1]
      
      --4. get the volume control ID  
      symRet = me.MixerGetLineControls(MIXERCONTROL_CONTROLTYPE_VOLUME,\
                                       lstControlHandle) 
      
      if
 symRet = #CALL_FAILED then 
        
        symRet=#
GET_VOLUME_ID_FAIL 
        
      else
  
        
        me
.m_dwVolumeControlID=lstControlHandle[1]
        
      end if
 --get the volume control ID
      
    end if
 --set the mute control ID
    
  end if

  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: MixerOpen 
--
-- wrapper for the following win32 API fn in winmm.dll -see mmsystem.h
--
-- MMRESULT mixerOpen(
--           LPHMIXER phmx, 
--           UINT uMxId, 
--           DWORD dwCallback, 
--           DWORD dwInstance, 
--           DWORD fdwOpen);

-- PURPOSE: 
-- opens a handle to a mixer device
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on MixerOpen me  
  
  symRet=#
OK
  
  strAPIName="mixerOpen"

  strRetValFormat="D"

  lstRetValList=[]
  
  --the 1st arg is of type LPHMIXER which is defined as 
  --a reference to a word data type in mmsystem.h 
  strArgFormat="*D,D,D,D,D"
  lstArgFormat=[0
,0,0,0,0]
  
  --call into the dll  
  nErr=mevCallDllFun(m_strDllName, \
                     strAPIName, \
                     strArgFormat, \
                     lstArgFormat, \
  strRetValFormat, \
                     lstRetValList)
  
  --most Win32 API calls return an integer representing the error   
  --status of the call. In this case we hope it equals MMSYSERR_NOERROR  
  dwRet=lstRetValList[1]  
  
  --skeleton error handling  
  if (nErr < 0) then  
    --the call to mevCallDllFun has failed 
    symRet=#CALL_FAILED  
    
  else if
 (dwRet <> MMSYSERR_NOERROR) then  
    --waveOutGetVolume has failed       
    --check dwRet against other values in mmsystem.h  
    symRet=#CALL_FAILED        
    
  else

    --all ok. get the handle from the call results
    me.m_hMixer=lstArgFormat[1]
    
  end if
  
  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: MixerGetLineInfo 
--
-- wrapper for the following win32 API fn in winmm.dll -see mmsystem.h
--
-- MMRESULT mixerGetLineInfoA(
--           HMIXEROBJ hmxobj, 
--           LPMIXERLINEA pmxl, 
--           DWORD fdwInfo);
--
-- the LPMIXERLINEA data type is defined in mmsystem.h as follows:
--
-- typedef struct tagMIXERLINEA {
--    DWORD   cbStruct; /* size of MIXERLINE structure */
--    DWORD   dwDestination; /* zero based destination index */
--    DWORD   dwSource; /* zero based source index (if source) */
--    DWORD   dwLineID; /* unique line id for mixer device */
--    DWORD   fdwLine; /* state/information about line */
--    DWORD   dwUser; /* driver specific information */
--    DWORD   dwComponentType; /* component type line connects to */
--    DWORD   cChannels; /* number of channels line supports */
--    DWORD   cConnections; /* number of connections [possible] */
--    DWORD   cControls; /* number of controls at this line */
--    CHAR    szShortName[MIXER_SHORT_NAME_CHARS];
--    CHAR    szName[MIXER_LONG_NAME_CHARS];
--    struct {
--     DWORD   dwType; /* MIXERLINE_TARGETTYPE_xxxx */
--     DWORD   dwDeviceID; /* target device ID of device type */
--     WORD    wMid;      /* of target device */
--     WORD    wPid;      
--     MMVERSION vDriverVersion;  
--     CHAR    szPname[MAXPNAMELEN]; 
--    } Target;
-- } MIXERLINEA, *PMIXERLINEA, *LPMIXERLINEA;
--
--
-- PURPOSE: 
-- retrieves information about a specific line of a mixer device
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on MixerGetLineInfo me  
  
  symRet=#
OK
  
  strAPIName="mixerGetLineInfoA"

  strRetValFormat="D"

  lstRetValList=[]
  
  strSTMixerLineFormat="{D,D,D,D,D,D,D,D,D,D,S16,S64,{D,D,W,W,D,S32}}"

  lstSTMixerLine=[]
  lstSTMixerLine[1
]=mevMemSizeOf(strSTMixerLineFormat)
  lstSTMixerLine[7
]=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
  lstSTMixerLine[11
]=""
  lstSTMixerLine[12
]=""
  lstSTMixerLine[13
]=[0,0,0,0,0,""]
  
  strArgFormat="D"
 & "*" & strSTMixerLineFormat & "D"
  lstArgFormat=[]
  lstArgFormat[1
]=me.m_hMixer
  lstArgFormat[2
]=lstSTMixerLine
  lstArgFormat[3
]=MIXER_GETLINEINFOF_COMPONENTTYPE
  
  --call into the dll  
  nErr=mevCallDllFun(m_strDllName, \
                     strAPIName, \
                     strArgFormat, \
                     lstArgFormat, \
                     strRetValFormat, \
                     lstRetValList)
  
  --most Win32 API calls return an integer representing the error   
  --status of the call. In this case we hope it equals MMSYSERR_NOERROR  
  dwRet=lstRetValList[1]  
  
  --skeleton error handling  
  if (nErr < 0) then  
    --the call to mevCallDllFun has failed 
    symRet=#CALL_FAILED  
    
  else if
 (dwRet <> MMSYSERR_NOERROR) then  
    --waveOutGetVolume has failed       
    --check dwRet against other values in mmsystem.h  
    symRet=#CALL_FAILED        
    
  else
 
    --all ok. we've got our audio line ID and the number of its channels
    me.m_dwLineID=lstSTMixerLine[4]
    me
.m_dwChannels=lstSTMixerLine[8]
    
  end if
  
  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: MixerGetLineControls 
--
-- wrapper for the following win32 API fn in winmm.dll -see mmsystem.h
--
-- MMRESULT mixerGetLineControlsA(
--           HMIXEROBJ hmxobj, 
--           LPMIXERLINECONTROLSA pmxlc, 
--           DWORD fdwControls);
--
--
--   typedef struct tagMIXERCONTROLA {
--    DWORD cbStruct;       /* size in bytes of MIXERCONTROL */
--    DWORD dwControlID;    /* unique control id for mixer device */
--    DWORD dwControlType;  /* MIXERCONTROL_CONTROLTYPE_xxx */
--    DWORD fdwControl;     /* MIXERCONTROL_CONTROLF_xxx */
--    DWORD cMultipleItems; /* if MIXERCONTROL_CONTROLF_MULTIPLE set */
--    CHAR  szShortName[MIXER_SHORT_NAME_CHARS];
--    CHAR  szName[MIXER_LONG_NAME_CHARS];
--    union {
--    struct {
--        LONG lMinimum; /* signed minimum for this control */
--        LONG lMaximum; /* signed maximum for this control */
--    };
--    struct {
--        DWORD dwMinimum; /* unsigned minimum for this control */
--        DWORD dwMaximum; /* unsigned maximum for this control */
--    };
--    DWORD dwReserved[6];
--    } Bounds;
--    union {
--    DWORD cSteps;         /* # of steps between min & max */
--    DWORD cbCustomData;   /* size in bytes of custom data */
--    DWORD dwReserved[6];  /* !!! needed? we have cbStruct.... */
--    } Metrics;
--   } MIXERCONTROLA, *PMIXERCONTROLA, *LPMIXERCONTROLA;
--
--
-- the LPMIXERLINECONTROLSA data type is defined in mmsystem.h thus:
--
--   typedef struct tagMIXERLINECONTROLSA {
--    DWORD cbStruct;   /* size in bytes of MIXERLINECONTROLS */
--    DWORD dwLineID;   /* line id (from MIXERLINE.dwLineID) */
--    union {
--     DWORD dwControlID;    /* MIXER_GETLINECONTROLSF_ONEBYID */
--     DWORD dwControlType;  /* MIXER_GETLINECONTROLSF_ONEBYTYPE */
--    };
--    DWORD cControls;  /* count of controls pmxctrl points to */
--    DWORD cbmxctrl;   /* size in bytes of _one_ MIXERCONTROL */
--    LPMIXERCONTROLA pamxctrl; /* pointer to 1st MIXERCONTROL array */
--   } MIXERLINECONTROLSA, *PMIXERLINECONTROLSA, *LPMIXERLINECONTROLSA;
--
--
-- PURPOSE: 
-- retrieves one or more controls associated with an audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on MixerGetLineControls me, dwControlType, lstControlHandle
  
  symRet=#
OK
  
  strAPIName="mixerGetLineControlsA"

  strRetValFormat="D"

  lstRetValList=[]
  
  strSTMixerControl="{D,D,D,D,D,S16,S64,{D,D,D,D,D,D},{D,D,D,D,D,D}}"
  
  lstSTMixerControl=[]
  
  strSTMixerLineCtrlsFormat="{D,D,D,D,D"
 & "*" & strSTMixerControl & "}"
  lstSTMixerLineCtrls=[]
  lstSTMixerLineCtrls[1
]=mevMemSizeOf(strSTMixerLineCtrlsFormat)
  lstSTMixerLineCtrls[2
]=me.m_dwLineID
  lstSTMixerLineCtrls[3
]=dwControlType
  lstSTMixerLineCtrls[4
]=1
  lstSTMixerLineCtrls[5
]=mevMemSizeOf(strSTMixerControl)
  lstSTMixerLineCtrls[6
]=lstSTMixerControl
  
  strArgFormat="D"
 & "*" & strSTMixerLineCtrlsFormat & "D"
  lstArgFormat=[]
  lstArgFormat[1
]=me.m_hMixer
  lstArgFormat[2
]=lstSTMixerLineCtrls
  lstArgFormat[3
]=MIXER_GETLINECONTROLSF_ONEBYTYPE
  
  --call into the dll  
  nErr=mevCallDllFun(m_strDllName, \
                     strAPIName, \
                     strArgFormat, \
                     lstArgFormat, \
                     strRetValFormat, \
                     lstRetValList)
  
  --most Win32 API calls return an integer representing the error   
  --status of the call. In this case we hope it equals MMSYSERR_NOERROR  
  dwRet=lstRetValList[1]  
  
  --skeleton error handling  
  if (nErr < 0) then  
    --the call to mevCallDllFun has failed 
    symRet=#CALL_FAILED  
    
  else if
 (dwRet <> MMSYSERR_NOERROR) then  
    --waveOutGetVolume has failed       
    --check dwRet against other values in mmsystem.h  
    symRet=#CALL_FAILED        
    
  else

    --all ok. get the handle from the call results
    lstControlHandle[1]=lstSTMixerControl[2
    
  end if
  
  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: MixerSetControlDetails 
--
-- wrapper for the following win32 API fn in winmm.dll -see mmsystem.h
--
-- MMRESULT mixerSetControlDetails(
--           HMIXEROBJ hmxobj, 
--           LPMIXERCONTROLDETAILS pmxcd, 
--           DWORD fdwDetails);

-- typedef struct tMIXERCONTROLDETAILS {
--    DWORD cbStruct;       /* size in bytes of MIXERCONTROLDETAILS */
--    DWORD dwControlID;    /* control id to get/set details on */
--    DWORD cChannels;      /* number of channels in paDetails array */
--    union {
--      HWND   hwndOwner; /* for MIXER_SETCONTROLDETAILSF_CUSTOM */
--      DWORD  cMultipleItems; /* if _MULTIPLE, the # items/channel */
--    };
--    DWORD cbDetails;    /* size of _one_ details_XX struct */
--    LPVOID paDetails;   /* pointer to array of details_XX structs */
-- } MIXERCONTROLDETAILS, 
--   *PMIXERCONTROLDETAILS, 
--   FAR *LPMIXERCONTROLDETAILS;
--
-- PURPOSE: 
-- sets the properties of a single control associated with an audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on MixerSetControlDetails me, dwChannels, dwControlID, lstChannelInfo
  
  symRet=#
OK
  
  if
 dwChannels = lstChannelInfo.count() then
    
    strAPIName="mixerSetControlDetails"

    strRetValFormat="D"

    lstRetValList=[]    
    
    strSTMxCtrlDetailsFormat="{D,D,D,D,D,["
 & dwChannels & "D]}"
    lstSTMxCtrlDetails=[]
    lstSTMxCtrlDetails[1
]=mevMemSizeOf(strSTMxCtrlDetailsFormat)
    lstSTMxCtrlDetails[2
]=dwControlID
    lstSTMxCtrlDetails[3
]=dwChannels
    lstSTMxCtrlDetails[4
]=0 --cMultipleItems
    lstSTMxCtrlDetails[5]=(dwChannels * mevMemSizeOf("D"))
    lstSTMxCtrlDetails[6
]=lstChannelInfo  
    
    --we define the 2nd arg as a struct of two WORDS -see above note  
    strArgFormat="D," & "*" & strSTMxCtrlDetailsFormat & "D"
    lstArgFormat=[]
    lstArgFormat[1
]=me.m_hMixer
    lstArgFormat[2
]=lstSTMxCtrlDetails
    lstArgFormat[3
]=0 --no flags
    
    --call into the dll  
    nErr=mevCallDllFun(m_strDllName, \
                     strAPIName, \
                     strArgFormat, \
                     lstArgFormat, \
                     strRetValFormat, \
                     lstRetValList)
    
    --most Win32 API calls return an integer representing the error   
    --status of the call. In this case we hope it equals MMSYSERR_NOERROR  
    dwRet=lstRetValList[1]  
    
    --skeleton error handling  
    if (nErr < 0) then  
      --the call to mevCallDllFun has failed 
      symRet=#CALL_FAILED  
      
    else if
 (dwRet <> MMSYSERR_NOERROR) then  
      --waveOutGetVolume has failed   
      --check dwRet against other values in mmsystem.h  
      symRet=#CALL_FAILED  
      
    end if
  
    
  else
 
    --number of channels does not match number of values in list        
    symRet=#CALL_FAILED  
    
  end if

  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: MixerGetControlDetails 
--
-- wrapper for the following win32 API fn in winmm.dll -see mmsystem.h
--
-- MMRESULT mixerGetControlDetailsA(
--           HMIXEROBJ hmxobj, 
--           LPMIXERCONTROLDETAILS pmxcd, 
--           DWORD fdwDetails);

-- typedef struct tMIXERCONTROLDETAILS {
--    DWORD cbStruct;       /* size in bytes of MIXERCONTROLDETAILS */
--    DWORD dwControlID;    /* control id to get/set details on */
--    DWORD cChannels;      /* number of channels in paDetails array */
--    union {
--      HWND   hwndOwner; /* for MIXER_SETCONTROLDETAILSF_CUSTOM */
--      DWORD  cMultipleItems; /* if _MULTIPLE, the # items/channel */
--    };
--    DWORD cbDetails;      /* size of _one_ details_XX struct */
--    LPVOID paDetails;     /* pointer to array of details_XX structs */
-- } MIXERCONTROLDETAILS, 
--   *PMIXERCONTROLDETAILS, 
--   FAR *LPMIXERCONTROLDETAILS;
--
-- PURPOSE: 
-- gets the properties of a single control associated with an audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on MixerGetControlDetails me, dwChannels, dwControlID, lstChannelInfo
  
  symRet=#
OK
  
  strAPIName="mixerGetControlDetailsA"

  strRetValFormat="D"

  lstRetValList=[]
  
  strSTMxCtrlDetailsFormat="{D,D,D,D,D,["
 & dwChannels & "D]}"
  lstSTMxCtrlDetails=[]
  lstSTMxCtrlDetails[1
]=mevMemSizeOf(strSTMxCtrlDetailsFormat)
  lstSTMxCtrlDetails[2
]=dwControlID
  lstSTMxCtrlDetails[3
]=dwChannels
  lstSTMxCtrlDetails[4
]=0 --cMultipleItems
  lstSTMxCtrlDetails[5]=(dwChannels * mevMemSizeOf("D"))
  lstSTMxCtrlDetails[6
]=lstChannelInfo  
  
  --we define the 2nd arg as a struct of two WORDS -see above note  
  strArgFormat="D," & "*" & strSTMxCtrlDetailsFormat & "D"
  lstArgFormat=[]
  lstArgFormat[1
]=me.m_hMixer
  lstArgFormat[2
]=lstSTMxCtrlDetails
  lstArgFormat[3
]=0 --no flags
  
  --call into the dll  
  nErr=mevCallDllFun(m_strDllName, \
                     strAPIName, \
                     strArgFormat, \
                     lstArgFormat, \
                     strRetValFormat, \
                     lstRetValList)
  
  --most Win32 API calls return an integer representing the error   
  --status of the call. In this case we hope it equals MMSYSERR_NOERROR  
  dwRet=lstRetValList[1]  
  
  --skeleton error handling  
  if (nErr < 0) then  
    --the call to mevCallDllFun has failed 
    symRet=#CALL_FAILED  
    
  else if
 (dwRet <> MMSYSERR_NOERROR) then  
    --waveOutGetVolume has failed   
    --check dwRet against other values in mmsystem.h  
    symRet=#CALL_FAILED  
    
  end if
 
  
  
  return
 symRet
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: GetChannelCount 
--
-- PURPOSE: 
-- returns the number of channels supported by the audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on GetChannelCount me
  
  return me
.m_dwChannels
  
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: SetVolume 
--
-- PURPOSE: 
-- sets the volume levels for each channel supported by the audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on SetVolume me, lstVolLevels
  
  symRet = me
.MixerSetControlDetails(integer(me.m_dwChannels),\
                                     me
.m_dwVolumeControlID,\
                                     lstVolLevels)
  
  return
 symRet
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: GetVolume 
--
-- PURPOSE: 
-- returns the volume levels for each channel supported by the 
-- audio line
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on GetVolume me, lstVolLevels  
  
  symRet = me
.MixerGetControlDetails(integer(me.m_dwChannels),\
                                     me
.m_dwVolumeControlID,\
                                     lstVolLevels)
  
  return
 symRet
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: SetMute 
-- 
-- PURPOSE: 
-- toggles mute on and off 
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on SetMute me, bMute
  
  --sets mute uniformly across all channels
  symRet = me.MixerSetControlDetails(1, me.m_dwMuteControlID, [bMute])
  
  return
 symRet
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
-- FUNCTION: IsMute 
-- 
-- PURPOSE: 
-- tests if mute is toggled on or off 
------------------------------------------------------------------------  
------------------------------------------------------------------------  
on IsMute me
  
  lstMuteSetting=[]
  symRet = me
.MixerGetControlDetails(1,\
                                     me
.m_dwMuteControlID,\
                                     lstMuteSetting) 
  if
 symRet=#OK then
    return
 lstMuteSetting[1]
  else

    return
 lstMuteSetting[1]
  end if

    
end


------------------------------------------------------------------------  
------------------------------------------------------------------------  
--end of script







  3d visualisation     data visualisation     interactive cds     printing     software     web design     training     games  
© Copyright 2006. Minds Eye Visualisation Services Limited. All Rights Reserved.