June 24, 2024, 12:14:12 AM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


PLAYMIDISTR

Started by Ionic Wind Support Team, June 02, 2006, 12:21:12 AM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

Ionic Wind Support Team

June 02, 2006, 12:21:12 AM Last Edit: June 02, 2006, 01:01:51 AM by Ionic Wizard
Converted the code for playing midi strings.  It will end up in a sound library eventually but I thought I would share.

There is a test "main" subroutine that plays a little tune for you.  I've also attached the code to the post since cutting an pasting from the forums seems to remove all tabs, at least on Mozilla.


#AUTODEFINE "OFF"

declare IMPORT,midiOutOpen(hMidi as UINT ByRef,uDeviceID as UINT,dwCallback as UINT,dwCBInstance as UINT,dwFlags as UINT),UINT;
declare IMPORT,midiOutSetVolume(hMidiOut as UINT,vol as INT),int;
declare IMPORT,midiOutClose(hMidiOut as UINT),int;
declare IMPORT,midiOutShortMsg(hMidiOut as UINT,dwMsg as UINT),int;
DECLARE IMPORT,midiStreamClose(hStream as UINT),INT;
DECLARE IMPORT,midiStreamOpen(lphStream as UINT ByRef,puDeviceID as UINT ByRef,cMidi as UINT,dwCallback as UINT,dwInstance as UINT,fdwOpen as UINT ),INT ;
DECLARE IMPORT,midiStreamOut(hStream as UINT,lpMidiHdr as POINTER,cbMidiHdr as UINT),INT;
DECLARE IMPORT,midiStreamRestart(hStream as UINT),INT;
DECLARE IMPORT,midiStreamStop(hStream as UINT),INT;
DECLARE IMPORT,midiStreamProperty(hStream as UINT,lpPropData as POINTER,dwProperty as UINT),INT;
DECLARE IMPORT,midiOutPrepareHeader(hmo as UINT,lpMidiOutHdr as POINTER,cbMidiOutHdr as UINT),INT;
DECLARE IMPORT,midiOutUnprepareHeader(hmo as UINT,lpMidiOutHdr as POINTER,cbMidiOutHdr as UINT),INT  ;
DECLARE IMPORT,CreateEventA(lpEventAttributes as POINTER,bManualReset as INT,bInitialState as INT,lpName as POINTER),UINT;
DECLARE IMPORT,CloseHandle(handle as UINT),INT;
DECLARE IMPORT,ResetEvent(hEvent as UINT),INT;
DECLARE IMPORT,SetEvent(hEvent as UINT),INT;
DECLARE IMPORT,WaitForSingleObject(handle as UINT,dwMilliseconds as UINT),INT;
DECLARE IMPORT,CreateThread(lpThreadAttributes as POINTER,dwStackSize as UINT,lpStartAddress as UINT,lpParameter as POINTER,dwCreationFlags as UINT,lpThreadId as UINT ByRef),UINT ;
DECLARE IMPORT,ResumeThread(hThread as UINT),INT;
CONST MM_MOM_CLOSE = 0x3C8;
CONST MM_MOM_DONE = 0x3C9;
CONST MM_MOM_OPEN = 0x3C7;
CONST MM_MOM_POSITIONCB = 0x3CA;
CONST MIDIPROP_GET = 0x40000000;
CONST MIDIPROP_SET = 0x80000000;
CONST MIDIPROP_TEMPO = 0x2;
CONST MIDIPROP_TIMEDIV = 0x1;
CONST CALLBACK_EVENT = 0x50000;
CONST CALLBACK_FUNCTION = 0x30000;
CONST INFINITE = 0xFFFFFFFF;
CONST MOM_POSITIONCB = MM_MOM_POSITIONCB;
CONST MOM_OPEN = MM_MOM_OPEN;
CONST MOM_DONE = MM_MOM_DONE;
CONST MOM_CLOSE = MM_MOM_CLOSE;
CONST TIME_BYTES = 0x4;
CONST TIME_MIDI = 0x10;
CONST TIME_MS = 0x1;
CONST TIME_SAMPLES = 0x2;
CONST TIME_SMPTE = 0x8;
CONST TIME_TICKS = 0x20;
CONST CALLBACK_NULL = 0x0;
CONST MEVT_F_CALLBACK = 0x40000000;
CONST MEVT_F_LONG = 0x80000000;
CONST MEVT_F_SHORT = 0;
CONST MEVT_TEMPO = 0x1;

CONST MLTYPENOTE =0x01;
CONST MLTYPEREST =0x02;
CONST MLTYPEPATCH =0x03;
CONST MLTYPETEMPO = 0x04;
CONST NOTE_ON = 0x90;
CONST NOTE_OFF = 0x80;
CONST PATCH_CHANGE = 0xC0 ;
CONST CONTROLLER_CHANGE = 0xB0;

CONST CREATE_SUSPENDED = 0x4;


struct MIDITHREAD
{
UINT hThread;
POINTER pStream;
UINT stopEvent;
}

struct MIDILISTDATA
{
UINT dataType;
POINTER midiEvent;
}

struct MIDIEVENTSHORT
{
    UINT dwDeltaTime;
    UINT dwStreamID ;
    UINT dwEvent;
}

struct MIDIEVENTLONG
{
    UINT dwDeltaTime;
    UINT dwStreamID ;
    UINT dwEvent;
UINT dwParams[0];
}

struct MIDIHDR
{
    POINTER  lpData;
    UINT  dwBufferLength ;
    UINT  dwBytesRecorded;
    UINT  dwUser ;
    UINT  dwFlags ;
    POINTER lpNext;
    UINT  reserved;
    UINT  dwOffset;
    UINT  dwReserved[4];
}

struct MIDIPROPTIMEDIV
{
UINT cbStruct;
UINT dwTimeDiv;
}

//test
global sub main()
{
print("A few bars of the Star Spangled Banner");
print("Press any key to close");
DSTRING midi[1000];
midi="N0 O6 V64 I0 T48 F12 D32 O5 A#8 O6 DF A#6 O7 D12";
midi=midi+"C32 O6 A#8 DEF4 F16 FO7 D6 C16 O6 A#8 A4 G16 AA#8 A#FD";
midi=midi+"O5 A#16 R16 O6  F12 D32 O5 A#8 O6 DFA#4 O7 D12 C32";
midi=midi+"O6 A#8 DEF4 F16 FO7 D6 C16 O6 A#8 A4 G16 AA#8 A#FDO5";
midi=midi+"A#";
pointer stream = PLAYMIDISTR(midi,TRUE);
while GetKey() == "";
STOPMIDISTR(stream);
}




   
/*format of string
On - octave
A(#)n through G(#)n - note
R1, full rest
R2, half rest
R4, quarter rest
R8, 8th rest
In, instrument number
Tn, Tempo change
Vn, Velocity change
Nn, Playback channel*/

global SUB PLAYMIDISTR(strMidi as STRING,OPT bAsync=FALSE as INT),POINTER
{
POINTER pTemp,pReturn;
MIDILISTDATA *pNote;
MIDIHDR *pHeader;
CPointerList lMidiList,lChordList;
INT cnIndex,octave,note,length,rest,timecurrent,instrument,velocity,channel,timebase,nEvents,tempo;
INT nTemp,timebase4,x,chord,chordmax,firstchord;
DSTRING strTemp[8];
byte strSelect;
INT aConv[7];
IF LEN(strMidi)
{
aConv = 9,11,0,2,4,5,7;
octave = 1;velocity = 127;channel=0;length=1;
cnIndex = 0;timecurrent = 0;nEvents = 0;chord=0;
timebase = 96;
timebase4 = timebase * 4;
//construct a list of midievents based on the string
//the list will be converted into memory later on
lMidiList.Create();
//reset all controllers
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPEPATCH;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | CONTROLLER_CHANGE | (channel & 0xF) | (121 << 8);
pNote->midiEvent = pTemp;
nEvents++;
//add a default instrument
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPEPATCH;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | PATCH_CHANGE | (channel & 0xF) | (0 << 8);
pNote->midiEvent = pTemp;
//1 event
nEvents++;
//set the default tempo
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPETEMPO;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | MEVT_TEMPO<<24 | ((60000000 / 120));
pNote->midiEvent = pTemp;
//1Event;
nEvents++;

WHILE strMidi[cnIndex] <> 0
{
note = -1;rest = -1;instrument=-1;tempo=-1;
strSelect = strupper(tochar(strMidi[cnIndex]));
strTemp = strmid(strMidi,cnIndex+2,5);
SELECT strSelect
{
CASE "A":
CASE& "B":
CASE& "C":
CASE& "D":
CASE& "E":
CASE& "F":
CASE& "G":
note = GetBaseNote(strTemp,octave,length);
IF note > -1
note += aConv[strSelect-'A'];
CASE "O":
nTemp = VAL(strTemp);
IF nTemp > 0 AND nTemp < 12
octave = nTemp;
CASE "I":
nTemp = VAL(strTemp);
IF nTemp > -1 AND nTemp < 128
instrument = nTemp;
CASE "R":
nTemp = VAL(strTemp);
IF ValidateNoteTime(nTemp)
rest = nTemp;
CASE "T":
tempo = VAL(strTemp);
IF tempo < 10 OR tempo > 300
tempo = -1;
CASE "V":
nTemp = VAL(strTemp);
if nTemp > -1 AND nTemp < 128
velocity = nTemp;
CASE "N":
nTemp = VAL(strTemp);
if nTemp > -1 AND nTemp < 16
channel = nTemp;
CASE "*":
chord = 1;
chordmax = 0;
lChordList.Create();
//timeoffset = 0
CASE ";":
chord = 0;
firstchord = TRUE;
void *pos;
FOR(pos = lChordList.GetFirst();pos != NULL;pos = lChordList.GetNext(pos))
{
pTemp =  lChordList.GetData(pos);
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPENOTE;
pNote->midiEvent = pTemp;
IF firstchord
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = chordmax
ELSE
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = 0;
firstchord = FALSE;
}
lChordList.RemoveAll(FALSE);
}
IF note > -1
{
//note on
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPENOTE;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | NOTE_ON | (channel & 0xF) |(note << 8) | (velocity << 16);
pNote->midiEvent = pTemp;
//note off
pTemp = NEW(MIDIEVENTSHORT,1);
if(!chord)
{
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPENOTE;
pNote->midiEvent = pTemp;
}
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = (timebase4 / length);
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | NOTE_OFF | (channel & 0xF) | (note << 8) | (velocity << 16);
IF chord
{
lChordList.Add(pTemp);
//calculate the maximum off time
if (timebase4 / length) > chordmax
chordmax = (timebase4 / length);
}
//time for next note
timecurrent = 0;
//2 events
nEvents += 2;
}
IF rest > -1
timecurrent = (timebase4 / rest);
IF instrument > -1
{
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPEPATCH;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | PATCH_CHANGE | (channel & 0xF) | (instrument << 8);
pNote->midiEvent = pTemp;
timecurrent = 0;
//1 event
nEvents++;
}
IF tempo > -1
{
pNote = lMidiList.Add(NEW(MIDILISTDATA,1));
pNote->dataType = MLTYPETEMPO;
pTemp = NEW(MIDIEVENTSHORT,1);
*(MIDIEVENTSHORT)pTemp.dwDeltaTime = timecurrent;
*(MIDIEVENTSHORT)pTemp.dwStreamID = 0;
*(MIDIEVENTSHORT)pTemp.dwEvent = MEVT_F_SHORT | MEVT_TEMPO<<24 | ((60000000 / tempo));
pNote->midiEvent = pTemp;
timecurrent = 0;
//1 event
nEvents++;
}
cnIndex++;
}
IF nEvents > 0
{
//create the event array and copy the data, prepare the header
pHeader = NEW(MIDIHDR,1);
pHeader->lpData = NEW(MIDIEVENTSHORT,nEvents);
x = 0;
FOR(pos = lMidiList.GetFirst();pos != NULL;pos = lMidiList.GetNext(pos))
{
pTemp = lMidiList.GetData(pos);
pHeader->*(MIDIEVENTSHORT)lpData[x] = *(MIDILISTDATA)pTemp.*(MIDIEVENTSHORT)midiEvent;
x++;
}
pHeader->dwBufferLength = LEN(MIDIEVENTSHORT) * nEvents;
pHeader->dwBytesRecorded = pHeader->dwBufferLength;
pHeader->dwFlags = 0;
IF bAsync
{
pTemp = NEW(MIDITHREAD,1);
*(MIDITHREAD)pTemp.pStream = pHeader;
*(MIDITHREAD)pTemp.hThread = CreateThread(NULL,0,&PlayMidiStreamAsync,pTemp,CREATE_SUSPENDED,ntemp);
*(MIDITHREAD)pTemp.stopEvent = CreateEventA(NULL,FALSE,FALSE,NULL);
IF(*(MIDITHREAD)pTemp.hThread <> NULL)
{
//OnExit(&KillAsyncMidi,pTemp);
ResumeThread(*(MIDITHREAD)pTemp.hThread);
pReturn = pTemp;
}
}
ELSE
{
pTemp = NEW(MIDITHREAD,1);
*(MIDITHREAD)pTemp.pStream = pHeader;
*(MIDITHREAD)pTemp.hThread = NULL;
PlayMidiStream(pTemp);
DELETE pHeader->lpData;
DELETE pHeader;
DELETE pTemp;
pReturn = NULL;
}
}
//free the temporary event list
FOR(pos=lMidiList.GetFirst();pos != NULL;pos = lMidiList.GetNext(pos))
{
pTemp = lMidiList.GetData(pos);
DELETE *(MIDILISTDATA)pTemp.midiEvent;
}
lMidiList.RemoveAll(TRUE);
lChordList.RemoveAll(TRUE);
}
RETURN pReturn;
}

global SUB STOPMIDISTR(MIDITHREAD *pStream)
{
IF pStream
{
IF pStream->hThread
{
SetEvent(pStream->stopEvent);
WaitForSingleObject(pStream->hThread,INFINITE);
CloseHandle(pStream->hThread);
pStream->hThread = NULL;
}
DELETE pStream;
}
}

SUB PlayMidiStreamAsync(MIDITHREAD *pParam)
{
PlayMidiStream(pParam);
DELETE pParam->*(MIDIHDR)pStream.lpData;
pParam->*(MIDIHDR)pStream.lpData = NULL;
DELETE pParam->pStream;
pParam->pStream = NULL;
//DELETE pParam
}

SUB PlayMidiStream(MIDITHREAD *pMidiThread)
{
POINTER pStream;
pStream = pMidiThread->pStream;
UINT hStream = NULL;
UINT hEvent = NULL;
INT err = 0;
MIDIPROPTIMEDIV prop;

hEvent = CreateEventA(NULL,FALSE,FALSE,NULL);
IF hEvent
{
err = -1;
IF midiStreamOpen(hStream, err, 1, hEvent, 0, CALLBACK_EVENT) = 0
{
ResetEvent(hEvent);
//set the time division

prop.cbStruct = LEN(MIDIPROPTIMEDIV);
prop.dwTimeDiv = 96;
err = midiStreamProperty(hStream, prop, MIDIPROP_SET|MIDIPROP_TIMEDIV);

IF midiOutPrepareHeader(hStream,pStream,LEN(MIDIHDR)) = 0
{
IF midiStreamOut(hStream,pStream,LEN(MIDIHDR)) = 0
{
IF midiStreamRestart(hStream) = 0
{
WHILE WaitForSingleObject(hEvent, 100) <> 0
{
//are we a separate thread?
IF pMidiThread->hThread
{
//Do we need to stop?
IF WaitForSingleObject(pMidiThread->stopEvent,0) = 0
{
midiStreamStop(hStream);
}
}
}
}
}
midiOutUnprepareHeader(hStream,pStream,LEN(MIDIHDR));
}
midiStreamClose(hStream);
}
CloseHandle(hEvent);
}
RETURN;
}

SUB GetBaseNote(strNote as STRING,octave as INT,length as INT BYREF),INT
{
int tempLength,nReturn;
nReturn = (octave-1) * 12;
if strNote[0] = "#"
{
nReturn++;
strNote = strmid(strNote,2);
}
tempLength = VAL(strNote);
IF ValidateNoteTime(tempLength) = TRUE
length = tempLength;
RETURN nReturn;
}

SUB ValidateNoteTime(time as INT),INT
{
SELECT time
{
CASE 1: //whole
CASE& 2: //half
CASE& 3: //.quarter
CASE& 4: //quarter
CASE& 6: //.eighth
CASE& 8: //eighth
CASE& 12: //.sixteenth
CASE& 16: //sixteenth
CASE& 24: //.32nd
CASE& 32: //32nd
CASE& 48: //.64th
CASE& 64: //64th
RETURN TRUE;
}
RETURN FALSE;
}
Ionic Wind Support Team

Ionic Wind Support Team

Updated to support dotted note times.
Ionic Wind Support Team