![]() |
Andrew Sorensen live-coding at OSCON 2014 |
Shortly after Andrew Sorensen began the performance segment of his keynote at OSCON 2014, the #oscon Twitter topic began erupting with posts about the live coding session. Comments, retweets, and additional links persisted for that day and the next. In short, Andrew was a hit :-)
My first encounter with Andrew's work was a few years ago when I was getting back into Lisp. I was playing with generative music with Overtone (and then, a bit later, experimenting with SuperCollider, Hy, and Twisted) and came across his piece A Study in Keith. You might want to take a break from reading this port and watch that now ...
When Andrew started up his presentation, I didn't immediately recognize him. In fact, when the code was displayed on the big screens, I assumed it was Clojure until I looked closely and saw he was using (define ...) and not (defun ...). This seemed very familiar, and then I remembered Impromptu, which ultimately lead to my discovery of Extempore (see more links below) and the realization that this is what Andrew was using to live code.
At the end of the performance a bunch of us jumped up and gave a standing ovation. (In fact, you can hear me yell out "YEAH" at the end of his presentation when he says "And there we go."). It was quite a show. It seemed that OSCON 2014 had been given a theme song. The next step was getting the source code ...
![]() |
Andrew's gist (Dark Github Theme) |
Andrew gave a presentation on Extempore in the ballroom right after the keynote. This too was fantastic and resulted in much tweeting.
Afterwards a bunch of us went up front and chatted with him, enthusing about his work, the recent presentation, the keynote, and his previously published pieces.
I had Andrew's ear for a moment, and asked him if he was interested in sharing his keynote source -- there had been several requests for it on Twitter (that also got retweeted and/or favourited). Without hesitation, he gave an enthusiastic "yes" and we were off and running for the lounge where we could sit down to create a gist (and grab a cappuccino!). The availability of the source was announced immediately, to the delight of many.
Setting Up Extempore
![]() |
Sublime Text 3 connected to Extempore |
To ensure anyone else who is not familiar with Extempore can also have this pleasure, I've put together the all the prerequisites and setup necessary in a forked gist, in multiple parts. I will go through those in this blog post. Also: all of my testing and live coding was done using Ben Swift's Extempore Sublime Text plugin.
The first step is getting all the dependencies. You'll want to start the downloads right away, since they are large (the sample files are compressed .wavs). While that's going on, you can install Extempore using Homebrew (this worked for me on Mac OS X with no additional tweaking/configuration necessary):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ git clone https://gist.github.com/8b4404e538e61c7996a5.git | |
$ cd 8b4404e538e61c7996a5 | |
$ mkdir salamander && cd salamander | |
$ curl -L -O https://archive.org/download/SalamanderDrumkit/salamanderDrumkit.tar.bz2 | |
$ curl -L -O https://github.com/johnsen/drumsandpercussion/blob/master/SalamanderKick/salamanderdrum-kick-r1.tar.gz | |
$ curl -L -O http://freepats.zenvoid.org/Piano/SalamanderGrandPianoV3_44.1khz16bit.tar.bz2 | |
$ tar xvfz salamanderDrumkit.tar.bz2 | |
$ tar xvfz salamanderdrum-kick-r1.tar.gz | |
$ mv Kick/kick* OH/ | |
$ tar xvfz SalamanderGrandPianoV3_44.1khz16bit.tar.bz2 | |
$ brew tap benswift/extempore | |
$ brew install extempore | |
$ cd /usr/local/Cellar/extempore/0.52 | |
$ ./extempore |
- load some libraries (this takes a while for them to compile),
- define some samples, and then
- define some musical note aliases for convenience (and visual clarity).
The easiest way to use the files below is to clone the gist repo and load them up in Sublime Text, executing blocks of text by hi-lighting them, and then pressing ^x^x.
Here is the file for the fist two bullets mentioned above:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;; Much of the content in this file is taken from various Extempore | |
;;;; blog posts by Ben Swift which are indexed here: | |
;;;; http://benswift.me/extempore-docs/index.html | |
;;;; | |
;;; Load the needed libraries -- this will take several minutes | |
;;; | |
(sys:load "libs/core/instruments.xtm") | |
(sys:load "libs/core/pc_ivl.xtm") | |
(sys:load "libs/external/instruments_ext.xtm") | |
;;; Set up the constants we're going to need | |
;;; | |
(define sample-path "/Users/oubiwann/Downloads/8b4404e538e61c7996a5/") | |
(define drum-path (string-append sample-path "salamander/OH/")) | |
(define piano-regex "^.*([ABCDEFG][#b]?[0-9])v([0-9]+)\.wav$") | |
;;; Add the samplers/instruments we're going to need to the dsp output | |
;;; callback | |
;;; | |
(define-sampler drums sampler_note_hermite_c sampler_fx) | |
(define-sampler piano sampler_note_hermite_c sampler_fx) | |
(bind-func dsp:DSP | |
(lambda (in time chan dat) | |
(cond ((< chan 2.0) | |
(+ (drums in time chan dat) | |
(piano in time chan dat) | |
(fmsynth in time chan dat))) | |
(else 0.0)))) | |
(dsp:set! dsp) | |
;;; Set up drum samples | |
;;; | |
(define add-drum-sample | |
(lambda (file-name const-name) | |
(set-sampler-index drums | |
(string-append drum-path file-name) | |
const-name | |
0 0 0 1))) | |
(define drum-sample-data | |
(list | |
(list "kick_OH_F_9.wav" *gm-kick*) | |
(list "hihatClosed_OH_F_20.wav" *gm-closed-hi-hat*) | |
(list "hihatFoot_OH_MP_12.wav" *gm-pedal-hi-hat*) | |
(list "hihatOpen_OH_FF_6.wav" *gm-open-hi-hat*))) | |
(define add-drum-samples | |
(lambda (data) | |
(map (lambda (sample-pair) | |
(add-drum-sample (car sample-pair) (cadr sample-pair))) | |
data))) | |
(add-drum-samples drum-sample-data) | |
;;; Set up piano samples | |
;;; | |
(define parse-salamander-piano | |
(lambda (file-list) | |
(map (lambda (fname) | |
(let ((result (regex:matched fname piano-regex))) | |
(if (null? result) | |
#f | |
;; load 4th velocity layer only | |
(if (= (string->number (caddr result)) 4) | |
(list fname | |
(note-name-to-midi-number (cadr result)) | |
0 | |
0) | |
#f)))) | |
file-list))) | |
(load-sampler | |
piano | |
;; Can't use a variable here; need the actual path string | |
"/Users/oubiwann/Downloads/8b4404e538e61c7996a5/salamander/SalamanderGrandPianoV3_44.1khz16bit/44.1khz16bit" | |
;; 'sound bank' index | |
0 | |
parse-salamander-piano) | |
;; Now that everything is loaded, try out some notes ... | |
(play-note (now) drums *gm-kick* 180 44100) | |
(play-note (now) drums *gm-open-hi-hat* 140 44100) | |
(play-note (now) piano 24 180 44100) | |
(play-note (now) fmsynth (random 60 80) 100 (* 1.0 *second*)) |
You will need to edit this file to point to the locations where your samples were downloaded. Also,
at the very end there are some lines of code you can execute to make sure that your samples are working.
Now let's define the note aliases. You can just hi-light the entire contents of this file in Sublime Text and then ^x^x:
At this point, we're ready to play!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(define base-note 12) | |
(define get-note | |
(lambda (octave offset) | |
(+ (* base-note octave) offset))) | |
(define C1 (get-note 1 0)) | |
(define C2 (get-note 2 0)) | |
(define C3 (get-note 3 0)) | |
(define C4 (get-note 4 0)) | |
(define C5 (get-note 5 0)) | |
(define C6 (get-note 6 0)) | |
(define C7 (get-note 7 0)) | |
(define D1 (get-note 1 2)) | |
(define D2 (get-note 2 2)) | |
(define D3 (get-note 3 2)) | |
(define D4 (get-note 4 2)) | |
(define D5 (get-note 5 2)) | |
(define D6 (get-note 6 2)) | |
(define D7 (get-note 7 2)) | |
(define E1 (get-note 1 4)) | |
(define E2 (get-note 2 4)) | |
(define E3 (get-note 3 4)) | |
(define E4 (get-note 4 4)) | |
(define E5 (get-note 5 4)) | |
(define E6 (get-note 6 4)) | |
(define E7 (get-note 7 4)) | |
(define G1 (get-note 1 7)) | |
(define G2 (get-note 2 7)) | |
(define G3 (get-note 3 7)) | |
(define G4 (get-note 4 7)) | |
(define G5 (get-note 5 7)) | |
(define G6 (get-note 6 7)) | |
(define G7 (get-note 7 7)) | |
(define A1 (get-note 1 9)) | |
(define A2 (get-note 2 9)) | |
(define A3 (get-note 3 9)) | |
(define A4 (get-note 4 9)) | |
(define A5 (get-note 5 9)) | |
(define A6 (get-note 6 9)) | |
(define A7 (get-note 7 9)) | |
(define B1 (get-note 1 11)) | |
(define B2 (get-note 2 11)) | |
(define B3 (get-note 3 11)) | |
(define B4 (get-note 4 11)) | |
(define B5 (get-note 5 11)) | |
(define B6 (get-note 6 11)) | |
(define B7 (get-note 7 11)) |
Playing the Music
To get started on the music, open up the fourth file from the clone of the gist and ^x^x the root, scale, and left-hand-notes-* constants.
Here is the evolution of the left hand part:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(define root E3) | |
(define scale (pc:scale 4 'aeolian)) | |
(define left-hand-notes-1 (list C3 G3)) | |
(define left-hand-notes-2 (list G4 G4 A4 B4)) | |
;; start with this | |
(define left-hand | |
(lambda (beat ps ds) | |
(play piano (car ps) 170 (* 2.0 (car ds))) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'left-hand (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
(left-hand (*metro* 'get-beat 4) left-hand-notes-2 (list 1)) | |
;; after a bit, switch to this | |
(define left-hand | |
(lambda (beat ps ds) | |
(play piano (car ps) 170 (* 2.0 (car ds))) | |
(play 1/2 piano root 170 (* 2.0 (car ds))) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'left-hand (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
;; don't wait too long to execute the next bit | |
(define left-hand | |
(lambda (beat ps ds) | |
(if (= 0 (modulo beat 8)) | |
(set! root (random (remove root left-hand-notes-1)))) | |
(play piano (car ps) 170 (* 2.0 (car ds))) | |
(play 1/2 piano root 170 (* 2.0 (car ds))) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'left-hand (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
(define left-hand-notes-1 (list E3 D3 C3)) |
Next, you'll want to bring in the right hand ... then bassline ... then the higher fmsynth sparkles for the right hand:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(define right-hand | |
(lambda (beat dur) | |
(play piano | |
(pc:quantize (cosr (+ root C2) (cosr 5 3 1/2) 7/3) scale) | |
(cosr 160 20 7/3) | |
(* 4.0 dur)) | |
(callback (*metro* (+ beat (* .5 dur))) 'right-hand (+ beat dur) dur))) | |
(right-hand (*metro* 'get-beat 4) 1/4) | |
(define bassline | |
(lambda (beat ps ds) | |
(play fmsynth root 100 (* (car ps) (car ds)) 1.0 0.5) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'bassline (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
(bassline (*metro* 'get-beat 4) (list .25 .25 .6) '(3/2 1 3/2)) | |
(define right-hand | |
(lambda (beat dur) | |
(play piano | |
(pc:quantize (cosr (+ root C2) (cosr 5 3 1/2) 7/3) scale) | |
(cosr 160 20 7/3) | |
(* 4.0 dur)) | |
(if (> (random) .6) | |
(play fmsynth | |
(pc:quantize (+ 7 (cosr (+ root 36) (cosr 5 3 1/2) 7/3)) scale) | |
(cosr 75 20 7/3) | |
(* .2 dur) 0.5 5.0)) | |
(callback (*metro* (+ beat (* .5 dur))) 'right-hand (+ beat dur) dur))) |
Then you'll increase the energy with the drum section:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(define kick | |
(lambda (beat dur) | |
(play drums 35 150 dur) | |
(callback (*metro* (+ beat (* .5 dur))) 'kick (+ beat dur) dur))) | |
(kick (*metro* 'get-beat 4) 1) | |
(define kick | |
(lambda (beat dur) | |
(play drums 35 150 dur) | |
(play -1/4 drums 35 130 (* .5 dur)) | |
(callback (*metro* (+ beat (* .5 dur))) 'kick (+ beat dur) dur))) | |
(define hats | |
(lambda (beat dur) | |
(play drums (random '(44 42)) (cosr 80 60 (random '(7/3 5/2))) dur) | |
(callback (*metro* (+ beat (* .5 dur))) 'hats (+ beat dur) dur))) | |
(hats (*metro* 'get-beat 4) 1/4) |
Finally, you'll bring it to the climax, and then start the gentle fade out:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(define left-hand-notes-1 (list E3 D3 C3 G3)) | |
(define right-hand | |
(lambda (beat dur) | |
(play piano | |
(pc:quantize (cosr (+ root C2) (cosr 5 3 1/2) 7/3) scale) | |
(cosr 170 20 7/3) | |
(* 4.0 dur)) | |
(if (> (random) 0) | |
(play fmsynth | |
(pc:quantize (+ 12 (cosr (+ root 36) (cosr 5 3 1/2) 7/3)) scale) | |
(cosr 60 20 7/3) | |
(* .2 dur) 0.5 5.0)) | |
(callback (*metro* (+ beat (* .5 dur))) 'right-hand (+ beat dur) dur))) | |
;; bring volume of sparkles up to 70 then 80 | |
(define left-hand-notes-1 (list C3 G3)) | |
(define left-hand-notes-1 (list E3 D3 C3 G3)) | |
(define right-hand | |
(lambda (beat dur) | |
(play piano | |
(pc:quantize (cosr (+ root C2) (cosr 5 3 1/2) 7/3) scale) | |
(cosr 170 20 7/3) | |
(* 4.0 dur)) | |
(if (> (random) .6) | |
(play fmsynth | |
(pc:quantize (+ 7 (cosr (+ root 36) (cosr 5 3 1/2) 7/3)) scale) | |
(cosr 80 20 7/3) | |
(* .2 dur) 0.5 5.0)) | |
(callback (*metro* (+ beat (* .5 dur))) 'right-hand (+ beat dur) dur))) | |
;; bring volume of sparkles down to 70 then 60 | |
(define left-hand-notes-1 (list C3 G3)) | |
;; now bring down the piano volume of 160 by increments to 10 | |
(define right-hand) | |
(define kick) | |
(define bassline) | |
(define hats) | |
(define left-hand | |
(lambda (beat ps ds) | |
(if (= 0 (modulo beat 8)) | |
(set! root (random (remove root left-hand-notes-1)))) | |
(play piano (car ps) 170 24.0) | |
(play piano root 170 24.0))) |
A slightly modified code listing for the final keynote form is here:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;; This is a slightly modiifed version of code for the OSCON Keynote | |
;;;; which Andrew Sorenson performed live in Portland Oregon in July, 2014. | |
;;;; | |
;;;; Some of the differences include: | |
;;;; * Free samples were used instead of the cuomst samples that Andrew uses | |
;;;; * Note definitions are used instead of numbers | |
;;;; * The sustain of the piano notes was increased | |
;;;; * Some of the relative volumes were changed. | |
;;;; | |
;;;; The original is preserved in the history of this gist, which was forked | |
;;;; from Andrew's gist. | |
;;;; | |
;;;; Final note: thank you so much, Andrew, for the inspiring performance | |
;;;; you gave to OSCON; it was an absolute delight to finally be able to | |
;;;; attend one of your live performances! | |
;;;; | |
(define root E3) | |
(define scale (pc:scale 4 'aeolian)) | |
;; (define left-hand-notes-1 (list E2 D2 C2 G2)) | |
;; (define left-hand-notes-2 (list G3 G3 A3 B3)) | |
;; (define left-hand-notes-1 (list E3 D3 C3 G3)) | |
(define left-hand-notes-1 (list E3 D3 C3)) | |
;; (define left-hand-notes-1 (list E4 D4 C4)) | |
(define left-hand-notes-2 (list G4 G4 A4 B4)) | |
;; (define left-hand-notes-1 (list E4 D4 C4 G4)) | |
;; (define left-hand-notes-2 (list G5 G5 A5 B5)) | |
;; (define left-hand-notes-1 (list E5 D5 C5 G5)) | |
;; (define left-hand-notes-2 (list G6 G6 A6 B6)) | |
(define left-hand) | |
(define left-hand | |
(lambda (beat ps ds) | |
(if (= 0 (modulo beat 8)) | |
(set! root (random (remove root left-hand-notes-1)))) | |
(play piano (car ps) 170 (* 2.0 (car ds))) | |
(play 1/2 piano root 170 (* 2.0 (car ds))) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'left-hand (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
(left-hand (*metro* 'get-beat 4) left-hand-notes-2 (list 1)) | |
(define right-hand) | |
(define right-hand | |
(lambda (beat dur) | |
(play piano | |
(pc:quantize (cosr (+ root 24) (cosr 5 3 1/2) 7/3) scale) | |
(cosr 160 20 7/3) | |
(* 4.0 dur)) | |
(if (> (random) .6) | |
(play fmsynth | |
(pc:quantize (+ 7 (cosr (+ root 36) (cosr 5 3 1/2) 7/3)) scale) | |
(cosr 60 20 7/3) | |
(* .2 dur) 0.5 5.0)) | |
(callback (*metro* (+ beat (* .5 dur))) 'right-hand (+ beat dur) dur))) | |
(right-hand (*metro* 'get-beat 4) 1/4) | |
(define bassline | |
(lambda (beat ps ds) | |
(play fmsynth root 110 (* (car ps) (car ds)) 1.0 0.5) | |
(callback (*metro* (+ beat (* .95 (car ds)))) 'bassline (+ beat (car ds)) | |
(rotate ps -1) | |
(rotate ds -1)))) | |
(bassline (*metro* 'get-beat 4) (list .25 .25 .6) '(3/2 1 3/2)) | |
(define kick | |
(lambda (beat dur) | |
(play drums 35 150 dur) | |
(play -1/4 drums 35 130 (* .5 dur)) | |
(callback (*metro* (+ beat (* .5 dur))) 'kick (+ beat dur) dur))) | |
(kick (*metro* 'get-beat 4) 1) | |
(define hats | |
(lambda (beat dur) | |
(play drums (random '(44 42)) (cosr 80 60 (random '(7/3 5/2))) dur) | |
(callback (*metro* (+ beat (* .5 dur))) 'hats (+ beat dur) dur))) | |
(hats (*metro* 'get-beat 4) 1/4) |
Variation on a Theme
I have recorded a variation of Andrew's keynote based on the code above, for your listening pleasure :-) You can listen to it in your browser or download it.
This version plays part of the left hand piano an octave lower. There's a tiny bit of clipping in places, and I accidentally jazzed it up (and for too long!) with a hi-hat change in the middle. There are also some awkward transitions and volume oddities. However, these should be inspiration for you to make your own variation of the OSCON 2014 Theme Song :-)
The "script" used for the recording can found here.
Links of Note
Some of these were mentioned above, some haven't been. All relate to Extempore :-)
- A Study in Keith.
- The Disklavier Sessions - 2013
- Andrew on Github: https://github.com/digego
- Twitter: https://twitter.com/digego
- Extempore:
6 comments:
Hi Duncan, nice article.
I've also been listening - on and off - to Andrew's music for the past few years. I did start looking at Extempore and Impromptu and then backed down as I realised that the learning curve was going to be too difficult without some help. And, I must say that forums are great, but a live class is better, as questions are answered immediately.
I'd be interested to read why you think/find Extempore better than Supercollider? I have to say that I can see the point that programs such as these are perfect for live coding, whereas PD and Max are great for building sound modules and working with sound design.
Lastly, I've always wondered why Andrew is doing his thing alone (with his university team) and not making it a little more user-friendly. It reminds me a little of the history of ChucK which hasn't taken off due to the developers at CalArts being unable to communicate with the rest of the world - programming wise. Extempore makes me think of Linux at the beginning, an excellent and very powerful musical tool, which, maybe in twenty years time, will be easily downloadable in a package with no need to compile libraries etc.
If you have some thoughts on the matter I'd be most interested to read them, either here or in a direct mail.
Big thanks for such an interesting article.
p.s. I don't know the Lisp language, which of course makes Extempore even harder.
By the way Duncan. Although I expect you know this already but if not, and you're interested, James Harkins web-page shows some of the possibilities of SC - http://www.dewdrop-world.net/audio/index.php
Very interesting, very musical - and often live.
All the best.
Hey Joe, thanks for your comments :-)
It's good to hear your perspective ... I've been immersed in so many different Lisps over the course of my career (even when I wasn't really using them), that I've completely lost the "beginner's mind" perspective. You are definitely right -- it's daunting at first. My only response is that, as with anything, practice generates familiarity, and from that one finds natural ease. You can do it!
I do agree too that classes would be great. Andrew has given workshops in the past, but, iirc, they seem to be a fairly rare occurrence :-/
I don't have a preference one way or another of Extempore vs. SuperCollider (Overtone, actually; I haven't used SuperCollider directly much at all). One thing that would help me evaluate would be to watch a live coding performance of "Study in Keith" with both Extempore and Overtone :-)
I'm not sure I believe that Andrew is doing it alone, per se. I think he's created a tool that he loves and he works with very well. I believe if more people use it, it will become more user-friendly for a larger audience. To be honest, I found it very easy to use ... a few blog posts, some threads on the mail list, and I had everything I needed.
Though I am a Lisper, I'm not a schemer, so I had to adjust my dialect a bit ... but I do have a passion for Lisps -- any opportunity to play with a new dialect, and I'm usually quite game :-) That would obviously be quite useful for picking up Extempore.
You have, though, got me thinking -- completely on a different tangent: what's the best way to transfer that delight to someone else (someone willing, of course!). There is a way to view the multitude of Lisps in a unified view ... in particular, I'm getting hints of a tiny project/tutorial (maybe a multi-dialect one) that could help those who are interested see the bigger picture, and "get it" ... I'll let you know if anything useful comes of these thoughts :-)
Glad you liked the article, though!
Very cool! I hadn't seen that -- thanks for sharing :-)
Tnx a lot for sharing the detailed reconstruction of Andrew's awesome performance.
I really enjoyed listening to your recoding of the music.
I've done a transcript of his performance a while ago for my own study and use
and just now published that on Gist as well. This version uses the default sample assets from extempore,
Had done a blogpost on the extempore list
about that which you might be interested as well.
https://groups.google.com/forum/#!topic/extemporelang/WCOMNC9-w2M
Tnx again for sharing.
@Circu -- you're very welcome! Also, thanks for the link -- good stuff! Hadn't thought about publishing on Sound Cloud ... great idea :-)
Post a Comment