Source Code for MIDI Player (External Process)


The following source files, led by a gray banner, contain code needed to build the external process simulator in Project MIDIplayer.  In addition, it uses a CMIDI class library originally developed by Jörg König. Except for few lines of code generated by Microsoft Visual Studio, the source files were handcrafted.  These source files and the CMIDI class files (MIDI.h and MIDI.cpp) are included in a project workspace in a console-based application program and compiled into a stand-alone executable program with a .exe extension.  For more details about the complete package, please read the MIDI Player for SansGUI Manual on-line.

External Process Source Files

Contents of MIDIplay.cpp

// MIDIplay.cpp : demonstration of playing MIDI file to be run in

//                an external process
// Copyright (C) 2002 ProtoDesign, Inc. All Rights Reserved.
//
// This console application demonstrates how to use Win32 shared memory to
// communicate with an in-process simulator running under SansGUI. This
// program is intended to be launched from the Run External Process Simulator
// button through which SansGUI will write out a Model File for this program
// to read. Two essential pieces of information in the Model File are:
// 1) The path of the MIDI file
// 2) The name of the shared memory
// Both are entered in a Project Model in the SansGUI Environment.
//
// The channel number and the data field of each MIDI event are recorded in

// the shared memory space containing two integers.  The In-Process simulator

// reads the data with a certain time interval; therefore, achieving

// asynchronous communication.  If your application requires synchronous

// communication, mutexes, semaphores, or event objects may be employed.
//
// For more information, please consult:
// 1) Chapter 5 Developing External Process Simulators of the SansGUI

//    Developer's Guide
// 2) Chapter 4 SansGUI Simulation Control of the SansGUI Reference Manual
// 3) Chapter 5 SansGUI Model File Format of the SansGUI Reference Manual
//
// This program uses a CMIDI class developed by Jörg König.
// Please see the headers of MIDI.h and MIDI.cpp files for copyright details.
// Both files have been modified to remove the dependency of Microsoft

// Foundation Class(MFC) library.

#include "StdAfx.h"
#include "conio.h"
#include "stdio.h"
#include "MIDI.h"

#define MAX_STRBUF_LEN 2047

// ===========================================================================
// class CMidiExt - extends CMIDI with overriding routines to handle events
// ---------------------------------------------------------------------------
class CMidiExt : public CMIDI
{
public:
    // overriding routines to handle MIDI out events
    // see the end of this source file for implementation
    void OnMidiOutDone(MIDIHDR & hdr);
    void OnMidiOutClose();
};

// ===========================================================================
// function prototypes
// ---------------------------------------------------------------------------
static int readModelFile(const char*);
static int readMidiFile(const char*, DWORD&, DWORD&);
static int createSharedMemory(void);
static int parseMidiFileName(char[]);
static int parseSharedMemoryName(char[]);
static DWORD getFileSize(FILE*);
static void cleanUp(void);

// ===========================================================================
// global variables, all are prefixed with g_*
// ---------------------------------------------------------------------------
BOOL g_bDone = FALSE;
int* g_pChannel;
int* g_pData;
HANDLE g_hMappedFile = (HANDLE)0;
LPVOID g_pSharedMem = (LPVOID)NULL;
LPVOID g_pMemBuffer = (LPVOID)NULL;
CMidiExt* g_pMidi = (CMidiExt*)NULL;

char g_cSharedName[MAX_PATH + 1];
char g_cMidiFileName[MAX_PATH + 1];

// ===========================================================================
// MIDIplay Main Program
// ---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
    int iRetCode;
    DWORD w2Size, w2Read;

    // ========== COMMAND LINE ARGUMENTS PROCESSING
    // check the command line parameters, passed by command script

    // bin\SGxProc.bat
    if (argc < 4)
    {
        fprintf(stderr, "Usage: %s <OptionCode> <ModelFile> <FileType>\n",

                argv[0] );
        return -1;
    }
    // check the model file type, we use Tabular Data Blocks format in

    // this example
    if (atoi(argv[3]) != 0)
    {
        fprintf(stderr,
        "Error: Select Tabular Data Blocks in the Simulation control object.\n" );
        return -2;
    }

    // ========== READ MODEL FILE TO OBTAIN MIDI FILE NAME AND SHARED MEMORY NAME
    // read and parse the model file created by SansGUI
    iRetCode = readModelFile(argv[2]);
    if (iRetCode != 0)
    {
        fprintf(stderr, "Error[%d]: Cannot open and read Model File [%s]\n",
                iRetCode, argv[2] );
        return -3;
    }

    // create the MIDI instance
    g_pMidi = new CMidiExt();

    // read the entire MIDI file into a memory buffer
    iRetCode = readMidiFile(g_cMidiFileName, w2Size, w2Read);
    if (iRetCode != 0)
    {
        if (g_pMidi != (CMidiExt*)NULL)
            delete g_pMidi;
        fprintf(stderr, "Error[%d]: Cannot open and read MIDI file [%s]\n",
                iRetCode, g_cMidiFileName );
        return -4;
    }
    // create shared memory using mapped file
    iRetCode = createSharedMemory();
    if (iRetCode != 0)
    {
        if (g_pMidi != (CMidiExt*)NULL)
            delete g_pMidi;
        fprintf(stderr,

        "Error[%d]: Cannot create shared memory [%s] for communication.\n",
        iRetCode, g_cSharedName );
        return -5;
    }

    // ========== PLAY THE MIDI FILE
    // write the MIDI file information
    fprintf(stderr,

            "MIDI FILE NAME: %s\nLENGTH [Bytes]: %d\n READ [Bytes]: %d\n",
            g_cMidiFileName, (int)w2Size, (int)w2Read );
    // play the MIDI music
    g_pMidi->Play();

    // ========== LOOP UNTIL IT ENDS OR WHEN USER HITS Ctrl-C
    while (!g_bDone && !_kbhit())
    {
        if (_getch() == 0x03)
        {   // Ctrl-C termination
            g_bDone = TRUE;
            fprintf(stderr,
            "\nKeyboard interrupt [Ctrl-C] detected. Closing MIDI channel...\n" );
        }
    }
    // ended normally, close the MIDI channel and return
    cleanUp();
    return 0;
}

 

int readModelFile(const char* cName)
{
    int iMidiBlock = 0; // see the switch.case statement below for its definition
    char cBuffer[MAX_STRBUF_LEN + 1]; // line buffer for reading
    char cMidiClass[] = "Collection.MIDI"; // class name defined in SansGUI
    FILE* pFile = fopen(cName, "r");
    if (pFile == (FILE*)NULL)
        return -1;

    while (!feof(pFile))
    {
        // fetch data line by line
        if (fgets(cBuffer, MAX_STRBUF_LEN, pFile) == (char*)NULL)
            break;
        // skip all comment lines
        if (cBuffer[0] == '#')
            continue;
        if (strncmp(cBuffer, cMidiClass, strlen(cMidiClass)) == 0)
        {   // mark inside MIDI class block
            iMidiBlock = 1;
            continue;
        }
        switch (iMidiBlock)
        {
        case 1: // read the first line with <ObjectName> <MIDI File Name>
            if (parseMidiFileName(cBuffer) > 0)
                iMidiBlock = 2;
            else
                return -2;
            break;
        case 2: // read the second line with <Shared Memory Name>
            if (parseSharedMemoryName(cBuffer) > 0)
                iMidiBlock = 3;
            else
                return -3;
            break;
        default:
            break;
        }
        if (iMidiBlock == 3) // completed
            break;
    }
    // strncpy(g_cSharedName, "MIDIplay Piano", MAX_PATH);
    // strncpy(g_cMidiFileName, "SJent.mid", MAX_PATH);

    fclose(pFile);
    return 0;
}

 

int readMidiFile(const char* cName, DWORD& w2Size, DWORD& w2Read)
{
    // read the MIDI file and attach it to the MIDI object
    FILE* pFile = fopen(cName, "rb");
    if (pFile == (FILE*)NULL)
        return -1;

    w2Size = getFileSize(pFile);
    if (w2Size < 1)
    {
        fclose(pFile);
        return -2;
    }
    // allocate memory buffer and read all MIDI data into memory
    g_pMemBuffer = (LPVOID)GlobalAlloc(GMEM_FIXED, w2Size);
    if (g_pMemBuffer == (LPVOID)NULL)
    {
        fclose(pFile);
        return -3;
    }
    w2Read = (DWORD)fread(g_pMemBuffer, sizeof(char), w2Size, pFile);
    if (!g_pMidi->Create(g_pMemBuffer, w2Read))
    {
        fclose(pFile);
        return -4;
    }
    fclose(pFile);
    return 0;
}

 

int createSharedMemory()
{
    // The shared memory contains only two integers for:
    // 1. channel number (where g_pChannel will point to)
    // 2. MIDI data (where g_pData will point to)
    // The sizeof(int)*2 parameter indicates that 2 integers are needed.
    g_hMappedFile = CreateFileMapping((HANDLE)0xffffffff, NULL,

                        PAGE_READWRITE, 0, sizeof(int) * 2, g_cSharedName );
    if (g_hMappedFile == NULL)
        return -1;

    g_pSharedMem = (LPDWORD)MapViewOfFile(g_hMappedFile, FILE_MAP_ALL_ACCESS,

                                          0, 0, 0 );
    if (g_pSharedMem == (LPVOID)NULL)
        return -2;

 

    g_pChannel = (int*)g_pSharedMem;
    g_pData = g_pChannel + 1; // warning: pointer arithmetic here

    return 0;
}

 

int parseMidiFileName(char cBuffer[])
{
    // the file name is enclosed by a pair of angle brackets < >
    int iNdxSrc = 0;
    int iNdxDst = 0;

    while (cBuffer[iNdxSrc] != '\0' && cBuffer[iNdxSrc] != '<')
        iNdxSrc++;

    if (cBuffer[iNdxSrc] != '<') // cannot find the opening bracket
        return 0;

    // now iNdxSrc is the index of the '<' character
    ++iNdxSrc;

    // copy the file name until the closing '>' or end of string is reached
    while (cBuffer[iNdxSrc] != '\0' && cBuffer[iNdxSrc] != '>' &&

           iNdxDst < MAX_PATH )
    {
        g_cMidiFileName[iNdxDst] = cBuffer[iNdxSrc];
        ++iNdxSrc;
        ++iNdxDst;
    }
    // NULL terminate the string
    g_cMidiFileName[iNdxDst] = '\0';

    return iNdxDst; // should be the length of the string
}

 

int parseSharedMemoryName(char cBuffer[])
{
    int iNdxSrc = 0;
    int iNdxDst = 0;

    // skip leading white spaces
    while (cBuffer[iNdxSrc] != '\0' &&
           (cBuffer[iNdxSrc] == ' ' || cBuffer[iNdxSrc] == '\t') )
        ++iNdxSrc;

    // skip beginning quote
    if (cBuffer[iNdxSrc] == '"' || cBuffer[iNdxSrc] == '\'')
        ++iNdxSrc;

    // copy shared memory name upto the end quote or end of string
    while (cBuffer[iNdxSrc] != '\0' && cBuffer[iNdxSrc] != '"' &&
           cBuffer[iNdxSrc] != '\'' && iNdxDst < MAX_PATH )
    {
        g_cSharedName[iNdxDst] = cBuffer[iNdxSrc];
        ++iNdxSrc;
        ++iNdxDst;
    }
    // NULL terminate the string
    g_cSharedName[iNdxDst] = '\0';

    return iNdxDst; // should be the length of the string
}

 

DWORD getFileSize(FILE* pFile)
{
    // get the file size in a more system-independent way
    DWORD w2Size = 0;

    if (fseek(pFile, 0L, SEEK_END) == 0)
    {
        w2Size = ftell(pFile);
        rewind(pFile);
    }
    return w2Size;
}

 

void cleanUp()
{
    if (g_pMidi != (CMidiExt*)NULL)
    {
        // also will stop playing MIDI
        delete g_pMidi;
        g_pMidi = (CMidiExt*)NULL;
    }

    // mark termination by setting the channel number to a negative value
    *g_pChannel = -1;

    if (g_pSharedMem != (LPVOID)NULL)
    {
        UnmapViewOfFile(g_pSharedMem);
        g_pSharedMem = (LPVOID)NULL;
    }
    if (g_hMappedFile != (HANDLE)0)
    {
        CloseHandle(g_hMappedFile);
        g_hMappedFile = (HANDLE)0;
    }
    if (g_pMemBuffer != (LPVOID)NULL)
    {
        GlobalFree(g_pMemBuffer);
        g_pMemBuffer = (LPVOID)NULL;
    }
}

 

// ===========================================================================
// Implementation of CMidiExt class functions -- MIDI event handlers
// ---------------------------------------------------------------------------
void CMidiExt::OnMidiOutDone(MIDIHDR& hdr)
{
    CMIDI::OnMidiOutDone(hdr);

    if (g_bDone)
    {
        fflush(stdout);
        return;
    }
    static int iCount = 0;
    MIDIEVENT* pEvent = (MIDIEVENT*)(hdr.lpData + hdr.dwOffset);
    int iChannel = MIDIEVENT_CHANNEL(pEvent->dwEvent);
    int iType = MIDIEVENT_TYPE(pEvent->dwEvent);
    int iData = MIDIEVENT_DATA1(pEvent->dwEvent);
    int iVolume = MIDIEVENT_VOLUME(pEvent->dwEvent);
    if (g_pSharedMem != (LPVOID)NULL)
    {   // when shared memory exists, write out MIDI message data for verification
        *g_pChannel = iChannel;
        *g_pData = iData;
        printf("%5d: [%3d %3d %3d %3d]\n", ++iCount, iChannel,

               iType, iData, iVolume );
    }
    else
    {   // when there is no shared memory, just print out a dot for

        // each MIDI message
        // do not print out any dot after MIDI out has been closed
        if (g_hMappedFile != (HANDLE)0)
            fprintf(stderr, ".");
    }
}

 

void CMidiExt::OnMidiOutClose()
{
    CMIDI::OnMidiOutClose();

    // mark termination by setting the channel number to a negative value
    *g_pChannel = -1;
    g_bDone = TRUE;

    fprintf(stderr, "\nEnd of MIDI file. Press a key to exit...\n");
}

Contents of StdAfx.h

// ===========================================================================
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#if !defined(AFX_STDAFX_H__AF78E414_2E30_11D6_B442_00A0CCE4A5C0__INCLUDED_)
#define AFX_STDAFX_H__AF78E414_2E30_11D6_B442_00A0CCE4A5C0__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

// TODO: reference additional headers your program requires here
#include <windows.h>

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__AF78E414_2E30_11D6_B442_00A0CCE4A5C0__INCLUDED_)

Contents of StdAfx.cpp

// stdafx.cpp : source file that includes just the standard includes
// midiplay.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "StdAfx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

 


MIDI Player for SansGUI version 1.0

Copyright © 2002 ProtoDesign, Inc. All rights reserved.

http://protodesign-inc.com