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]]