You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
19KB

  1. This describes the latest (version 1.1) implementation of SKINI.
  2. Synthesis toolKit Instrument Network Interface
  3. for the Synthesis Toolkit in C++ by Perry R. Cook.
  4. *********************************
  5. * Too good to be true? *
  6. * Have control and read it too? *
  7. * A SKINI Haiku. *
  8. *********************************
  9. Profound thanks to Dan trueman, Brad Garton, and
  10. Gary Scavone for input on this revision. Thanks
  11. also to MIDI, the NeXT MusicKit, ZIPI and all
  12. the creators and modifiers of these for good bases
  13. upon/from which to build and depart.
  14. 1) MIDI Compatibility
  15. SKINI was designed to be MIDI compatible wherever possible,
  16. and extend MIDI in incremental, then maybe profound ways.
  17. Differences from MIDI, and motivations, include:
  18. Text-based messages are used, with meaningful names
  19. wherever possible. This allows any language or system
  20. capable of formatted printing to generate SKINI.
  21. Similarly, any system capable of reading in a string
  22. and turning delimited fields into strings, floats,
  23. and ints can consume SKINI for control. More importantly,
  24. humans can actually read, and even write if they want,
  25. SKINI files and streams. Use an editor and search/
  26. replace or macros to change a channel or control number.
  27. Load a SKINI score into a spread sheet to apply
  28. transformations to time, control parameters, MIDI
  29. velocities, etc. Put a monkey on a special typewriter
  30. and get your next great work. Life's too short to debug
  31. bit/nybble packed variable length mumble messages. Disk
  32. space gets cheaper, available bandwidth increases, music
  33. takes up so little space and bandwidth compared to video
  34. and grapics. Live a little.
  35. Floating point numbers are used wherever possible.
  36. Note Numbers, Velocities, Controller Values, and
  37. Delta and Absolute Times are all represented and
  38. scanned as ASCII double-precision floats. MIDI byte
  39. values are preserved, so that incoming MIDI bytes
  40. from an interface can be put directly into SKINI
  41. messages. 60.0 or 60 is middle C, 127.0 or 127 is
  42. maximum velocity etc. But, unlike MIDI, 60.5 can
  43. cause a 50cent sharp middle C to be played. As with
  44. MIDI byte values like velocity, use of the integer and
  45. SKINI-added fractional parts is up to the implementor
  46. of the algorithm being controlled by SKINI messages.
  47. But the extra precision is there to be used or ignored.
  48. 2) WHY SKINI?
  49. SKINI was designed to be extensable and hackable for a number
  50. of applications: imbedded synthesis in a game or VR simulation,
  51. scoring and mixing tasks, real-time and non-real time applications
  52. which could benefit from controllable sound synthesis,
  53. JAVA controlled synthesis, or eventually maybe JAVA synthesis,
  54. etc. SKINI is not intended to be "the mother of scorefiles,"
  55. but since the entire system is based on text representations
  56. of names, floats, and ints, converters from one scorefile
  57. language to SKINI, or back, should be easily created.
  58. I am basically a bottom-up designer with an awareness of top-
  59. down design ideas, so SKINI above all reflects the needs of my
  60. particular research and creative projects as they have arisen and
  61. developed. SKINI 1.1 represents a profound advance beyond
  62. versions 0.8 and 0.9 (the first versions), future SKINI's might
  63. reflect some changes. Compatibility with prior scorefiles
  64. will be attempted, but there aren't that many scorefiles out
  65. there yet.
  66. 3) SKINI MESSAGES
  67. A basic SKINI message is a line of text. There are only three
  68. required fields, the message type (an ASCII name), the time (either
  69. delta or absolute), and the channel number. Don't freak out and
  70. think that this is MIDI channel 0-15 (which is supported), because
  71. the channel number is scanned as a long int. Channels could be socket
  72. numbers, machine IDs, serial numbers, or even unique tags for each
  73. event in a synthesis. Other fields might be used, as specified in the
  74. SKINItbl.h file. This is described in more detail later.
  75. Fields in a SKINI line are delimited by spaces, commas, or
  76. tabs. The SKINI parser only operates on a line at a time,
  77. so a newline means the message is over. Multiple messages are
  78. NOT allowed directly on a single line (by use of the ; for
  79. example in C). This could be supported, but it isn't in
  80. version 1.1.
  81. Message types include standard MIDI types like NoteOn, NoteOff,
  82. ControlChange, etc. MIDI extension message types (messages
  83. which look better than MIDI but actually get turned into
  84. MIDI-like messages) include LipTension, StringDamping, etc.
  85. NonMIDI message types include SetPath (sets a path for file
  86. use later), and OpenReadFile (for streaming, mixing, and applying
  87. effects to soundfiles along with synthesis, for example).
  88. Other non-MIDI message types include Trilling, HammerOn, etc. (these
  89. translate to gestures, behaviors, and contexts for use by
  90. intellegent players and instruments using SKINI). Where possible
  91. I will still use these as MIDI extension messages, so foot
  92. switches, etc. can be used to control them in real time.
  93. All fields other than type, time, and channel are optional, and the
  94. types and useage of the additional fields is defined in the file
  95. SKINItbl.h.
  96. The other important file used by SKINI is SKINImsg.h, which is a
  97. set of #defines to make C code more readable, and to allow reasonably
  98. quick re-mapping of control numbers, etc.. All of these defined
  99. symbols are assigned integer values. For JAVA, the #defines could
  100. be replaced by declaration and assignment statements, preserving
  101. the look and behavior of the rest of the code.
  102. 4) C Files Used To Implement SKINI
  103. Skini.cpp is an object which can either open a SKINI file, and
  104. successively read and parse lines of text as SKINI strings, or
  105. accept strings from another object and parse them. The latter
  106. functionality would be used by a socket, pipe, or other connection
  107. receiving SKINI messages a line at a time, usually in real time,
  108. but not restricted to real time.
  109. SKINImsg.h should be included by anything wanting to use the
  110. Skini.cpp object. This is not mandatory, but use of the __SK_blah_
  111. symbols which are defined in the .msg file will help to ensure
  112. clarity and consistency when messages are added and changed.
  113. SKINItbl.h is used only by the SKINI parser object (Skini.cpp).
  114. In the file SKINItbl.h, an array of structures is declared and
  115. assigned values which instruct the parser as to what the message
  116. types are, and what the fields mean for those message types.
  117. This table is compiled and linked into applications using SKINI, but
  118. could be dynamically loaded and changed in a future version of
  119. SKINI.
  120. 5) SKINI Messages and the SKINI Parser:
  121. The parser isn't all that smart, but neither am I. Here are the
  122. basic rules governing a valid SKINI message:
  123. a) If the first (non-delimiter (see c)) character in a SKINI
  124. string is '/' that line is treated as a comment and echoed
  125. to stdout.
  126. b) If there are no characters on a line, that line is treated
  127. as blank and echoed to stdout. Tabs and spaces are treated
  128. as non-characters.
  129. c) Spaces, commas, and tabs delimit the fields in a SKINI
  130. message line. (We might allow for multiple messages per
  131. line later using the semicolon, but probably not. A series
  132. of lines with deltaTimes of 0.0 denotes simultaneous events.
  133. For read-ability, multiple messages per line doesn't help much,
  134. so it's unlikely to be supported later).
  135. d) The first field must be a SKINI message name. (like NoteOn).
  136. These might become case-insensitive in future versions, so don't
  137. plan on exciting clever overloading of names (like noTeOn being
  138. different from NoTeON). There can be a number of leading
  139. spaces or tabs, but don't exceed 32 or so.
  140. e) The second field must be a time specification in seconds.
  141. A time field can be either delta-time (most common and the only one
  142. supported in version 0.8), or absolute time. Absolute time
  143. messages have an '=' appended to the beginning of the floating
  144. point number with no space. So 0.10000 means delta time of
  145. 100 ms, while =0.10000 means absolute time of 100 ms. Absolute
  146. time messages make most sense in score files, but could also be
  147. used for (loose) synchronization in a real-time context. Real
  148. time messages should be time-ordered AND time-correct. That is,
  149. if you've sent 100 total delta-time messages of 1.0 seconds, and
  150. then send an absolute time message of =90.0 seconds, or if you
  151. send two absolute time messages of =100.0 and =90.0 in that
  152. order, things will get really fouled up. The SKINI parser
  153. doesn't know about time, however. The WvOut device is the
  154. master time keeper in the Synthesis Toolkit, so it should be
  155. queried to see if absolute time messages are making sense.
  156. There's an example of how to do that later in this document.
  157. Absolute times are returned by the parser as negative numbers
  158. (since negative deltaTimes are not allowed).
  159. f) The third field must be an integer channel number. Don't go
  160. crazy and think that this is just MIDI channel 0-15 (which is
  161. supported). The channel number is scanned as a long int. Channels
  162. 0-15 are in general to be treated as MIDI channels. After that
  163. it's wide open. Channels could be socket numbers, machine IDs,
  164. serial numbers, or even unique tags for each event in a synthesis.
  165. A -1 channel can be used as don't care, omni, or other functions
  166. depending on your needs and taste.
  167. g) All remaining fields are specified in the SKINItbl.h file.
  168. In general, there are maximum two more fields, which are either
  169. SK_INT (long), SK_DBL (double float), or SK_STR (string). The
  170. latter is the mechanism by which more arguments can be specified
  171. on the line, but the object using SKINI must take that string
  172. apart (retrived by using getRemainderString()) and scan it.
  173. Any excess fields are stashed in remainderString.
  174. 6) A Short SKINI File:
  175. /* Howdy!!! Welcome to SKINI, by P. Cook 1999
  176. NoteOn 0.000082 2 55 82
  177. NoteOff 1.000000 2 55 0
  178. NoteOn 0.000082 2 69 82
  179. StringDetune 0.100000 2 10
  180. StringDetune 0.100000 2 30
  181. StringDetune 0.100000 2 50
  182. NoteOn 0.000000 2 69 82
  183. StringDetune 0.100000 2 40
  184. StringDetune 0.100000 2 22
  185. StringDetune 0.100000 2 12
  186. //
  187. StringDamping 0.000100 2 0.0
  188. NoteOn 0.000082 2 55 82
  189. NoteOn 0.200000 2 62 82
  190. NoteOn 0.100000 2 71 82
  191. NoteOn 0.200000 2 79 82
  192. NoteOff 1.000000 2 55 82
  193. NoteOff 0.000000 2 62 82
  194. NoteOff 0.000000 2 71 82
  195. NoteOff 0.000000 2 79 82
  196. StringDamping =4.000000 2 0.0
  197. NoteOn 0.000082 2 55 82
  198. NoteOn 0.200000 2 62 82
  199. NoteOn 0.100000 2 71 82
  200. NoteOn 0.200000 2 79 82
  201. NoteOff 1.000000 2 55 82
  202. NoteOff 0.000000 2 62 82
  203. NoteOff 0.000000 2 71 82
  204. NoteOff 0.000000 2 79 82
  205. 7) The SKINItbl.h File, How Messages are Parsed:
  206. The SKINItbl.h file contains an array of structures which
  207. are accessed by the parser object Skini.cpp. The struct is:
  208. struct SKINISpec { char messageString[32];
  209. long type;
  210. long data2;
  211. long data3;
  212. };
  213. so an assignment of one of these structs looks like:
  214. MessageStr$ ,type, data2, data3,
  215. type is the message type sent back from the SKINI line parser.
  216. data<n> is either
  217. NOPE : field not used, specifically, there aren't going
  218. to be any more fields on this line. So if there
  219. is is NOPE in data2, data3 won't even be checked
  220. SK_INT : byte (actually scanned as 32 bit signed long int)
  221. If it's a MIDI data field which is required to
  222. be an integer, like a controller number, it's
  223. 0-127. Otherwise) get creative with SK_INTs
  224. SK_DBL : double precision floating point. SKINI uses these
  225. in the MIDI context for note numbers with micro
  226. tuning, velocities, controller values, etc.
  227. SK_STR : only valid in final field. This allows (nearly)
  228. arbitrary message types to be supported by simply
  229. scanning the string to EndOfLine and then passing
  230. it to a more intellegent handler. For example,
  231. MIDI SYSEX (system exclusive) messages of up to
  232. 256 bytes can be read as space-delimited integers
  233. into the 1K SK_STR buffer. Longer bulk dumps,
  234. soundfiles, etc. should be handled as a new
  235. message type pointing to a FileName, Socket, or
  236. something else stored in the SK_STR field, or
  237. as a new type of multi-line message.
  238. Here's a couple of lines from the SKINItbl.h file
  239. {"NoteOff" , __SK_NoteOff_, SK_DBL, SK_DBL},
  240. {"NoteOn" , __SK_NoteOn_, SK_DBL, SK_DBL},
  241. {"ControlChange" , __SK_ControlChange_, SK_INT, SK_DBL},
  242. {"Volume" , __SK_ControlChange_, __SK_Volume_ , SK_DBL},
  243. {"StringDamping" , __SK_ControlChange_, __SK_StringDamping_, SK_DBL},
  244. {"StringDetune" , __SK_ControlChange_, __SK_StringDetune_, SK_DBL},
  245. The first three are basic MIDI messages. The first two would cause the
  246. parser, after recognizing a match of the string "NoteOff" or "NoteOn",
  247. to set the message type to 128 or 144 (__SK_NoteOff_ and __SK_NoteOn_
  248. are #defined in the file SKINImsg.h to be the MIDI byte value, without
  249. channel, of the actual MIDI messages for NoteOn and NoteOff). The parser
  250. would then set the time or delta time (this is always done and is
  251. therefore not described in the SKINI Message Struct). The next two
  252. fields would be scanned as double-precision floats and assigned to
  253. the byteTwo and byteThree variables of the SKINI parser. The remainder
  254. of the line is stashed in the remainderString variable.
  255. The ControlChange spec is basically the same as NoteOn and NoteOff, but
  256. the second data byte is set to an integer (for checking later as to
  257. what MIDI control is being changed).
  258. The Volume spec is a MIDI Extension message, which behaves like a
  259. ControlChange message with the controller number set explicitly to
  260. the value for MIDI Volume (7). Thus the following two lines would
  261. accomplish the same changing of MIDI volume on channel 2:
  262. ControlChange 0.000000 2 7 64.1
  263. Volume 0.000000 2 64.1
  264. I like the 2nd line better, thus my motivation for SKINI in the first
  265. place.
  266. The StringDamping and StringDetune messages behave the same as
  267. the Volume message, but use Control Numbers which aren't specifically
  268. nailed-down in MIDI. Note that these Control Numbers are carried
  269. around as long ints, so we're not limited to 0-127. If, however,
  270. you want to use a MIDI controller to play an instrument, using
  271. controller numbers in the 0-127 range might make sense.
  272. 8) Objects using SKINI
  273. Here's a simple example of code which uses the Skini object
  274. to read a SKINI file and control a single instrument.
  275. Skini score;
  276. Skini::Message message;
  277. instrument = new Mandolin(50.0);
  278. score.setFile( argv[1] );
  279. while ( score.nextMessage( message ) != 0 ) {
  280. tempDouble = message.time;
  281. if (tempDouble < 0) {
  282. tempDouble = - tempDouble;
  283. tempDouble = tempDouble - output.getTime();
  284. if (tempDouble < 0) {
  285. printf("Bad News Here!!! Backward Absolute Time Required.\n");
  286. tempDouble = 0.0;
  287. }
  288. }
  289. tempLong = (long) ( tempDouble * Stk::sampleRate() );
  290. for ( i=0; i<tempLong; i++ ) {
  291. output.tick( instrument->tick() );
  292. }
  293. tempDouble3 = message.floatValues[1] * NORM_MIDI;
  294. if ( message.type == __SK_NoteOn_ ) {
  295. if ( tempDouble3 == 0.0 ) {
  296. tempDouble3 = 0.5;
  297. instrument->noteOff( tempDouble3 );
  298. }
  299. else {
  300. tempLong = message.intValues[0];
  301. tempDouble2 = Midi2Pitch[tempLong];
  302. instrument->noteOn( tempDouble2, tempDouble3 );
  303. }
  304. }
  305. else if ( message.type == __SK_NoteOff_ ) {
  306. instrument->noteOff( tempDouble3 );
  307. }
  308. else if ( message.type == __SK_ControlChange_ ) {
  309. tempLong = message.intValues[0];
  310. instrument->controlChange( tempLong, tempDouble3 );
  311. }
  312. }
  313. When a SKINI score is passed to a Skini object using the
  314. Skini::setFile() function, valid messages are read from
  315. the file and returned using the Skini::nextMessage() function.
  316. A Skini::Message structure contains all the information parsed
  317. from a single SKINI message. A returned message type of zero
  318. indicates either an invalid message or the end of a scorefile.
  319. The "time" member of a Skini::Message is the deltaTime until the
  320. current message should occur. If this is greater than 0,
  321. synthesis occurs until the deltaTime has elapsed. If deltaTime is
  322. less than zero, the time is interpreted as absolute time and the
  323. output device is queried as to what time it is now. That is used
  324. to form a deltaTime, and if it's positive we synthesize. If it's
  325. negative, we print an error, pretend this never happened and we
  326. hang around hoping to eventually catch up.
  327. The rest of the code sorts out message types NoteOn, NoteOff
  328. (including NoteOn with velocity 0), and ControlChange. The
  329. code implicitly takes into account the integer type of the
  330. control number, but all other data is treated as double float.