CSOUND JOURNAL ISSUE 23

INDEX | ABOUT | LINKS






Introduction

With the Python opcodes[1] in Csound it is now possible to access a database. We use SQLite[2] here since it utilizes a flat file type that can be accessed using SQL (Structured Query Language) commands. In this first article, or part I, we will learn some basics about databases and SQL, how to insert scores and samples into a database using the FireFox SQLite manager addon[3], and how to retrieve and use these in Csound. The examples provided also includes a sample database, that already include some midi drum scores and also contains guitar and piano samples. In a next article, or part II, of this series, I will discuss how to insert data, such as midi events, from Csound into the database. Some knowledge of the following subjects is useful: Python, SQL and the Csound Python Opcodes for a complete understanding of topics covered in this article.

I. The Database Advantage

A container for many kinds of data

A typical database is a unit that can contain all sorts of data, usually grouped in tables. Tables contain related data, and can also relate to other tables. In our case there is a table for samples that contains actual binary samples, the name of the sample and two midi notes that denote the range where the sample should be used. You can download and view the sample database and complete examples shown in this article from the following link: sqlite.zip. The code snippet below shows the contents of the samples table. The columns in this table are: rowid, sample, name, midibase and midimax. There are now 13 rows (or records) in this table. When defining a column name, you also have to specify the data type to be included in the table.

The create statement, shown below, reveals what these datatypes are, where

CREATE TABLE "samples" ("sample" BLOB DEFAULT (null) , "name" TEXT, "midibase" INTEGER, "midimax" INTEGER)

SQLite plugin

Figure 1. Screenshot of free SQLite plugin.

The picture above is a screenshot of the free SQLite plugin, that has to be installed in the FireFox web browser. This permits the creation of a database with tables inside it, define column names and their data types, browse the tables and enter data into it. To summarize: a database is a container for related data.

Queries

The SQL language allows us to retrieve certain data from a database, or put data into it. For instance, the following query would return the name and the sample using the name "guitarA3". This would return the name column and the sample column of one record.

SELECT sample, name FROM samples WHERE name = 'guitarA3'

If you wanted all columns, then use the query shown below.

SELECT * FROM samples WHERE name = 'guitarA3'

This would return the sample, name, midibase and midimax column of the record. To get all the records where the name starts with "JazzGuitar", use the LIKE query.

select name, sample from samples where name like 'JazzGuitar%'

All six records associated with the term "JazzGuitar" would be returned. To see this in action, unzip the sqlite.zip file, provided in the downloads links above, to a folder of your choice.

II. The example csds

A score player

The content of the unzipped, downloadable folder listed above, shows the following files:

The SQLite database test.db, included in the folder, also contains a table called scores. It contains three midi drum scores with the names: 'Rock3', 'Bossa' and '12BRock'. Each score is one large text field containing lines like the ones shown below.

i10	0.000000	0.500227	84	37
i10	1.000000	0.500227	84	37
i10	2.000000	0.500227	64	37
i10	2.500000	0.500227	84	37
i10	3.000000	0.500227	64	37

Basically, the lines shown above, are normal score lines for instrument 10 (a General Midi drum instrument). Let us take a closer look at the first example, sqlite_score1.csd. Some definitions made in the the header block of the csd (instrument 0) are shown below.

pyinit

pyruni {{
import sqlite3 as lite
sco = []		# define a global list that will contain the score
def getscore(i): 
    global sco	# have to use this in python when you modify a global 
			
    # variable in a function
    con = lite.connect('test.db')
    cur = con.cursor()
    cur.execute("SELECT score FROM scores WHERE name='Rock3' ")
    rows = cur.fetchall()
    rowstring = rows[0][0]
    con.close()
    sco = rowstring.rstrip('\\n').split('\\n')
    return 1.0 
 

The line, import sqlite3 as lite, tells Python to use the built-in library sqlite3. In the function getscore the following code is executed:

con = lite.connect('test.db')
cur = con.cursor()
cur.execute("SELECT score FROM scores WHERE name='Rock3' ")

In the code above, a connection is made with the database, then a cursor is created from this connection, then the query is executed to get the score for 'Rock3'. The score is read into a python global variable named sco. There is also a function defined in the file sqlite_score1.csd, as shown below.

 def getnext(idx): line = sco[int(idx)]	
 elms = line.split('\t')
 kwhen = float(elms[1])	# start time
 kdur = float(elms[2])	# duration
 p4 = float(elms[3])		# midi volume for drum note
 p5 = float(elms[4])		# midi drum note number
 return kwhen, kdur, p4, p5

This function is called from the csd in a k-rate loop from within sqlite_score1.csd, as shown below.

instr Schedule_score	
kCycle timeinstk
kIndex = 0
;	get score length from the global python 'sco' list
kMax pyeval "float(len(sco))"	
while kIndex < kMax do		
  ; get next score line from score
  kwhen, kdur, kp4, kp5 pycall4 "getnext", kIndex		
  schedkwhen kIndex, 0, 1, 10, kwhen, kdur, kp4, kp5		
  kIndex += 1
od

In other words, all the events in the score are queued using the schedkwhen opcode to be executed at their appropriate time. In this example the name of the score ('Rock3') is hard-coded but it would be nice to select the name dynamically. Since we cannot pass strings from Csound to the Python opcodes, a workaround is shown in the second example, sqlite_score2.csd.

In pyruni there is now a list defined, as shown below.

pattern = ['Rock3', 'Bossa', '12BRock']

The function getscore(i) has an argument i, being the index in this pattern list. The select query in this function is shown below.

cur.execute("SELECT score FROM scores WHERE name='%s' " % pattern[int(i)])

The index loads the appropriate pattern name. In the csd there is also an instrument named Getscore_n.

instr Getscore_n
ival pycall1i "getscore", p4
endin

The instrument is called from the score as shown below.

i "Getscore_n" 0 .1 1
i "Schedule_score" 1 15
i "Getscore_n" 16 .1 2
i "Schedule_score" 17 27

Of course it would be nice not to have to hard-code the pattern list in pyruni as well but instead retrieve the names from the database. This is left as an exercise for the reader, however a hint is given below where the SQL query for this is shown below.

SELECT name from scores

A midi sample player

Creating a midi sample player requires some thought. Ideally, every note should have its own samples. I say samples here as you might want to account for different key velocities with different samples expressing different timbres. We will not go that far here. Also, instead of one sample per note, the sample is spread across four whole notes (a quart). Connect your midi keyboard and run the sqlite_samplePlay1.csd example. When you play the file, you should hear the 'Jazz Guitar' sound. The samples are loaded from the database table samples and placed in the samples folder via code within a pyruni loop. In the score section of sqlite_samplePlay1.csd you will see the following score lines:

f 1 0 0 1 "samples/JazzGuitarE3.wav" 0 0 0
f 2 0 0 1 "samples/JazzGuitarA3.wav" 0 0 0
f 3 0 0 1 "samples/JazzGuitarD4.wav" 0 0 0
f 4 0 0 1 "samples/JazzGuitarG4.wav" 0 0 0
f 5 0 0 1 "samples/JazzGuitarB4.wav" 0 0 0
f 6 0 0 1 "samples/JazzGuitarE5.wav" 0 0 0 

Each guitar string (E3, A3, D4, G4, B4, E5) has its own sample. Depending on the midi note, one of the samples within the range is chosen. This is done by using the midibase and midimax fields. For the E3 string midibase = 40 (E3) and midimax = 44 (G#3). So playing a note within this range will select the first sample. Playing above that range, up to D4, will select the second sample, and so on. This approach is shown in the following code from the sqlite_samplePlay1.csd example.

icps	cpsmidi	; read in midi note data as cycles per second
iNote notnum ; also get as midi note number to determine what sample to play
;print iNote
iamp	ampmidi 1 ; read in midi velocity (as a value within the range 0 — 1)
if iNote >= giMidibase1 && iNote <= giMidimax1 then
    gitable = 1
    iMidiRef = giMidibase1
elseif iNote >= giMidibase2 && iNote <= giMidimax2 then
	
    gitable = 2
    iMidiRef = giMidibase2
elseif 
…

In the example file, sqlite_samplePlay2.csd, the limitation of the hard-coded sample names in the sample folder is overcome by using names like 'sample1.wav', 'sample2.wav', etc.

Also this example defaults to a nice piano sample. You can change what sample to load by commenting out the appropriate select statement in sqlite_samplePlay2.csd, or by the utilizing more of the techniques shown in the example sqlite_score2.csd.

Some final notes

There is room for improvement in the examples provided. Some suggestions for changes and improvements include the following:

    1) All the samples provided in the examples are in mono. Higher quality stereo samples would greatly improve the sound quality.
    2) Level switching per sample could be implemented. This would require that more samples are added with additional level fields (columns) in the samples table.
    3) An additional feature could be to clean up all wav files (*.wav) in the samples folder once the score performance has completed.

References

[1] Andrés Cabrera, and Joachim Heintz, V.6, 2015. FLOSS MANUALS. "Python Inside Csound." [Online] Available: http://write.flossmanuals.net/csound/b-python-inside-csound/. [Accessed December 1, 2016].

[2] Tutorialspoint, 2016. "SQLite Tutorial." [Online] Available: http://www.tutorialspoint.com/sqlite/. [Accessed December 1, 2016].

[3] lazierthanthou, Firefox Add-ons, 2015. "Firefox SQLite Manager." [Online] Available: https://addons.mozilla.org/nl/firefox/addon/sqlite-manager/. [Accessed December 1, 2016].

Biography

Bemmelen image Richard van Bemmelen is a composer, keyboardist and guitarist based in Breda in the Netherlands. In his daily life Richard is a software engineer in the field of industrial automation.

email: zappfinger AT gmail.com