In this Developer's Corner, we reveal the internal structures of the MIDI player program elements and how they were implemented. If you have not read the Introduction section of this manual, please do so now and come back here after you know what this program does from a user's point of view. We will start out by showing an extension to the Program Elements in the previous section.
The diagram depicts a little more in-depth view of the MIDI Player utility program than the one from the user's point of view. The primary difference is that, from a developer's point of view, the MIDI player program contains two separately compiled modules:
External Process Program Module (.EXE): a stand-alone, console-based program that reads a Model File, process the contents to know the MIDI file name, and then send the MIDI stream to the Windows multimedia facility. Although this program is currently written in Microsoft Visual C++, it can be implemented in any programming language or environment.
In-Process Program Module (.DLL): a dynamic linked library that is tightly coupled with SansGUI for data display, charting, and user interaction. It has been created with the skeleton code generated by the SansGUI Source Code Framework. Both Visual C++ and Compaq Visual Fortran implementations are included with full source code. The C/C++ and Fortran implementations are independent. Either one of them is sufficient.
The external process program is a data producer, pumping out MIDI channel numbers and data whenever a MIDI event is processed. Through Win32 shared memory, the in-process DLL is a data consumer that reads in the MIDI channel numbers and data and distributes it to the channel components (parts) according to the channel numbers. In fact, it is the class functions associated with the individual channel component (part) that fetches the data when the channel number in the MIDI events matches its own in each sampling cycle.
The Invocation Script is a batch command file under Windows environment. It is, by default, SG_xProc.bat in the bin subdirectory of the SansGUI installation directory. When the user clicks on the Run External button, this script file is called with necessary parameters, including the Model File name and current working directory, passed in. Please consult the Understanding the Invocation Script section in Chapter 5 Developing External Process Simulators of the SansGUI Developer's Guide for customization details.
The External Process MIDI Player is a console-based program currently written in Visual C++. The project workspace can be found in the samples\MIDIplay\MIDIplay_1_0X subdirectory under the SansGUI installation directory. There are four code sections in this program, described in the following:
The program processes the command line arguments to obtain the name of the Model File. We use the Tabular Data Blocks format (type 0) in this application. It looks for the following information from the Model File in the readModelFile function in MIDIplay.cpp:
MIDI File Name: the name of a MIDI file to be played.
Shared Memory Name: a unique string identifier for opening a Win32 shared memory segment.
These two pieces of information are located in an object derived from the Collection.MIDI class. We simply find the class label in the Model File and fetch the information from the first object. When you load in the Piano.sgp Project Model and play the music, a Model File named Piano.txt, by default, is created in the same directory. Please examine the contents of the file, using your favorite text editor. Except the two pieces of information, all other contents of the Model File are ignored in this application.
The second code segment is located in the readMidiFile function in MIDIplay.cpp. It opens the MIDI file and reads its contents into a dynamically allocated memory area. Because the working directory is handled within the invocation script, there is no need to find out in which directory the MIDI file is located. We can simply open the file with the name string passed in from SansGUI, whether it contains a full directory path or not.
The shared memory contains only two integers in this application, one for the channel number and the other for the channel data. The name of the shared memory is indicated in the Model File and is also known to the in-process MIDI Player because it is located in the Collection.MIDI reference object. The code to setup the shared memory is in the createSharedMemory function in MIDIplay.cpp. Two global pointers, g_pChannel and g_pData are initialized to the two integer memory space.
The primary MIDI playing mechanism is contained in the CMIDI class library, created by Jörg König (J.Koenig@adg.de). We have made some modifications to his original MIDI.h and MIDI.cpp source files so that the class library can be used with any type of applications, rather than being Microsoft Foundation Class (MFC) specific.
In order to handle the MIDI events, we created a CMidiExt extension class from the CMIDI class and overrode its OnMidiOutClose and OnMidiOutDone functions:
OnMidiOutClose: When this routine is called, we know that the MIDI stream has been played and ended. We set a negative value (-1) in the channel number in the shared memory to let the in-process player know the fact.
OnMidiOutDone: After each MIDI event is processed, this routine is called. We fetch the MIDI data from the header structure and, then, 1) write out the MIDI event information to the console, and 2) write out the channel number and channel data to the shared memory.
When the user press Ctrl+C, the program detects it and stops. The code is located in a while loop in the main program.
To understand the structure of the in-process MIDI Player, let's examine its Schema Definition file. You need to have a proper license key to activate the SansGUI Development Environment in order to load the MIDIplay.sgs Schema Definition file. To find out if the development environment is activated on your computer, simply run SansGUI and select File>New. You will see the New dialog being displayed as following:
As it comes out, the Project Model (.sgp) is selected. If you don't have the development environment, the Schema Definition (.sgs) is disabled, and you cannot create or load any Schema Definition file. If you would like to try out the SansGUI Development Environment, you can register your computer to obtain a 30-day evaluation license for the full-feature SansGUI Professional Edition. To register, select Help>About SansGUI... and then click on the Register... button in the About SansGUI dialog while your computer is on-line with Internet access. Please click on the Cancel button above to close the New dialog for now.
Once you are sure that you have the SansGUI Development Environment, you can load the MIDIplay.sgs file from the samples\MIDIplay subdirectory of your SansGUI installation directory.
Select File>Open from the pull-down menu.
From the Open dialog, select SG Schema Definitions (.sgs) from the Files of type: drop-down box.
Click on the MIDIplay file with a blue SansGUI icon from the above subdirectory, and then click on the Open button. The MIDIplay Schema Definition will be loaded into SansGUI.
From the first Simulator tab in the Left Pane, you can see there are MIDIplay classes created under Cycle and XProc simulation control classes. These classes indicate that we will have both in-process (Cycle) and external process (XProc) programs implemented. There is no additional attribute introduced in these two classes; all are inherited from the parent intrinsic classes.
Also from the Simulator tab, you can see a Time unit object and four other unit objects with the names Strip-n. We created these four units because SansGUI merges data curves into the same plot when their measuring unit is the same. With different unit objects, the user can control which data channels are to be plotted together and which are separate. We created four units for up to four plotting strips. You can create more if your project requires more strips.
The component classes in MIDI Player for SansGUI are quite simple and all are derived from the Base component class. There are three attributes needed:
Channel Number (iNumber): the number entered by the user so that it can be used to match the channel number in the MIDI events.
MIDI Message Object (rRef): the name of the one and only one MIDI reference object. It is referred to for accessing the MIDI event data.
MIDI Data (iData): the MIDI data fetched from a MIDI event whenever the channel number in the MIDI event matches the Channel Number value in the instance of this class.
We could have created only one Base.Channel class that contains all three attributes above and that's it. Instead, we created a Base.Channel class that includes the first two attributes and derived four subclasses, S-1 through S-4, from it. With the four S-n subclasses, we have the last attribute, MIDI Data (iData), defined with different unit objects, Strip-1 through Strip-4, respectively, in order to allow multiple strip charts.
The last class we created for this utility program is the Collection.MIDI class under the Reference tab in the Left Pane. This class defines the MIDI file, sampling interval and the shared memory data for inter-process communication (IPC). The reason why we cannot define all these attributes in a simulation control class is because they are shared by both the in-process and external process programs. Please see class MIDI in the reference section for more details.
When the in-process program module is invoked via the Run In-Process button in the Run toolbar, it detects if the shared memory for IPC has been established. If not, it simply displays a message and quits.
During each sampling cycle, the Pre-Evaluation function in the MIDI message reference object is called to fetch the MIDI event data from the shared memory. After that, the Evaluation function in each of the MIDI channel parts is called to update the MIDI data when the channel number in the part matches that of the current MIDI event.
The in-process program implements data pulling from the shared memory with a user settable delay in the MIDI message reference object. It simply calls the Sleep system function during the Post-Evaluation cycle. This is not an implementation of a genuine real-time system.
Please consult the class references in the next few pages and the source files that are in the distribution for all the implementation details.
MIDI Player for SansGUI Version 1.0
Copyright © 2002-2003 ProtoDesign, Inc. All rights reserved.