Not power-of-two audiofiles as wavetable (AKWT)

Hi guys,
today I’m making some experiments with wavetable synthesis and I would like to use some of the AKRT wavetable samples.

Taking inspiration from a previous post by @GhostChamb3r on the wavetable loading topic, I will show the code and discuss about an issue I’m not sure how to solve.

// First, variable 'a' to open the sound file and prepare for reading contents
a = SoundFile.openRead( "/path/to/an/AKWF/waveform.wav");

// variable 'b' to create a float array that is the same size as the sound file
b = FloatArray.newClear(a.numFrames);

// put the sample data of the sound file 'a' 
// into the array provided as variable 'b'
a.readData(b);

// variable 'd' will be a signal, obtained converting the array 'b' 
// (which should now contain the data from the sample file) 
d = b.as(Signal); 

Now the critical part: I want to convert the Signal to the SC wavetable format in order to play it with, let’s say, the Osc Ugen.

// variable 'e' will contain the signal converted in the SC wavetable format
e = d.asWavetable;

I’m getting the error:

ERROR: Signal size not a power of two.
ERROR: Primitive '_SignalAsWavetable' failed.

Yes, of course I tell to myself. That’s because, as the AK guy also tell, his wavefile are 600 samples long which is not a power of two size.

So, here’s the questions:

  • how can I convert/stretch an arbitrary-long-single-wave-cycle soundfile to a power-of-two size Signal in order to correctly convert it to a SC wavetable?
  • Is there any “out of the box” method, function?

I’ve also came across this undocumented method which gave me no errors:

e = d.asWavetableNoWrap;
  • is that the method I’m looking for?

in case I will be able to do what I want, I eventually will go on with this last line of code in order to obtain the final buffer:

// variable 'f' will load the wavetable created in the previous step in variable 'e' 
// into a buffer on the server
f = Buffer.loadCollection(s, e);
1 Like

hey, you can use this code for resampling the audiofiles:

( // run once to convert and resample wavetable files
var paths, file, data, n, newData, outFile;
paths = (PathName(thisProcess.nowExecutingPath).parentPath ++ "buffers - serum/wavetables/*.wav").pathMatch;
Routine({
	paths.do { |it i|
		// 'protect' guarantees the file objects will be closed in case of error
		protect {
			// Read original size of data
			file = SoundFile.openRead(paths[i]);
			data = Signal.newClear(file.numFrames);
			file.readData(data);
			0.1.wait;
			// Convert to n = some power of 2 samples.
			// n = data.size.nextPowerOfTwo;
			n = 4096;
			newData = data.resamp1(n);
			0.1.wait;
			// Convert the resampled signal into a Wavetable.
			// resamp1 outputs an Array, so we have to reconvert to Signal
			newData = newData.as(Signal);
			0.1.wait;

			// save to disk.
			outFile = SoundFile(paths[i] ++ "_4096")
			.headerFormat_("WAV")
			.sampleFormat_("float")
			.numChannels_(1)
			.sampleRate_(44100);
			if(outFile.openWrite.notNil) {
				outFile.writeData(newData);
				0.1.wait;
			} {
				"Couldn't write output file".warn;
			};
		} {
			file.close;
			if(outFile.notNil) { outFile.close };
		};

	}
}).play
)

and maybe want to have a look at this video: 5: Wave Terrainish adventures with VOsc - Musical Sound Design In Supercollider - YouTube

Power-of-two versions (64/512/1024 samples long) of the AK wavetables can be found in this github repo (there’s also a SoX script in case you don’t want to download the entire repo and just convert your existing library instead):

2 Likes

Thank you @dietcv for your reply,
I was eventually able to test your code and it worked for me: a clean, understandable and very instructive code for me.

I made just a few changes to make it more verbose and to correct perhaps a small flaw in the way the new resampled files were renamed.
Here it is:

(
// run once to convert and resample wavetable files
var paths, file, data, n, newData, outFile;
//paths = (PathName(thisProcess.nowExecutingPath).parentPath ++ "buffers - serum/wavetables/*.wav").pathMatch;
paths = PathName("/path/to/an/AKWF/wavetables/folder/*.wav").pathMatch;
Routine({
	paths.do { |it, i|
		("processing item: " ++ it ++ ";").postln;
		// 'protect' guarantees the file objects will be closed in case of error
		protect {
			// Read original size of data
			file = SoundFile.openRead(paths[i]);
			data = Signal.newClear(file.numFrames);
			file.readData(data);
			0.1.wait;
			// Convert to n = some power of 2 samples.
			// n = data.size.nextPowerOfTwo;
			n = 4096;
			newData = data.resamp1(n);
			0.1.wait;
			// Convert the resampled signal into a Wavetable.
			// resamp1 outputs an Array, so we have to reconvert to Signal
			newData = newData.as(Signal);
			0.1.wait;

			// save to disk.
			//outFile = SoundFile(paths[i] ++ "_4096")
			outFile = SoundFile(paths[i].replace(".wav", replace:"_4096.wav"))
			.headerFormat_("WAV")
			.sampleFormat_("float")
			.numChannels_(1)
			.sampleRate_(44100);
			if(outFile.openWrite.notNil) {
				outFile.writeData(newData);
				0.1.wait;
			} {
				"Couldn't write output file".warn;
			};
		} {
			file.close;
			if(outFile.notNil) { outFile.close };
		};

	};
	"process terminated".warn;
}).play
)

Thank you so much also to you @bovil43810 , I didn’t know about the existence of the repository with the already prepared waveforms!

Isn’t the wavetable conversion missing? The earlier post said that it converts to wavetable format, but it looks to me like it only resamples.

You could use the resampled files to simulate Osc using Phasor and BufRd, but to use Osc you also need to add .asWavetable here:

newData = newData.as(Signal).asWavetable;

After resampling to 4096 samples, and converting to wavetable format, the resulting files will be 8192 samples.

hjh

1 Like

sorry ive made a mistake. yes its just resampling and not converting into wavetable format.

ive then been using this function for loading the wavetables into buffers:

(
~makeWavetables = {

	var wtsize, wtpaths, wtbuffers;

	wtsize = 4096;
	wtpaths = (PathName(thisProcess.nowExecutingPath).parentPath ++ "/AKWF/AKWF_0002/4096/*.wav").pathMatch;
	wtbuffers = Buffer.allocConsecutive(wtpaths.size, s, wtsize * 2, 1);
	wtpaths.do { |it i| wtbuffers[i].read(wtpaths[i])};

	~wtbufnums = List[];
	~wavetables = ();

	wtpaths.do { |it i|
		var name = wtbuffers[i].path.basename.findRegexp(".*\.wav")[0][1].splitext[0];
		var buffer = wtbuffers[i].bufnum;
		~wavetables[name.asSymbol] = buffer;
		~wtbufnums.add(buffer);
	};
};
~makeWavetables.();
)
1 Like

Thank you @jamshark70 and @dietcv for your clarifications.
I am glad that this topic has elicited this feedback: thank you very much, I have learned several new things!
see you soon

Thanks for including me in the discussion! I came across this same problem when trying to use some wav files as wavetables and had to do a deep dive into understanding wav file structures better. I like the solutions everyone provided. Thanks @dietcv @bovil43810 @jamshark70 !
What I still don’t fully understand is what sample size to use when converting? I’m guessing you have to just guesstimate based off the length of the sample. If I have a wav file recorded at 44.1k and it’s roughly a half a second long I can resample it if necessary to 2048 samples but then I’m guessing I’m losing a lot of information by the time it’s converted to a wavetable and sent through Osc in a synthdef?

Or maybe there’s something I’m missing in regards to the structure of wav files etc?

I know a wav is basically metadata, flags, and sample data separated into chunks. I’m guessing that these chunks of sample data are what we see when we convert a wav into a signal and can see the array values in SC as we do that.

I’m trying to understand it all a little better because at some point I’d like to load several wavetables into a single oscillator and be able to morph between them with a simple linear crossfade. Right now I can create one longer wav file from multiple single cycle waveforms using either serum or something like Single Cycle Waveform Editor and then bring that into SC and convert it from a wav to a signal and then to a wavetable and load that into an Oscillator. I can’t specify at which point the oscillator begins cycling through the wavetable or morph through the wavetable at a certain rate. I saw that there is a Vosc3 which creates 3 wavetables and they can each be detuned differently but it would be awesome to be able to morph across multiple wavetables with a crossfade using an LFO or an envelope.

I don’t know if this would be possible with the oscillators available in SC or if a new Ugen would have to be programmed. It would be amazing to have an Oscillator Ugen that can load up to 256 wavetables each at 2048 samples and morph across them. I’m thinking something like Massive or Serum’s oscillator, then you could write up a synthdef using it and have two or more of these oscillators (depending on the limits of your hardware I guess).

I was trying to get a better understanding of how Serum’s oscillators treat wav files and ran across this forum post Wavetable file format? - DSP and Plug-in Development Forum - KVR Audio

Steve Duda came along and provided some very useful information about how the oscillators treat the wav data, which is pretty cool.

I’m mostly interested in morphing wavetable synthesis for SC tho since I live code and I want to create sounds that are closer to what I do in Ableton with my Tidal Cycles sets.

Vosc ugen can morph between multiple wavetables. ive mentioned it before but maybe you want to look at this video: https://www.youtube.com/watch?v=S9fmNHgUKzc&t=1698s the whole channel by @alikthename is really great.

im not sure if it is necessary to crossfade between these many wavetables from a musical perspective.

Necessary? I’ve never heard anything so asinine in my whole life! Is it necessary for a violin to have 4 strings when 2 would suffice? Is it necessary for sonic artists to use ambisonics when a stereo pair of speakers would suffice? Is it necessary for a guitar to be amplified when it already can fill a large room with sound? Is it necessary for their to be micro tuning scales when 12 notes is sufficient to most people? When is anything in music necessary?

Serum already allows you to crossfade between that many wavetables and lots of producers already work with patches that use that many. If anything 256 starts to become limiting after working with it for a great length of time.

I did look at that video but it’s incredibly dry and boring. I’ll tinker with Vosc and refer to the video where necessary :wink:

this is something you have to decide by yourself while creating it.

FWIW: You control how many wavetables you allocate and you also control the bufpos signal given to VOsc. That is, SC imposes no hard limit on the number of wavetables VOsc can crossfade over.

Don’t downsample! You will lose a lot of frequency information.

A 20000-sample wavetable can contain up to 10000 sinusoidal partials. A 2048-sample wavetable can contain up to 1024 partials. If you squash the 20000 samples down to 2048, then (more or less) all partials above 1024 will fold over into the range 0 to 1024. I’d expect the result to be very noisy.

If you must downsample, you could write your own DFT (to handle non-power-of-two sizes), toss out the high partials, and IFFT to the desired power-of-two samples.

Be aware also that all of SC’s wavetable oscillators downsample the wavetable when you play it back at a frequency > 1.0 Hz. (They have to: a 441 Hz wave needs 100 samples for one cycle, taken from a 2048-point table = decimation by a factor of over 20.) These oscillators don’t oversample like Serum does, so it’s very easy to get aliasing. It would be nice to have oversampling anti-aliasing wavetable oscillators; I’m not sure if anyone’s done that in an extension.

hjh