Introduction
After designing Csound instruments for signal generation or production, a higher level of function is useful for the organization and manipulation of instruments leading toward the facilitation of phrases, sections, structures, form or complete compositions. Csound opcodes, which lead towards higher organization, are not one type of opcode, but are rather many different opcodes, dispersed throughout the general collection, each designed for a specific purpose. Csound contains several opcodes for instrument manipulation as tools to aid in the creation of larger structures which we might call macro or meta level processing. These opcodes allow for compiling, building, combining, mixing, loading, reading, removing, and time placement, among other tasks, which we may describe as meta things. Versions used for this article were Fedora 24, Csound Version 6.08.
I. Top-down, Bottom-up
Working at the level of instrument design and audio quality is important for a solid beginning design foundation, but at a certain point we need to begin to think about the organization of the performance of the sounds. In Csound, the note list or score is still the standard means for placing instrument output in time, although it also possible to chose a scoreless design. On a higher level we might design lists of note lists, which then can become a meta level task. Gottfried Michael Koening has written that "sound production is not interesting as a composition process until it becomes integral, i.e. until the structure-generating grammar refers to sound data instead of to given sound elements"[1]. Koening seems to be writing about the awareness of process and procedure, moving from the small to the large, and by extension to the extra large.
In Csound, many opcodes were created to fulfill a needs requirement. For example, a tool is needed to perform a specific task, and because that task might also be useful to others, an opcode is implemented. An opcode can fall into a general pattern of usability or usefulness based on how amazing, functional, predictable, robust or utilitarian its performance. In some cases a developer discovers the need for an opcode thru their own creative work, for performing calculations, opening pathways, facilitating interoperability, establishing a theorem, or supporting a frontend feature, among other tasks. The opcode is implemented for all to use, but others may not utilize the tool in quite the same manner as the original developer. In practice, as opcodes are placed into blocks, based on their output values, instruments are fashioned, and a functional orchestra file or files begin to emerge. Then moving to a higher level of organization, additional opcodes, so designed, can provide the means to further the work of our existing instruments and scores. Thus Koening makes a good point about sound production becoming compositionally interesting when the structure-generating grammar refers to the sound data, instead of the sound elements.
Much of the musical discussion concerning the progress towards a higher level of organization tends to be centered around the general subject of algorithmic composition. Nick Collins cites several authors and theories in his article "Musical Form and Algorithmic Composition"[2] and writes about the bottom-up (material-driven) or top-down (template-determined) construction for works. While this article is not about composition in the main, we can continue the top-down, or bottom-up approach looking also at computer coding in general. Turing, for example, wrote about the bottom-up approach as connectionism, and interestingly found the top-down approach more useful for symbolic artificial intelligence[3]. But the attempt here in this article is only to describe a few opcodes which may be useful in what might be described then as a bottom-up approach.
Viewing the current list of opcodes there can be many opcodes which could be seen as furthering the work of instruments and scores. Compileorc, compilecsd, and compilestr can be utilized to help with the initial compilation of external files. Scoreline, scoreline_i, readf, readscore, directory, and ftsamplebank read from external files and directories. Ficlose, and fiopen can be used for saving, closing, and opening files, for example. Below is a short list of additional opcodes which may be useful for higher level organization or meta things.
-
clockon, clockoff - turning on/off clocks
cpumeter, cpuprc - memory management
system - external operating system calls
imageload - external image file loading
insremot - controlling remotely
remove - remove an instrument definition
setscorepos - set score playback position
turnoff, turnon - turning on/off instruments
macros - various macros which function like C preprocessor macros
zak - mixing, combining instruments
various opcodes for mixing - combining sounds, sources, or streams
There may be other opcodes too, which help with overall higher level organization, but only a few of these opcodes with specific examples will be provided below.
II. Compiling
Compilecsd, compilestr, and compileorc opcodes [4][5][6] by Victor Lazzarini, all allow for compiling a new orchestra from an ASCII file. The opcodes, defined in the file compile_ops.c, rely also upon their API function counterparts listed in the file csound.h, and compile, but do not perform, one or more instruments at init time[4]. With these opcodes, we are able to read code from external files, and place the files as strings in memory to be compiled for the current running engine. Thus we could, for example, utilize one file to call several external files, place them in memory, compile them, and then add code to perform the compiled instruments in some order by calling opcodes such as using schedule or event.
You can view the examples for the opcodes shown below in "The Canonical Csound Reference Manual" using the links in the References section below, and you can also download and view the complete examples shown in this article from the following link: meta.zip. In the examples provided, proof of concept is rendered by simple beeps. The meta or higher level organization is the focus for these examples, and not the details of the sound performing instrument or opcodes, which in most cases is represented by a simple oscillator. Therefore all the examples are meant to be considered basic ones, or starter code, to be expanded upon more creatively for a final compositional result. A detailed analysis of a lengthy, complex compositional example is beyond the scope of this article which is only intended to present some initial possibilities using several opcodes, as tools to aid in creativity. To view the console output, and print statements for data of some of the examples, it is helpful to have a compiled Csound version without parser output. For more efficient compilation, all printing could be turned off.
For compiling, compilecsd can return a negative value, shown the code except shown below, to indicate the compilation failed. External file or files should be able to be compiled or else an error will result when trying to employ this opcode.
ires compilecsd "instr1 does_not_exist.csd" print ires ; -1 as could not compileires compilecsd " /home/myfiles/mytest.csd" print ires ; 0 as compiled successfully
Using compilestr, it is also possible to write and compile an instrument from within an existing outer or encapsulating instrument block, and perform it with the help of the opcode scoreline_i. The code below performs one long tone from instr 1
, as the outer instrument, but also calls the inner instrument, 2002
with four shorter tones to be performed at the same time.
<CsoundSynthesizer> <CsOptions> csound -s -d -+rtaudio=ALSA -odevaudio -b1024 -B16384 </CsOptions> <CsInstruments> sr = 44100 kr = 4410 ksmps = 10 nchnls = 2 0dbfs = 1 instr 1 kenv linseg 0, p3*0.25, .5, p3*0.75, 0 a1 oscil kenv, p4, p5 outs a1, a1 ires compilestr {{ instr 2002 kenv linseg 0, p3*0.25, .5, p3*0.75, 0 a1 oscil kenv, p4, p5 outs a1, a1 endin }} print ires ; ... and returns 0 if successful ;call the new instrument from within instr1 scoreline_i {{ i 2002 0 5 400 1 i 2002 6 5 500 2 i 2002 12 5 600 3 i 2002 18 5 500 4 }} endin </CsInstruments> <CsScore> ; One period of a sine wave f 1 0 8192 10 1 ; One period of an approximate sawtooth wave f 2 0 8192 10 1 0.5 0.333 0.25 0.2 0.1667 0.1429 0.125 0.111 0.1 ; One period of an approximate square wave f 3 0 8192 10 1 0 0.333 0 0.2 0 0.1429 0 0.111 0 ; One period of an approximate triangle wave f 4 0 8192 10 1 0 -0.111 0 0.04 0 -0.0204 0 0.0123 0 -0.0083 0 0.0059 i1 0 20 1200 4 </CsScore> </CsoundSynthesizer>
III. Scoreline
Scoreline and scoreline_i [7][8] by Victor Lazzarini, are opcodes which can be used to read lines of score from within an instrument, or from an external file, if used along with the readf, or readfi opcodes[9][10] by John ffitch and Joachim Heintz. The code excerpt shown below shows the instrument reading four score lines from an external file at i-rate. The complete example, along with score lines in an external file, are provided with the downloadable examples for this articles.
gSfilename = "myscore.txt" instr 1 read: Sline, iline readfi gSfilename scoreline_i Sline if iline < 4 then igoto read else turnoff endif endin
IV. Ftsamplebank
The source code for ftsamplebank and directory [11][12] by Rory Walsh, reward further study. Defined in ftsamplebank.cpp within the Opcodes directory of the Csound source code, the opcodes are not listed per usual in entry.c or loaded dynamically, but instead are listed as external modules defined in int_static_modules.c of the Top directory of the source code. Thus ftsamplebank.cpp, which includes the code for directory, is a statically linked, self-contained external module, and after a cmake build, on my machine, results in the library, ftsamplebank.so.
The example below shows the use of ftsamplebank, to read a directory of similar formatted samples, such as all .wav files, at the k-rate. kNumberOfFiles
holds the output value of the opcode which can be utilized in a loop to access the samples which are loaded into function tables and performed using the event opcode.
instr 1 kFirstTableNumber = 60; kFileCount init 0 kTrig = 1 kNumberOfFiles ftsamplebank "/ftsamplebank/examples", kFirstTableNumber, kTrig, 0, 4, 0 play: event "i", 1000, kFileCount + 1 , 1, kFirstTableNumber+kFileCount kFileCount = kFileCount+1 if (kFileCount < kNumberOfFiles) then kgoto play elseif (kFileCount >= kNumberOfFiles) then turnoff endif endin instr 1000 iTable = p4 aOut loscil3 1, 1, iTable, 1, 0; outs aOut, aOut endin
V. Directory
Directory outputs string arrays of file names it reads from an indicated path to a directory which can be performed by an opcode such as schedule. With the use of directory, the sample types do not necessarily need to be the same format, but your code should take that into account too, by passing the correct file extension as a string. Also "The Canonical Csound Reference Manual" mentions it is more efficient to use an array to play several files when using diskin2 for the performance part of your code[12]. The example below also shows instr1
reading the directory, storing the files to an array as SFilenames
, applying the schedule opcode, and in instr2
using an a-rate array with diskin2 to perform the samples.
<CsoundSynthesizer> <CsOptions> csound -s -d -+rtaudio=ALSA -odevaudio -b1024 -B16384 </CsOptions> <CsInstruments> sr = 48000 kr = 4800 ksmps = 10 nchnls = 2 0dbfs = 1 giCntr init 0 giNum init 0 gaArr[] init 7 giArrLen = 7 gSArr[] init giArrLen ;------------------------------------- instr 1 SFilenames[] directory "/directory/mydirectory", ".wav" iNumberOfFiles lenarray SFilenames printf_i "***iNumberOfFiles = %d \n", 1, iNumberOfFiles idur = 5 iwhen = 0 while giCntr < iNumberOfFiles do schedule 2, iwhen, idur iwhen = iwhen + 5 printf_i "***instr1, GiCntr = %d \n", 1, giCntr giCntr = giCntr+1 od endin ;------------------------------------- instr 2 gSArr[] directory "/directory/mydirectory", ".wav" iNumberOfFiles lenarray gSArr printf_i "----instr2, GiNum = %d \n", 1, giNum if (giNum == 6) then turnoff else igoto play endif play: gaArr[giNum] diskin2 gSArr[giNum], 1, 0, 0, 0, 1, 0 outs gaArr[giNum], gaArr[giNum] giNum = giNum+1 end: endin </CsInstruments> <CsScore> i1 0 30 </CsScore> </CsoundSynthesizer>
VI. Include
Include can be of two types: the c or c++ preprocessor include
, or Csound's own #include[13] by John ffitch.
We can utilize the Csound API, compiling it with c or c++, using the c or c++ preprocessor to include functions from additional applications within our API code. The preprocessor adds and calls header files which contain data needed for compilation for certain parts of the code. We can demonstrate the use of include
as an additional API application header by performing a simple Csound .csd file, and at the same time including additional headers for something simple such as opening a GL window to display a shape by including the use of gl and freeglut headers in our code. This approach is also shown in the readf example further below, where the CsoundAPI and Boost are used to generate files of random numbers and also perform those numbers as frequencies. To compile the code below, linking to the Csound library and the additional required libraries, such as GL, GLU, and glut is also needed.
gcc -Wall -w -O2 -DUSE_DOUBLE -I/opt/csound6-git -I/usr/include -L/usr/lib64 -lGL -lGLU -lglut -o triangletest triangletest.c -L. -lcsound64 -lsndfile -ldl -lm -lpthread -lfftw3
#include "GL/freeglut.h" #include "GL/gl.h" #include "stdio.h" #include "stdlib.h" #include "include/csound.h" uintptr_t csThread(void *clientData); typedef struct { int result; CSOUND *csound; int PERF_STATUS; } userData; void drawTriangle() { glClearColor(0.4, 0.4, 0.4, 0.4); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glBegin(GL_TRIANGLES); glVertex3f(-0.7, 0.7, 0); glVertex3f(0.7, 0.7, 0); glVertex3f(0, -1, 0); glEnd(); glFlush(); } int main(void) { int finish; void *ThreadID; userData *ud; ud = (userData *)malloc(sizeof(userData)); MYFLT *pvalue; const char *argv[] = {"csound", "-+rtaudio=ALSA", "-odac", "-s", "-d", "-b1024", "-B1024","/triangletest.csd" }; char *myargv [1]; int myargc=1; myargv [0]=strdup ("Myappname"); //glut init and create window glutInit(&myargc, myargv); glutInitDisplayMode(GLUT_SINGLE); glutInitWindowSize(500, 500); glutInitWindowPosition(100, 100); int win = glutCreateWindow("OpenGL - Creating a triangle"); //create and compile csound ud->csound = csoundCreate(NULL); ud->result = csoundCompile(ud->csound, 8, (char **)argv); if (!ud->result) { ud->PERF_STATUS = 1; ThreadID = csoundCreateThread(csThread, (void *)ud); //draw the glut gl triangle from the thread glutDisplayFunc(drawTriangle); glutMainLoop(); } else { return 1; } void glutDestroyWindow(win);
//triangletest.csd <CsoundSynthesizer> <CsOptions> csound -s -d -+rtaudio=ALSA -odevaudio -b1024 -B16384 </CsOptions> <CsInstruments> sr = 44100 kr = 4410 ksmps = 10 nchnls = 2 0dbfs = 1 instr 2002 kenv linseg 0.01,p3*.33,1,p3*.33,1,p3*.33,0.01 a1 oscil kenv, 440, p4 outs a1, a1 endin </CsInstruments> <CsScore> ; One period of an approximate sawtooth wave f 2 0 8192 10 1 0.5 0.333 0.25 0.2 0.1667 0.1429 0.125 0.111 0.1 i 2002 0 4 2 </CsScore> </CsoundSynthesizer>
From within a Csound file we can also utilize Csound's own #include statement to include portions of code which may be distributed in various other files. For example we may have score data placed in a separate .inc file, and call that file using a Csound #include statement from within the score section of a Csound .csd file, as shown below.
<CsScore> #include "/examples/mylist.inc" e </CsScore>
The contents of the external .inc file may represent the usual score statements.
;mylist.inc ; One period of a sine wave f 1 0 8192 10 1 ; One period of an approximate sawtooth wave f 2 0 8192 10 1 0.5 0.333 0.25 0.2 0.1667 0.1429 0.125 0.111 0.1 ; One period of an approximate square wave f 3 0 8192 10 1 0 0.333 0 0.2 0 0.1429 0 0.111 0 ; One period of an approximate triangle wave f 4 0 8192 10 1 0 -0.111 0 0.04 0 -0.0204 0 0.0123 0 -0.0083 0 0.0059 i 2002 0 5 1 i 2002 6 5 2 i 2002 12 5 3 i 2002 18 5 4
VII. Readf
Readf [9] reads files from disc. The following example shows using fprints in the first instrument to write an external file of random numbers. The second instrument calls the event opcode to perform instrument 2. Instrument 3 uses readk to read the external frequences and perform them using poscil.
<CsoundSynthesizer> <CsOptions> csound -s -d -+rtaudio=ALSA -odevaudio -b1024 -B16384 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giSine ftgen 0, 0, 2^10, 10, 1 gSname = "/readf/test." gSext init "something" gSwholename init "something" instr 1 ;write files, at i-rate icntr init 0 ifreq init 0 until icntr >= 5 do iprint init 0 gSext sprintf "%d", int(icntr) Sout strcat gSname, gSext ;puts Sout, icntr while iprint < 10 do ifreq random 300, 2300 fprints Sout, "%f\n", ifreq iprint = iprint + 1 od icntr = icntr + 1 enduntil endin ;--------------------------------- instr 2 ;calls event_i at i-rate icntr init 0; iNumberOfFiles init 5; iname init 0; iwhen init 0; idur init 3; while icntr < iNumberOfFiles do print icntr, iname event_i "i", 3, iwhen, idur, iname iwhen += idur icntr += 1 iname += 1 od endin ;--------------------------- instr 3 ; read and perform files itrig init 1; gSext sprintf "%d", p4 ;convert kcntr to string printf_i "ext is now: '%s'\n", itrig, gSext gSwholename strcat gSname, gSext ;concat strings printf_i "filename is now: '%s'\n", itrig, gSwholename kfreq readk gSwholename, 7, .25 if (kfreq > 0) then printk2 kfreq endif aout poscil 1, kfreq, giSine outs aout, aout endin </CsInstruments> <CsScore> i 1 0 5 i 2 6 25 e </CsScore> </CsoundSynthesizer>
A final example shown uses the Csound API and Boost to first generate an external file of random numbers, then perform a simple .csd file from the API, to read and play the random generated numbers as a list of frequencies. Parts of the code have been omitted here to save space here, however the complete example is provided from the downloadable examples link listed above.
uintptr_t csThread(void *clientData); typedef struct { int result; CSOUND *csound; int PERF_STATUS; //int initialized; } userData; int main() { int finish; void *ThreadID; userData *ud; ud = (userData *)malloc(sizeof(userData)); MYFLT *pvalue; const char *argv[] = {"csound", "-+rtaudio=ALSA", "-odevaudio", "-s", "-d", "-b1024", "-B16384","readBoost.csd" }; //BOOST code begins here string path ("/test/readf"); int k; double x; // Define a base random number generator and initialize it with a seed. boost::minstd_rand baseGen(static_cast(std::time(0))); // Define a distribution const double median = 1050.0; const double sigma = 600.0; boost::cauchy_distribution<> cauReal(median, sigma); // Define a random variate generator using the base generator and distribution boost::variate_generator<boost::minstd_rand&, boost::cauchy_distribution<> > cauRealGen(baseGen, cauReal); std::cout << endl; std::cout << "Ten samples of reals in a cauchy distribution:\n" <<endl; //file IO string CauchyDatAppend ("/cauchy."); string CauchyDatPath = path + CauchyDatAppend; //adds numbers as file extension while (k < 5) { ofstream outf1(CauchyDatPath.c_str() + std::to_string(k)); // ...stdout and also print to file for(int i = 0; i < 10; i++) { next: x = cauRealGen(); if (x<=0 ) goto next; //std::cout << cauRealGen() << '\n'; std::cout << x << '\n'; //print to file outf1 << x << '\n'; } std::cout << '\n'; outf1.close(); k++; } //end while //BOOST code ends here ud->csound = csoundCreate(NULL); ud->result=csoundCompile(ud->csound, 8, (char **)argv); if (!ud->result) { ud->PERF_STATUS = 1; ThreadID = csoundCreateThread(csThread, (void *)ud); } else { return 1; } /* keep performing until user types a number and presses enter */ scanf("%d", &finish); ud->PERF_STATUS = 0; csoundDestroy(ud->csound); free(ud); return 0; } /* performance thread function */ uintptr_t csThread(void *data) { userData *udata = (userData *)data; if (!udata->result) { while ((csoundPerformKsmps(udata->csound) == 0) && (udata->PERF_STATUS == 1)); csoundDestroy(udata->csound); } udata->PERF_STATUS = 0; return 1; }
;readBoost.csd giSine ftgen 0, 0, 2^10, 10, 1 gSname = "/test/cauchy." gSext init "something" gSwholename init "something" ;--------------------------------- instr 1 ;event_i i-rate icntr init 0; iNumberOfFiles init 5; iname init 0; iwhen init 0; idur init 3; while icntr < iNumberOfFiles do print icntr, iname event_i "i", 2, iwhen, idur, iname iwhen += idur icntr += 1 iname += 1 od endin ;--------------------------- instr 2 ; read files itrig init 1; gSext sprintf "%d", p4 ;convert kcntr to string printf_i "ext is now: '%s'\n", itrig, gSext gSwholename strcat gSname, gSext ;concatenate strings printf_i "filename is now: '%s'\n", itrig, gSwholename kfreq readk gSwholename, 7, .25 if (kfreq > 0) then printk2 kfreq endif aout poscil 1, kfreq, giSine outs aout, aout endin </CsInstruments> <CsScore> i 1 1 25 </CsScore>
VIII. Conclusion
External file inclusion for compilation or the use of opcodes which read needed data from external files, utilized in various combinations, can help provide powerful methods for the expansion of a variety of instruments, note lists, and data. Being able to write code which manages other bits of code is a higher level organizational design which can lead to efficient and productive results.
A brief look at the console output, below, for the scoreline_i.csd example which reads four lines of score from an external file, shows the efficiency for compiling, sorting, and performance.
Elapsed time at end of orchestra compile: real: 0.005s, CPU: 0.004s Elapsed time at end of score sort: real: 0.005s, CPU: 0.005s Elapsed time at end of performance: real: 8.690s, CPU: 0.203s 388 2048 sample blks of 64-bit floats written to devaudio
If the printing of data to the console is disabled in the example, then there is a slight gain in overall performance when running the example again and viewing the console output.
Elapsed time at end of orchestra compile: real: 0.002s, CPU: 0.002s Elapsed time at end of score sort: real: 0.003s, CPU: 0.002s Elapsed time at end of performance: real: 8.649s, CPU: 0.179s 388 2048 sample blks of 64-bit floats written to devaudio
When our Csound session work calls for a higher level of function for the organization and manipulation of instruments, Csound contains several powerful, efficient, and useful opcodes for instrument manipulation as tools to aid in the creation of larger structures for meta level processing.
References
[1]Gottfried Michael Koening, 1980. "Composition Processes." In M. Battier and B. Truax, eds. "UNESCO Computer Music: Report on an international project including the international workshop held at Aarhus, Denmark in 1978." Canadian Commission for UNESCO, pp. 105-126.
[2]Nick Collins, 2009. "Musical Form and Algorithmic Composition." Contemporary Music Review, Vol 28, No. 1, February 2009, 103-114.
[3]Jack Copeland, 1999 - 2016. "What is Articicial Intelligence." AlanTurning.net, Reference Articles on Turing, "Top-Down AI vs Bottom-Up AI." [Online] Available: http://www.alanturing.net/turing_archive/pages/reference%20articles/what_is_AI/What%20is%20AI09.html. [Accessed November 20, 2016].
[4] Barry Vercoe et al., 2003. "compilecsd." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/compilecsd.html. [Accessed November 20, 2016].
[5] Barry Vercoe et al., 2003. "compilestr." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/compilestr.html. [Accessed November 20, 2016].
[6] Barry Vercoe et al., 2003. "compileorc." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/compileorc.html. [Accessed November 20, 2016].
[7] Barry Vercoe et al., 2003. "scoreline." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/scoreline.html. [Accessed November 20, 2016].
[8] Barry Vercoe et al., 2003. "scoreline_i." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/scoreline_i.html. [Accessed November 20, 2016].
[9] Barry Vercoe et al., 2003. "readf." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/readf.html. [Accessed November 20, 2016].
[10] Barry Vercoe et al., 2003. "readfi." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/readfi.html. [Accessed November 20, 2016].
[11] Barry Vercoe et al., 2003. "ftsamplebank." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/ftsamplebank.html. [Accessed November 20, 2016].
[12] Barry Vercoe et al., 2003. "directory." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/directory.html. [Accessed November 20, 2016].
[13] Barry Vercoe et al., 2003. "#include." The Canonical Csound Reference Manual, Version 6.07 [Online] Available: http://csound.github.io/docs/manual/include.html. [Accessed November 20, 2016].
Additional Links and Info
Beman Dawes, et al., 2016. "Boost c++ Libraries." [Online] Available: http://www.boost.org/. [Accessed November 20, 2016].
Khronos Group. "OpenGL SDK." [Online] Available: https://www.opengl.org/sdk/. [Accessed November 21, 2016].
Khronos Group. "GLUT - The OpenGL Utility Toolkit." [Online] Available: https://www.opengl.org/resources/libraries/glut/. [Accessed November 21, 2016].
Biography
Jim Hearon has been Csounding since the early 1990s, and has helped to edit "Csound Journal" since 2005. Jim's articles continue to cover a wide range of topics and approaches. In his spare time Jim also enjoys playing electric violin, piano, and tenor guitar.
email: j_hearon AT hotmail.com