Each program supported requires an instrument template, which can take up quite a bit of RAM, especially if it is a sampled sound instrument template. Therefore, the fewer programs supported, the better. To reduce the memory required for the PIMap, the original MIDI score should be created to use the lowest possible program number for its program changes. For example, if a score uses nine programs, they should have program numbers 0 to 8, not numbers scattered throughout the range of 0 to 127. The original score should not use program numbers that are higher than the maximum number of programs, because there is no PIMap entry to support a number that high.
Each voice supported requires DSP resources to play a note, a NoteTracker
data structure to keep track of its status, and a mixer input. So reducing the number of voices supported is important to conserve system resources. The most likely limiting factor is DSP resources, which are used up by the number and type of instruments playing. If all instruments are simple and use few DSP resources, then the DSP can support many voices. If instruments are complex, use many DSP resources, and many of them play at once, then the DSP cannot support as many voices.
A task should support no more than the maximum number of voices required by the original MIDI score. The original score should be created with careful consideration of the type of instruments used to play its notes. If the instruments are complex, the score may have to use fewer voices so that it does not overtax DSP resources.
A task can define its maximum voice and program support in constants that are used in later Music library calls. For example,
These values can be used later to set program and voice limits. The call
#define MAX_NUM_PROGRAMS (9)
#define MAX_NUM_VOICES (8)
CreateScoreContext()
sets the maximum number of programs, and the call InitScoreMixer()
or InitScoreDynamics()
sets the maximum number of voices.
ScoreContext *CreateScoreContext( int32 MaxNumPrograms )
MaxNumPrograms
, which sets the number of programs supported by the score context. This number can be no greater than 128. If the task defined this limit earlier with a constant, it can supply that constant as the argument.
When CreateScoreContext()
executes, it creates a ScoreContext
data structure and sets its elements to default settings. That data structure includes an array of ScoreChannel
data structures to control channels. CreateScoreContext()
also creates a PIMap
array with as many elements as it needs to accommodate the number of programs specified. The ScoreContext
data structure includes a pointer to that array. It also includes a pointer to an array of NoteTrackers
, which is set later when the NoteTrackers
are created with the InitScoreDynamics()
call.
CreateScoreContext()
returns a pointer to the created ScoreContext
data structure if successful. If unsuccessful, it returns a negative value (an error code).
Setting PIMap Entries
After you have created a score context and the PIMap that goes with it, you must set the entries in the PIMap to show which instrument template is designated for each program number. You can do this in one of two ways: you can first load the instrument templates you need and then directly set each of the PIMap's entries using the SetPIMapEntry()
call. Or you can specify instrument templates and program numbers in a text file and use the LoadPIMap()
call to read the file, load the appropriate instrument templates, and make the appropriate PIMap entries. The second method is the simpler of the two.
int32 SetPIMapEntry( ScoreContext *scon, int32 ProgramNum, Item InsTemplate, int32 MaxVoices, int32 Priority )
*scon
, is a pointer to the score context that owns the PIMap. The second, ProgramNum
, is a value from 0 to 127 that specifies which PIMap entry to set. The third, InsTemplate
, is the item number of an instrument template to be assigned to the specified PIMap entry. The fourth is the maximum number of voices that can be played simultaneously with this program. And the fifth is the priority number, from 0 to 200, for this program.
There are some important considerations for these arguments. First, you should note that program numbers in MIDI sometimes range from 1 to 128, and at other times range from 0 to 127, depending on a manufacturer's or publisher's whimsy. In general, user interfaces that are designed for musicians rather than programmers number programs from 1 to 128. User interfaces designed for programmers instead of musicians number programs from 0 to 127. When you set the ProgramNum
argument for this call, keep this discrepancy in mind if you are trying to match the program number in the original score.
You should also note that the maximum number of voices set by MaxVoices is not the maximum number of voices available to the full score context. It is the maximum number of voices for that program alone. It should be a number equal to or less than the maximum number of voices available to the score. This value allows you to limit the number of voices played using a complex instrument template that takes up DSP resources. For example, you may wish to limit a program using a complex lead instrument to two voices so that it does not overload the DSP by playing too many notes simultaneously.
The Priority
argument sets a program to use a low-priority instrument (set with a lower value) or a high-priority instrument (using a higher value). When voices are allocated to notes, the Music library is more likely to steal a voice from a low priority program than it is to steal a voice from a high priority program. Set this argument to a low value if the program is not likely to be used for conspicuous notes, for example, a chorus instrument. Set it to a high value if the instrument is used for solo work. Set all PIMap entries to the same priority (100, for example) if you do not want any program to have priority over another.
When SetPIMapEntry()
executes, it finds the PIMap associated with the specified score context, finds the specified entry, and then writes the instrument template item number, maximum voice value, and priority value to that entry. It returns 0 if successful, or a negative value (an error code) if unsuccessful.
LoadPIMap()
to read the file, load the appropriate instrument templates, and set the appropriate PIMap entries. The file format for a PIMap file is simple:
sampler.dsp
or varmono8_s.dsp
.
The sample rate is cut in half so the maximum pitch is reduced by a factor of
two. The instrument also plays with less fidelity since the output is not
interpolated up to 44 Khz. See the description of the AF_TAG_CALCRATE_DIVIDE
tag in the
3DO Music and Audio Programmer's Reference
The Music library in general cleaves to the programmer's model of numbering programs from 0 to 127; the one exception is the PIMap file, which numbers programs from 1 to 128.
LoadPIMap()
finds and loads the sampled sound instrument most appropriate for the sample.
Note that if you specify several different samples for the same PIMap entry, they are all attached to the sampled sound instrument template for that entry. This allows you to specify multisamples in the PIMap file for more realistic sounding sampled instruments or for varied timbres in one instrument, such as a drum kit. To create a multisample, simply list all of the samples required, one sample per line, and assign each sample to the same program number. The high and low ranges in the AIFF sample file can set the ranges of each sample in the multisample, or you can use the -l
, -b
, -h
, and -d
options to override those ranges and set new ones.
The instrument first specified for a program number is the instrument used to play all samples later assigned to that program number, so it is important to make sure that later samples match the program's instrument. If they do not match, chaos can follow. For example, if you first specify a 16-bit sample for program 4, LoadPIMap()
loads a 16-bit sampled-sound instrument to play that sample. If you then assign an 8-bit sample to program 4, the 8-bit sample is played using the 16-bit sampled-sound instrument, a guaranteed muddle.
As a PIMap file example, consider the text file shown in Example 1. Notice several things: the gong is fixed in pitch; the clarinet is set up as a multisample with octave spacing; the organ has a maximum voice option set to four, which allows it to play chords of up to four voices; and the sample bell.aiff is guaranteed to be played by the instrument varmono16.dsp
because the instrument is specified before the sample is specified. This technique is often used with ARIA instruments.
Example 1: PIMap file example.
; comments are allowed after a semicolon
; define multisample for clarinet
1 clarinet1.aiff -l 30 -b 48 -h 53
1 clarinet2.aiff -l 54 -b 60 -h 66
1 clarinet3.aiff -l 67 -b 72 -h 84
2 gong.aiff -f
3 bass.aiff -p 150
4 organ.aiff -m 4
; play bell using explicit instrument
5 varmono16.dsp
5 bell.aiff
6 sawenv.dsp
int32 LoadPIMap( ScoreContext *scon, char *Filename )
*scon
, a pointer to the score context whose PIMap you want to reset; and *Filename
, a pointer to a character string containing the filename of the PIMap file.
When it executes, LoadPIMap()
reads the PIMap file and imports the specified instrument templates and sampled sounds. If an entry in the file specifies a sampled sound (it ends in .AIF, .AIFF, or .AIFC), LoadPIMap()
attaches the sampled sound to a sampled sound instrument template if the entry already has one assigned. If the entry does not have a template assigned, LoadPIMap()
determines the appropriate sampled sound instrument to play the sampled sound, assigns it to the entry, and attaches the sampled sound. The call then revises all effected PIMap entries in the score context's PIMap.
LoadPIMap()
returns 0 if successful, or a negative value (an error code) if unsuccessful.
The highest program number in your PIMap file should not exceed the number of entries in the score context's PIMap.
There can never be more instruments playing score notes than there are maximum voices set in the score context. For example, if there are a maximum of seven voices for the score context, there can never be more than seven instruments connected to the mixer at one time. This makes it easy to choose an appropriate mixer for a score: choose the one with the smallest number of inputs able to accommodate the maximum number of voices. The choices are mixer2x2.dsp
, with two inputs; mixer4x2.dsp
, with four inputs; mixer8x2.dsp
, with eight inputs; and mixer12x2.dsp
, with twelve inputs. The best choice for a seven-voice maximum score would be mixer8x2.dsp
.
To create and initialize a mixer instrument for MIDI score playback, use this call:
int32 InitScoreMixer( ScoreContext *scon, char *MixerName, int32 MaxNumVoices, int32 Amplitude )
*scon
is a pointer to the score context for which the mixer is used. *MixerName
is a pointer to a character string containing the name of the mixer template used to create the mixer. MaxNumVoices
is an integer value that sets the maximum number of voices available for score playback. (You may want to use a maximum voice number constant defined earlier.) And Amplitude
is a value that sets the maximum amplitude possible for each instrument connected to the mixer.
When executed, InitScoreMixer()
first loads the specified mixer template and then creates a mixer instrument using that template. It writes the mixer's item number into the score context so that the MIDI calls know where to find the mixer. It also writes the maximum amplitude per instrument value into the score context so that each note plays at amplitudes no higher than that value. InitScoreMixer()
then calls InitScoreDynamics()
and passes *scon
and MaxNumVoices
as arguments.
InitScoreDynamics()
creates an array of NoteTracker
data structures, one for each possible voice. It then writes a pointer to the array into the score context so MIDI calls know where to find the note trackers.
InitScoreMixer()
returns 0 if successful, or a negative value (an error code) if unsuccessful.
InitScoreMixer()
, you can be assured that you never overload the DAC if you take the maximum available system amplitude, divide it by the maximum number of voices used, and take the resulting value as the maximum voice amplitude. This means that if all voices sound simultaneously at maximum amplitude and all panned to one side, the combined signal will not exceed the maximum system amplitude. In practice, it is an extremely rare occurrence for this to occur, so you may want to increase the maximum voice amplitude by 200 or 300 percent to give the resulting audio signal a boost. There is a slim chance that you may clip the signal, but in most cases it is highly unlikely. If you hear clipping, reduce the amplitude.
Example 2 shows an example of InitScoreMixer()
being used to set the maximum voice amplitude to the absolutely safe level. It divides the maximum system amplitude by the number of voices.
Example 2: Maximizing voice amplitude.
Note that, in this example, MAX_NUM_VOICES is a constant defined earlier in the program.
maxsysamplitude = AllocAmplitude( 0x7FFF );
Result = InitScoreMixer( scon, "mixer8x2.dsp", MAX_NUM_VOICES,
(maxsysamplitude/MAX_NUM_VOICES) );
maxsysamplitude
is a variable that contains the maximum system amplitude returned by the AllocAmplitude()
call. (Amplitude allocation is discussed in Preparing Instruments.") When you are finished with the task, free the allocated system amplitude so that other tasks can use it.
Setting Up Mixerless MIDI Playback
In future Portfolio releases, you may be able to use instruments that are not DSP instruments, for example, instruments that are attached hardware synthesizer voices. If so, you may want to set up for score playback using entirely non-DSP instruments, in which case you do not need a mixer. You still need to allocate note trackers, but you do not want to use the InitScoreMixer()
call, which creates a mixer in addition to allocating note trackers. You would instead use this call directly:
int32 InitScoreDynamics( ScoreContext *ScoreCon, int32 MaxNumVoices )
*ScoreCon
, a pointer to the score context to which you want to associate note trackers; and MaxNumVoices
, an integer value that sets the maximum number of voices available for score playback.
When executed, InitScoreDynamics()
creates an array of NoteTracker
data structures, one for each of the maximum possible voices. It then writes a pointer to the array into the specified score context. It returns 0 if successful, or a negative value (an error code) if unsuccessful.