Technion - Israel Institute of Technology Faculty of Electrical Engineering Laboratory of Computer Music MUZIKA - A Musical Notes and Scores Editor Internal Description of the Software Presented By: Lavy Libman & Yakov Aglamaz Conducted By: Noam Amir Contents: 1. Introduction 2. General description of the database 3. General description of the program modules 1. Introduction This part of the MUZIKA documentation intends to describe the internal structure of the software. We shall assume that the reader has learnt the User's Guide and knows how the editor looks and "feels", and how to operate the different features in it. We also assume the reader to be an experienced programmer in the C++ language under the Microsoft Windows environment (preferably using Borland C++ for Windows, as this is the compiler which was used to compile the software). No general C++ or Windows terms, unless related specifically to MUZIKA, will be explained herein. While reading the internal documentation, it should be remembered that the MUZIKA project is not a finished, commercial-style application. Indeed, from the very start the intention of the project was to write the minimum amount of code to implement the features required from a musical score editor, stressing the ease with which the code can be extended and reused. The program consists altogether of about 5400 lines of C++ code (not counting the source code of the class libraries provided by Borland), making an executable file of about 120K. We hope that this description of the internal structure of the code, as well as the extensive commenting in the code itself, will help the reader clarify how MUZIKA functions from the inside. 2. General description of the database Before we explain how the software functions from the inside, it is necessary to explain the structure of the database which keeps the melody in memory. In light of the database description, it will be easier to explain the tasks performed by the different functions in the software through the changes they make to the data in the database. We recommend the reader, as he goes through the explanations below, to browse at the MUZIKA.H file, which contains all kinds of global definitions relevant to different modules of the software, including the definitions of the classes that comprise the melody database. 2.1. The melody database At the very top, the melody has a few global parameters, followed by a list of parts. Each part has its information kept separately from other parts; the structure of a part will be explained later. This is implemented by having the Melody class - the topmost class in the database, of which there is precisely one instance at any given time - inherit its parameters from the MelodyParameters class, and add to that an IndexedList of parts. The global parameters of the melody include the staff width (in pixels); this is global to the entire melody, for this width has to be common to all staves in the melody if score displays are to be shown and printed. Other parameters that may be added here can be, for example, the name of the melody or the text color. On the other hand, the key signature and the time signature should not be kept as melody parameters, as these can change throughout the different sections of the music. The IndexedList class is an extension of the Array class from Borland's class library, designed specifically for the purpose of holding lists at the different levels of the MUZIKA database. It is essentially an Array, with the following functions added: - insertAt(Object &obj, int index) inserts an object at the given index in the list, pushing the other objects from this index one forward. - detachAt(int index) removes an object from the given index in the list, pulling objects from subsequent indexes to close the gap. The object itself is not destroyed. - destroyAt(int index) is similar to detachAt, except that the object is destroyed (i.e. its destructor is called). - printOn(ostream &out) writes the objects in the list to the stream by calling the printOn virtual function once for every object in the list. Continuing with the description of the MUZIKA database, the same idea of a few global parameters followed by a list is now repeated at the part level: the Part class inherits from the PartParameters class a few parameters global to the part, and follows with an IndexedList of staves. The parameters of a part in the current implementation are: - name is the part name. - multiplicity is the staff multiplicity, i.e. the number of single staves that are grouped together to create a multiple staff with common bars. - editYMin is the Y-coordinate of the topmost visible line of the part. This is not a constant parameter of the part, but rather used for displaying to remember which staves are visible at the moment. Keeping this parameter for all parts, instead of just for the currently visible one, ensures that when the display is flipped from one part to another (via the Layout/Page... menu selection), the staves initially visible in the new part will be those that were visible the last time the part was shown. Other parameters which a part may have can include, for example, the musical instrument that plays the part. Next, we come to how a staff is implemented. No surprises here; as the reader should have guessed by now, the staff is again no more than a few parameters followed by two lists this time: the list of point objects in the staff and the list of the continuous objects. These lists were separated to ease the treatment of the two object type groups, which is subtly different in most cases. The Staff class inherits its parameters from a StaffParameters class and has two IndexedList items corresponding to the two object lists. The staff parameters include, in the current implementation, only its coordinates: X, Y, and width. The Y coordinate of a staff is invariant to display scrolling; the actual Y coordinate of a staff on the screen is calculated as its Y coordinate minus the part's editYMin. The PointObject and ContinuousObject classes, both derived from MusicalObject, are the base classes defining the properties of a generic point object or continuous object, respectively. Every class defining a specific musical object type (such as Note, Pause, etc.) is derived from one of these two base classes. Actually, the MusicalObject class already defines the properties that every musical object must have, letting the PointObject and ContinuousObject classes only add the properties specific to the respective object type groups. An attribute that every musical object must have is the location of the object relative to the staff in which it is. The location attribute can take one of the following values (all defined in MUZIKA.H): - INSTAFF means that the object can be anywhere in the staff. Therefore, at the time of creation, the constructor of the object receives both the X and Y coordinates of the mouse cursor relative to the staff. - ABOVESTAFF means that the object is always located above the staff. Therefore, at the time of creation, the constructor of the object receives only the X coordinate of the mouse cursor. - BELOWSTAFF means that the object is always located below the staff. Objects having this attribute value are treated similarly to those which are ABOVESTAFF. - ABOVEMULTIPLE means that the object is always located above the multiple staff. Therefore, at the time of creation, no matter where the mouse was clicked, the object is inserted in the appropriate list of the topmost staff of the multiple staff. - BELOWMULTIPLE means that the object is always located below the multiple staff. Therefore, at the time of creation, no matter where the mouse was clicked, the object is inserted in the appropriate list of the bottommost staff of the multiple staff. - COMMONMULTIPLE means that a copy of the object will always be in every staff in the multiple staff. Therefore, at the time of creation, no matter where the mouse was clicked, a copy of the object is inserted in the list of every staff comprising the multiple staff. In addition, the value of ONEPERSTAFF can be added to any of the values above to indicate that only one such object can exist in a staff. Thus, for example, a Key object has a location attribute of INSTAFF+ONEPERSTAFF, meaning that there can be only one key in a staff, and the thick bar limits at the start or end of a staff have a location attribute of COMMONMULTIPLE+ONEPERSTAFF, meaning that there can only be one such bar limit per staff, and in that case there is a copy of it in every staff comprising the multiple staff. The MusicalObject class also defines a few member functions that every musical object should have. These are: - Draw(HDC hDC) draws the graphic image of the object in the given display context. This function is used to display the object. - printOn(ostream &out) saves the object data in the given output stream. This function is used when the melody is saved in a file. - clipOn(void far &*clipboard) saves the object data in the given memory location. This function is used during clipping (i.e. cutting or copying). The PointObject class, derived from MusicalObject, adds the properties that every point object must have. These include the X coordinate of the object, and the following functions: - Format(int &X) sets the object's X coordinate to the given value. This function is used when the part is reformatted. If a point object has any special actions required when its coordinate is changed, they can be added by having the specific class define its own version of this virtual function. - Width() returns the object width in pixels. Usually, this is the general object width defined in the Layout/Page... dialog box and kept in the pixelsPerObject variable. This function, too, is used in the process of part reformatting. - MIDIPlay(ostream &out) writes the MIDI event which the object represents to a temporary file. This function is used during the first pass of the MIDI file creation. - Duration() returns the duration of the MIDI event which the object represents, in units of 148-th of a full note. This function, too, is used in the process of creating the temporary file during the MIDI first pass. The ContinuousObject class, derived from MusicalObject, adds the properties that every continuous object must have. These include the Xleft and Xright coordinates of the object, and the following functions: - FormatLeft(int &Xleft) sets the object's Xleft coordinate to the given value. This function is used when the part is reformatted. If a continuous object has any special actions required when its left coordinate is changed, they can be added by having the specific class define its own version of this virtual function. - FormatRight(int &Xright) is similar to FormatLeft, but alters the Xright coordinate of the object. Summarizing what has been said about the database so far, we see that it has a structure of a 4-level tree, at the root of which sits the melody, on the second level - the parts, on the third level - the staves, and finally, on the leaves - the musical objects themselves. The object-oriented design of the database is readily seen. It should be emphasized that none of the functions, anywhere in the software, does not - in fact, cannot - make any assumptions about the nature of the objects at the leaves, e.g. test the type of a specific object and act differently if it is a loudness sign than if it is a note sign. Whatever actions are specific to the musical object classes are made in virtual member functions of the classes themselves. Having said this much, let us turn and see what specific musical object types have been defined in the current implementation of MUZIKA, remembering that this is only a minimal set, intended to demonstrate the tremendous possibilities of the design. No doubt does this set have to be extended to include many more class types for the application to become a really useful tool for editing high-featured scores. Anyway, the classes that have been implemented are: - Note is the class of note objects. The different note durations (full, half, etc.) are distinguished by the value of the class's duration attribute. In addition, the class defines an Y attribute representing the note height, and eventually its frequency. - Pause is the class of pause objects. The different pause durations are distinguished by the value of the class's duration attribute. - Key is the class of key objects. The different key types are distinguished by the value of the class's type attribute. An instance of this class always has a location value of INSTAFF+ONEPERSTAFF. - Beat is the class of time signatures (i.e. beats per bar). The different time signatures are distinguished by the value of the class's type attribute. - Bar is the class of bar limits. The different bar limit types (single, double, etc.) are distinguished by the value of the class's type attribute. An instance of this class always has a location value of COMMONMULTIPLE, with or without the addition of ONEPERSTAFF. - Loudness is the class of loudness point objects (forte, fortissimo, piano and pianissimo). The different loudness signs are distinguished by the class's loudness attribute. - Crescendo is the class incorporating the crescendo and diminuendo signs. The two signs are distinguished by the class's direction attribute. - Text is the class of text signs and instructions above or below the staves. The text itself (up to 16 characters) is kept in text. This concludes the description of the part of the database classes which is defined in the MUZIKA.H and OBJECTS.H header file. To get the impression of how things operate at the lowest levels of the database, the reader is invited to catch a glimpse of the member function definitions in OBJCLASS.CPP. 2.2. The symbols database The second large part of the database is the database of the symbols. After all, the user does not have a direct access to the melody database; rather, he creates the musical objects using symbols on the left of the screen. The symbols database is conceptually simpler than the melody database. Every symbol has its own class, from which there is exactly one instance. All the symbol classes are derived from the generic SymbolClass base class, which defines the properties that every symbol must have. The symbol class instances are held in a constant array called symbolArray. The following properties are defined in SymbolClass: - symbolID is a unique number that identifies the symbol. The ID is not just a random unique number; rather, it represents the symbol set where it belongs and its number within the set. In more detail, the formula to which a symbolID value must conform is: symbolID = 16 * (symbol set) + (symbol offset) where symbol set is the symbol set number (e.g. 1 for Notes, 2 for Keys, etc.), and symbol offset is the offset of the symbol, counted from 0 upwards (e.g. 0 for the topmost leftmost symbol, 1 for the topmost rightmost, etc.). - symbolType is a slightly misleading name for the attribute: it is actually the type of the object (either POINTOBJECT or CONTINUOUSOBJECT) that is created by the symbol. - BitmapName(LPSTR, int) returns the name of the symbol bitmap in the resource file, without the "B_" prefix. As can be easily verified by browsing in the resource file (MUZIKA.RES) using any resource editor, all the bitmaps are named "B_xxxx". - DrawSymbol(HDC, HDC, BOOL) draws the symbol in the given display context at the coordinates that are calculated from its own symbolID. This function uses the previous one to load the symbol bitmap from the resource file. The bitmap is drawn in reverse video if the symbol is active. - CreateObject(int, int, int) creates the object corresponding to the symbol. Sometimes, an single object type (such as Note) is created by several symbols; in that case, it is the duty of CreateObject to construct the appropriate version of the object and load its attribute variables with the proper values. It is pointless to give here a list of all the symbol classes derived from SymbolClass. Far better is to direct the reader to the SYMCLASS.CPP file, which is actually a library of the derived symbol classes and their versions of the three virtual functions defined in SymbolClass, above. As with the library of musical classes, we would like to emphasize how straightforward it is to add a new symbol to the library: just define the new class, derived from SymbolClass, and write its own versions for the three virtual functions BitmapName, DrawSymbol, and most important, CreateObject, all after having designed the symbol bitmap in the resource file (MUZIKA.RES) - and that is all there is to it. 3. General description of the program modules The source code of MUZIKA consists of over 5400 lines of C++ lines, not including the sources of Borland's class library. Naturally, a project of this size could not have been maintained without some sort of dividing the code into smaller modules. In fact, MUZIKA contains 16 *.CPP files, corresponding to logical modules of the software, containing functions that take care of different tasks. In addition to that, we should mention the *.H header files, especially MUZIKA.H and OBJECTS.H, that although do not contain any actual statements, they provide the definitions of classes, global variables, and function prototypes to be used by all the modules. In the sequel, we attempt to describe the operation of each module independently from the others, as much as this proves possible. When it was not possible, we ordered the module descriptions in such a way that there would only be references to modules described previously. In our descriptions, we do not provide an exact list of the functions, their parameters and return values. We feel that that would be a rather useless duplication of the source code itself, which is well and extensively documented as it is. Instead, we concentrate on the duty that the module fulfills within the overall design, and explain the implemented algorithms on the conceptual level. Either way, we recommend the reader to browse through the enclosed source code listings as he reads the description; this will without doubt help in understanding the logic behind the software design. 3.1. The main module (MAIN.CPP) The main module contains several initialization functions and the main window message-processing and painting functions. The entry point to the program is at the beginning of this module, in function WinMain, which does no more than calling the initialization functions and establishing a message-dispatching loop, as is customary in every Windows application. The initialization functions register the main window class, create and display the main and edit windows for the first time. The initialization code is entirely straightforward and is not much different from what can be found in any other Windows application. The main window function processes messages originated by Windows that are intended for the main window. For example, a WM_COMMAND message, indicating that the user has triggered an action using the menu, causes it to call the ProcessMenu function in the menu processing module. A WM_LBUTTONDOWN message, sent when the left mouse button is clicked, causes a call to the symbol identification function in the symbols module, to check if the cursor has been clicked on a symbol and make it active if so. Finally, a WM_PAINT message, sent when the main window needs repainting (i.e. when it has been resized, or another window has been removed from over it), triggers a call to the PaintEditWindow function, described next. The PaintEditWindow function is in charge of painting the edit window. It draws the lines separating between the symbols, and then calls the symbol-drawing functions in the symbols module. To summarize, then, the main module play a role of a dispatcher; any messages intended for the main window are, this is true, recognized here, but the actions taken in response are actually a part of other modules (specifically the menu processing module and the symbols module). 3.2. The IndexedList class module (INDEXED.CPP) The IndexedList module is not as important by itself as it is for other modules, that use the functions defined here constantly. In a previous chapter we have already explained the use of the IndexedList class, which is an extension of the Array class from Borland's class library, in the melody database. The functions that actually extend IndexedList over Array are defined here. These are the insertAt, detachAt, destroyAt, and printOn functions, all except the last similar to those of Array but keep the list elements at contiguous indexes at all times (as opposed to an Array, which allows for empty slots in the list). 3.3. The database module (DATABASE.CPP) The name of the database module may be slightly misleading: functions that make changes in the database are scattered throughout all parts of the software, without any central database management unit that would take care of keeping the database intact. What there actually is in the database module are member functions of classes that comprise the database. Among the functions defined here for all the class levels (Melody, Part, and Staff) are LoadFrom, which loads the appropriate class instance data from a file, and printOn, which does just the opposite - saves the class instance data in a file. There are also a few class constructor and destructor functions. Finally, the only Melody class instance is declared at the file end. The code in this module is straightforward and contains no subtleties; we feel therefore confident that the commenting of the code in the source file itself provides a sufficient explanation. 3.4. The display module (DISPLAY.CPP) The display module is responsible for displaying the information stored in the melody database in either format supported by MUZIKA (single part or score, in the current implementation). There are two important functions in the module, namely EditWindowProc, the edit window function, and PaintEditWindow, which is responsible for displaying staves and objects in the edit window. The other two functions, used only during initialization, are less important. By the way, the edit window is a child of the main window, containing the main window client area minus the area that is occupied by the symbols and status line. The edit window is automatically resized every time the main window changes its size. Staves and musical signs are drawn only in the edit window. The EditWindowProc function responds to all messages intended for the edit window, including cursor movement, mouse button clicks and double-clicks, and scrollbar changes. Covering this quite complex function's tasks is impossible without elaborating on the way it responds to each and every message type, so let's do just that. A WM_PAINT message, requesting a repaint of the edit window, is treated by the PaintEditWindow function, described later in this section. A WM_MOUSEMOVE message does not offer any cause for alarm, either - all that has to be done is change the cursor shape according to the current edit mode symbol (pencil, eraser or hand). The problems begin with the WM_LBUTTONDOWN message, because a simple clicking of the left button can trigger a variety of actions, according to what symbol is active at that moment. In any case, the actions which eventually lead to making the required changes in the database are done in the edit module (EDIT.CPP), but it is the duty of EditWindowProc to select the operation to be triggered. First of all, EditWindowProc checks that a single part is displayed (no editing is allowed on a score display). Then, it checks what the current symbol is. - If it is the pencil, a staff creating function in the edit module is called. - If it is the eraser, an object deleting function in the edit module is called. - If it is a symbol whose symbolType is POINTOBJECT, a point object inserting function in the edit module is called. In the remaining cases (the active symbol is the hand or corresponds to a CONTINUOUSOBJECT), no operation can be triggered just yet, because we need the point where the mouse button is released as well as where it is clicked. In that case, the mouse is captured to the edit window and the capture mode variable is set accordingly, to let the required operation to be completed after the mouse button is released. A WM_LBUTTONDBLCLK is processed similarly: if the current symbol is the eraser, the staff erasing function is called immediately, while if the current symbol is the hand, the mouse is captured until the time it is released, and only then will the staff movement operation be completed. In light of the above, a mouse button release notification message cannot be just thrown away: an operation may need to be completed at this stage. Therefore, in response to a WM_LBUTTONUP message, EditWindowProc checks whether the mouse has been captured to the edit window, indicating that there is indeed an unfinished operation pending for the mouse button release point. The nature of the unfinished operation is kept in the capture variable. Whatever the operation is (inserting a new continuous object or moving an object or a staff), the appropriate function in the edit module is called with the mouse button clicking and releasing points. There is yet another message which EditWindowProc has to process, and that is WM_VSCROLL, indicating an operation of some kind on the vertical scroll bar (line up, line down, page up, page down, or direct thumb movement). In response to the message, the thumb position is set to its new location, and the screen is refreshed. By the way, the scroll bar range is identical at all times to the range of Y coordinates of the staves in the current part (that is, the scroll bar values range from 0 to the Y coordinate of the last staff). To summarize, EditWindowProc takes much of the sting out of the editing process: it makes the relatively difficult decision of which operation is to be performed, leaving the edit module functions only to update the database given all the needed coordinates. The other nontrivial function in the display module, as we have noted, is PaintEditWindow, whose job is to display a part of the melody database on the screen. The display algorithm is similar whether it is a single-part display or a score display. In both cases, the relevant staves are drawn (using the Staff class Draw function, also included in this module at the end of the file), and the musical signs are drawn on those staves that reported that they were not entirely clipped, by going through the point and continuous object lists, calling the Draw virtual function for every item in the lists. The difference is only which staves are drawn; obviously, when displaying a score the parallel staves of all parts take part in the algorithm, while when displaying a single part only that part's staves are included. Anyway, after having drawn the staves, the status line in the main window is updated with information about which staves are visible. Strictly speaking, having the main window updated in one of its children's window function is a violation of the Windows "correct" programming style; what should have been done instead is define the status line area as another child window and have the edit window send an update message to that child whenever it needed an update, but the implemented solution was chosen for the sake of saving space (and even more modules). 3.5. The edit module (EDIT.CPP) The edit module functions are responsible for updating the melody database in response to a edit operation initiated by the user. Except for the first couple, all the functions are called from the display module, after the edit window function has selected the operation and obtained the cursor screen coordinates for it. The operations supported here include inserting, deleting and moving staves and point and continuous objects. To understand how these operations are taken care of the reader should have a clear view of the melody database structure, for which he should refer to section 2.1. Basically, all the edit functions operate similarly: they scan an appropriate list (either a list of staves in a part or of objects in a staff) in an attempt to find the list index where the operation takes place, and then insert or delete at that index, according to the operation that needs to be done. There are, however, certain subtleties here and there, that need specific care to be taken (a value of COMMONMULTIPLE for an object's location attribute, for example). The code of the functions is well commented, but we still feel that a general explanation of each function's task here will be helpful. InsertEmptyStaff inserts a new empty staff in the specified staves list given the staff Y location and the part multiplicity. The list is scanned for a staff having a Y coordinate larger that the inserted staff's one, and the new staff is inserted at that index. All the subsequent staves get their Y coordinate moved down by a staff height. Therefore, the list is kept sorted by the Y locations in ascending order. Also, if there is a marked block past where the staff is inserted, the block marking variables are incremented too. NewMultipleStaff creates a new multiple staff, after the user has clicked the pencil-on-staff symbol. A number of single staves equal to the part's staff multiplicity is inserted in the part's staff list by calling InsertEmptyStaff that number of times. The scroll bar range is readjusted to the new Y coordinate range (recall that the scroll bar range, from Windows' point of view, is from 0 to the last staff's Y coordinate). IdentifyStaff finds the index of the staff to which the given Y coordinate (obtained from the current cursor position) is closest. It does this by scanning the list of staves, returning one which actually contains the point, if it exists. Failing that, it returns the staff from which the point's Y distance is minimal, but in any case not more than half a staff's total width, as defined in the page layout dialog box and kept in the pixelsPerStaff global variable. DeleteMultipleStaff deletes the multiple staff identified by a Y coordinate (obtained from the current cursor position). Having identified the staff using IdentifyStaff, the function simply removes all the staves in the same multiple staff from the list. If the multiple staff contains any objects, the user is requested to confirm the operation. NewPointObject is probably one of the most important functions of the software, though it is not conceptually difficult. First of all, it creates a point object corresponding to the active symbol at the given cursor position (using the current symbol's CreateObject function - there is no idea about what the object is). Next, it checks the just created object's location attribute to decide where to insert it. If the object has a location value of INSTAFF, ABOVESTAFF, or BELOWSTAFF, it is simply inserted in the staff identified by IdentifyStaff. An object with a location value of ABOVEMULTIPLE or BELOWMULTIPLE is inserted in the top or bottom staff of the multiple staff, respectively. Finally, in case of a COMMONMULTIPLE object, a copy of it is inserted in every staff comprising the multiple staff. In addition to all of the above, if the object has the ONEPERSTAFF bit set, the staff is checked not to contain another object at the same place, reporting an error if this condition is not met. NewContinuousObject is mostly similar to NewPointObject, just described. It creates a continuous object corresponding to the active symbol at the given cursor coordinate), and inserts it in one of the staves from which the multiple staff is constructed, according to the object location attribute. DeleteMusicalObject may look frightening at first sight, considering the amount of code in it, but this actually follows from the majority of special cases that need to be taken care of. Basically, it scans the point and continuous object lists of the staff identified by IdentifyStaff, deleting the objects that are within (pixelsPerObject/2) pixels away from the given cursor position. If any of the deleted objects is COMMONMULTIPLE, the other staves in the same multiple staff are also scanned and this object is deleted from them too. MoveStaff moves a multiple staff from one place to another in the same part. Before actually moving, the destination is checked to be free from other staves. In the process of moving (achieved by detaching each staff of the multiple staff from the staff list and reinserting it at its new index), any staves that the moving has crossed (and are now on a different side relative to the moved staff) get their Y coordinate updated, and so is done to the block marking variables in case there was a marked block in the area. MoveMusicalObject moves a musical object by using the CutBlock and PasteBlock functions in BLOCK.CPP. Therefore, to understand the object moving operation, the user is referred to the description of the block operations module later in this chapter. 3.6. The menu processing module (MENU.CPP) The job of this module is to dispatch various requests made by the user through the main menu to the modules that take care of them. The ProcessMenu function is thus not more than one big switch statement, interpreting the menu item codes and either calling the appropriate functions in other modules directly or by creating a dialog box function instance first. Another function in this module is InitializeMenu, used during the software initialization. According to the Boolean parameter it receives, it enables or grays out the menu items that cannot be used before a melody is loaded in. 3.7. The file operations module (FILE.CPP) The file operations module is an interface to the functions offered to the user by the File menu, namely creating a new melody, loading and saving a melody, and converting a melody to a MIDI file. The functions themselves are not implemented in this module; what is rather contains is the functions of the dialog boxes created when the user selects different file operations. The first function in the source file is AskSave, which does the job of confirming any operation that may destroy the current melody (such as loading a new one or quitting the application) when the current melody has been modified since the last time is was saved. The information about whether the melody has been modified is kept in the melodyModified global variable, which is set to TRUE by all the editing functions that modify the melody, and to FALSE by the saving function. The rest of the module functions are straightforward dialog box functions, whose duty is to process typical dialog box messages, such as WM_INITDIALOG to initialize a dialog box or WM_COMMAND to respond to an action made by the user. Actually, they differ little except for what they do after the user selects OK to confirm his selections: - DialogNew empties the current melody from any parts it might have contained, and creates a new melody with a single part named UNNAMED, with a staff multiplicity of 1. It also puts default values into a few global variables, such as melodyModified and scoreDisplay. - DialogOpen opens the file whose name the user has given (reporting an error if that file does not exist) and issues a call to the LoadFrom function of the Melody class to load the melody. - DialogSaveAs opens the file whose name the user has given (issuing a warning if that file already exists) and calls the printOn function of the Melody class to save the melody. - DialogCreateMIDI calls the MIDI file creation module to convert the current melody to a MIDI file. 3.8. The printing module (PRINT.CPP) The printing module contains the functions that make a hard copy of a melody or of one of its parts on a printer. In any case, the printer selected as the Windows default is used. There are two functions, PrintSinglePart and PrintScore, that take care of the process of printing a single part or the score of all parts. The interface function to the module is PrintEditWindow, which creates a printer device context and calls one of the other two printing functions according to what the current display setting. Both printing functions operate quite similarly, the main difference being the source of their staves, taken either from only one part or all parts, respectively. Both use the staves' and objects' Draw functions to create a multiple staff image in the printer device context and then flush it to the printer. The multiple staves are therefore printed one by one. 3.9. The layout module (LAYOUT.CPP) The layout module implements the layout dialog box functions. Specifically, it contains three functions: - DialogParts, the Layout/Parts... dialog box function; - DialogPage, the Layout/Page... dialog box function; - DialogNewPart, the Layout/Parts.../New... dialog box function. The dialog box functions code is tailored to the specific form of the dialog boxes themselves, designed in the MUZIKA.RES resource file under the names D_PARTS, D_PAGE and D_NEWPART respectively. To understand the code, the reader must try using the MUZIKA editor for a while and get the feel of how the mentioned dialog boxes look, but once that stage is over, the code is as straightforward as any dialog box function code usually is. All the three functions respond to messages intended for their dialog boxes, such as WM_INITDIALOG to initialize a dialog box (which means, for the mentioned dialog boxes, to fill the list boxes with the lists of part names), and WM_COMMAND to respond to an action made by the user. After the user selects OK to confirm his selections, these functions complete their duties by copying these selections to global variables, such as pixelsPerStaff for the staff height and pixelsPerObject for the object width, both selected in the Page dialog box. It is pointless to elaborate on any of these functions further, as they are commented well enough for the reader to be able to understand them easily by himself. 3.10. The About dialog box module (ABOUT.CPP) The About dialog box module is the smallest module of the software. As its name suggests, it has no more than the function for the dialog box that appears upon the user's selection of Help/About... The trivial function code should pose no problem for the reader who has had the experience with the File and Layout dialog box much more complicated functions. 3.11. The reformatting module (FORMAT.CPP) The reformatting module implements a part reformatting algorithm. In its current version, the algorithm reformats each multiple staff by itself; bars and musical objects do not move from one staff to another. Let the reader, however, have no illusion; even the implemented algorithm is complicated enough as it is, which is easily proved at least by the length of the module. The central function of the module is FormatMultipleStaff, which adjusts the X coordinates of musical objects on one multiple staff. The interface function of the module is FormatEntirePart, that does no more than calling FormatMultipleStaff once per every multiple staff contained in the part. Before describing the algorithm itself, a word should be said about the data structures it uses. At the beginning of FormatMultipleStaff a few lists are allocated on the heap for later use. The lists are as long as there are single staves in a multiple staff, i.e. every list item holds a datum about the single staff corresponding to it. These lists are: - duration holds the durations left (in units of 148-th of a full note) for the current object on the staves. Whenever this duration is over, the next object should be brought in on the corresponding staff. - index holds the indexes of the current objects for all staves. - atCommon holds TRUE for a staff who has stopped on a COMMONMULTIPLE object. It is used to ensure that such objects remain at the same X coordinate on all staves after the reformatting process. - staffEnd holds TRUE for a staff that has no objects left to reformat. The next few lines create two lists combining the continuous object lists of all the single staves, one sorted by Xleft and one sorted by Xright. This operation is required because later, when the X coordinates are changed considering only the point objects, the continuous objects' left and right coordinates will not be changed simultaneously. Note that during this stage, the continuous objects appear on three lists at once! However, this does not contradict in any way the normal usage of the IndexedList class, holding only pointers to the actual objects instead of copies of the objects themselves. Next comes the main loop of the algorithm, which lasts as long as the multiple staff has not been completely reformatted. At every stage of the loop the minimum value in the duration list is subtracted from all the others. In those staves at which the duration reaches zero as a result of the subtraction, the next object is placed at the current X position, denoted by the currX variable. If several objects were at the same place originally (for example, an accord), they are all moved to their new place together. This includes the continuous objects having one of the coordinates at the current X position as well as the point objects. To conclude the loop, the index list of indexes is updated and the current X position is advanced by the default object width, held in pixelsPerObject. If the current object in any staff is a COMMONMULTIPLE object, the atCommon flag is marked for that staff. Later, no objects will be reformatted further in that staff before atCommon is raised to TRUE at all staves. This ensures that COMMONMULTIPLE objects remain at a common X coordinate (though possibly different from the original). As a consequence, objects cannot cross bar limits; whatever is in a bar before reformatting stays there afterwards, even if there is a logical error and the object durations do not sum up to the same value. 3.12. The MIDI file creation module (MIDI.CPP) The MIDI file creation functions are gathered in this module. They are activated when the user chooses Create MIDI... from the File menu. The length of the file witnesses that the problem of creating a MIDI file is far from being simple. We hope to clarify our approach to the solution in the sequel. The conversion of a melody to a MIDI file is performed in two stages, or passes, as they are called in the program text. During the first pass, a temporary file is created with information about the MIDI events represented by the objects in the melody. The second pass adds information that was not be available during the first translation pass, such as track lengths, and creates a final MIDI file. The first pass is implemented by the MIDIFirstPass function, which simply calls MIDIStaff once per every multiple staff in the score. The algorithm implemented in MIDIStaff bears some resemblance to that of reformatting a multiple staff (see function FormatMultipleStaff in the reformatting module description). In particular, there are the duration and index arrays having the same purpose. The main loop of the first-pass translation algorithm lasts as long as the multiple staff has not been completely reformatted. At every stage of the loop the minimum value in the duration list is subtracted from all the others. In those staves at which the duration reaches zero as a result of the subtraction, the next object's MIDIPlay virtual function is called to put a description of the MIDI event represented by that object in the temporary file. If several objects were at the same place originally (for example, an accord), they are all MIDIPlayed together. To conclude the loop, the index list of indexes is updated, advancing the current object in the staves that took part in the play. Before we can explain the second pass of the algorithm, it is necessary to elaborate on the format of the temporary file available after the first pass. Ignoring for a while the meta-events defined by objects other than notes and pauses, the temporary file contains note-on events written by the Note objects and note-off events written by the Pause objects. The events are simply there in the file one after another, each event keeping such information as part number, note frequency, and most important - the duration. However, the MIDI file format does not use durations of events but rather delta times between events. This fact creates a translation problem from the temporary file format to the final MIDI file format, because each note event having any duration is translated to two MIDI events: a note-on at the beginning and a note-off at the end of its duration. This problem is solved in the second translation pass. The second-pass translation could be achieved by building a list of events from the temporary file, adding two events to the list for every note and keeping the list ordered by the time of the note appearances. This is precisely how MIDISecondPass works, except for two differences: one is that there are several such lists, one of each part (kept in the event[] array), and the other is that it does not build the list in its entirety before writing it to the MIDI file. Instead, as soon as it becomes clear that a specific event will not be preceded by another, that event is written to the MIDI file. Otherwise there would have been a danger of getting out of memory very quickly during the translation process. The class that is instantiated in the list(s) described above is MIDIEvent. To what every list item (of class Object) has, it adds the properties of event code, frequency, and delta time. In the algorithm implemented in MIDISecondPass, the lists of MIDIEvent instances are kept as delta lists, in which the delta time of an event is its actual delta time from the previous event on the list. Such arrangement eases the insertion and deletion of events from the lists. In the main loop of MIDISecondPass, events are read from the temporary file and inserted in the part lists as described above until no lists remain empty. At this point, it is clear that one of the events already in the lists must come before any subsequent ones. Among the first events in the lists, the one with the minimum delta time is chosen for writing into the MIDI file. After it is written and removed from the list, the temporary file is again read until no list is empty, etc. The process is finished at the end of the temporary file, after which the events already in the lists are flushed in an appropriate order to the MIDI file. Of course, the general structure of a MIDI file (its division to a header and playing tracks) is preserved in the MIDISecondPass function that creates the final-version MIDI file. 3.13. The block operations module (BLOCK.CPP) The block operations module defines four functions, corresponding to the four operations that can be performed on blocks: marking, copying, cutting and pasting. The functions are executed from the block operation symbol class instances when the user clicks the mouse with any of these symbols active. The block marking function (MarkBlock) code is trivial: it only has to assign the block marking variables, which are markBeginStaff, markBeginX, markEndStaff and markEndX, with the proper values. These variables being global, the display module's PaintWindowProc function can figure out where to display the musical text in reverse video. Also, any editing operation that may alter the block marking variables (such as inserting a new staff) must be sure to update their values whenever a possibility of that happening exists. The other functions, however, are not that simple. The operation of CutBlock and CopyBlock is extremely similar, the only difference being that CutBlock destroys the objects it clips, whereas CopyBlock doesn't. Both allocate a memory block in which the clipped objects' information will be written and that will be attached to the clipboard later on. Then they loop on the staves' point and continuous object lists, finding those objects contained in the marked block, and call their clipOn virtual functions, that write the objects' information in the memory block. CutBlock also removes the clipped objects from the lists and destroys them. Finally, the memory block handle is attached to the Windows clipboard. From now on, the information in the clipboard is independent of the current MUZIKA application instance, and it can be pasted in any other MUZIKA window. PasteBlock does the reverse operation of restoring musical objects out of the information available in a clipboard handle. During pasting, a block that is entirely contained in one multiple staff is treated differently from a block scattered among several multiple staves. The difference is expressed by the fact that a one-staff block can be pasted at any location on an existing staff, enabling objects to be moved horizontally as well, whereas pasting a many-staves block recreates the staves with the original X coordinates of the objects. The pasting process is performed by reading the object types one by one from the clipboard handle and reinstantiating them at their new places. Actually, PasteBlock distinguishes only between a staff object and whatever is not a staff (i.e. is a MusicalObject). Further separation to the different musical class types is done by the PasteObject function in the musical class library contained in OBJCLASS.CPP. 3.14. The symbol operations module The symbol operations module defines the functions that are common to all symbols. This is the module that works extra hours when the user chooses a symbol set from the Symbols submenu, or when he changes the active symbol within a given set. The functions presented by the module are summarized below. We recommend the reader to review the symbols database organization, described in section 2.2. DisplayEditModeSymbols displays the edit mode symbols on the screen top, marking the current by reverse video. In the symbols' module terminology, edit mode symbols are the three constantly appearing symbols at the screen top: the pencil, the eraser and the hand. The active edit mode symbol, if any, is marked by the variable activeEditMode. Whether an edit mode symbol is active at all (a regular symbol from a set on the screen left can be active instead) is marked in the variable editModeSymbolActive. IdentifyEditModeSymbol checks if the given cursor position is on one of the edit mode symbols. If so, it makes that symbol active and refreshes the display accordingly. RefreshSymbols displays the current symbol set on the screen left, marking the current symbol, if any, by reverse video. The active symbol is specified by the pair of variables activeSymbolSet and activeSymbol. In addition, a pointer to the current symbol class instance is held in the currentSymbol pointer. IdentifySymbol checks if the given cursor position is on one of the symbols on the screen left. If so, it makes that symbol active and refreshes the display accordingly, updating the currentSymbol pointer. Finally, GetActiveSymbol and GetCurrentSymbol return the current symbol, by ID or as a pointer to the symbol class instance, respectively. 3.15. The symbols class library (SYMCLASS.CPP) The symbols class library gathers the definitions of the symbol classes. As the reader will recall from section 2.2, for every symbol there is exactly one symbol class, derived from the generic base class SymbolClass, with one instance. In particular, a symbol class must redefine the virtual functions defined in SymbolClass, which are BitmapName, DrawSymbol, and CreateObject. Probably the most important of these is CreateObject, that creates a musical object corresponding to the symbol and returns a pointer to it. Only because it is defined as virtual for all the symbol classes does it become possible for the editing process, especially the NewPointObject and NewContinuousObject functions, to behave in a truly object-oriented manner, without knowing the types of the objects on which they operate at all. The symbol class instances - or more precisely, pointers to them - are kept in a list called symbolList, sorted by their ID code. For information about what a symbol ID is, refer to section 2.2. This list, declared at the end of SYMCLASS.CPP, is searched whenever the active symbol or symbol set changes. Except for that, there is little we can add to the above sentences. The reader is invited to browse the SYMCLASS.CPP to see the symbol classes define, in an entirely straightforward manner, their own versions of the virtual functions listed above. 3.16. The musical class library (OBJCLASS.CPP) The musical class library contains the definitions of the musical classes' versions of the pure virtual functions defined in MusicalClass (the base class for all musical object types). The different musical classes themselves are defined in the OBJECTS.H header file. More specifically, each musical class (of which there are currently seven: Note, Pause, Key, Beat, Bar, Loudness, Crescendo, and Text) has its own versions of the following functions: - Three different constructor functions: one that receives the instance data directly as parameters; one that gets as a parameter the input stream from which to read the instance data; and one that gets the instance data from a memory block attached to the clipboard. - A Draw function, that draws the object in the given display context, using (if needed) the display module's global variables staffX, staffY, and staffLoc. This functions is used to draw object images during displaying and printing. - A printOn function, that writes the object data to the given output stream in a way that enables its complete reconstruction. This function is used when the melody is saved. - A clipOn function, that writes the object data to the given memory block, presumably attached to a clipboard handle. This function is used when the object is inside a clipped (i.e. cut or copied) block. In addition, point object classes are allowed to redefine the Format, Width, MIDIPlay, and Duration virtual functions, and continuous object classes can make their own versions of FormatLeft and FormatRight. This may be needed in cases where an object has actions to perform during formatting or translating a MIDI file other than the default ones, defined in MUZIKA.H.