CL, Music and SLIME Tutorials
Sunday, May 1, 2005
I was listening to Luke Gorrie's presentation at ECLM the other night (both the actual presentation in Emacs Lisp and the bootleg video are available off of Edi Weitz's ECLM page). In it, he mentioned that SLIME was being used in a music course at Stanford University. I googled for that and found out that Fernando Lopez-Lezcano, a Lecturer, Composer and System Administrator at Stanford University is teaching "220b: Synthesis Techniques, Compositional Algorithms, Psychoacoustics and Spatial Processing" at Stanford. The course sounds really interesting, being a mixture of music and Lisp:
"This is the second course in the 220 series. It covers some basic sound synthesis techniques not covered last quarter (see 220a), algorithmic composition techniques, spatialization and some psychoacoustics. The course uses the CLM-3 (Common Lisp Music) environment to create all sound examples and the Common Lisp programming language (in which clm-3 itself is implemented) for all programming examples.Although the course itself looks pretty neat, there is also some interesting material linked off of the course description page. In addition to using CLM in the course, Fernando recommends using Emacs and SLIME. Now, for CL afficionados, the combination of Emacs and SLIME is pretty much the de facto Common Lisp development environment (definitely so for open source CL's, but also very popular with users of the major commercial CL products as well); however, it is not an easy combination for CL newbies to get into. One of the nice things about SLIME is that it has so much neat functionality. One of the "not so nice" things about SLIME (at least, from a newbie standpoint) is that the user manual is more of a reference than a way to learn SLIME. However, as part of the first assignment in the course, there is a SLIME tutorial that demonstrates some of the basic features of using SLIME! Here are some snippets from the tutorial:
Common Lisp Music (CLM) is a public domain sound design language written on top of Common Lisp, currently running in Macintosh PowerPCs, Windows and several UNIX environments including SGI, Sun, NeXT and Linux.
Evaluation consists of 4 assignments and a final project. Each assignment builds on the knowledge of the previous one, and the final outcome is a final project which is usually a computer music composition. This project is going to be presented during the finals week."
"SLIMEIt's nice to see a new university course being taught with CL, Emacs, and SLIME!
The last software package you need to know about is SLIME, the "Superior Lisp Interaction Mode for Emacs". SLIME is the system that runs Lisp from within Emacs and does all kinds of wonderful fancy things to integrate your Lisp interpreter with your text editor. The only SLIME documentation we know about is the SLIME user manual, which you can get as a PDF file from the web. There's also a copy on the CCRMA filesystem at /usr/share/doc/slime-1.0/slime.pdf which you can look at with the Linux "acroread" program. I recommend that you start learning SLIME by following the tutorial below; once you've gotten through that and are comfortable with Emacs then the SLIME user manual will make a lot more sense.
- Learn Emacs
Learn at least enough to move the cursor around, open and save files, follow the instructions below (which are in terms of the "Control" and "Meta" keys), and get out of trouble if you get confused.
I recommend the built-in Emacs tutorial or some of the other resources listed above.
- Install SLIME
You need to add some mumbo-jumbo to a file called ~/.emacs whose purpose is to customize Emacs.
Add these lines to that file:
;; slime (setq inferior-lisp-program "/usr/bin/cm-cmucl") (add-to-list 'load-path "/usr/share/slime") (require 'slime) (slime-setup)
Minus 10 points if you retyped those 133 characters into Emacs! I would have done it by selecting that text in the Mozilla window (with left-click and drag), and then pasting it into the Emacs window (with middle-click).
Save your .emacs file. Quit emacs. Start emacs. Type <meta>x (meta is the ALT key) Type slime<enter> Watch slime initialize.
Cool, huh? OK, quit Emacs again and go back to the Unix terminal.
- Make a directory for the course and put some goodies in it
Make yourself a directory called 220b, go into it, and make a subdirectory for this tutorial:
cd mkdir 220b cd 220b mkdir tutorial
Make a symbolic link giving you easy access to the CLM instruments we'll use in the course, then copy the fm-violin instrument from there into your tutorial directory, then go into that directory:
ln -s /usr/share/common-lisp/source/clm . cp clm/v.ins tutorial cd !$
- Basic Interaction with SLIME
Start Emacs, then type M-X slime to start SLIME. You'll see that the Emacs window is now labeled "slime-repl"; that stands for "Read-Eval-Print Loop", meaning that in this window SLIME continuously reads what you type, evaluates it, and prints the result. That's the basic mode of interaction with any Lisp interpreter.
Type a number like 234 and hit return. As soon as you hit return, SLIME changes what you typed to bold face to show that it was passed to Common Lisp. Below that, SLIME prints 234 again, this time not in bold. That's the "eval-print" part; it means that (according to Common Lisp) the value of 234 is 234. Deep, huh?
Now we'll intentionally cause an error so you can get used to dealing with them. Type "hello" and hit enter. The screen will split in two, with the "slime-repl" window you just typed into at the top, and a new "sldb" window at the bottom. First of all, notice that in the upper window, "hello" is now in bold, showing that it was sent to Lisp, but there's nothing below it, meaning that "hello" has no value, i.e., Lisp was unable to evaluate it.
Now let's look at the "sldb" window. I believe this stands for "SLime DeBugger." Unfortunately, like most "debuggers," this one doesn't actually remove your bugs; it just lets you poke around and try to figure out for yourself what the problem was.
The most important thing is the error message itself, which came from Lisp:
Error in KERNEL::UNBOUND-SYMBOL-ERROR-HANDLER: the variable HELLO is unbound. [Condition of type UNBOUND-VARIABLE]
That word "unbound" appears three times, so it must be important. Indeed, it means that Lisp was trying to interpret something (in this case, "hello"), as a variable but that variable doesn't exist, i.e., there's no value "bound" to it.
The next text in the SLDB window is a list of options for "restarts". They're called "restarts" because they're your options for dealing with the error and getting back to work. Sometimes when you make an error you might be able to correct it directly from the SLDB window and then carry on with the rest of what your program was doing. So you're going to see more "Restart" options for other errors. In this case, there are only two:
Restarts: 0: [ABORT] Abort handling SLIME request. 1: [ABORT] Return to Top-Level.
To bail out completely, select "Return to Top-Level". This is always a good idea if you want to just start over and try something else; it will take you out of the debugger and back to the normal mode of interaction. Move the cursor to that line and hit enter. Now the SLDB window goes away and you're back to just the slime-REPL window filling the whole screen. Now, below the bold "hello", you'll see this:
; Evaluation aborted
That's SLIME's way of reminding you that you aborted the evaluation of the expression "hello". You'll see another prompt, so now you're back to the read-eval-print loop. (The semicolon is a comment character.)
To finish our tour of the SLDB window, let's cause the same error again but in a different context. Type this:
(+ hello 2)
(By the way, before you hit return, notice that when you type the close parenthesis, the cursor briefly flashed to the corresponding open parenthesis. For complicated Lisp programs this "blink" feature is extremely helpful.)
Once again the SLDB window will open, with the same error message and list of two possible restarts. At the bottom of the SLDB window is a section called "Backtrace":
Backtrace: 0: (EVAL HELLO) 1: (EVAL (+ HELLO 2)) --more--
The cursor is on the first of these lines. It's telling you that the immediate context in which the error occured was trying to evaluate the expression HELLO. Below that you can see that Lisp was trying to evaluate the expression HELLO because it was trying to evaluate the expression (+ HELLO 2). You can imagine that this kind of "what was going on when the error occured?" information could be useful in debugging more complicated programs; it's called a "backtrace" because it traces backwards from the error through all the stages of what the program was trying to do when the error occurred.
If you hit enter on any of these backtrace lines then you get even more information about what was going on when the error occurred. For this simple example that information isn't very useful, but it's nice to know it's there.
You can always type the "q" key to skip out of the debugger.
Let's make a different kind of error. Ask Lisp to spell out all the numbers from one to a billion:
(loop for x from 1 to 1000000000 do (format t "~R~%" x))
Well, it's very impressive, but this is obviously going to take too long to complete. So interrupt Lisp by typing C-c C-c, which will take you back to the debugger, where you can abort this program.
- Make a sound with CLM
Tell Common Lisp to use the CM/CLM/CMN package:
Tell Common Lisp to compile and load the fm-violin instrument that you copied into this directory. (What do I mean by "this" directory? Common Lisp has a notion of the "current" directory, just like the Unix shell. So does Emacs. When you start Emacs, Emacs' current directory is set to the current directory of the Unix shell in which you typed "emacs". Likewise, when you start Common Lisp from Emacs (via SLIME), Common Lisp's current directory is set to Emacs' current directory. So since you were in your ~/220b/tutorial directory when you launched Emacs, and since you didn't change Emacs' current directory before starting SLIME, Common Lisp is "in" your ~/220b/tutorial directory too.)
(compile-file "v.ins") (load "v.cmucl")
Invoke the fm-violin instrument:
(with-sound () (fm-violin 0 1 440 0.1))
You should hear a one-second long note with pitch 440 Hertz (A above middle C).
- Fancy Interactive SLIME
Let's enjoy some of the cool features of SLIME.
Type only the beginning of a call to fm-violin:
Now hit the tab key. A new Emacs window named "completions" comes up listing all the Lisp names in the current package (which includes CM, CLM, and CMN) that begin with the letters "fm". Type the next two characters, "-v":
Now hit the tab key again. SLIME knows that everything whose name begins "fm-v" actually begins with "fm-violin", so it automatically filled in the "iolin" then showed you everything whose name begins with "fm-violin". Nice, huh?
Automatic documentation retrieval
Now type space, and an Emacs window pops up at the bottom of the screen listing all the parameters to fm-violin, like this:
(fm-violin startime dur frequency amplitude &key (fm-index 1.0) (amp-env '(0 0 25 1 75 1 100 0)) (periodic-vibrato-rate 5.0) (random-vibrato-rate 16.0) (periodic-vibrato-amplitude 0.0025) (random-vibrato-amplitude 0.005) (noise-amount 0.0) (noise-freq 1000.0) (ind-noise-freq 10.0) (ind-noise-amount 0.0) (amp-noise-freq 20.0) (amp-noise-amount 0.0) (gliss-env '(0 0 100 0)) (glissando-amount 0.0) (fm1-env '(0 1 25 0.4 75 0.6 100 0)) (fm2-env '(0 1 25 0.4 75 0.6 100 0)) (fm3-env '(0 1 25 0.4 75 0.6 100 0)) (fm1-rat 1.0) (fm2-rat 3.0) (fm3-rat 4.0) (fm1-index nil) (fm2-index nil) (fm3-index nil) (base nil) (frobber nil) (reverb-amount 0.01) (index-type :violin) (degree nil) (distance 1.0) (degrees nil) (no-waveshaping nil) (denoise nil) (denoise-dur 0.1) (denoise-amp 0.005) &allow-other-keys)
Of course you don't know what all of these mean yet, but the point is that it's telling you all the possible arguments to fm-violin. The first four, startime, dur, frequency, and amplitude, are regular arguments and must appear every time you call fm-violin. The rest are optional "keyword" arguments. Don't worry about all this detail now, just appreciate the way SLIME is trying to help you. History of expressions sent to Lisp
The other thing I love about SLIME is scrolling through previous inputs to Lisp. Get back to a top-level REPL prompt and type M-p. It calls up the "p"revious thing that you sent to Lisp. Each time you type M-p it goes back through the history of what you sent Lisp. M-n goes to the "n"ext one. These are just like C-p and C-n for moving up and down by lines, except that they move up and down by expressions you sent to Lisp.
Now type "(w" at a top-level REPL prompt and then M-p. It goes back to the last expression from your history that begins with "(w", which is probably the previous call to with-sound.
Say you want to synthesize a "song" with three notes. You could type them in all on one line, but that's ugly and hard to read:
(with-sound () (fm-violin 0 1 440 0.1) (fm-violin 1 1 392 0.1) (fm-violin 2 1 350 0.1))
Instead, type just "(with-sound ()" and then hit return. Notice that SLIME automatically indents your cursor to be underneath the letter "i", so you can see visually that what you're about to type will be inside the body of the call to with-sound. Notice also that what you've typed so far has not turned bold, meaning that it hasn't been sent to Lisp (because it's not yet a complete expression (because the parentheses aren't balanced yet, like this parenthetical comment.
Now type the first call to fm-violin on the second line and hit return again. Keep going, and you'll end up with this much more attractive and easy-to-understand formatting:
(with-sound () (fm-violin 0 1 440 0.1) (fm-violin 1 1 392 0.1) (fm-violin 2 1 350 0.1))
Experienced Lisp programmers rarely count parentheses; they rely on Emacs to indent their code properly and learn to read the indentation level to see what's inside what.
- Writing Programs in Emacs
Split Emacs into two vertical windows with C-X 2. In one window, use C-X C-F to open a new file called mysound.lisp. Because the name of the file you're working on ends in ".lisp", Emacs figured out that you're going to type a Lisp program, and so the mode of that buffer is "(Lisp Slime)". Even though this isn't the SLIME REPL buffer where you talk to Lisp, you'll get a lot of the nice features of SLIME, including paren blinking, automatic indentation, automatic documentation retrieval, and tab completion.
Type your own little Lisp program into this buffer, with a call to with-sound and one or more calls to fm-violin.
When you're ready to try your program, put the cursor inside it somewhere and type C-M-x. This will send your program to Lisp. If there's an error it will bring up the debugger as before. Otherwise, your program will run, and you'll see the return value (which will probably be the string "test.snd") at the very bottom of Emacs.
Another way to run your program is to save the file (C-X s) and then load it into Lisp:
You can also explicitly copy and paste between the buffer that contains your file and the SLIME REPL buffer, M-C-k is super useful, because it cuts an entire Lisp expression (i.e., a complete set of balanced parentheses and everything between them).
- Managing Emacs buffers
So far we've used many different Emacs buffers:
* * slime-repl*, SLIME's interactive read-eval-print loop * sldb, SLIME's debugger * *completions*, where Emacs listed all the things you might want to type that began with the letters "fm". * *Messages*, where SLIME shows you the arguments to functions * The buffer in which you're editing the file mysound.lisp * Behind the scenes, the buffer *inferior-lisp*, where CMUCL is actually running
For the most part, SLIME does a good job of popping up these buffers in Emacs windows when you need them, then making them go away when you're done with them. But eventually you're going to have to manage these buffers yourself. I get by with these five commands:
- C-x o: move the cursor to the "o"ther window.
- C-x 1: Make there be only one window (the one with the cursor in it)
- C-x 2: Split the current window vertically into two windows
- C-x b: Switch the current window to a different buffer, whose name I will type. (Space does name completion.)
- C-x C-b: Show a list of all the buffers (along with some other information like what Emacs mode each one is in, what file is loaded into each one, etc.). Moving the cursor over one and hitting enter loads that buffer into the current window.
Also, if you click on the name of the buffer (which is in bold at the bottom of a window), it cycles through all the buffers.
- Loading and Modifying Programs We Provide
This simple program is based on the idea of "forking" a note; at each time step each note becomes two notes, one higher and one lower:
Copy this file into your tutorial directory and load it into Lisp:
Don't type the whole filename; after the letter "i" hit tab to get filename completion. You don't have to compile it because it's not an instrument; it's just a program that calls the fm-violin instrument.
Loading the file into Lisp is not the same thing as loading it into Emacs. What you've done so far is just make Lisp aware of one new function called violin-forker. To look at the file yourself, edit it, etc, you should load it into Emacs with C-X C-F.
In your violin-forker.lisp Emacs buffer, scroll down to "Some examples". Evaluate these to run my procedure.
I hope you will be inspired to modify the violin-forker function to do something different."