Example 1: Playing a Format 1 MIDI file.
#include <audio.h>
#include <event.h> /* control pad */
#include <Juggler.h> /* Juggler */
#include <operror.h>
#include <score.h> /* score player */
#include <stdio.h>
#define VERSION "V24.0"
#define PRT(x) { printf x; }
#define ERR(x) PRT(x)
#define DBUG(x) /* PRT(x) */
/*****************************************************************/
/* Macro to simplify error checking. */
#define CHECKRESULT(val,name) \
if (val < 0) \
{ \
Result = val; \
PrintError(0,"\failure in",name,Result); \
goto cleanup; \
}
#define MAXPROGRAMNUM (128) /* Save memory by lowering this to highest
program number. */
#if 1
#define MAX_SCORE_VOICES (8)
#define MIXER_NAME ("mixer8x2.dsp")
#else
#define MAX_SCORE_VOICES (12)
#define MIXER_NAME ("mixer12x2.dsp")
#endif
/*
Raise this value till the sound clips then drop it till you feel safe.
(MAXDSPAMPLITUDE/MAX_SCORE_VOICES) is guaranteed safe if all you are
doing is playing scores
*/
#define MIXER_AMPLITUDE (MAXDSPAMPLITUDE/MAX_SCORE_VOICES)*2
/* Prototypes */
ScoreContext *pmfSetupScoreContext( char *mapfile );
Jugglee *pmfLoadScore( ScoreContext *scon, char *scorefile );
Err pmfPlayScore( Jugglee *JglPtr, uint32 NumReps );
Err pmfCleanupScoreContext( ScoreContext *scon );
Err pmfUnloadScore( ScoreContext *scon, Jugglee *CollectionPtr );
Err pmfPlayScoreMute ( Jugglee *JglPtr, uint32 NumReps );
Err PlayMIDIFile( char *scorefile, char *mapfile, int32 NumReps);
/*****************************************************************/
int main (int argc, char *argv[])
{
/*****************************************************************/
int32 Result=0;
PRT(("Play MIDI File, %s\n", VERSION));
if (argc < 3) {
PRT(("Usage: %s <MIDI file> <PIMap file> [<num repeats>]\n",
argv[0]));
return 0;
}
/* Initialize the EventBroker. */
Result = InitEventUtility(1, 0, LC_ISFOCUSED);
if (Result < 0)
{
PrintError(0,"init event utility",0,Result);
goto cleanup;
}
/* OpenMathFolio to get MathBase */
Result = OpenMathFolio();
if (Result < 0)
{
PrintError(0,"open math folio",0,Result);
ERR(("Did you run grafmath?\n"));
return(-1);
}
/* Initialize audio, return if error. */
if (OpenAudioFolio())
{
ERR(("Audio Folio could not be opened!\n"));
return(-1);
}
/* Required before playing scores. */
InitJuggler();
/* Play MIDI file */
Result = PlayMIDIFile (argv[1], argv[2], (argc > 3) ? atoi(argv[3]) : 1);
CHECKRESULT( Result, "PlayMIDIFile" );
cleanup:
TermJuggler();
CloseAudioFolio();
KillEventUtility();
PRT(("%s finished.\n", argv[0]));
return (int) Result;
}
/******************************************************************
** Create a ScoreContext, load a MIDIFile and play it.
******************************************************************/
Err PlayMIDIFile( char *scorefile, char *mapfile, int32 NumReps)
{
Jugglee *CollectionPtr;
int32 Result;
ScoreContext *scon;
Result = -1;
CollectionPtr = NULL;
scon = pmfSetupScoreContext( mapfile );
if( scon == NULL ) goto cleanup;
CollectionPtr = pmfLoadScore( scon, scorefile );
if( CollectionPtr == NULL) goto cleanup;
/*
** Play the score collection. Alternatively, you could use the
** pmfPlayScoreMute function instead to mute channels.
*/
Result = pmfPlayScore( CollectionPtr, NumReps );
cleanup:
pmfUnloadScore( scon, CollectionPtr );
pmfCleanupScoreContext( scon );
return Result;
}
/******************************************************************
** Create a ScoreContext, and load a PIMap from a text file.
******************************************************************/
ScoreContext *pmfSetupScoreContext( char *mapfile )
{
ScoreContext *scon;
int32 Result;
/* Create a context for interpreting a MIDI score and tracking notes. */
scon = CreateScoreContext( MAXPROGRAMNUM );
if( scon == NULL )
{
return NULL;
}
/* Specify a mixer to use for the score voices. */
Result = InitScoreMixer( scon, MIXER_NAME, MAX_SCORE_VOICES, =
MIXER_AMPLITUDE );
CHECKRESULT(Result,"InitScoreMixer");
/* Load Instrument Templates from disk and fill Program Instrument Map. */
/* As an alternative, you could use SetPIMapEntry() */
Result = LoadPIMap ( scon, mapfile );
CHECKRESULT(Result, "LoadPIMap");
return scon;
cleanup:
pmfCleanupScoreContext( scon );
return NULL;
}
/******************************************************************
** Scale the timestamps in a Juggler Sequence.
******************************************************************/
static int32 pmfScaleSequenceTimes ( Sequence *Self, frac16 Scalar )
{
Time *EventPtr;
int32 Indx;
int32 EventSize, Many;
/* Get info from Sequence using Tags. */
{
TagArg Tags[4];
Tags[0].ta_Tag = JGLR_TAG_MANY;
Tags[1].ta_Tag = JGLR_TAG_EVENTS;
Tags[2].ta_Tag = JGLR_TAG_EVENT_SIZE;
Tags[3].ta_Tag = TAG_END;
GetObjectInfo( Self , Tags );
Many = (int32) Tags[0].ta_Arg;
EventPtr = (Time *) Tags[1].ta_Arg;
EventSize = (int32) Tags[2].ta_Arg;
}
DBUG(("pmfScaleSequenceTimes: Many = %d, EventPtr = %d, EventSize = %d\n",
Many, EventPtr, EventSize ));
/* Scan through array and scale each timestamp. */
Indx = 0;
while (Indx < Many)
{
/* Scale time stamp. */
*EventPtr = MulSF16( (*EventPtr), Scalar );
/* Index to next Event */
EventPtr = (Time *) (((char *)EventPtr) + EventSize);
Indx++;
}
return 0;
}
/******************************************************************
** Create a collection and load a score into it from a MIDI File.
******************************************************************/
Jugglee *pmfLoadScore( ScoreContext *scon, char *scorefile )
{
MIDIFileParser MFParser;
Jugglee *CollectionPtr;
int32 Result;
frac16 OriginalRate;
frac16 Scalar;
int32 i, Many;
Jugglee *Seq;
/* Create a collection and load MIDI File into it. */
CollectionPtr = (Jugglee *) CreateObject( &CollectionClass );
if (CollectionPtr == NULL)
{
ERR(("pmfLoadScore: Failure to create Collection\n"));
goto cleanup;
}
/* Set context used to play collection. */
{
TagArg Tags[2];
Tags[0].ta_Tag = JGLR_TAG_CONTEXT;
Tags[0].ta_Arg = (TagData) scon;
Tags[1].ta_Tag = TAG_END;
SetObjectInfo(CollectionPtr, Tags);
}
/* Load it from a MIDIFile */
Result = MFLoadCollection ( &MFParser, scorefile ,
(Collection *) CollectionPtr);
if (Result)
{
ERR(("Error loading MIDI File = $%x\n", Result));
goto cleanup;
}
/* Query how many sequences it contains. */
{
TagArg Tags[2];
Tags[0].ta_Tag = JGLR_TAG_MANY;
Tags[1].ta_Tag = TAG_END;
GetObjectInfo(CollectionPtr, Tags);
Many = (int32) Tags[0].ta_Arg;
}
PRT(("Collection contains %d sequences.\n", Many));
/* Don't change the audio clock because that might interfere with other tasks. */
OriginalRate = GetAudioRate();
Scalar = DivSF16( OriginalRate, MFParser.mfp_Rate );
PRT(("Scale sequence times. mfp_Rate = 0x%x, Scalar = 0x%x\n",
MFParser.mfp_Rate, Scalar));
for(i=0; i<Many; i++)
{
Result = GetNthFromObject( CollectionPtr, i, &Seq );
CHECKRESULT(Result, "GetNthFromCollection");
/* WARNING - assumes contents of Collection are sequences. */
Result = pmfScaleSequenceTimes( (Sequence *)Seq, Scalar );
CHECKRESULT(Result, "ScaleSequenceTimes");
}
return CollectionPtr;
cleanup:
pmfUnloadScore( scon, CollectionPtr );
return NULL;
}
/******************************************************************
** Unload the collection which frees the sequences, then
** destroy collection.
******************************************************************/
Err pmfUnloadScore( ScoreContext *scon, Jugglee *CollectionPtr )
{
if (CollectionPtr != NULL)
{
MFUnloadCollection( (Collection *) CollectionPtr );
DestroyObject( (COBObject *) CollectionPtr );
}
return 0;
}
/******************************************************************
** Unload the PIMap which frees the instruments and samples, then
** delete the ScoreContext.
******************************************************************/
Err pmfCleanupScoreContext( ScoreContext *scon )
{
if( scon != NULL )
{
UnloadPIMap( scon );
TermScoreMixer( scon );
DeleteScoreContext( scon );
}
return 0;
}
/*****************************************************************
** Play a Collection, a Sequence or any other Juggler object.
** This routine is likely to be carved up for use in your application.
*****************************************************************/
int32 pmfPlayScore( Jugglee *JglPtr, uint32 NumReps )
{
AudioTime CurTime, NextTime;
int32 Result;
int32 NextSignals, CueSignal, SignalsGot;
Item MyCue;
uint32 Joy;
int32 QuitNow;
int32 IfTimerPending;
ControlPadEventData cped;
QuitNow = 0;
NextSignals=0;
IfTimerPending = FALSE;
/* Create a Cue for timing the score. */
MyCue = CreateCue( NULL );
CHECKRESULT(MyCue, "CreateCue");
CueSignal = GetCueSignal ( MyCue );
/* Drive Juggler using Audio timer. */
/* Delay start by adding ticks to avoid stutter on startup. Optional. */
NextTime = GetAudioTime() + 40;
CurTime = NextTime;
DBUG(("pmfPlayScore: Start at time %d\n", NextTime ));
/* This tells the Juggler to process this object when BumpJuggler() is
** later called. Multiple objects could be started and will be
** juggled by Juggler.
*/
StartObject ( JglPtr , NextTime, NumReps, NULL );
do
{
/* Read current state of Control Pad. */
Result = GetControlPad (1, FALSE, &cped);
if (Result < 0) {
PrintError(0,"read control pad in","pmfPlayScore",Result);
}
Joy = cped.cped_ButtonBits;
/* Forced Quit by hitting ControlX */
if(Joy & ControlX)
{
StopObject( JglPtr, CurTime );
QuitNow = TRUE;
}
else
{
/* Request a timer wake up at the next event time. */
if( !IfTimerPending )
{
Result = SignalAtTime( MyCue, NextTime);
if (Result < 0) return Result;
IfTimerPending = TRUE;
}
/* Wait for timer signal or signal(s) from BumpJuggler */
SignalsGot = WaitSignal( CueSignal | NextSignals );
/* Did we wake up because of a timer signal? */
if( SignalsGot & CueSignal )
{
IfTimerPending = FALSE;
CurTime = NextTime;
}
else
{
/* Get current time to inform Juggler. */
CurTime = GetAudioTime();
}
/* Tell Juggler to process any pending events, eg. Notes. Result > 0 if done. */
Result = BumpJuggler( CurTime, &NextTime, SignalsGot, &NextSignals );
}
} while ( (Result == 0) && (QuitNow == 0));
DeleteCue( MyCue );
cleanup:
return Result;
}
/************** YOU MAY IGNORE THE FOLLOWING CODE ****************
** It was written to illustrate muting and pause functions and is
** not currently called from the above code.
*****************************************************************/
/*****************************************************************
** Set the Mute flag for the Nth child of a collection.
*****************************************************************/
int32 MuteNthChild( Jugglee *JglPtr, int32 SeqNum, int32 IfMute )
{
Jugglee *Child;
int32 Result;
DBUG(("Set mute of #%d to %d\n", SeqNum, IfMute));
Result = GetNthFromObject( JglPtr, SeqNum, &Child);
if(Result < 0)
{
ERR(("Jugglee has no %dth child.\n", SeqNum));
return 0;
}
{
TagArg Tags[2];
Tags[0].ta_Tag = JGLR_TAG_MUTE;
Tags[0].ta_Arg = (TagData)IfMute;
Tags[1].ta_Tag = TAG_END;
Result = SetObjectInfo(Child, Tags);
}
return Result;
}
/*****************************************************************
** Play a Collection, a Sequence or any other Juggler object.
** Fancy version incorporates Muting and Pause tests.
*****************************************************************/
Err pmfPlayScoreMute ( Jugglee *JglPtr, uint32 NumReps )
{
AudioTime CurTime, NextTime, PauseTime, ResumeTime, DeltaTime;
int32 Result;
int32 NextSignals, CueSignal, SignalsGot;
Item MyCue;
uint32 Joy;
int32 QuitNow, i;
int32 Muted[8]; /* Keep track of which ones are muted. */
ControlPadEventData cped;
for( i=0; i<8; i++) Muted[i] = 0;
QuitNow = 0;
NextSignals=0;
MyCue = CreateItem ( MKNODEID(AUDIONODE,AUDIO_CUE_NODE), NULL );
CHECKRESULT(MyCue, "CreateItem Cue");
CueSignal = GetCueSignal ( MyCue );
/* Drive Juggler using Audio timer. */
/* Delay start by 1/2 second by adding to avoid stutter on startup. */
NextTime = GetAudioTime() + 120;
CurTime = NextTime;
DeltaTime = 0;
DBUG(("pmfPlayScoreFancy: Start at time %d\n", NextTime ));
StartObject ( JglPtr , NextTime, NumReps, NULL );
do
{
/* Use Control Pad to experiment with Muting (optional). */
/* Read current state of Control Pad. */
Result = GetControlPad (1, FALSE, &cped);
if (Result < 0) {
ERR(("Error in GetControlPad\n"));
PrintError(0,"read control pad in","pmfPlayScoreMute",Result);
}
Joy = cped.cped_ButtonBits;
/* Define a macro to simplify code. */
#define TestMute(Mask,SeqNum) \
if((Joy & Mask) && !Muted[SeqNum]) \
{ \
MuteNthChild( JglPtr, SeqNum, TRUE); \
Muted[SeqNum] = TRUE; \
} \
else if( !(Joy & Mask) && Muted[SeqNum] ) \
{ \
MuteNthChild( JglPtr, SeqNum, FALSE); \
Muted[SeqNum] = FALSE; \
}
TestMute(ControlA, 0);
TestMute(ControlB, 1);
TestMute(ControlC, 2);
TestMute(ControlUp, 3);
TestMute(ControlRight, 4);
TestMute(ControlDown, 5);
TestMute(ControlLeft, 6);
if(Joy & ControlLeftShift)
{
PRT(("Time = 0x%x\n", GetAudioTime() ));
}
/* Pause by slipping Juggler time against clock time. */
if(Joy & ControlRightShift)
{
PauseTime = GetAudioTime();
PRT(("Pause at %d\n", PauseTime ));
do
{
Result = GetControlPad (1, TRUE, &cped);
if (Result < 0) {
PrintError(0,"read control pad in","pmfPlayScoreMute",Result);
}
} while( (cped.cped_ButtonBits & ControlRightShift) == 0);
ResumeTime = GetAudioTime();
PRT(("Resume at %d\n", ResumeTime ));
DeltaTime += (ResumeTime - PauseTime);
GetControlPad (1, TRUE, &cped);
}
if(Joy & ControlX) /* Forced Quit */
{
StopObject( JglPtr, CurTime );
QuitNow = TRUE;
}
else
{
/* Request a timer wake up at the next event time. */
Result = SignalAtTime( MyCue, NextTime + DeltaTime );
if (Result < 0) return Result;
/* Wait for timer signal or signal(s) from BumpJuggler */
SignalsGot = WaitSignal( CueSignal | NextSignals );
/* Sleeping now until we get signalled. */
if (SignalsGot & CueSignal)
{
CurTime = NextTime;
}
else
{
CurTime = GetAudioTime() - DeltaTime;
}
/* Tell Juggler to do its thing. Result > 0 if done. */
Result = BumpJuggler( CurTime, &NextTime, SignalsGot, &NextSignals );
}
} while ( (Result == 0) && (QuitNow == 0));
DeleteItem( MyCue );
cleanup:
return Result;
}