Project 7: Too Many Notes
Project 7 is to make use of python to create sounds that consist of hundreds of notes, without having to write more than a few lines of code. Csound is very powerful, but to take advantage of it you need to be able to generate notes algorithmically. You can't create hundreds of notes for each small time period by hand.
A Csound file is just a text file, which means it's a sequence of
characters. Let's assume we have a very simple instrument with one
oscillator and a linear envelope to shape the sound.
; one oscillator, fast rise, long decay instr 1 k1 linen 1, 0.05*p3, p3, 0.7*p3 a1 oscil p4*k1, p5, 1 out a1 endin
A single note for this instrument looks like the following, which plays a 440 Hz note for 1 second at an amplitude of 10000.
; sin wave table f 1 0 8192 10 1 ; notes i 1 0.00 1.00 10000 440.0
Using MacCsound, create an instrument and score as above and test it out.
If I wanted to create 100 notes that all played simultaneously at
slightly different frequencies, it would be nice if I could write a
python function to do it for me. Creating such a file by hand would
be tedious, at best. Fortunately, generating strings and writing them
to a file is something we can do quite easily in Python.
Opening a file and writing the above string to it is as simple as the following three lines of code, which write one line of text and a carriage return '\n' to the file notefile.csd.
fp = file( 'notefile.csd', 'w') fp.write( 'i 1 0.00 1.00 10000 440.0\n' ) fp.close()
But what if we wanted to write 100 notes to that file? We need a loop, we need a way of generating random values, and we need to be able to insert numbers into a string. All three are easy to do.
import random fp = file( 'notefile.txt', 'w') for i in range(100): # loop 100 times pitch = str( 440.0 + (random.random() - 0.5)*100 ) volume = str( 10000 / 100 ) fp.write( 'i 1 0.00 1.00 ' + volume + ' ' + pitch + '\n' ) fp.close()
The function random.random() generates a number between 0 and 1. If we subtract 0.5 from it and multiply the result by 100, then we get random numbers between -50 and 50. Adding these to 440 and gives us a range of pitches from 390 to 490.
To make the total volume add up to a particular value we need to calculate the volume of each individual note. The second line in the for loop takes care of that.
In the line that writes the string to the file, the addition of strings just concatenates them together.
In TextWrangler, create a python file out of the code above and run it. Then open up the notefile.txt file in TextWrangler and copy and paste the notes into the MacCsound score. The score should have one line that begins with an f to create the sin wave table and then the list of notes. Play the note.
What if we wanted to parameterize the frequency and the range? We
should create a function to create and write out the notes. Then we
could call the function with different frequency and range values. We
should also parameterize the start time and duration. Your function
might look like the following.
def makenotes( fp, start, duration, volume, pitch, pitchRange, N ): for i in range(N): # loop N times pitchStr = str( pitch + (random.random() - 0.5)*pitchRange ) volumeStr = str( volume / 100 ) startStr = str(start) durationStr = str(duration) fp.write( 'i 1 ' + startStr + ' ' + durationStr + ' ' + volumeStr + ' ' + pitchStr + '\n' )
After creating the above function, we can call it with different parameters and generate many more notes across time.
fp = file( 'notefile.txt', 'w') makenotes( fp, 0, 2, 12000, 220, 100, 100 ) makenotes( fp, 2, 2, 12000, 440, 100, 100 ) makenotes( fp, 4, 2, 12000, 880, 100, 100 ) fp.close()
Again, copy and past from notefile.txt into the MacCSound score.
What if we wanted to make a sound that was a whole series of note
clusters with random base pitches that plays quickly? We should be
able to do with by putting calls to makenotes into a loop.
start = 0 duration = 0.15 for i in range(100): pitch = random.randint( 50, 4000 ) pitchRange = pitch/2 makenotes( fp, start, duration, 15000, pitch, pitchRange, 100 ) start += duration
Copy the notes and play the sequence of sounds.
Create a few 10s sounds. Try using makenotes with different parameter
sets to see how different numbers of notes sound. There are several
different random distributions you can use, including a Gaussian
distribution (normal distribution). For example:
pitchStr = str( random.gauss( pitchBase, pitchRange ) )