Play WAV and MP3 on Win32 with Odyce

This is an example of using Odyce to extend eTcl so it can plays either multimedia files (WAV, MP3, ...).

  package require odyce
  package require critcl

  # DLLs can be imported for all Odyce handles
  # ::odyce::odyce dllimport winmm.dll
  ::odyce::odyce dllimport user32.dll
  ::odyce::odyce dllimport winmm

  # PlaySound() is in winmm.dll on Win32 systems
  # critcl::clibraries winmm
  # And in coredll.dll on WinCE. Since coredll is 
  # automatically imported at startup, no need to
  # load library explicitely

  # Odyce doesn't embed all Win32 headers. Either provide
  # your own winmm.h, or define here required symbols
  critcl::ccode {
  #include <windows.h>
  #include <winuser.h>

  /* Support both UNICODE and ANSI versions */
  extern BOOL PlaySoundA(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound);
  extern BOOL PlaySoundW(LPCWSTR pszSound, HMODULE hmod, DWORD fdwSound);

  #define SND_SYNC               0
  #define SND_ASYNC              1
  #define SND_NODEFAULT          2
  #define SND_MEMORY             4
  #define SND_LOOP               8
  #define SND_NOSTOP            16
  #define SND_NOWAIT        0x2000
  #define SND_ALIAS        0x10000
  #define SND_ALIAS_ID    0x110000
  #define SND_FILENAME     0x20000
  #define SND_RESOURCE     0x40004
  #define SND_PURGE           0x40
  #define SND_APPLICATION     0x80
  #define SND_ALIAS_START     0x00

  #define MCI_OPEN    0x803
  #define MCI_CLOSE   0x804
  #define MCI_ESCAPE  0x805
  #define MCI_PLAY    0x806
  #define MCI_SEEK    0x807
  #define MCI_STOP    0x808
  #define MCI_PAUSE   0x809

  #define MCI_NOTIFY             1
  #define MCI_WAIT               2
  #define MCI_FROM               4
  #define MCI_TO                 8

  #define MCI_OPEN_SHAREABLE   256
  #define MCI_OPEN_ELEMENT     512
  #define MCI_OPEN_TYPE_ID  0x1000
  #define MCI_OPEN_TYPE     0x2000

  typedef DWORD MCIERROR;
  typedef UINT MCIDEVICEID;
  #define MCI_ALL_DEVICE_ID ((MCIDEVICEID)-1)

  typedef struct tagMCI_OPEN_PARMSA {
    DWORD dwCallback;
    MCIDEVICEID wDeviceID;
    LPCSTR lpstrDeviceType;
    LPCSTR lpstrElementName;
    LPCSTR lpstrAlias;
  } MCI_OPEN_PARMSA,*PMCI_OPEN_PARMSA,*LPMCI_OPEN_PARMSA;
  typedef struct tagMCI_OPEN_PARMSW {
    DWORD dwCallback;
    MCIDEVICEID wDeviceID;
    LPCWSTR lpstrDeviceType;
    LPCWSTR lpstrElementName;
    LPCWSTR lpstrAlias;
  } MCI_OPEN_PARMSW,*PMCI_OPEN_PARMSW,*LPMCI_OPEN_PARMSW;

  typedef struct tagMCI_PLAY_PARMS {
    DWORD dwCallback;
    DWORD dwFrom;
    DWORD dwTo;
  } MCI_PLAY_PARMS,*PMCI_PLAY_PARMS,*LPMCI_PLAY_PARMS;

  /* Prototypes for functions */
  MCIERROR mciSendCommand(
    MCIDEVICEID IDDevice,   
    UINT        uMsg,       
    DWORD       fdwCommand, 
    DWORD_PTR   dwParam     
  );

  BOOL WINAPI mciGetErrorStringA(MCIERROR,LPSTR,UINT);

  #define MCI_SETAUDIO            0x0873
  #define MCI_DGV_SETAUDIO_VOLUME 0x4002
  #define MCI_DGV_SETAUDIO_ITEM   0x00800000
  #define MCI_DGV_SETAUDIO_VALUE  0x01000000
  #define MCI_DGV_STATUS_VOLUME   0x4019
  }

  if {0} {
  # NOT YET IMPLEMENTED

  # http://msdn2.microsoft.com/en-us/library/ms711484.aspx
  # http://msdn2.microsoft.com/en-us/library/ms711491.aspx
  # Set volume, range range 0 - 1000
  critcl::ccode {
  typedef struct tagMCI_DGV_SETAUDIO_PARMS {
    DWORD dwCallback;
    DWORD dwItem;
    DWORD dwValue;
    DWORD dwOver;
    PChar lpstrAlgorithm;
    PChar lpstrQuality;
  } MCI_DGV_SETAUDIO_PARMS,*PMCI_DGV_SETAUDIO_PARMS,*LPMCI_DGV_SETAUDIO_PARMS;

  typedef struct tagMCI_STATUS_PARMS {
    DWORD dwCallback;
    DWORD dwReturn;
    DWORD dwItem;
    DWORD dwTrack;
  } MCI_STATUS_PARMS,*PMCI_STATUS_PARMS,*LPMCI_STATUS_PARMS;
  }

  critcl::cproc setvolume {int volume} void {
    /* TMediaPlayer MP; */
    MCI_DGV_SETAUDIO_PARMS p;

    p.dwCallback = 0;
    p.dwItem = MCI_DGV_SETAUDIO_VOLUME;
    p.dwValue = Volume;
    p.dwOver = 0;
    p.lpstrAlgorithm = nil;
    p.lpstrQuality = nil;
    mciSendCommand(0 /*MP.DeviceID*/, MCI_SETAUDIO, MCI_DGV_SETAUDIO_VALUE | MCI_DGV_SETAUDIO_ITEM, &p);
  }

  # Get volume, range range 0 - 1000
  critcl::cproc getvolume {} int {
    /* TMediaPlayer MP; */
    MCI_STATUS_PARMS p;

    p.dwCallback = 0;
    p.dwItem = MCI_DGV_STATUS_VOLUME;
    mciSendCommand(0 /*MP.DeviceID*/, MCI_STATUS, MCI_STATUS_ITEM, &p);
    return p.dwReturn;
  }
  }

  # Either define a simple play function using cproc
  # See
  # http://msdn2.microsoft.com/en-us/library/ms712864.aspx
  critcl::cproc playsimple {char* name} void {
    MCI_OPEN_PARMSA openParms;
    MCI_PLAY_PARMS pp;
    MCIDEVICEID pDevice;
    int mcicode;

    openParms.dwCallback = NULL;
    openParms.lpstrElementName = name;

    /* Open command */
    mcicode=-1;
    if (mcicode!=0) {
      openParms.lpstrDeviceType = (char*) "waveaudio";
      mcicode=mciSendCommandA(0, MCI_OPEN,
                              MCI_OPEN_TYPE|MCI_OPEN_ELEMENT, 
                              (DWORD) (LPVOID) &openParms);
    }
    if (mcicode!=0) {
      /* Retry with different device type */
      openParms.lpstrDeviceType = (char*) "MPEGVideo";
      mcicode=mciSendCommandA(0, MCI_OPEN,
                              MCI_OPEN_TYPE|MCI_OPEN_ELEMENT, 
                              (DWORD) (LPVOID) &openParms);
    }

    if (mcicode!=0) {   
      // mciGetErrorStringA(mcicode, szErr, 128);
      MessageBoxA(NULL,name,"MCI error",
                  MB_ICONSTOP|MB_OK|MB_TASKMODAL|MB_SETFOREGROUND);

      return;
    }

    pDevice = openParms.wDeviceID;

    /* Play command */
    pp.dwCallback = NULL;
    pp.dwFrom = 0;
    mciSendCommandA(pDevice, MCI_PLAY, MCI_NOTIFY|MCI_FROM, (DWORD) &pp);

    return;

    if (name==NULL || name[0]==0) {
      PlaySoundA(NULL,NULL,SND_SYNC);
    } else {
      PlaySoundA(name,NULL,
                 SND_LOOP|SND_ASYNC|SND_NODEFAULT|SND_FILENAME);
    }
    return;
  }

  # A more complete implementation
  critcl::ccommand play {dummy interp objc objv} {
    int i;
    int rc;
    DWORD sndopts;
    Tcl_DString ds;

    Tcl_Obj *filenameObj;
    CONST char *filename;
    CONST char *nativename;
    int filenameLength;

    int subindex;

    int optsync;
    int optloop;
    int optstop;
    int optwait;
    int optcomplain;

    static CONST char *playswitches[] = {
      "-sync","-loop",
      "-nostop","-nowait",
      "-nocomplain",
      (char *) NULL
    };
    enum playopts { 
      SUBCMD_PLAY_SYNC,SUBCMD_PLAY_LOOP,
      SUBCMD_PLAY_NOSTOP,SUBCMD_PLAY_NOWAIT,
      SUBCMD_PLAY_NOCOMPLAIN
    };

    /* Default options for playing sound */
    optsync=0;
    optloop=0;
    optstop=1;
    optwait=-1;
    /* Default generic options */
    optcomplain=1;

    /* For debugging, stdout and stderr are redirected to console */
    fprintf(stderr,"Test PlaySound() nbargs=%d\n",objc);

    if (objc<=1) {
      /* Stop any sound playing */
      filename=NULL;
    } else {      
      for (i=1;i<objc-1;i++) {
        if (Tcl_GetIndexFromObj(interp, objv[i], playswitches, 
                                "option", 0, &subindex)!= TCL_OK) {
          return TCL_ERROR;
        }

        if (subindex==(int) SUBCMD_PLAY_SYNC) {
          optsync=1;
        } else if (subindex==(int) SUBCMD_PLAY_LOOP) {
          optloop=1;
        } else if (subindex==(int) SUBCMD_PLAY_NOSTOP) {
          optstop=0;
        } else if (subindex==(int) SUBCMD_PLAY_NOWAIT) {
          optwait=0;
        } else if (subindex==(int) SUBCMD_PLAY_NOCOMPLAIN) {
          optcomplain=0;
        }
      }

      if (optsync && optloop) {
        Tcl_AppendResult(interp,"using -sync and -loop would enter infinite loop", (char *) NULL);
        return TCL_ERROR;
      }

      /* filenameObj=Tcl_FSGetNormalizedPath(interp, objv[objc-1]); */
      filenameObj=objv[objc-1];
      filename = Tcl_GetStringFromObj(filenameObj, &filenameLength);
      printf("Playoung sound file %s\n",filename);
    }

    if (optwait<0) {
      if (optsync) {
        optwait=1;
      } else {
        optwait=1;
      }
    }

    sndopts=SND_NODEFAULT|SND_FILENAME;
    if (optsync) {
      sndopts |= SND_SYNC;
    } else {
      sndopts |= SND_ASYNC;
    }
    if (!optwait) {
      sndopts |= SND_NOWAIT;
    }
    if (optloop) {
      sndopts |= SND_LOOP;
    }
    if (!optstop) {
      sndopts |= SND_NOSTOP;
    } 

    Tcl_ResetResult(interp);
    rc=TCL_OK;

    if (filename==NULL || filename[0]==0 || 
        (filename[0]=='-' && filename[1]==0)) {
      PlaySoundW(NULL,NULL,sndopts);
    } else {
      nativename = Tcl_WinUtfToTChar(filename, -1, &ds);
      if (!PlaySoundW(nativename,NULL,sndopts)) {
        if (optcomplain) {
          rc=TCL_ERROR;
          Tcl_AppendResult(interp,"can't play sound", (char *) NULL);
        }
      }
      Tcl_DStringFree(&ds);
    }

    return rc;
  }

  # This is a Win32/WinCE demo. Tk should be always available, 
  # let's use it.
  package require Tk

  # Fit toplevel to screen on Windows Mobile
  if {[llength [info command ::etcl::automanage]]>0} {
    ::etcl::automanage .
  }

  if {[catch {set os [set ::tcl_platform(os)]}]} {
    set os "unknown"
  }

  # A menu is always recommended on PocketPC and Smartphone
  # Force menu theme on PocketPC
  set menuargs [list]
  lappend menuargs -tearoff 0

  if {[string match "Windows CE" $os]} {
    lappend menuargs -borderwidth 0
    lappend menuargs -background "white"
    lappend menuargs -foreground "black"
    lappend menuargs -activebackground "#000080"
    lappend menuargs -activeforeground "white"
    lappend menuargs -relief flat
  }

  # Default menu options on X11 are just ugly...
  if {![string compare "x11" [tk windowingsystem]]} {
    lappend menuargs -borderwidth 1
    lappend menuargs -relief raised
    lappend menuargs -activeborderwidth 1
    lappend menuargs -activeforeground "\#000000"
    lappend menuargs -activebackground "\#e0e0e0"
    # lappend menuargs -font $S(font)
  }

  set mb [eval [list menu .mb] $menuargs]

  set m [eval [list menu $mb.file] $menuargs]      
  $m add command -label "Exit" -command {destroy .}

  $mb add cascade -label "File" \
      -menu $m -underline 0

  . configure -menu $mb


  # Display console if it exists
  # catch {console show}


  update

  puts "Odyce  [package present critcl]"
  puts "Critcl [package present critcl]"

  # Play standard WAV using PlaySound()
  after 1000 [list play -loop "tada.wav"]
  # And cancel play after 5s
  after 5000 [list play -]
  # Play any multimedia file (MP3, ...) using MCI
  # playsimple [file nativename [file normalize test.mp3]]