Loading a wavetable file into a buffer for wavetable synthesis

I’m keen to see if I can make a wavetable based synth.
I’ve been doing some searching to try to work out how to make it possible for a wavetable (for example, one of the AKWF wavetables in the Sonic Pi repo) to be used as the argument to Overtone’s (osc ...) function. To do this I assume I need to load the wavetable file’s data into a buffer. It’s probably very simple, but I’m not quite sure how to do it still. Does anyone have advice about how to do this?

1 Like

Hi Ethan
I had a brief play at retrieving the single sample waveforms to buffers and playing them, which was a fun exercise to do, but I don’t think this is what you are after. Not sure how you deal with the wavetable files for each sample which presumably contain info on combining the samples.
Effectively all I’ve done is to produce duplicate copies of the wav files in the .sonic-pi/store/default/cached_samples folder and then access them via buffers.

I did also experiment with playing buffers simultaneously to get an additive effect, but soon ran into can’t keep up problems.

#Experimenting with a wavetable sample
#Adjust path for your system

b=[] #list of buffers
for n in (0..9) do #retrieve first 10 samples
    path="/Users/rbn/src/sonic-pi/etc/wavetables/AKWF/AKWF_0001/"
    d=sample_duration path,n #calculate duration of current sample
    puts "original sample duration: #{d}"
    b[n]=buffer(("k"+n.to_s).to_sym,d) #create buffer using duration for length
    with_fx :record,buffer: b[n] do #record sample to buffer
      sample path,n
      sleep d
    end
    sleep 0.1 #delay to allow processing
  end
  
  
  sleep 1
  live_loop :playbuffers do
    r=2 #speeded them up a bit with rate 2
    for n in (0..9) do
        puts b[n] #print buffer details
        #play 100 times so you can hear it
        100.times do
          sample b[n],rate: r # play recorded buffer
          sleep b[n].duration/r #retrieve duration from buffer details
        end
      end
    end

We briefly discussed wavetable synthesis in the old Google Group. As Robin said, he’s done some work on this. Interestingly enough, the reason Adventure Kid put these waveforms into the Public Domain was to help SPi!
An initial attempt had been made to treat these waveforms as mere samples, which obviously doesn’t work. As you say, treating them as oscillators makes a lot more sense. Could make for a large variety of neat new synths, maybe with a way to pick them up in a kind of catalogue.
Actual wavetable synthesis would be another approach. There, not only do you use waveforms as oscillators but you use a table concept to morph from one to another. Wolfgang Palm, who pioneered the technique, is still developing new tricks based on this.
Thankfully (?), there are ways to do wavetable synthesis using the SuperCollider engine which runs behind Sonic Pi. It does sound a bit involved. But people have done it.
In line with what you’re saying, @ethancrawford, Overtone is still the best way to go to create new synths for SPi (tried in SuperCollider and, though it works, you don’t get all the niceties like _slide).
So, anyone who has experience in Overtone and a bit of time to work on this should be able to create a new WT synth.
Would be ace!

1 Like

As I mentioned in that Google groups post, from my research the best synth in SuperCollider to handle this would be VOsc3 but if you literally just wanted to play the single buffer there are probably other viable approaches.

I put my mess of experiments up as a gist here and I’ve tried to add some comments, although this code was from at least a year ago so they might be more confusing than helpful :slight_smile: https://gist.github.com/xavriley/ce1becd7f2d97d93aced74e88ae7ba54

The outcome of this was that I couldn’t get those AKWF to playback in SC without problems with the zero crossing bit at the start and end of the looped sample. Basically everything I tried sounded pretty nasty :frowning: I’m sure that a more experienced SC guru would be able to make this work but I fell at that particular hurdle. If I can help anyone get further though I’ll do my best…

3 Likes

Just played around with the code in the gist. I’m no SuperCollider expert by any means, but (I think) I was able to make an oscillator play sound from a single buffer cleanly, when reading data from a .wav file and converting it to a signal and then a wavetable. See the below code:

f = SoundFile.openRead("~/repos/sonic-pi/etc/wavetables/AKWF/AKWF_clarinett/AKWF_clarinett_0001.wav".standardizePath);
// An array to load the data
a = FloatArray.newClear(f.numFrames);
f.readData(a);
f.close;

a = a.as(Signal);
a = a.asWavetable;

s.boot;
b = Buffer.loadCollection(s, a);
e = Env([1.0, 0.0], [0.5]);
g = EnvGen.ar(e, doneAction: 2);
x = { Osc.ar(b, 60.midicps, 5.6.mod(2pi), 0.3) }.play;
x.free;

That seems to produce a clean clarinet sound I think?

The real challenge I guess, is working out how to create a Wavetable in memory without having to convert a .wav file to a Signal and then to a Wavetable.
I tried using the following code for this: (based on an example I found at http://www.audiosynth.com/files/sc-users-archive/v01.n117)

g = File.new("~/repos/sonic-pi/etc/wavetables/AKWF/AKWF_clarinett/AKWF_clarinett_0001.wav".standardizePath, "r");
a = Wavetable.readNew(g);
g.close;

But that just produces the following error:

ERROR: Signal size not a power of two.
ERROR: Primitive '_SignalAsWavetable' failed.
Failed.
RECEIVER:
Instance of Signal {    (0x1192c6080, gc=90, fmt=03, flg=00, set=0A)
  indexed slots [523]
      0 : 2.16117e+11
      1 : 2.94903e-17
      2 : 2.12576e+14
      3 : 2.80336e+23
      4 : 2.52435e-29
      5 : 2.35106e-38
      6 : 3.49011e-39
      7 : 6.98022e-39
      8 : 9.40855e-38
      9 : 1.66356e+22
     10 : 7.34684e-40
     11 : 2.54099e-29
     12 : -3.64829e+16
     13 : 1.19918e-10
     14 : -1.12143e-19
     15 : 0.000127949
     16 : 4.72238e-16
     17 : 2.74006e-29
     18 : -3.1684e-11
     19 : 6.66176e-36
     20 : 1.49893e+08
     21 : -5.09552e-13
     22 : 5.31103e-10
     23 : -2.40128e+06
     24 : 2.60667e+15
     25 : -1.99073e+38
     26 : 7.65901e+32
     27 : -4.49283e-37
     28 : 8.72171e-12
     29 : -1.39157e-10
     30 : 1.87256e+26
     31 : 4.71293e+25
    ...
}
1 Like

Agreed that it sounds very good!

Understood but… would it be relatively simple to create a SPi synth from this code? Even if it were one “instrument” at a time, it could still be a load of fun! In fact, that one sound is already pretty satisfying, in terms of getting something “woody” than the typical FM. (And it should be more flexible than a sample.)
Still haven’t got a clue as to how to convert a SynthDef into a proper Sonic Pi synth as it uses Overtone. My previous attempts with loading .scsyndef files directly in SP led me to some fun stuff, but missing the _slide ops was a bummer.

For note, there’s no “proper” synthdef other than supercollider synthdefs. Overtone is just another approach for creating supercollider synthdefs. There’s no extra magic. The only thing to be aware of is that sonic Pi needs specifically named synth args (as documented in the tutorial). The slide args are explicitly added in to all sonic Pi synths as part of their design - so if you don’t add them yourself they won’t be there :slight_smile:

1 Like

Oh? Missed that, then. Everything else was working (was using the right names) but the slide arguments weren’t recognized. So, my mistake. And it’s good news, as creating synthdefs in SC really isn’t too hard. Really thought Overtone was needed to make the synths complete and put those synth creation projects “on the backburner” (got a lot of those little projects on hold). Will go back to those, then. Especially with the Physical Modelling synths.

Thanks a lot for the cue!

Have attempted to load a wav file into a buffer, get the buffer data and pass this into an Overtone function (signal->wavetable ...) to then pass in to the osc ugen. No luck with that so far either - I get a buffer overflow error:

 java.nio.BufferOverflowException
	at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
	at java.nio.ByteBuffer.put(ByteBuffer.java:859)
	at sun.reflect.GeneratedMethodAccessor19.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
	at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:28)
	at overtone.osc.encode$encode_blob.invoke(encode.clj:23)
	at overtone.osc.encode$osc_encode_msg.invoke(encode.clj:51)
	at overtone.osc.peer$send_loop$fn__2246.invoke(peer.clj:104)
	at overtone.osc.peer$send_loop.invoke(peer.clj:102)
	at clojure.lang.AFn.applyToHelper(AFn.java:172)
	at clojure.lang.AFn.applyTo(AFn.java:151)
	at clojure.core$apply.invoke(core.clj:617)
	at overtone.osc.peer$sender_thread$fn__2307.invoke(peer.clj:212)
	at clojure.lang.AFn.run(AFn.java:24)
	at java.lang.Thread.run(Thread.java:748)
Exception in send-loop:  #<BufferOverflowException java.nio.BufferOverflowException> 

Here’s the code if someone thinks they might be able to help troubleshoot it: