The MKPart and MKScore classes are designed to group MKNote objects. As their names imply, a MKPart represents a series of MKNotes that are realized on the same MKInstrument; a MKScore represents all or part of a composition and consists of some number of MKParts. For storage, a MKScore can be written to the disk as a scorefile or a midifile.
The first part of this section explains the methods used to add MKNotes to MKParts, MKParts to MKScores, and to write a MKScore as a file. The second part, the Section called Retrieving MKScores, MKParts, and MKNotes, presents methods and techniques for retrieving, querying, and further manipulating MKParts, MKScores, and the MKNotes they contain. Finally, the special MKNotes called MKPart info and MKScore info are described.
A MKPart is a time tag sorted collection of MKNote objects. A MKPart can contain any number of MKNotes. While the MKNotes within a MKPart are sorted by time tag value, every MKNote in the MKPart needn't have a unique time tag; a MKPart can represent simultaneous (and otherwise overlapping) MKNotes. However, a single MKNote can only belong to one MKPart at a time.
There are three methods for adding a MKNote to a MKPart:
addToPart:, a MKNote method
addNote:, a MKPart method
addNoteCopy:, also a MKPart method
The first two are functionally equivalent, allowing you to add a MKNote to a MKPart by messaging either object:
[myNote addToPart:myPart]; /* is the same as */ [myPart addNote:myNote]; |
Since a MKNote object can only belong to one MKPart at a time, when you add a MKNote to a MKPart, it's first removed from the MKPart that it's currently a member of, if any. Both addNote: and addToPart: return the id of the MKNote's old MKPart (the MKPart from which the MKNote was removed).
addNoteCopy: creates (and returns) a new MKNote object as a copy of its argument and adds the copy to the receiver. The argument MKNote itself isn't removed from its MKPart. The method creates the MKNote copy by invoking MKNote's copy method.
You can continue to modify a MKNote (setting its parameter values, note type, time tag, and so on) after it has been added to a MKPart object. A MKPart sorts its MKNotes automatically, so you can add them in any order―you don't have to add them by order of their time tag values. If you change the time tag of a MKNote after you add it to a MKPart, the MKNote is automatically repositioned in the MKPart.
Methods for adding a collection of MKNotes to a MKPart―for instance, adding the contents of one MKPart to another MKPart―are described below, in the Section called Adding and Removing Groups of MKNotes.
A MKPart object has a print name that's used when writing it to a scorefile (or, more accurately, when the MKScore to which the MKPart belongs is written to a scorefile). The MKNameObject() function is used to name a MKPart; like all MusicKit names, a MKPart's name is case-sensitive and consists of a letter followed by a string of alphanumeric characters:
MKNameObject("Solo", aPart); |
To retrieve a MKPart's name, call the C function MKGetObjectName():
char *aName = MKGetObjectName(aPart); |
To add a MKPart to a MKScore, invoke one of these methods:
addToScore:, a MKPart method
addPart:, a MKScore method
A MKScore can contain any number of MKParts; a MKPart can belong to only one MKScore at a time. Both of the MKPart-adding methods first remove the MKPart from its present MKScore, if any.
Just as you can modify a MKNote after it has been added to a MKPart, you can continue to modify a MKPart (by adding and removing MKNotes, for example) after it has been added to a MKScore.
You write a scorefile by sending one of the following messages to a MKScore object:
writeScorefile: takes a file name (char *) argument.
writeScorefileStream: takes a stream pointer (NXStream *) argument.
The first of these opens and closes the file for you. Every time you send the writeScorefile: message, the named file is overwritten. By convention, scorefiles are given a “.score” extension.
writeScorefileStream: expects a stream pointer that's already open for writing. The method leaves the stream open after it returns, allowing you to write additional, application-specific information to the end of the file. It's left to your application to close the stream.
Rather than write an entire MKScore to a file, you can specify a particular section by sending the writeScorefile:firstTimeTag:lastTimeTag:timeShift: message (a similar method, with an initial keyword of writeScorefileStream:, is provided for writing a section of a MKScore to a stream). For example:
[myScore writeScorefile: "Adagio.score" firstTimeTag: 3.5 lastTimeTag: 10.2 timeShift: 0.0] |
Here, only those MKNotes with time tag values between 3.5 and 10.2, inclusive, are written to the file. As a convenience, you can use the constant MK_ENDOFTIME as the lastTimeTag: argument to write from some position in the MKScore to its end:
[myScore writeScorefile: mySfile firstTimeTag: 3.5 lastTimeTag: MK_ENDOFTIME timeShift: 0.0] |
To write from the beginning of the MKScore, you specify 0.0 as the firstTimeTag: argument. The timeShift: takes a value that specifies the number of beats by which the MKNotes are time-shifted as they're represented in the file. Only the file representation is affected by this value; in other words, the MKNotes themselves aren't time-shifted (their time tags aren't affected).
For each of the scorefile-writing methods, there is an analogous method that writes a Standard MIDI file.
You can fill a MKScore with information simply by reading a scorefile (or MIDI file; for brevity, scorefiles are used exclusively in the examples). The methods provided for reading a scorefile are analogous to those for writing:
readScorefile: takes a file name argument.
readScorefileStream: takes an NXStream pointer argument.
When you read a scorefile into a MKScore object, MKPart and MKNote objects are automatically created to accommodate the information in the file.
You can specify a section of the scorefile and a time shift on that section by adding the firstTimeTag:lastTimeTag:timeShift: keywords. For example:
[myScore readScorefile: "Adagio.score" firstTimeTag: 6.5 lastTimeTag: MK_ENDOFTIME timeShift: -6.5] |
Notes represented in the file that have time tag values greater than or equal to 6.5 are read into the MKScore. Within the MKScore, each MKNote's time tag is shifted ahead in time (toward the chronological beginning of a MKPart) by 6.5 beats.
The complete set of a MKScore's MKParts, regardless of how the MKScore was created, can be retrieved through MKScore's part method. The method returns the MKParts in a NSArray. The MKScore method partCount gives the number of MKParts that it contains.
There are a number of ways to retrieve a MKNote from a MKPart. One way is to access the MKNote by its time tag value through one of the following MKPart methods:
The atTime: method returns the first MKNote object with the specified time tag value.
atOrAfterTime: returns the first MKNote with a time tag greater than or equal to the argument.
Both methods take a double argument and they both return nil if they can't find an appropriate MKNote. The following example illustrates the difference between the two methods:
/* Create a MKPart, two MKNotes, and an id for return values. */ MKPart *aPart = [MKPart new], MKNote *aNote = [MKNote newSetTimeTag:2.0]; MKNote *bNote = [MKNote newSetTimeTag:3.0]; MKNote *returnNote; /* Add the MKNotes to the MKPart. */ [aPart addNote:bNote]; [aPart addNote:aNote]; /* Retrieve the MKNote at time 1.5. (Returns nil; no such MKNote.) */ returnNote = [aPart atTime:1.5]; /* Retrieve the MKNote at or after 1.5. (Returns aNote.) */ returnNote = [aPart atOrAfterTime:1.5]; |
You can also retrieve a MKNote by its ordinal position within its MKPart by sending the message nth: to the MKPart object. It takes an integer argument n and returns the nth MKNote, zero-based, in the MKPart. Using the same MKPart object from the previous example, the message
[aPart nth:1] |
returns bNote, the second MKNote in the MKPart. Recall that within a MKPart, MKNotes are ordered by their time tag values; the order of MKNotes with equivalent time tags reflects the order in which they were added to the MKPart.
The time tag and the ordinal methods of retrieving MKNotes are combined in the methods atTime:nth: and atOrAfterTime:nth:. The first of these methods returns the nth MKNote with the specified time tag; through this method you can retrieve a particular MKNote in a chord. The atOrAfterTime:nth: method returns the nth MKNote with a time tag equal to or greater than the first argument.
The next: method also retrieves MKNotes based on ordinal position: It returns the MKNote that immediately follows the MKNote given as the argument. next: can be used to access each MKNote in a MKPart in turn:
/* Initially set aNote to the first MKNote in the MKPart. */ MKNote *aNote = [aPart nth:0]; /* Access each MKNote in the MKPart and change it to MK_mute. */ while(aNote = [aPart next:aNote]) [aNote setNoteType:MK_mute]; |
next: returns nil if there is no next MKNote, or if the argument isn't a member of the MKPart. A more efficient way of accessing each MKNote in a MKPart is to create a NSArray of the MKNotes in a MKPart and step down the NSArray. The previous example can be rewritten as follows:
/* Create a Sequence over the MKPart's MKNotes. */ id notes = [aPart notes]; int noteCount = [aPart noteCount]; int i; /* Access each MKNote in the Sequence and change its note type. */ for (i = 0; i < noteCount; i++) [[notes objectAtIndex:i] setNoteType:MK_mute]; |
The MKPart class defines two methods for removing individual MKNotes from a MKPart object:
removeNote: removes the MKNote object specified in the argument.
removeNotes: removes all MKNotes common to the receiver and the NSArray specified in the argument.
You can also remove a MKNote from its MKPart by sending the removeFromPart: message to the MKNote object. The MKPart object from which the MKNote was removed is returned.
The MKPart class defines methods that allow you to add and remove MKNotes as a collection. There are two methods for adding a collection of MKNotes:
addNotes:timeShift:
addNoteCopies:timeShift:
The first argument is a NSArray of MKNotes. The second argument is an optional value in beats (a double) that's used to offset each MKNote's time tag. The addNotes:timeShift: method removes each MKNote in the collection from the MKPart that it's currently a member of before adding it to the receiver. The addNoteCopies:timeShift: method adds copies of each MKNote in the NSArray.
To remove a NSArray of MKNotes, you can invoke the method removeNotes:, which takes a NSArray of MKNotes and removes each one from the receiver.
Finally, you can remove all the MKNotes from a MKPart through the empty method.