So the past few months, I’ve been wanting to create a wavetable oscillator like that of Serum, Surge, or Vital.
I’ve done a few hours of research, but I keep getting misleading results, so I figured I’d ask here.
I figured out how to morph between tables using the tabmorph opcode. As well as how to load audio files into tables using GEN1.
However I cannot seem to figure out how to load an audio file and morph between the individual waveforms of it.
My thoughts was that maybe I have to figure out how to “dump” the waveforms of the audio file into a table to morph between them. But I’m unsure if this is the best solution.
If anyone has a solution or could link me to an example. I’d really appreciate it
I can use ntrpol or tabmorph to morph between tables.
But whenever I tried GEN1 it always plays the entire sample. (Not individual waveforms.)
Basically I want a way to morph through the waveforms of a sample with a slider.
I’m unaware if GEN1 is just loading the sample, or if it automatically dumps the waveforms as well. But I couldn’t get it working with a slider.
Possibly? Please excuse me if I brought up the wrong term
Oddly in synthesis I keep getting misleading results due to similar words being thrown around. Csound also has a lot of opcodes regarding wavetable synthesis, so I couldn’t figure out which one I was looking for.
Basically I just want to implement a method for users to load in custom wavetables, and then use a slider to morph between the “frames” of the wavetable.
I’d thought that Figure 2.4 in the link I’d sent was the answer. But it had an error due to line not having valid arguments. I fixed it, but wasn’t able to get any audio. So I couldn’t tell if it did what I wanted to or not.
To do this you need to make sure the waveforms are going to be bandlimited when they are played back. Look at vco2init for this purpose. It will let you load any file, but will make sure no aliasing occurs.
The function table morphing opcode will work in this context, as you will already have them loaded to a table. @Retornz does this in his ToneZ synth.
And he’s taken it even further with the new version which offers even more control over your sounds. It’s all open source and very well written, and it’s only 3000 lines of code
Hello!
I’ve been developing a synth similar to what you’re thinking for a few months now. The one for you, in my opinion, is the ftmorf opcode which exactly morphs between multiple ftables. the important thing is that they all have the same length in samples. http://www.csounds.com/manual/html/ftmorf.html
in my case I exported single waveform cycles directly from the serum wavetable editor at 1024 samples in length. check out this repository:
it’s still in development but already sounds pretty nice.
This is a really late response. But I hope it still helps someone.
I think the problem here is that for Vital, Serum etc. a wavetable is an array of what is usually a table in csound. There are some opcodes in csound like ftmorf, tabmorph which are designed to handle sounds from a mixture of tables. But they have two disadvantages: 1. They interpolate the full tables which may be an overkill if one only wants a few entries between changing from one table to another. 2. Vital has 256 frames of 2048 samples. That would require to construct 256 csound tables. Not impossible, but tedious.
I therefore came up with a method which reads Vital style wavetables into one csound table and picks the correct entries for a given frame number and sample-in-frame index. (A quasi-standard for Vital seems to be that the frames are written consecutively to a single wav file with 256*2048=524288 samples.)
<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue"), pluginId("def1")
keyboard bounds(8, 158, 381, 95)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables.
ksmps = 32
nchnls = 2
0dbfs = 1
;instrument will be triggered by keyboard widget
instr 1
kx madsr 0.001, .3, 0, 0
kframe = int(255-255*kx)
aphi phasor p4
aOut tab int(2048*(kframe+aphi)), 100
kEnv madsr .01, .2, .6, .4
outs aOut*kEnv, aOut*kEnv
endin
</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
f100 0 524288 1 "primes.wav" 0 0 0
</CsScore>
</CsoundSynthesizer>
The disadvantage of this solution is that the scheme does not interpolate between frames and also not between samples. That could be implemented by reading two samples each from two of the frames and do a 4-point 2d interpolation.
@RZorn
First, congrats on the ToneZ V2, that’s some impressive work!
Constructing any number of tables isn’t tedious unless one attempts to do this manually. Of course if one isn’t happy with ftmorf, which will only interpolate between adjacent samples, and prefers one of the tabmorph opcodes which allow interpolating between 2 non-adjacent tables (4 tables can be morphed simultaneously) that’s an issue.
Depending on one’s needs, a relatively simple solution for a wavetable synth is using ftsamplebank to load a folder of single cycle waveforms then interpolate with ftmorf.
The while loop constructs the wavetable. The preload instr should precede any playback instruments as it can take a fraction of a second for all the tables to be created although even on my phone .1 sec seems adequate. One just has to alter the path to the sample folder. Of course all the samples in the folder must be the same size. The code will automatically find the size of the imported files and compensate (see iSize).
The only drawback is that as ftsamplebank must allocate a specific series of table numbers care has to be taken if using tables elsewhere in a csd. Usually I prefer to let Csound choose it’s own table numbers and reference by name but that’s not an option except outside of the preload instr which should come first.
I’ve never had an issue with having to bandlimit the tables, usually the files from external/imported wavetables are already bandlimited. If not then generally a lpf rolling off above 16kHz works fine.
<CsoundSynthesizer>
<CsOptions>
-odac -d
</CsOptions>
;==================================
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 1
0dbfs = 1
instr preload
; must be > 1 as that is reserved for giWavetable
giFirstTabNum = 2
; alter path to sample folder accordingly
; note that all imported files must be the same size
; they do not have to be power of 2
iNumFiles ftsamplebank "...path to wavetable sample folder...",\
giFirstTabNum, 0, 0, 1
iTabNum init 0
giWavetable = ftgen(1, 0, -iNumFiles, -2, 0)
while iTabNum < iNumFiles do
tabw_i(iTabNum + giFirstTabNum, iTabNum, giWavetable)
iTabNum += 1
od
endin
instr play
iSize = ftlen(giFirstTabNum)
iMorf = ftgenonce(0, 0, -iSize, -2, 0)
iLen = ftlen(giWavetable)
kNdx = linseg(0, p3, iLen - 1)
ftmorf(kNdx, giWavetable, iMorf)
aSig = poscil(.8, 110, iMorf)
out aSig
endin
</CsInstruments>
;==================================
<CsScore>
i"preload" 0 .1
i"play" .1 32
</CsScore>
</CsoundSynthesizer>
Another option might be loading in a folder of files to array using the directory opcode and the using ntrpol which could be quite flexible for more advanced morphing (such as using using more than one instance of ntrpol.
Guess I didn’t read carefully and the goal here is dealing with wavetables written as a single audio file.
One approach is to slice it up into individual tables which allows for easier morphing or single/randomized waveform playback/selection. In the create instr you need to know & enter the size (in samples) of each waveform in the source table.
For ex., in some of the wavetables here (there’s about 695): https://waveeditonline.com/
you can see there are 8 rows of 8 waveforms so 64 waveforms. So if the file is 16384 samples (such as TRANSFOR & PRECIPIC) the iSize should be 256 (samples).
I tried with the primes.wav posted here (2048, which creates 257 tables) but the result was slightly clicky. I don’t know how Vital deals with interpolation internally but one solution would be to window the tables in the loop as they’re written.
If windowed properly and bandlimited (which could be added in the loop) it works fairly well to create wavetables from different source material, for example speech (if there aren’t large pauses) or sustained notes like a cello or pad timbre.
First of all, honour whom honour is due. ToneZ was written by @Retornz. Maybe my user name is a bit similar.
Thanks to @ST_Music for the solutions. Regarding the first one: Maybe I overlooked some updates, but I am not aware of the opcode ftsamplebank. I cannot find it on http://www.csounds.com/manual/html/. In principle, it would be possible to generate 256 wav files of 2048 samples each with a small modification to the program I used to generate primes.wav. But I think that even if they are collected in a single subdirectory that may get a bit confusing. Also, there is a large overhead reading 256 files compared to reading one longer file.
The second solution is actually what I intend. (And the example is great! Amazing what you can get out of fox.wav.) The only problem I can imagine is that ftmorf calculates the interpolation for the whole slice table at k-rate. This may be more important if the slice is 2048 samples instead of 256.
On the other hand, this solution provides correct interpolation of the slices and between samples. So I implemented the interpolation into the code I posted before:
This approach just calculates 3 interpolations per output sample, so 96 per k-cycle. Fully interpolating two 2048 samples splices would be 2048 interpolations per k-cycle plus 32 from the a-rate interpolation. But of course that calculation does not take into account that using compiled opcodes is faster than explicitly coding the interpolations in csound. (I was really disappointed that there isn’t even an aaa version of ntrpol.)
Concerning the clicks, I had this problem too when I did not consider that if you directly work on the unspliced table the next sample after the last in a splice is the first of the next splice. That’s why there is the modulus 2048 calculated, aindexi1 = (aindexi+1)%2048. After putting this line in, there were no more clicks from the code posted here. But that also should not happen in your version, @ST_Music, because you splice the table before and poscil3 should handle the wraparound correctly.
Just to note here that csounds.com is not the official Csound website. it’s csound.com, and you can find the ftsamplebank opcodes here…
Btw, whilst nowhere near as interesting as the examples you guys are posting, I created a simple ftmorf demo using p5js and Csound. I find the visualisation quite nice
Thanks for telling me about the correct documentation pages. So I was using obsolete docs for two years now. Appropriate that I post on the “Noobs” topic.
PS: I just studied the js/csound demo. I am impressed. The sound movement is great. And also it is interesting to see how this works with the js code calling the csound.