Cabbage Logo
Back to Cabbage Site

Reading speed of a GEN table

Hello,

My aim is to change the pitch of a sample kept in a GEN01 table.
How do I change the reading speed of a GEN table?
Also as there are so many opcode for reading GEN tables… from phasor to oscil that I don’t know which one to choose and maybe these are not the most appropriate.

Could some one propose a set of short Cabbage examples showing how to uses these opcodes (loscil family, phasor, line…) with a GEN01 table, and how these can be manipulated to change speed / pitch /duration of the sample. Maybe explaining the advantages and drawbacks of each method ?

Many thanks for help.

Function tables, quick recap…

Function tables are used to store a sequence of numbers in your computer’s RAM. The sequence of numbers may represent a waveform such as an audio file, an envelope, a series of MIDI notes, etc. The data created/loaded and stored in a function table is for the most part determined by the GEN routine. For example, GEN10 creates composite waveforms while GEN01 loads sound files from disk. A full list of GEN routines can be found here. From hereon in we will deal with GEN01 function tables.

Opcodes that read function tables
The opcodes you choose to read data from a function table largely on what data is stored there. Oscillators are the go-to tool for reading function tables that contain composite wave forms. Oscillators can also be used for reading back large tables, but many people will prefer to use one of the so-called direct access table opcodes, such as the table family of opcodes. Direct access means we tell Csound exactly which sample to read at any particular time. It also means we can quickly skip to any place in the table quite easily.

The syntax for the tab opcode is as follows:

ar tab xndx, ifn[, ixmode]

xndx is the table index. If we passed the number 10 as this parameter, the output variable ar would be the value of the 10th sample from the table we are reading. It obviously doesn’t make much sense to pass constants, so instead we use an audio signal to specify which sample we want. (we’ll return to this later). ifn is the table number we are trying to read from, and ixmode determines how the table’s range is to be set. If you don’t set this parameter, then the table range will go from 0 the the number of samples in the table, ergo, your index will also need to be kept within this range. If you set this parameter to 1, then the table range will be from 0 to 1. This makes life a lot easier as you don’t have to know in advance how many samples are contained in your sound file.

Reading data from a function table
If you’ve looked through any Csound source code, you see quite a lot that uses a combination of phasor and tab. They make a great pair. phasor outputs a signal that moves between 0 and 1 n times a second.

If we set our table opcode’s ixmode to 1 then we use the output from a phasor to read samples from our table:

giSoundfile ftgen 1, 0, 0, 1, "06_Piano.wav", 0, 4, 1 

instr 1
aPhs phasor 1
aTab tab aPhs, giSoundfile, 1
outs aTab, aTab
endin

As aPhs moves continuously from 0 and 1, tab will output all samples from the table in quick succession. Only there is a problem here, the playback frequency is not right. The piano sample used in this example is 3 minutes long. Yet we have our phasor set to a frequency of 1. This means out tab opcodes will be reading the entire table once every second. So we are actually reading the table about 180 times quicker than normal speed.

Setting the right frequency for our phasor
In order to set the correct frequency for playback we need to know how long the sound file is. We can use the ftlen opcode to find this out. ftlen return the number of samples in a sound file. Once we know the number of samples in the file we can work out the correct frequency of the phasor by dividing the current sampling rate by the length of the table in samples.

Let’s consider for a moment that there are only 2 seconds worth of audio sample in our sound file. Assuming a sample rate of 44100, ftlen will return 88200 when we query the table length. Frequency is defined as the number of cycles per second. A single cycle in this context is the entire table. If we set our phasor frequency to 1 we will play this entire table in a single second. A phasor with a frequency of .5 on the other hand will only get through half a table in a second. The following single line of code will work out what the frequency of your phasor should be, depending on the length of your sound file.

iFreq = sr/ftlen(giSoundfile)

Changing the speed of playback
Once you have the correct frequency for your phasor you can start to manipulate it quite easily. If for example you multiple the normal speed of playback by 2 for example you will increase the pitch of the sample by an octave. If you half it you will reduce the speed of playback by an octave. You can use any frequency ratio to alter the pitch. You could also use a slider as in the following example.

<Cabbage>
form caption("Untitled") size(400, 300), colour(58, 110, 182), pluginID("def1")
hslider bounds(12, 8, 200, 30), channel("speed"), range(0, 2, 1, 1, .1)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
sr = 44100
ksmps = 32
nchnls = 2
0dbfs = 1

giSoundfile ftgen 1, 0, 0, 1, "06_Piano.wav", 0, 4, 1 

instr 1
iFreq = sr/ftlen(giSoundfile)
aPhs phasor iFreq*chnget:k("speed")
aTab tab aPhs, giSoundfile, 1
outs aTab, aTab
endin

</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>

Note that the above instrument is reading only the left channel of the sound file. We can tell by looking at the last parameter passed to ftgen when we created our GEN01 function table. A 1 instructs Csound to read the left channel, a 2 the right, 3, a third channel, etc. GEN01 can support any number of channels. 0 instructs Csound to read both channels and can be useful if using one of the oscillator opcodes that support stereo tables, such as loscil. If you wish to preserve the stereo nature of the file you should use two function tables, one for the left and one for the right. You’ll also need to use two tab opcodes.

giSoundfile ftgen 1, 0, 0, 1, "06_Piano.wav", 0, 4, 0 
giSoundfile ftgen 1, 0, 0, 1, "06_Piano.wav", 0, 4, 1 

instr 1
iFreq = sr/ftlen(giSoundfile)
aPhs phasor iFreq*chnget:k("speed")
aTabL tab aPhs, giSoundfile, 1
aTabR tab aPhs, giSoundfile, 1
outs aTabL, aTabR
endin

Conclusion
Function tables are at the heart of thousands of Csound files, but they are not the only way to read data from disk. One can also use the soundin or diskin2 opcodes. diskin2 provides an input parameter that allows one to quickly change the play-pack speed. On top of that, it will always plays back the sound files at the correct pitch by default. Here’s a quick example of an instrument that will play back a sound file at its correct speed over and over for as long as the instrument is running.

instr 1
aOutL, aOutR diskin2 "06_Piano.wav", 1, 0, 1
outs aOutL, aOutR
endin

So why not use diskin2? While it does provide a quick way of reading sounds from disk, it doesn’t provide the same level of control you can get from using a table opcode. On top of this, it only reads from disk, it doesn’t load the sound files into the computer’s RAM. Reading samples from RAM is usually quicker than reading from a hard disk, although the difference these days is getting smaller and smaller with the advent of high speed SSDs.

Many many thanks for the clearest tutorial ever on this topic !

Keep the requests coming. I’m happy to help out if I can :wink:

1 Like

Lots of thanks for this series of topics!
They are explained in a simple and profound way. They can understand anyone

R

Short question about stereo and GEN table
When you write:

If I understand well, it means that the GEN tables store the samples as mono sample (mixing the channel if using 0), thus whatever the input file is, it is a mono sample in GEN table. Therefore in order to render a stereo sample, you need to load the Right voice into one table and then load the Left voice into another table ?
Is what is below acceptable ?

giSoundfile ftgen 1, 0, 0, 1, “06_Piano.wav”, 0, 4, 0 ; This mix channel into one table mono

giSoundfile ftgen 101, 0, 0, 1, “06_Piano.wav”, 0, 4, 1 ; This reads left channel into mono table Num 101
giSoundfile ftgen 201, 0, 0, 1, “06_Piano.wav”, 0, 4, 2 ; This reads right channel into mono table Num 201

If yes, then the Csound man page of loscil family is confusing (and would be partly wrong) it states :

Read sampled sound (mono or stereo) from a table, with optional sustain and release looping, using cubic interpolation.

In fact, if I understand well, it should state :

read mono sound from a table and output either mono or stereo …

Or did I get wrong again ?
Thank you for your help

loscil can output in stereo if you use two output arguments. The table family of opcodes can only support one output argument. Setting 0 reads all channels and makes them available to any opcode that can read stereo tables, such as loscil. if you set 0, and try to read using a tab opcode for example, you will only get the left channel, and not a stereo mix down, as I wrote. I will fix that now!

P.S. The Csound manual isn’t exactly clear about this. I will propose a change to it on the Csound list.

The Csound manual is in fact accurate. GEN01 can load a stereo sound file as stereo interleaved data. This can then be read using loscil with two outputs to recreate the original stereo audio. There are just a few opcodes that can make use of multi-channel interleaved GEN01 tables, ftconv is another.

It is indeed accurate, but I think it could be made clearer by stating that only a small handful of opcodes can use stereo tables. I just posted to the Csound list about updating the GEN01 manual. The text you provided above would do the job!

So, I was … wrong again :slight_smile: . Thank you for your precise answer !