random frequencies

We continue learning Csound by trying to produce a potentially infinite sequence of random frequencies, like a sci-fi robot or an old computer or modem.

The first thing we need is to create a csd file very similar to our Hello, world example.

<CsoundSynthesizer>
<CsOptions>
-odac
</CsOptions>
<CsInstruments>
instr 1
//Next, instead of using a fixed 440 Hz frequency, we leave it as a variable.
//Specifically, p4 is the 4th argument of the score line.
//The score will determine the frequency and the oscillator will use it.
aSin      oscil      0dbfs/4, p4 // sine
          outs       aSin
endin
</CsInstruments>
<CsScore bin="./generate_random">// mind the call to an executable called "generate_random"
//no need to write code here, since the score is provided by the "generate_random" binary
</CsScore>
</CsoundSynthesizer>

In essence, we have the hello, world program with two modifications. The first one is that the frequency of the oscillator is now variable, the 4th argument of the score line, called p4. We did not use this argument in the previous example because the frequency was fixed by hand.

The second thing is that we are not writing a score here. Instead, we will ask an external program to write the score for us. As such, this code will play whatever the program "score" will dictate.

So now it is time to see what this mysterious external program is about.

You can write the external program in whatever language you like. What is importante is that such program must accept two arguments, because Csound, when calling the program, will pass two arguments of its own. You will never see the names of such arguments, unless you visit the /tmp folder afterwards, of course! Then, two new files will be there, but it is not important.

My choice is to write this in C++, not because I consider it a better language, but because it is the one I am comfortable with. You can easily translate this to your language(s) of choice.

I write the code here with several comments.

//generate scores for Csound
#include<iostream> //input-output
#include<fstream> //deals with writing and reading files
#include<math.h>
using namespace std;
//·····random generator (uniform)·····//+
double U() {
	double rmax=(double) RAND_MAX;
	return random()/rmax;
}
//·····random generator·····//-
/*
Next, the main function, which has two arguments:
argc is the number of arguments.
If you call your program as ./program, then argc=1, because C++ (like C), considers the name of the program as an argument.
If you call the program as ./program hello bye, then argc=3.
This is the case for which we must be ready, since Csound needs two arguments after the program's name.
argv is an array with the actual names of the arguments you provide. 
In the latter example:
argv[0] = "./program" //yes, it takes ./ if you call the program like this
argv[1] = "hello"
argv[2] = "bye"
*/
int main(int argc, char *argv[]) {
	//Cscore will call the program passing two arguments
	//the first is a copy of the score with .ext extension
	//the second is an empty file with .sco extension
	cout<<argv[1]<<" "<<argv[2]<<"\n";//To see the names of the arguments used by Csound
	string Path1 = string(argv[1]);//converts the arguments to strings
	string Path2 = string(argv[2]);
	ofstream out (Path1);//creates the first needed file (left empty, as required by Csound)
	ofstream out2 (Path2);//creates the second needed file
	ofstream out3 ("my_random_score.txt");//creates a 3rd file in which we store our results
	int n = 100;//number of notes we will produce
	float begin=0.;//beginning time for each note
	float dur = 0.01; //duration of note in seconds
	float freq; // frequency
	for(int i=0;i<n;i++) {
		//random frequencies
		freq = 50 + 800*U();//random frequency between 50 and 800 Hz
		begin += dur;//updates the start time
		out2<<"i 1 "<<begin<<" "<<dur<<" "<<freq<<"\n";//writes to file needed by Csound
		out3<<"i 1 "<<begin<<" "<<dur<<" "<<freq<<"\n";//writes a copy of the score for us
	}///end for i
	out.close();//close the files
	out2.close();
	out3.close();
}//end main

You can cancel the execution at any time with control-c. After it is completed, you can check the score generate in the txt file "my_random_score.txt". This is the score that Csound has played.

What if you play the program again? Will it play a different sequence of sounds? No! It will play exactly the same (pseudo)random sequence, because the C++ program will always begin with the same *seed* for that generator. If you want a different sequence each time, there are many ways, but here is an (inelegant) one: add the following code to the main function, just after the main() function is called. Just like this:

int main(int argc, char *argv[]) {
	//·····seeds·····//+
	ifstream read_seed ("./seed.dat");
	int semilla,semilla2;
	read_seed>>semilla;
	read_seed.close();
	ofstream write_seed ("./seed.dat");
	srandom(semilla);
	semilla2=random();
	write_seed<<semilla2;
	write_seed.close();
	//·····seeds·····//-

This will create a file called "seed.dat" that will be overwritten with each execution. If you delete it before launching the program, then you will get again our original sequence. This is convenient, since we have (pseudo)random sequences, but thanks to their pseudo character, we have a controlled and reproducible environment.

From here, the possibilities are endless. But we are going to focus on one important improvement. Have you noticed an annoying sound every time a new frequency is played? This is because we are playing each note too aggressively.

With synthesisers, we usually have four basic quantities that shape how a note is *enveloped* when we play it. These quantities can be summarised as ADSR (not AMSR!, but both have things in common):

      A = attack   -> in s
      D = decay    -> in s
      S = sustain  -> level
      R = release  -> in s


     /\
    /  \
   /    \_________________     
  /             |         \    
 /              |S         \     
/               |           \    
<----><->                 <->
   A   D                   R

The attack (A) is the time for the initial curve, from 0 amplitude.

The decay (D) is the time for the decrease of the amplitude after the attack.

The sustain (S) is the *level* or amplitude of the signal after the decay.

The release (R) is the time for the decrease of the signal from its sustain level to a zero final level.

This is called an *envelope. And be careful: if a note lasts 1 s, we cannot set A=0.4 s, ten D = 0.4 S and then R = 0.3 s, since A+D+R > total duration! When these issues appear, Csound gives preference in this order: R, A, D. But better not to arrive to this.

Csound has many ADSR envelopes, but we will use madsr, which acts as a function.

It takes, as you would expect, 4 input arguments, and there is 1 output argument.

The syntax is:

kres    madsr    iatt, idec, islev, irel

kres = output variable in which we store the result of the envelope

madsr = the envelope function

iatt = the attack (A)

idec = the decay (D)

islev = the sustain (S) (from 0 to 1)

irel = the release (R)

Clearly, our previous experiment had a too-sharp attack and release. We want to smooth them out.

Let's rewrite the whole csd file:

<CsoundSynthesizer>
<CsOptions>
-odac
</CsOptions>
<CsInstruments>
instr 1
iAtt = 0.03 // the attack time
iDec = 0.01 // the decay time
iSus = 0.9  // the sustain level (normalised from 0 to 1)
iRel = 0.03 // the release time
//now we pass these 4 arguments through the madsr envelope
//we store the output of the function in the kEnv variable
kEnv madsr iAtt, iDec, iSus, iRel
//we generate our oscillation as before
aSin      oscil      0dbfs/4, p4 // sine
          out        aSin*kEnv // sends the instrument to out
endin
</CsInstruments>
<CsScore bin="./generate_random">
</CsScore>
</CsoundSynthesizer>

random2.csd

You will see how now the transitions are way smoother. But not perfect! I don't know how to improve them yet.

I have run the program without the odac function so that I get a test.wav file. Then I converted it into an ogg audio file with the command

oggenc test.wav random.ogg

You can download it here

[audio] random.ogg


I think there are many variations that you can play here: changing durations, frequencies, types of oscillator...