tjt@cbnewsh.att.com (timothy.j.thompson) (11/26/90)
(This continues a conversation that began several weeks ago.) In article <98@generic.UUCP>, taob@pnet91.cts.com (Brian Tao) writes: > tjt@cbnewsh.att.com (timothy.j.thompson) writes: > >Rather than aim for that ultimately impossible goal, why not make > >the sequencer programmable > the chance. When you say "programmable", do mean hooks and entry points into > the application for various "modules"? That's one way to do it, demonstrated by the Soundscape and Bars&Pipes software for the Amiga. The way I prefer is to embed a music-specific programming language within the sequencer. This makes it easier for non-programmers to use it (since a separate compiler isn't required, and because an application-specific language can make it easier to learn). It also makes it more portable and easier for people to share things, since the same language can be provided on different machines. An example of this approach is Cakewalk 3.0, which has a language named CAL that allows you to write editing transformations that can be invoked from within their sequencer. My preference is to make the language a much more tightly integrated part of the sequencer, so that users can create new features that are completely equivalent to the 'normal' features of the sequencer (e.g. being able to combine mouse action, graphics, console input and MIDI I/O). In fact, if the language is suitable enough, you can even use it to implement all of the 'normal' features of the sequencer. Besides being extremely convenient for development, this allows users to change existing features of the sequencer to suit their own taste. As you might guess, I've implemented such a system, call Keynote. (See Computing Systems, Volume 3 Number 2, Spring 1990, for a description.) It takes a bit of up-front effort to build such a language, but I can testify to the fact that it's a huge win after you do it (right). As an example of what you can do with it, I've enclosed below the complete Keynote code that implements a graphical drum-pattern editor (which is quite different from the piano-roll-sequencer-style-editor that Keynote was originally developed to support). The initial program was written in under 100 lines of code, and after a few convenience features it reached 150 or so. ...Tim Thompson...AT&T Bell Labs/Holmdel/NJ...tjt@twitch.att.com... ==================================================================== # This code implements a drum-pattern editor. To specify the MIDI # channel and pitches of your drum machine, create a file that looks like: # # chan 10 # bass 35 # snare 38 # closed_hat 42 # open_hat 46 # # Then, invoke: # # kboom(nsteps,filename) # # where nsteps is the number of steps you want in the drum pattern. # The step size is 1b/8 (a 32nd) by default, but you can set it (Stepsize) # to whatever you want. # # The screen will show a matrix (drums on vertical axis, beats on # horizontal axis). The drum pattern will be playing constantly - # click mouse button 1 wherever you want to add/delete drum hits. # Press 's' to start and stop the pattern, 'q' to quit. Nsteps = 0 Stepsize = 1b/8 function kboom(nsteps,mapfile) { readdrums(mapfile) arrayinit(Dbeat) Nsteps = nsteps for ( n=0; n<Nsteps; n++ ) Dbeat[n] = '' Showstart = Showlow = Showbar = 0 Showhigh = 128 Kbcount = -1 Kbon = 1 Kbxinc = (Nsteps*Stepsize)/50 Kbxinit = Kbxinc * 12 Kbyinc = Showhigh / (Kbdrums+1) Showleng = Kbxinit + Nsteps * Stepsize Grid = '' # set up actions for triggering drums and user interaction sched(Dbeat[0],0) # scheduled only once sched(kbtrig,Stepsize/2,Stepsize) # repeatedly scheduled temposet(1) # allows tempo control from console action(BUTTON1DOWN,kbmouse) interrupt(kbcmd,CONSOLE) print "Press 's' to toggle sound, 'q' to quit." kbdrawall() realtime() # realtime loop during which everything happens # Construct a single phrase containing completed pattern. Kbpattern = '' for ( n=0; n<Nsteps; n++ ) { ph = Dbeat[n] ph.time = n * Stepsize Kbpattern |= ph } Kbpattern.length = Nsteps * Stepsize print "Result is in Kbpattern." } function kbcmd(c) { # handle keyboard commands if ( c == "q" ) stop() else if ( c == "s" ) { # toggle sound Kbon = 1 - Kbon if ( Kbon ) Kbcount = -1 # reset to beginning of pattern } } function drawbeat0() { # draw flashing marker on beat 0 y = kbystart(0)+1 draw(kbxstart(0)+1,y,kbxstart(1)-1,y,XOR) } function kbtrig(b) { # repeatedly executed every Stepsize beats if ( Kbon ) { b = (Kbcount++)%Nsteps sched(Dbeat[b],Now+Stepsize/2) if ( b == 0 ) { # make beat 0 obvious with a flashing line sched(drawbeat0,Now+3*Stepsize/8) sched(drawbeat0,Now+11*Stepsize/8) } } } function kbxstart(n) { # return x value of beat n in matrix display return Kbxinit+n*Stepsize; } function kbystart(n) { # return y value of drum n in matrix display return Showhigh-(n+1)*Kbyinc; } function kbmouse() { # handle mouse button presses, adding/deleting drums kb = kd = -1 # figure out which beat (b) and drum (d) is being chosen for ( b=0; b<Nsteps; b++ ) { if ( Mouseclick > kbxstart(b) && Mouseclick < (kbxstart(b+1))){ kb = b; break; } } for ( d=0; d<Kbdrums; d++ ) { if ( Mousepitch < kbystart(d) && Mousepitch > (kbystart(d+1))){ kd = d; break; } } if ( kb < 0 || kd < 0 ) return; if ( Kbdrumnote[kd] in Dbeat[kb] ) Dbeat[kb] -= Kbdrumnote[kd] # if already there, delete it else Dbeat[kb] |= Kbdrumnote[kd] # if not there, add it # draw (or undraw) an X in the specified box draw(kbxstart(kb),kbystart(kd),kbxstart(kb+1),kbystart(kd+1),XOR) draw(kbxstart(kb),kbystart(kd+1),kbxstart(kb+1),kbystart(kd),XOR) } function kbdrawall() { # draw matrix and labels redraw() # clear screen for ( b=0; b<Nsteps; b++ ) { x = kbxstart(b) draw(x,Showlow,x,Showhigh) # draw line # show beat number at top draw(string(b+1),x+3*Stepsize/8,Showhigh-Kbyinc/2) } for ( d=0; d<Kbdrums; d++ ) { y = kbystart(d) draw(0,y,Showleng,y) # draw line # show drum label on left draw(Kbdrum[d],Kbxinc,y-5*Kbyinc/8) } } function readdrums(mapfile, arr) { # read a drum map file arrayinit(arr) Kbdrums = 0 Kbchan = 1 while ( read ln < mapfile ) { split(ln,arr) if ( arr[0] == "chan" ) { Kbchan = 0 + arr[1] continue } Kbdrum[Kbdrums] = arr[0] Kbdrumnote[Kbdrums] = makenote(0+arr[1],Stepsize,'a'.vol,Kbchan) Kbdrums++ } close mapfile }