I use a combination of code I wrote, and Csound. Csound is a very powerful text based sound generation and signal processing system. I use it for its ability to support sample based sound synthesis and microtonality. Over the years I’ve learned how to control more and more aspects of the audio environment using Csound.
Auto Just Intonation
Source code on github.
This repo contains code written in python to automatically tune Bach chorales to just intonation. The code optimizes the tuning of intervals in SATB chords to minimize the size of the numerator and denominator of the ratios of all the intervals in each chord, while maintaining closeness to the original 12 tone equal temperament notes.
It also contains jupyter notebooks that implement a kind of subtractive composition and orchestration of the resulting just intonation chorales. The algorithm is highly dependent on probabalistic and aleatoric methods. Each chorale consists of 16 measures, with up to 16 1/16th notes in each to result in 256 chords. I stretch them out, effectively slowing them down, into up to about 1,000 – 4,000 chords, then duplicate them to provide notes for each of the collections of 16 instruments, and then into groups of collections to make the ensemble. Then it selectively turns notes off for each instrument, or each group, or each collection for varying numbers of notes. I stretch out the octaves to encompass the full range of each instrument, staying in the same octave for a varying amount of time. In the end, something like 99% or more of the notes are either eliminated or attenuated until I am left with a density that makes musical sense.
In the end, what I am doing is retuning and orchestrating the Bach chorales, maintaining the harmonic direction that characterizing Bach, while drastically altering its presentation.
I use a pre-processor called Samples that takes a text file and creates the input to Csound. Here’s a write-up I did of Samples in 2004, revised over the years:
Samples: A Macro Pre-processor for Csound Files
by Prent Rodgers
Samples is a program I wrote back in 1996 to take a text file, process it, and produce a Csound input file ready for processing. I’ve been enhancing it every since, for over 20 years. The purpose of the program is to allow the composer to think in terms of notes, tuning systems, timbres, and other musical ideas, and not about digital signal processing. The best way to illustrate is to show an example. The following is a typical input file and the resulting output of the program. If none of this makes sense, then there might be a better place to start. A tutorial would help. If I write it, you will be the first to know. . .
Explanation of the Sample program:
The input file is read by the samples.pas program, interpreted, and written out as an input file that Csound understands.
The Csound code that is created is a sample-based instrument. It assumes that each note in the Csound score will include some key parameters, like instrument number (always 1), start time, duration, volume, tone in scale (I use Partch’s 43 tone scale), octave, stereo placement, envelope (I choose from about 10-20 different envelopes as needed), glissando (there are about 100 different glissandi), and up-sample (which is my term for picking an instrument sample that is some number of samples up or down from the one that was originally planned for this note).
The program translates note events into Csound score instrument note events. Note events in the input file are specified by variables with command names and values. The command names specify such things as note number, octave, velocity. Values are carried from one note to the next automatically.
There are command letters, that trigger the program to do something special. The most important are listed here:
- l Literal. Strip off the letter “L” and send the rest directly to the output file. This is useful for comments that you would like to appear in the output, or for actual Csound code that does not need alteration. Notice that nothing really happens to these lines. They are just moved to the output as is.
- @ Comment. Anything after the @ is ignored
- . Period. This is the macro facility. All the text after the period up to the first blank is the macro name. Anything after the period is the macro contents. The first macro in this score is “.C t0”. Define a macro called “C” and place the value “t0” in that macro. When you want to retrieve the macro, refer to C as “&C.” That is, ampersand, case sensitive macro name, period. The period is very important, and the program does very unpredictable things if the period is missing. If you fingernails burst into flames, it’s not my fault. I’m a composer, not an error handler. Macros are the basic tool that allow the composer to move higher and higher in abstraction. A macro can include other macros. In this case, all notes are referred to by their note names. Later on, &C. is used in &c__maj1-1. to refer to the first note in the c major scale. It can also be the second note in the a# major scale. Combine many macros together to create a chord. More on this later.
- n Name the instrument. This is part of a two part command n0m73. The number after the n is the channel and the number after the m is the sample based instrument number. In this case, we are assigning channel 0 to instrument number 73, which is defined in the file “mcgill.dat” as a cello martelle. The samples I use are from the McGill University Master Samples CD-ROM of orchestra instruments. A fabulous collection with lots of rough edges. I created a file that cross references the samples with the file names that contain the samples. The program takes the number in the m command and builds the Csound f tables necessary to reproduce the notes called for in the score. Automatically, sort of. It sometimes gets confused if you up-sample or down-sample too far, resulting in attempts to play trumpets with envelope f tables by mistake. Live with it.
- m Master Sample number. See n above for more complete description.
- c Channel number. This is used to represent the channel that the following note should use. A note is a set of commands that ends in a space or a new line. All the values carry from the previous note. For example, consider “c0t0d8 t14 t25”. This is a set of three notes in channel 0 (assigned to an instrument previously with n0m73). The first note is t0, or C. 8 beats later is played note t14, or E. 8 beats later is t25, or G. A major triad ascending. Neat. This allows different instruments to have different channels. Assign each channel to a different instrument with the nXmX command pair. Then play the instrument by defining notes with the c command.
- z Z value. I ran out of letters. Z value is my term for variations in the time that the note should start. For example, in normal drum playing, the drummer may not always hit the beat right on the nose. A high Z value means he misses it by a mile. A low Z value means he is close. Z0 is right on the money. Also useful for guitar chords to simulate a fast strum.
- r Random. There is lots of opportunity for indeterminacy in the program. This is one. An R value of 16 means the note always plays. Anything less means that it might be silent. R0 is always silent. R8 is 50% chance of sounding. Imagine a guitar player who doesn’t always stroke every note in the guitar chord every time.
- & Ampersand. What follows is the macro that needs to be replaced with the contents of the macro. If the macro name ends in an asterisk (*) then any macro that matches the string up to the point of the * is o.k. There is another variable, P, which is used to influence the macro chosen in this case. It might be a random choice, it might be exactly what what chosen the last time, it might be the next one, the previous one, it might be one that has been least chosen so far. See description of P below.
- How to pick a random macro. This is useful if you don’t really care which chord or note comes next. I like to use indeterminacy to pick the movement of a piece. I don’t always care if it picks C E G or E G C or some other chord. If you set up your macros with similar names up to a point, you can let the program pick which one to use. For example suppose you had the following macros:
Then suppose that at some point in the song, you didn’t care if the program picked C, D, E, or any other of the six notes. In the program, you could use the random macro facility like this:
- The program will then pick a macro that matches the string up to the asterisk. The result would be either &C., &D., &E., &F++., &G., or &A++. These would then get rendered as t0, t7, t14, t20, t25, or t34. Depending on the value of P, the program would pick either a random macro that met the terms of the macro name, or it might pick the next one in the sequence. P0 says pick the one least often picked before. P1 says pick try to pick any random one, but if you just picked it the last time, pick another. P8 says pick a random one. P16 says keep picking to try to match the one you picked last time, but if you try 8 times, pick the one you pick. P32 says pick the one you picked last time no matter what. This is surprisingly useful for many compositional techniques. I think it is anyway, and since I wrote the program, I get to add the features I use. Consider a piece where you don’t really care which theme is picked at any given moment, but if it is picked, you want to repeat it several times. But not too many times. And just because the bass part repeats, doesn’t mean the piano part should. Consider the possibilities.
- u Up-sample. This provides a mechanism to pick the “wrong” sample. U0 picks the one that was intended for a note. U1 picks one that is intended for higher notes and uses it for the requested tone. The result is a mellower sound. U255 picks one intended for lower tones, which results in a brighter sound. U values higher than 1 pick progressively higher samples, and more mellow sounds. U values less than 255 pick lower samples, and result in brighter tones. Imagine a six inch long piano, or a forty foot long piccolo.
- g Glissando. Apply a function table to the pitch value. g0 applies a flat table, which does nothing to the note. If you have other function tables that have slopes in them, then the pitch will be multiplied by the values in the function table as the note plays. This is useful for sliding a note up or down to a particular destination. Imagine a slide piano. A value of 1 in the table keeps the same pitch. 2 is an octave higher. G0 picks glissando table F301. G1 picks table F302, if it exists. If it doesn’t, Csound fails.
- h Hold value. This is how long a note should be held. The program takes integer values for all the commands. H0 holds the note for the same length as the note duration (see d below). H1 holds it for one beat. H24 holds it for 24 beats. I generally use a time value of 24 beats per measure, and then warp the time value with the Csound t command. Consider t 0 700. This sets the time to 700 beats per minute starting at time 0. Then consider t 0 700 2000 3000. This starts at 700 beats per minute at time zero, but gradually increases the time to 3000 at time 2000. Lots of opportunity to move around.
- t Tone. This is the particular tone to pick. It is the number that corresponds to a table in the Csound score. T0 is C. T7 is D.
- o Octave. Which octave to play. O5 is around the middle of the piano keyboard, but the sample files are not consistent.
- v Velocity or Volume. How loud to play. V0 is silent. V90 is pretty loud. Pick the value that makes sense. I use macros to keep the volume variable.
- d Duration. This is how long before the next note starts. It is not how long the note lasts. H (Hold) is how long it is held. Duration is how long before the next note in the channel starts. For example, to play a major chord, try c0t0d0h24 t14 t25d24. This is a C E G chord starting at time 0, and holding for 24 beats. An arpeggio would be stated as c0t0d24h24 t14 t25, in which each note would finish before the next one began. It’s all a matter of combining H values with D values in the proper way. D is the time before the next note in the channel, H is the time before this note stops sounding.
- s Stereo side. The S value specifies where in the stereo field the instrument will play this note. S0 is full right, S16 is full left. Or is it the other way around?
- e Envelope. This is the amplitude function table that will be applied to the note to specify its sound envelope. I normally have 10-20 different function tables set aside for different attack, decay, sustain and release slopes. Some crescendo, others decrescendo. Some grow and shrink over long periods of time. Lots of variety. Consider the typical Ellington orchestra horn part. Nothing simple here. Lots of swells and pops. Envelopes start at function table 298 and move down from there. e0 picks F298. E1 picks F297, and so on.
- w right side envelope. This is the mirror image of e Envelope, for the other channel. Having a left and right envelope allows the composer to have a sound move around the field of audio from left to right and back, depending on the shapes of the e and w envelopes.
- p Pattern of randomization. Low numbers of p mean that the preprocessor will try to avoid playing the same thing again. High numbers, like 20-31 mean he will try to repeat as much as possible. p16 is pure random. Anything higher will tend towards repetition, and anything lower will tend towards not repeating. Special cases are:
- p32 – always repeat whatever was chosen last time, regardless of how many times it is called.
- p33 – always pick the next one in the list of choices.
- p33 – always pick the previous one in the list pf choices.
- p35 – Use the Markov Chain Drunakrd’s Walk. Pick the preceding or next in the sequence.
- q overall tempo value. Csound has a special function table that sets the tempo depending on the value in the table. If you want each measure to have a different tempo, set q to a number that changes each measure.
- x write out a line’s content to xref.txt for debugging. This can be useful for tracking the value of a macro chosen at a given time.
The program also supports math operations on integers. In fact, only integers are allowed. For example, if you want to double the duration of a note, d*2 would do it. d/2 results in 1/2 the duration. v-12 cuts the velocity by 12. v+6 increases the velocity. Supported operators are divide, multiply, add, and subtract, but only on integers.
Samples Error Recovery
This program is terrible at error handling. It often will not realize that an error has occurred, and keep going with meaningless and impossible to track results. Some errors are discovered, but not many. Life is hard, get used to it. Some errors will result in music that is better than any you could have come up with on your own, so consider mistakes a learning lesson.
If the program discovers errors it can understand, if exits with error level set to non-zero values. Some of the detected errors include the following:
- Attempt to Find music file failed. Io =
- Attempt to Create Sco file failed. Io =
- Attempt to Find McGill Sample Description file. Io =
- Attempt to create AudNoteFile failed. Io =
- Could not find “McGill.dat” in current Directory. Io =
- Could not find instrument #’ Ins:4 ‘ in McGill.dat
- Invalid base number for instrument sample. Sample name: “‘ Input
- Too many instruments. Attempt to set FunctionTableNumber > 280
- Invalid sample offset for instrument sample. Sample name: “‘ Input
- Too many samples to handle. Max is ‘ McGillSamples
- Attempt to Find sample file ‘ FileName ‘ failed. Io =
- Ran out of room in 640k limit for another macro – Can you believe I still haven’t fixed this? Actually, I did back in 2012.
- No room for Macro named “‘ MacroName
- Invalid macro. Name too long “‘ MacroName
- A Macro near the one Named . . . did not terminate in a dot as it should have – This one is a real stinker. It is usually caused by forgetting to end a macro with a dot, but not always…
- Too many similar macros Macro called “‘ Name
- Wild Card Macro Not Found. Name = “‘ Name
- Macro Not Found. Name = “‘ Name
- Invalid factor. “‘ Factor – not add, subtract, multiply or divide, I guess?
- Attempt to Create Xref file failed. Io =
- lots of others that crop up at the worst possible time.
Fancy tricks and advice
The program creates a file called xref.txt, which contains a listing of all macros and how many times they were used. This can be helpful to track some of the indeterminacy and what the program has decided to do in a given situation. It also shows macros that were never used.
There are some files that need to exist for this program to work. The first of these is mcgill.dat, which contains a list of valid sample files and some characteristics of them (stereo or mono, looping or not, delayed start time, among other things).
The program creates some files when it runs to store some temporary information. These can be deleted when the program finishes. I use the following batch file to simplify execution from the DOS command line:
samples %1.mac %1.csd 1997
@rem set SFDIR=C:\Documents and Settings\Prentice Rodgers\My Documents\Prent\Csound
@rem set path=bin;%path%
@if errorlevel 1 goto error
@rem del notes.fil
@rem directcsoundcon %1.csd >csound.log
csoundav_con %1.csd >csound.log
@rem csound %1.csd >>csound.log
@rem direct~2 %1.csd
@rem echo direct~2 convolv.csd
@find /i "invalid" csound.log | sort >save.dat
@deldup save.dat con
@find /i "range" csound.log
@find /i "overall" csound.log
@find /i "init error" csound.log
@find /i "replacing previous ftable" csound.log
@find /i "Total processing time:" csound.log
copy samples.ly \cygwin\home\prodgers /y
@echo Samples %1.mac %1.csd 1997 failed, will not execute csound
The call to samples.exe includes the parameter 1997, which is used to distinguish something that changed in 1997. I can’t remember what it is, but it has something to do with using the McGill University Master Samples instead of some that shipped with my 1997 IBM Aptiva computer. Deldup is a DOS program that deletes duplicate error messages from the error log file Csound produces. The “find” commands look in the Csound log file for error messages that are important to me, like “out of range” or “missing sample files”.
Supported Operating Systems
Any Microsoft or other operating system that has a DOS box with minimum 640k of storage. I have run it under OS/2, Windows 95, 98, ME and 2000. It would not run under Windows 7, since Turbo Pascal is a 16 bit program. I ported to Free Pascal in 2012, and the program now runs in Windows 7. I ported Csound to Linux on System z at IBM, as a proof for how easy it could be.
It still generates executable files that run well on any command line interface. Since most high end Intel and AMD chips have 512KB cache, the entire program and working data set fit in cache. As a result the program runs real fast. A typical run with 60,000 individual notes completes in less than a minute. As it runs, the program puts out a description of the notes it is creating, every thousand notes.
Mercer Island, WA
December 6, 2001
Revised March 26, 2004 and
Snoqualmie Pass, WA
October 5, 2016
“It’s cold, but it’s a damp cold.”