Home Information Classes Download Usage Mail List Requirements Links Tutorial
Too good to be true?
Have control and read it too?
A SKINI haiku.
Profound thanks to Dan trueman, Brad Garton, and Gary Scavone for input on this revision. Thanks also to MIDI, the NeXT MusicKit, ZIPI and all the creators and modifiers of these for good bases upon/from which to build and depart.
Differences from MIDI, and motivations, include:
I am basically a bottom-up designer with an awareness of top-down design ideas, so SKINI above all reflects the needs of my particular research and creative projects as they have arisen and developed. SKINI 1.1 represents a profound advance beyond versions 0.8 and 0.9 (the first versions), future SKINI's might reflect some changes. Compatibility with prior scorefiles will be attempted, but there aren't that many scorefiles out there yet.
Fields in a SKINI line are delimited by spaces, commas, or tabs. The SKINI parser only operates on a line at a time, so a newline means the message is over. Multiple messages are NOT allowed directly on a single line (by use of the ; for example in C). This could be supported, but it isn't in version 1.1.
Message types include standard MIDI types like NoteOn, NoteOff, ControlChange, etc. MIDI extension message types (messages which look better than MIDI but actually get turned into MIDI-like messages) include LipTension, StringDamping, etc. Non-MIDI message types include SetPath (sets a path for file use later), and OpenReadFile (for streaming, mixing, and applying effects to soundfiles along with synthesis, for example). Other non-MIDI message types include Trilling, HammerOn, etc. (these translate to gestures, behaviors, and contexts for use by intellegent players and instruments using SKINI). Where possible I will still use these as MIDI extension messages, so foot switches, etc. can be used to control them in real time.
All fields other than type, time, and channel are optional, and the types and useage of the additional fields is defined in the file SKINI.tbl.
The other important file used by SKINI is SKINI.msg, which is a set of #defines to make C code more readable, and to allow reasonably quick re-mapping of control numbers, etc.. All of these defined symbols are assigned integer values. For Java, the #defines could be replaced by declaration and assignment statements, preserving the look and behavior of the rest of the code.
SKINI.msg should be included by anything wanting to use the Skini.cpp object. This is not mandatory, but use of the __SK_blah_ symbols which are defined in the .msg file will help to ensure clarity and consistency when messages are added and changed.
SKINI.tbl is used only by the SKINI parser object (Skini.cpp). In the file SKINI.tbl, an array of structures is declared and assigned values which instruct the parser as to what the message types are, and what the fields mean for those message types. This table is compiled and linked into applications using SKINI, but could be dynamically loaded and changed in a future version of SKINI.
/* Howdy!!! Welcome to SKINI, by P. Cook 1999 NoteOn 0.000082 2 55 82 NoteOff 1.000000 2 55 0 NoteOn 0.000082 2 69 82 StringDetune 0.100000 2 10 StringDetune 0.100000 2 30 StringDetune 0.100000 2 50 NoteOn 0.000000 2 69 82 StringDetune 0.100000 2 40 StringDetune 0.100000 2 22 StringDetune 0.100000 2 12 // StringDamping 0.000100 2 0.0 NoteOn 0.000082 2 55 82 NoteOn 0.200000 2 62 82 NoteOn 0.100000 2 71 82 NoteOn 0.200000 2 79 82 NoteOff 1.000000 2 55 82 NoteOff 0.000000 2 62 82 NoteOff 0.000000 2 71 82 NoteOff 0.000000 2 79 82 StringDamping =4.000000 2 0.0 NoteOn 0.000082 2 55 82 NoteOn 0.200000 2 62 82 NoteOn 0.100000 2 71 82 NoteOn 0.200000 2 79 82 NoteOff 1.000000 2 55 82 NoteOff 0.000000 2 62 82 NoteOff 0.000000 2 71 82 NoteOff 0.000000 2 79 82
struct SKINISpec { char messageString[32]; long type; long data2; long data3; };
so an assignment of one of these structs looks like:
MessageStr$ ,type, data2, data3,
type
is the message type sent back from the SKINI line parser.
data<n>
is either:
Here's a couple of lines from the SKINI.tbl file
{"NoteOff" , __SK_NoteOff_, SK_DBL, SK_DBL}, {"NoteOn" , __SK_NoteOn_, SK_DBL, SK_DBL}, {"ControlChange" , __SK_ControlChange_, SK_INT, SK_DBL}, {"Volume" , __SK_ControlChange_, __SK_Volume_ , SK_DBL}, {"StringDamping" , __SK_ControlChange_, __SK_StringDamping_, SK_DBL}, {"StringDetune" , __SK_ControlChange_, __SK_StringDetune_, SK_DBL},
The first three are basic MIDI messages. The first two would cause the parser, after recognizing a match of the string "NoteOff" or "NoteOn", to set the message type to 128 or 144 (__SK_NoteOff_ and __SK_NoteOn_ are #defined in the file SKINI.msg to be the MIDI byte value, without channel, of the actual MIDI messages for NoteOn and NoteOff). The parser would then set the time or delta time (this is always done and is therefore not described in the SKINI Message Struct). The next two fields would be scanned as double-precision floats and assigned to the byteTwo and byteThree variables of the SKINI parser. The remainder of the line is stashed in the remainderString variable.
The ControlChange spec is basically the same as NoteOn and NoteOff, but the second data byte is set to an integer (for checking later as to what MIDI control is being changed).
The Volume spec is a MIDI Extension message, which behaves like a ControlChange message with the controller number set explicitly to the value for MIDI Volume (7). Thus the following two lines would accomplish the same changing of MIDI volume on channel 2:
ControlChange 0.000000 2 7 64.1 Volume 0.000000 2 64.1
I like the 2nd line better, thus my motivation for SKINI in the first place.
The StringDamping and StringDetune messages behave the same as the Volume message, but use Control Numbers which aren't specifically nailed-down in MIDI. Note that these Control Numbers are carried around as long ints, so we're not limited to 0-127. If, however, you want to use a MIDI controller to play an instrument, using controller numbers in the 0-127 range might make sense.
Skini score; Skini::Message message; instrument = new Mandolin(50.0); score.setFile( argv[1] ); while ( score.nextMessage( message ) != 0 ) { tempDouble = message.time; if (tempDouble < 0) { tempDouble = - tempDouble; tempDouble = tempDouble - output.getTime(); if (tempDouble < 0) { printf("Bad News Here!!! Backward Absolute Time Required.\n"); tempDouble = 0.0; } } tempLong = (long) ( tempDouble * Stk::sampleRate() ); for ( i=0; i<tempLong; i++ ) { output.tick( instrument->tick() ); } tempDouble3 = message.floatValues[1] * NORM_MIDI; if ( message.type == __SK_NoteOn_ ) { if ( tempDouble3 == 0.0 ) { tempDouble3 = 0.5; instrument->noteOff( tempDouble3 ); } else { tempLong = message.intValues[0]; tempDouble2 = Midi2Pitch[tempLong]; instrument->noteOn( tempDouble2, tempDouble3 ); } } else if ( message.type == __SK_NoteOff_ ) { instrument->noteOff( tempDouble3 ); } else if ( message.type == __SK_ControlChange_ ) { tempLong = message.intValues[0]; instrument->controlChange( tempLong, tempDouble3 ); } }
When a SKINI score is passed to a Skini object using the Skini::setFile() function, valid messages are read from the file and returned using the Skini::nextMessage() function.
A Skini::Message structure contains all the information parsed from a single SKINI message. A returned message type of zero indicates either an invalid message or the end of a scorefile.
The "time" member of a Skini::Message is the deltaTime until the current message should occur. If this is greater than 0, synthesis occurs until the deltaTime has elapsed. If deltaTime is less than zero, the time is interpreted as absolute time and the output device is queried as to what time it is now. That is used to form a deltaTime, and if it's positive we synthesize. If it's negative, we print an error, pretend this never happened and we hang around hoping to eventually catch up.
The rest of the code sorts out message types NoteOn, NoteOff (including NoteOn with velocity 0), and ControlChange. The code implicitly takes into account the integer type of the control number, but all other data is treated as double float.
The Synthesis ToolKit in C++ (STK) |
©1995-2004 Perry R. Cook and Gary P. Scavone. All Rights Reserved. |