Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

How To Write A Music Component: Difference between revisions

From Higher Intellect Vintage Wiki
No edit summary
No edit summary
Line 2: Line 2:
<h2> How To Write A Music Component - Based On The Generic Music Component </h2>
<h2> How To Write A Music Component - Based On The Generic Music Component </h2>


<hr>


1995
1995
Line 21: Line 20:


-22 May 1996    Revised to nearly QuickTime 2.5 accuracy
-22 May 1996    Revised to nearly QuickTime 2.5 accuracy
<br>
<br>
<strong><em>Introduction</em></strong>
<strong><em>Introduction</em></strong>

Revision as of 01:08, 2 September 2020

How To Write A Music Component - Based On The Generic Music Component


1995

Apple Computer

David Van Brink

Revision History

-31 January 1995 First Revision

-12 March 1996 Minor corrections

-22 May 1996 Revised to nearly QuickTime 2.5 accuracy


Introduction

A music component is a driver recognized by the QuickTime Music Architecture that can play musical notes. All of the device specific capabilities are accessed through this driver. A music component may be written for a particular type of MIDI synthesizer, or for a particular musical synthesizer hardware add on such as a Nubus card, or even to control a synthesizer which has been implemented entirely in software, such as the QuickTime Music Synthesizer provided with QuickTime 2.5.

There is a set of API calls which must be implemented in a music component, including MusicGetDescription(), to report some of the capabilities of the synthesizer; MusicSetInstrumentNumber(), to request a certain sound to be played; or MusicPlayNote(), to produce an audible tone.

Many of these routines are handled for you if you base your music component on the Generic Music Component which is built into the QuickTime Music Architecture. For example, the synthesizer description returned by MusicGetDescription() is created automatically based on resources in your music component. Also, lists of instruments and knobs are similarly assembled.

Depending upon the device that the music component supports, you will need to implement different routines, and include different resources.

All Synthesizers
General Information
Instrument List

Any Synthesizer with Knobs
Knob List (Instrument and Part knobs)

Nubus Synthesizer

MIDI Device
MusicSetMIDIProc()

The Resources
All resources are offset by a constant, kBaseResID, which your component passes to GenericMusicConfigure() in the component's Open() routine. Examples, here, are taken from the source code to the MT32 music component.

General Information
The resMiscLongList resource contains a set of longwards to roughly define the capabilities of the synthesizer. Most of this information is returned by MusicGetDescription(), which is implemented in the Generic Music Component.

resource 'Long' (kBaseResID+resMiscLongList)
{
    {
    32,            /* maximum polyphony */
    8,0,0xFFFF,    /* instrument: parts, user insts, midi channels */
    1,0,0xFFFF,    /* drum: parts, user insts, midi channels */
    2,0,           /* outputs, Latency in  Sec. */
    kSynthesizerUsesMIDIPort
        + kSynthesizerDynamicVoice,        /* flags */
    0x0000,        /* hw, bank where gm sounds live (if any) */
    0x0000,        /* hw, bank where gm drums live (if any) */
    }
};



Synthesizer Name
The resMiscStringList presently contains a single item: the name of the synthesizer.

resource 'STR#' (kBaseResID+resMiscStringList)
{
    {
    "MT32"
    }
};



Bits List
The resBitsLongList resource contains three sets of 128 bits, which indicate which controllers the synthesizer recognizes, and which General MIDI instruments and drumkits are supported by the device. The latter two sets of bits determines which instruments are italicized in the intrument picker dialog. The information is also returned by MusicGetDescription().

resource 'Long' (kBaseResID+resBitsLongList)
{
    {
    0x82000001, 0x00000000, 0x00000000, 0x00000000,    /* controllers */
    0x00000000, 0x00000000, 0x00000000, 0x00000000,    /* GM Insts */
    0x00000000, 0x00000000, 0x00000000, 0x00000000    /* GM Drums */
    }
};


Instrument List
The resInstrumentList resource contains a list of instrument names used by the device. Interspersed with the instrument names are the category names, which are shown in the instrument picker. This information is returned by the MusicGetInstrumentInfo() routine, which is implemented by the Generic Music Component.

The list consists of pairs of instrument names and instrument numbers. Non-GM instrument numbers should be in the range 0x8000 to 0xBFFF. Non-GM drumkits should be in the range 0xC000 to 0xFFFF. The instrument numbers are sent to MIDI devices as program change commands. If the instrument number is above 256, then the low 6 bits of the hight byte are interpretted as a bank change.If the MIDI device supports one of the standard bank switching protocols (controller 0, or controller 32), then the bank change command may be sent automatically, by setting the appropriate bits in the flags passed to GenericMusicConfigure(). Currently, bank changes only up to 0x3F are supported by this method; if your driver requires more sophisticated bank switching, you can implement it in the MusicDerivedSetInstrumentNumber() routine.

Category names are followed by the numbers , 0, 0.

resource 'zLst' (kBaseResID+resInstrumentList)
{
    {
    "Piano",0,0, // category
    "Acoustic Piano 1",0x8000,0,
    "Acoustic Piano 2",0x8001,0,
    "Acoustic Piano 3",0x8002,0,
    "Electric Piano 1",0x8003,0,
    "Electric Piano 2",0x8004,0,
    "Electric Piano 3",0x8005,0,
    "Electric Piano 4",0x8006,0,
    "Honkytonk",0x8007,0,
    "Organ",0,0, // category
    "Electric Organ 1",0x8008,0,
    "Electric Organ 2",0x8009,0,
    ...
    "Drum Kit",0,kInstListFlagDrumKit,        // drum category
    "MT32",0xc000,kInstListFlagDrumKit
    }
};


General MIDI Equivalents
The Generic Music Component can use a resource in your component to perform the mapping from a General MIDI instrument number, which the standard way of specifying an instrument, into an instrument that exists on the device.

resource 'Long' (kBaseResID+resGMTranslation,"GM Equivalents")
{
    {
    1,2,5,8, 4,6,18,21,            /* pianos */
    23,102,98,103, 105,104,103,102,    /* chromo perc */
    9,10,11,13, 13,16,16,16,        /* organs */
    60,61,62,63, 63,63,63,63,        /* elec guitars */
    ...
    }
};


Synthesizer Knobs
Knobs which are global to the synthesizer, such as global reverb or volume settings, are in the resKnobDescriptionList resource. Each knob in the resource has the following structure.

String Name of the knob
integer[2] The lowest and highest value the user sees.
integer The default value of the knob. You should produce a default tone if all knobs are set to their default values.
integer A set of flags specifying what kind of control it is.
integer[3] Up to the music component to interpret. This can be a "hardware address" within the MIDI or Nubus synthesizer, or any other kind of useful information to help set the knob.
integer The resource ID of a KSLs resource, which contains the names of settings of this knob. This is for a popup or checkbox type of instrument.

resource 'Knob' (kBaseResID+resKnobDescriptionList,"Knobs")
{
    {
    "Effect Type",0,3,1,0,
            0x40001,0,0,0,
    "Effect Time",0,7,1,0,
            0x40002,0,0,0,
    "Effect Level",0,7,1,0,
            0x40003,0,0,0,
    }
};


Instrument Knobs
The instrument knobs -- knobs which define an instrument patch for a particular synthesizer -- use a similar structure to the global knobs. Since many synthesizers have a repetition of knobs for sub-parts within the synthesizer, there is some provision for automatically repeating a section of the knob list. For example, the MT32 has up to four oscillators ("voices" in MT32 nomenclature) per voice, each of which uses an identical set of parameters.

To repeat a section of the knob list, you include a knob name of the form " xy" where x and y are characters in the range '0' to '9'. Then, that section is repeated xy times. The last four numbers in each knob entry are then incremented by the amount in the repeat-entry knob entry.

Also, all occurences of the character "#" are replaced by the repeat number. Thus, the following resource generates knobs for "V1 Pitch Course," "V2 Pitch Course," "V3 Pitch Course," and "V4 Pitch Course."

resource 'Knob' (kBaseResID+resInstrumentKnobDescriptionList,
       "Instrument Knobs")
{
    {
    "General",0,0,0,kKnobTypeGroupName,0,
            0,0,0,0,
    "Voice 1&2 Structure",0,0xC,0,kKnobTypeSetting,kBaseKnobID+38,
            0x00A,0,0,kStructures,
    "Voice 3&4 Structure",0,0xC,0,kKnobTypeSetting,kBaseKnobID+39,
            0x00B,0,0,kStructures,
    "Enable Voices",0,15,1,kKnobTypeBoolean,kBaseKnobID+40,
            0x00C,0,0,kVEnables,
    "Kill Sustain",0,1,0,kKnobTypeBoolean,kBaseKnobID+41,
            0x00D,0,0,0,

    "  04",0,1,2,3,
            0x03a,0,0,0,

    "V# Pitch Course",0,60,36,kKnobGroupStart,
            0x00e,0,0,0,
    "V# Pitch Fine",-50,50,0,0,
            0x00f,0,0,0,
    ...
    }
};


A few notes about the fields for each knob may be useful. The first five fields are identical to those found in a knob description. The last four (on the second line each time in the above example) are just for your music component to use. The first four fields are name, low value, high value, and default value. The next field, usually marked with a constant, is the knob's flags field. The allowable flags are the same once returned in a knob description. Following that is a knob "ID". This longword is used by applications to refer to a particular knob. These ID's should never change for a particular knob, although it is safe to rearrange the order of the knob descriptions within the resource, if the component is revised.

The last four fields are for a derived music component to talk to its hardware. Generally they're used to store some "hardware address" for the particular parameter, although that usage is optional. The very last field is used for the resource number of a list of settings values, as described next.

Knob Value Names
Note that the last number in the "Voice Structure" knobs above is the constant, kStructures. This is a resource ID -- defined in the resource file -- for a list of text representations of the values. In this case, a list of cryptic voice algorithm names.

resource 'KSLs' (kStructures)
{
    {
    "S+S",0,
    "S+SrS",0,
    "P+S",0,
    "P+PrS",0,
    "S+SrP",0,
    "P+P",0,
    "P+PrP",0,
    "S,S",0,
    "P,P",0,
    "SrS",0,
    "PrS",0,
    "SrP",0,
    "PrP",0
    }
};



The Routines
In general, for a derived Music Component, you won't have to implement any

 of the standard Music Component calls. Rather, you will write a set of calls all 

prefixed by MusicDerived-. These calls are made only by the base Generic Music Component. Furthermore, your component can make calls to the generic music component which are not part the standard music component calls; These calls are prefixed by MusicGeneric-. The calls will be listed here intermixed by theme and typical order of use.

MusicGenericConfigure
pascal ComponentResult
local MusicGenericConfigure(MusicComponent mc,

       long mode,long flags,long baseResID)


The first thing your derived music component should do is attempt to open up the generic music component, and issue it a Configure call. This tells the generic component what kinds of services your component will require, and also point it at the necessary resources.

The mode parameter must be zero. The baseResID parameter will be the lowest resource ID used by your component for the standard resources described above. Since the resource numbers are relative to this, you can include several music components in a single system extension.

The flags in the configuration call are as follows.

kGenericMusicDoMIDI
Implement normal MIDI messages for note, controllers, and program changes 0-127.

kGenericMusicBank0 , kGenericMusicBank32
If kGenericMusicBank0 is set, then bank changes for instruments numbered above 127 will be send on controller zero; if kGenericMusicBank32, then on controller 32. If both flags are set, then the bank is sent on controller zero, and then a zero value is sent to controller 32.

kGenericMusicErsatzMIDI
Some musical devices, such as Nubus cards, may internally be driven by a MIDI stream, but should not appear to the user to be an external MIDI device. The kGenericMusicErsatzMIDI flag instructs the Generic Music Component to allocate channels appropriately, and construct MIDI packets. The MIDI packets are always sent to the routine MusicDerivedMIDISend(), and never to an external MIDI port.

kGenericMusicCallKnobs
Specifies that the derived component should receive calls to its routine MusicDerivedSetKnob() for changes to global or part knobs. This flag should be set if your component implements any knobs.

kGenericMusicCallParts
Specifies that the derived component should receive calls to its routine MusicDerivedSetPart(), in order to alter a specific part's polyphony or, in the case of a MIDI device, MIDI channel number.

kGenericMusicCallInstrument
Specifies that the derived component should receive calls to its routine MusicDerivedSetInstrument(), in order to set a part to an new instrument. This is for devices that support complete user-instruments with knob lists. If this flag is not set, then the Generic Music Component will call the derived component many times to set the value of each knob in the instrument.

kGenericMusicCallNumber
Directs the Generic Music Component to call the derived component's MusicDerivedSetInstrumentNumber() routine, rather than sending standard MIDI program- and bank-change messages.

kGenericMusicCallROMInstrument
Allows instruments that appear to the user as instruments built into the synthesizer to be stored in the derived component's resource file, as "ROMi" resources. The derived component will get a call to MusicDerivedSetInstrument() when one of these instruments is requested.


MusicDerivedMIDISend
pascal ComponentResult
MusicDerivedMIDISend(MusicComponent mc,MusicMIDIPacket *packet)

       ComponentCallNow(kMusicDerivedMIDISendSelect,4);


If the derived component is configured with the flag kGenericMusicErsatzMIDI then this routine will be called with a MIDI packet to be passed to the device.

MusicDerivedSetKnob

pascal ComponentResult
MusicDerivedSetKnob(MusicComponent mc,
        long knobType,long knobNumber,long knobValue,
        long partNumber,Part *p,
        GenericKnobDescription *gkd)
        ComponentCallNow(kMusicDerivedSetKnobSelect,24);

/* knobTypes for MusicDerivedSetKnob */
#define kGenericMusicKnob 1
#define kGenericMusicInstrumentKnob 2
#define kGenericMusicDrumKnob 3


This routine is called when any knob on the synthesizer is altered. It should look at the Part record and the GenericKnobDescription record and address the synthesizer hardware appropriately to set the new knob value. For a MIDI device, this means to construct a system-exclusive MIDI packet, and send it to the MIDI routine received by the MusicDerivedSetMIDI() call.

MusicDerivedSetPart

pascal ComponentResult
MusicDerivedSetPart(MusicComponent mc,
        long partNumber,Part *p)
        ComponentCallNow(kMusicDerivedSetPartSelect,8);


In response to this call, the derived component should set the specified part to the new desired polyphony embedded in the Part record.

MusicDerivedSetInstrument

pascal ComponentResult
MusicDerivedSetInstrument(MusicComponent mc,
        long partNumber,Part *p)
        ComponentCallNow(kMusicDerivedSetInstrumentSelect,8);


Requests that the derived component deliver the complete instrument defined by the Part record to the synthesizer. This will either be by hardware addressing in the case of a Nubus card, or by constructing a MIDI packet for an external synthesizer.

MusicDerivedSetInstrumentNumber

pascal ComponentResult
MusicDerivedSetInstrumentNumber(MusicComponent mc,
        long partNumber,Part *p)
        ComponentCallNow(kMusicDerivedSetInstrumentNumberSelect,8);


The component should set the specified part to the instrument number in the Part record. Note for a MIDI device that either only supports instruments from 0 to 127, or that supports one of the standard bank-switching controller messages, this call should not be needed. You would set the kGenericMusicBank0 or kGenericMusicBank32 (or both) flags, instead.

MusicDerivedSetMIDI

pascal ComponentResult
MusicDerivedSetMIDI(MusicComponent mc,
        MusicMIDISendProcPtr midiProc,long refcon,long midiChannel)
        ComponentCallNow(kMusicDerivedSetMIDISelect,12);


A derived component for a MIDI synthesizer will receive this call soon after it is opened. It should store the midiProc, refCon, and midiChannel in its globals. When the derived component needs to communicate with the synthesizer, it will call the midiProc with this refcon. The midiChannel variable specifies the "system channel" of the device.

MusicDerivedStoreInstrument

pascal ComponentResult
MusicDerivedStoreInstrument(MusicComponent mc,
        long partNumber,Part *p,long instrumentNumber)
        ComponentCallNow(kMusicDerivedStoreInstrumentSelect,12);


If the synthesizer has any user instruments, this routine will be called to store the instrument from a particular part number into one of the user instrument locations.



Sample Music Component

Download source files for a complete example software synthesizer: https://web.archive.org/web/20131223085839/http://www.srm.com/qtma/gmc/binaries/ExSy.sea.hqx


The following files constitute a driver for the Roland MT32 synthesizer. This is a fairly typical and straightforward music component, which is based upon the generic music component described in this document. <menu>

  • MT32MusicComponent.c
  • MT32MusicComponent.r

  • <GenericMusicComponent.r
  • QuickTimeMusic.h
  • MusicComponent.k.h </menu>