CS 198: Project #8

Project 8: Patterns and Clouds

This week we'll look at how we can combine rhythms and clouds of notes in a musical way.


Creating on FM Note

  1. Download the following fm instrument template file. Open it in TextWrangler and take a look at it. The instrument is an implementation of the foscili function using two oscillators so that all of the relationships are clearly defined.

    What we want to do is grab the text from this template file, add our own notes, and then write it all out to a new file. In python this is easy to do. We open the template file, read all of the lines, close the template file, then we write all of the lines into a new file.

    def copyTemplate( fp, tfilename ):
        template = file( tfilename, 'r' )
    
        lines = template.readlines()
    
        template.close()
    
        for line in lines:
            fp.write( line )
            
        return
    

    After we write our notes, we also want to write the closing lines of a Csound file, as below.

    def endScore( fp ):
        s = 'e\n</CsScore>\n</CsoundSynthesizer>\n'
        fp.write(s)
        return
    

    Finally, we can combine these two functions in a main function, as below.

    def main():
        
        fp = file( 'tmp.csd', 'w' )
    
        copyTemplate( fp, 'fm-template.csd' )
    
        # write out any notes here
    
        endScore( fp )
    
        fp.close()
    
    
    if __name__ == "__main__":
        main()
    

    Try running this program. It should produce a Csound file with no notes in it.

  2. Now we want to write a function that writes a single note for the FM instrument. The FM instrument takes in 12 parameters. The parameters are below, along with each data type
    1. Instrument index (integer)
    2. Start time (float)
    3. Duration (float)
    4. Amplitude (float)
    5. Frequency (pitch notation)
    6. Carrier Ratio (float)
    7. Modulator Ratio (float)
    8. Maximum index value (float)
    9. Minimum index value (float)
    10. Base Wave Table (integer index)
    11. Envelope Wave Table (integer index)
    12. Index Shape Wave Table (integer index)

    We want to write a function that takes in a file reference and all 12 parameters, generates a string with all of the parameters in the string, and then writes the string to the file.

    The easiest way to write out the string in the format we want is to use a formatted string. A formatted string can contain fields into which python sticks values from variables. The fields define how the value should be formatted. What you need to know are the following fields.

    • %8.3f - writes a floating point number in an 8 space field with 3 decimals.
    • %8d - writes an integer in an 8 space field. %s - writes a string %02d - writes an integer in a 2 space field, pre-padding with zeros if necessary.

    To create a formatted string with one float field, one integer field, and one string field, we could do the following.

    s = '%8.3f %8d %s' % (3.2, 4, 'blah')
    

    The above would make the string ' 3.200 4 blah'

    Using a format string, we can write a function to generate a note line for the FM instrument as follows.

    def writeFMNote( fp, start, duration, amp, freq, carRatio, modRatio, maxidx, minidx, waveTable, envTable, idxTable):
        s = 'i 1 %8.3f %8.3f %8d %s %8.3f %8.3f %8.3f %8.3f %8d %8d %8d\n' % ( start,
                                                                               duration,
                                                                               amp,
                                                                               freq,
                                                                               carRatio,
                                                                               modRatio,
                                                                               maxidx,
                                                                               minidx,
                                                                               waveTable,
                                                                               envTable,
                                                                               idxTable )
        fp.write(s)
    
  3. Generate a function that writes a cloud of FM instrument notes. Fix some of the parameters and have the function randomly vary others.
  4. Define a rhythm as list of triples: duration in ticks, an amplitude, and a pitch. For example, the list below is a simple 4-beat per bar rhythm. In each sublist, the first value is the number of ticks, the second is the amplitude, and the third is the pitch.
        beat1 = [[6, 8000, '5.00'], [6, 6000, '5.00'], [6, 6000, '5.00'], [6, 6000, '5.00']]
    
  5. We can now define a function that writes out N bars of the rhythm. The algorithm is as follows.
    def fmTune(fp, tunelist, bars, startTime, tickDuration ):
    
        # assign to a variable curTime the value in startTime
        # for the number of bars
            # assign to a variable tick the value 0
            # assign to a variable note the value 0
            # while the value of note is less than the length of the tunelist
                # if tick is equal to 0
                    # write out the fm note. 
                    # Use curTime as the start time argument
                    # Use tunelist[note][0]*tickDuration as the duration argument
                    # Use tunelist[note][1] as the amplitude argument
                    # Use tunelist[note][2] as the pitch argument
                    # specify the rest by hand, for now
                # increment tick by 1
                # increment curTime by tickDuration
                # if tick is greater than the current note curation (tunelist[note][0])
                    # assign to tick the value 0
                    # increment note by 1
        # return
    

    In your main function, have it write two bars of the beat1 rhythm.

        fmTune(fp, beat1, 2, 0, 0.08)
    

    Run your python file and then run csound and listen to the result.

  6. Mix the fmMix and fmTune functions to create a piece.
  7. How can we create shape to our fmMix function? One way is to put a triangular enveloped defined by the peak time as a fraction of the total time. The following shows how to compute an amplitude modulator that will increase from zero at the start to 1 at the peak time and then decrease linearly to zero at the end.
            notepos = startTime/totalTime
            if notepos < peaktime:
                ampmod = notepos/peaktime
            else:
                ampmod = 1.0 - (notepos - peaktime) / (1.0 - peaktime)
    

    If you multiply the amplitude argument to fmNote by ampmod, then the mixture of sounds will collectively increase and decrease.

    A short example piece.