Showing posts with label howto. Show all posts
Showing posts with label howto. Show all posts

Thursday, January 16, 2014

Source Code for the First Piece I Ever Wrote in SuperCollider

This is presented more as an example of how not to write music in SuperCollider - it was within the first week or 2 of me trying things out.

The piece is a drone piece based on an instrument that plays a set of overtones of a base frequency, and slowly bends those frequencies by a microtonal increment over a period of several minutes - creating cascading overtones at higher frequencies as the sound waves cross in space and cancel each other out.
The piece starts with a base drone over 20hz and later fades in complementary drones at (20*21/16)hz and (20*4/3)hz - a septimal major third and perfect fourth.
The piece ends after 30 minutes - when the pitch bending of first drone cycle returns to a unison.
This pattern of slow pitch bending in sine clusters was something I experimented for several years. I still write a piece in this style every now and then for fun. I've learned a lot about sound design since then so it's always good to go back and apply those learnings. Orbital Slingshot - composed in late 2011 is the most recent piece I've released in this style.
Commentary follows the source code.

(

SynthDef("sine-phase-additive",  {
arg baseFreq = 32, baseFreqLFORate = 1/3600, baseFreqLFOMult = 0.01, baseFreqLFOPhase = 0 , baseFreqLFOAdd = 2, pan = 0, amp = 0.03, partials = #[1,2,4,8,16,32,3,6,9,12,18,24,25,27,36,40,48,54,56,60,64];

var base;

base = baseFreq * SinOsc.kr(baseFreqLFORate, baseFreqLFOPhase, baseFreqLFOMult, baseFreqLFOAdd);

Out.ar(0,(Mix.ar(Pan2.ar(FSinOsc.ar(base*partials,0,amp),pan))));
}).load(s);
)

s=Server.local;
s.boot;




s.sendMsg("/s_new", "sine-phase-additive", 1005);
s.sendMsg("/n_set", 1005, "baseFreq",20*(4/3));
s.sendMsg("/n_set", 1005, "pan", 0);
s.sendMsg("/n_set", 1005, "amp", 0.013);
s.sendMsg("/n_set", 1005, "baseFreqLFOPhase", 0);
s.sendMsg("/n_set", 1005, "baseFreqLFOMult", 0.0125);
s.sendMsg("/n_set", 1005, "baseFreqLFORate",1/300);
s.sendMsg("/n_set", 1005, "pan", 0.3);

s.sendMsg("/s_new", "sine-phase-additive", 1001);
s.sendMsg("/n_set", 1001, "baseFreq",20*(21/16));
s.sendMsg("/n_set", 1001, "pan", 0);
s.sendMsg("/n_set", 1001, "amp", 0.012);
s.sendMsg("/n_set", 1001, "baseFreqLFOPhase", pi);
s.sendMsg("/n_set", 1001, "baseFreqLFOMult", 0.0325);
s.sendMsg("/n_set", 1001, "baseFreqLFORate",1/120);
s.sendMsg("/n_set", 1001, "pan", -0.3);


(





s.sendMsg("/s_new", "sine-phase-additive", 1000);
s.sendMsg("/n_set", 1000, "baseFreq", 20);
s.sendMsg("/n_set", 1000, "pan", 0);
s.sendMsg("/n_set", 1000, "amp", 0.030);
s.sendMsg("/n_set", 1000, "baseFreqLFOPhase",0);
s.sendMsg("/n_set", 1000, "baseFreqLFOMult", 0.00325);
s.sendMsg("/n_set", 1000, "baseFreqLFORate",1/1200);
s.sendMsg("/n_set", 1000, "pan", 1.0);





//static drone
s.sendMsg("/s_new", "sine-phase-additive", 1003);
s.sendMsg("/n_set", 1003, "baseFreq", 20);
s.sendMsg("/n_set", 1003, "amp", 0.040);
s.sendMsg("/n_set", 1003, "baseFreqLFOMult", 0.0);
s.sendMsg("/n_set", 1003, "baseFreqLFOPhase",0);
s.sendMsg("/n_set", 1003, "pan", 0.0);


//base  hourly cycle drone
s.sendMsg("/s_new", "sine-phase-additive", 1002);
s.sendMsg("/n_set", 1002, "baseFreq", 20);
s.sendMsg("/n_set", 1002, "amp", 0.030);
s.sendMsg("/n_set", 1002, "baseFreqLFOMult", 0.00125);
s.sendMsg("/n_set", 1002, "baseFreqLFOPhase",0);
s.sendMsg("/n_set", 1002, "baseFreqLFORate", 1/1800);
s.sendMsg("/n_set", 1002, "pan", -1.0);





SystemClock.sched(480.0, {

s.sendMsg("/s_new", "sine-phase-additive", 1001);
s.sendMsg("/n_set", 1001, "baseFreq",20*(21/16));
s.sendMsg("/n_set", 1001, "pan", 0);
s.sendMsg("/n_set", 1001, "amp", 0.012);
s.sendMsg("/n_set", 1001, "baseFreqLFOPhase", pi);
s.sendMsg("/n_set", 1001, "baseFreqLFOMult", 0.0325);
s.sendMsg("/n_set", 1001, "baseFreqLFORate",1/120);
s.sendMsg("/n_set", 1001, "pan", -0.3);


 });

SystemClock.sched(1200.0, {

s.sendMsg("/s_new", "sine-phase-additive", 1005);

s.sendMsg("/n_set", 1005, "baseFreq",20*(4/3));
s.sendMsg("/n_set", 1005, "pan", 0);
s.sendMsg("/n_set", 1005, "amp", 0.013);
s.sendMsg("/n_set", 1005, "baseFreqLFOPhase", 0);
s.sendMsg("/n_set", 1005, "baseFreqLFOMult", 0.0125);
s.sendMsg("/n_set", 1005, "baseFreqLFORate",1/300);
s.sendMsg("/n_set", 1005, "pan", 0.3);



 });

SystemClock.sched(1800.0, {

s.sendMsg("/n_free",1000);
s.sendMsg("/n_free",1001);
s.sendMsg("/n_free",1002);
s.sendMsg("/n_free",1003);
s.sendMsg("/n_free",1005);
 }


)

At this point I wasn't using either the Synth or Routine abstractions. I was creating a synth and giving it attributes by sending osc messages directly to the server, and manually allocating a node ID for each synth. My timing routine was to create events on the system clock at the outset and then delay them before being played - so the command to end the piece (by freeing the synth nodes, not sending them any kind of envelope) after 1800 seconds is already set when the piece starts.
Among things I hadn't learned about synth design yet - envelopes, gates and amplitude balancing. Amplitude balancing with additive synthesis can be a little tricky. I'm starting with a base frequency (20hz in this case) and sending it an array of partials = #[1,2,4,8,16,32,3,6,9,12,18,24,25,27,36,40,48,54,56,60,64] ) which is all well and good (note that there are no multiples of 5 until you get into the highest octave). 2 things to note about amplitude for your partials - 1) sound waves at the same amplitude carry more energy at higher frequencies (more wavefronts are hitting your ears over the same span of time) so they sound louder. Also, when you're creating an array of integers, there will always be more integers in the higher range of the array. To use the standard harmonic series as an example - in the 1st octave, there are only overtones at 1 and 2 times the fundamental frequency. 3 octaves up you have 8 overtones at 8,9,10,11,12,13,14,15,16. The higher you go, the more tones are likely to fit into your tuning scheme, even if you eliminate overtones that have factors higher than some number. For this reason, you should compensate for your amplitude using something like amp = (1/(f**k)) where f is your frequencies and k is a constant - usually from 1 (pink noise) to 2 (brown noise).

Thursday, June 20, 2013

How to Probably get Pandoc Running on a Linux Server

(

Drink-by date: This post was written based based on work I did in June, 2013. The GHC, Haskell, Cabal and pandoc were all current versions installed in Ubuntu 10.04. If you're reading this documentation after June, 2015, consider it out of date.

This blog post was the original inspiration to use Pandoc

)

A few months ago I started putting my resumé on Github. As a freelance software dev it's quicker to send somebody to 1 spot to see both code for projects I work on and descriptive, bulleted-list of accomplishments (without all of the monetization noise of LinkedIn). Recently I spent 5 hours on one of those endlessly-recursive campaigns of compiling things from source code in order to save myself a few minutes of maintaining my online resumé in different formats (pdf and markdown) and since it deals with some bugs in some standard Ubuntu / Debian packages, I'll post instructions here for how I fixed them.

My goal was to be able to update my resumé in markdown, and have run a single deploy script that would commit changes, generate a pdf from the markdown and push the updated markdown and pdf to GitHub. There's a Linux utility called "pandoc" that does this and a whole lot more. It's written in Haskell, a powerful functional programming language that is beloved by academics and high-frequency stock traders and otherwise hasn't broken into mainstream software developement.

My first time around, I did a standard "sudo apt-get install pandoc" on my Ubuntu server and thought I was good to go. The syntax for a basic conversin is simple, and like ffmpeg, the input and output formats are inferred by file extension:

$ pandoc resume.md -o resume.pdf

Except that this yielded the following error:

pandoc: resume.md: hGetContents: invalid argument (Invalid or incomplete
multibyte or wide character)

A character-encoding bug. Removing curly quotes from the markdown file confirmed this since it worked fine if the input document was all ascii. But what's the point of a PDF if you have to limit yourself to ugly straight quotes and spelling "resumé" as "resume"? There was some chatter on google groups that this was an issue with setting the right LANG in your locale, but that in later version of pandoc this bug was fixed. Rather than fiddle with global settings, I opted to upgrade pandoc.

The version of pandoc, as well as Haskell and GHC (the Glasgow Haskell Compiler) are all years out of date in debian / ubuntu. (The computer I was working on was running Lucid 10.04 which is supported through 2015). So geting the latest pandoc woudln't be as simple as an apt-get upgrade, and would require upgrading the GHC, Haskell, the Haskell Package Manager (cabal) and finally pandoc. All of these are source-code installs except for the final upgrade of pandoc.

If you've already tried to install pandoc or Haskell via apt-get you'll need to remove the packages via the following command:

$ sudo apt-get autoremove ghc6

Now we're ready to start the installation process. First, install the dependencies via apt-get:

$ sudo apt-get install libgmp3c2 freeglut3 libedit2 libedit-dev freeglut3-dev libglu1-mesa-dev
pandoc uses LaTeX for formatting, so you'll need to get pdflatex on your system.
$ sudo apt-get install texlive-full

Now you'll want to get the source code to build the GHC and the Haskell Platform. First the GHC. This a standard configure / make install build.

Cabal, the Haskell Package manager, is included in the Haskell Platform

$ wget http://lambda.haskell.org/platform/download/2013.2.0.0/haskell-platform-2013.2.0.0.tar.gz
$ gunzip haskell-platform-2013.2.0.0.tar.gz 
$ tar -xvvf haskell-platform-2013.2.0.0.tar 
$ cd haskell-platform-2013.2.0.0
$ ./configure
$ ./make
$ ./sudo make install

You can also get the source for Cabal from GitHub.

Once you have Cabal up and running, first refresh the packages list:

$ cabal update
Now, you can install pandoc
$ cabal install pandoc
Cabal will install a bunch of dependencies. This will tak a few minutes. Cabal installs executables to ~/.cabal/bin/pandoc - you can symlink this to a directory that's already in your path:
sudo ln -s ~/.cabal/bin/pandoc /usr/local/bin/pandoc

Now you should be ready to go. For some pointers as to how I actually automated the generating of pdf's by committing changes to my resumé, check out this shell script: https://github.com/erstwhile/resume/blob/master/deploy

Sunday, January 6, 2013

Almost Anything in Ruby can be a Hash Key

This was a definite huh / aha moment - finding the idiomatic Ruby way to solve a typical problem, in this case, taking an array of objects, and grouping them in a hash so that each element in the hash was all of the objects belonging to a particular user. You could parse out some unique value from your user object (which was the brain-damaged PHP way I had been thinking of this problem) and use that has a hash key:
#ugly
slug = (post.user.name +" "+post.user.id.to_s).to_sym
posts_by_user[slug] << post
When it turns out that you can use objects themselves as hash keys in Ruby, and then use the Hash.keys method to grab those objects back when you need them. So if you had a Post, which belonged to User, and you wanted to get them all and group them by user (assuming that your ORM or whatever won't do it for you in this instance):
posts = Post.all
posts_by_user = Hash.new {|h,k| h[k]=[]}
posts.each do |post|
  #if there is no entry for this user, create one with an empty array
  posts_by_user[post.user] ||= []
  #shovel the post on to the group for that user
  posts_by_user[post.user] << post
end
Iterate through them like this -
posts_by_user.keys.each do |user|
  #the user object
  p user.inspect
  #referencing the hash with the user object as key
  posts_by_user[user].each do |post|
    #each post belonging to that user
    p post.inspect
  end
end

Friday, July 6, 2012

Stream Supercollider on an Ubuntu Cloud Server using Darkice, Jackd and Icecast

The scenario - you have some kind of generative music app that you've written in Supercollider, or you want to have a Supercollider server in a stable place that's accessable to the whole internet, perhaps to have it collect data from or send data to other apps.  A cloud server is just a Linux machine in the cloud, and Supercollider runs on Linux, so why not run Supercollider in the cloud.

TL; DR (and caveat) - I never really had a solid deployment with this setup, and I suspect this is probably easier to do with Overtone.  Overtone is a Supercollider client / server implementation built in Clojure.  Clojure is an implementation of Common Lisp built on top of Java, so you get the whole ecosystem of tried and true Java apps wrapped in a fun client language.

If you have a supercollider stack running on a webserver, please comment - the deployment which I'm describing here is not optimal and I'd like to hear how others have done it.

Getting this set up took me a lot of moving parts and the process could probably be improved.  The deal is you get sclang running, which lets you build synths and control them, and then you need to take the signal generated by scsyth and pipe it to your Icecast server, but do it all without actually having a soundcard on your virtual machine.  This is where the recursively-named JACK Audio Connection Kit  comes into play.  Jack does a lot of things - one of them is to act as a virtual patch cable that will let you send audio signal between running apps without the use of a soundcard.

So, the whole setup is Sclang -> Scsynth -> Jackd -> Darkice -> Icecast2, plus Monit to make sure the whole thing is running, which is a lot of moving parts.


Sclang to run your supercollider code on the server.  (You could also control supercollider from a separate client on a home computer).  Scsynth to generate audio signal.  Jackd to patch that signal into your streaming server.  Darkice is the streaming server, which encodes the audio signal for the web, and Icecast2 is the application that takes the audio stream and serves it up in a web page, or for an app like iTunes to listen to.

Here's some pages that I found helpful in getting things  set up:

Setting up Darkice

Jackd and darkice, with jackd running dummy soundcards

Running darkice as a background process

What is Darkice


One of the problems I had with this set up is that if you're running under constant CPU load you're much more likely to crash on a virtual server than on a desktop computer or other dedicated hardware. The CPU allocation on a virtual server isn't really constant and spikes in CPU usage can cause your CPU allocation from scsynth to overload and the scsynth process will crash.  You can use something like Supervisor or Monit to restart the process after a crash.

This is a blog post from howtonode.org about using monit and upstart to restart a process after it crashes.

I'm not currently running this stack on backtrace.us - If I have time to get a stable deployment going, I'll post an update with more notes on configuration.