samples as SEMs in SynthEdit

Modular Synth design and releases (Reaktor, SynthEdit, Tassman, etc.)
RELATED
PRODUCTS

Post

this method is probably best if only applied to very short samples, so more useful for doing '909 hihat roms' than encoding huge sample libraries. if anyone has experience with stored data and can suggest improved methods, please do :)


here's how my method works:

use wavosaur, the free wav editor. under the file pin, select 'export as text,' you'll get a .txt file with content like..

0.0001910
-0.0012646
-0.0020047
0.0203930


use a text editor and add
f,
at the end of each line (indicating the values are float variables if you're new to c++) and pull the unneeded zeroes before the decimal points to save space..

.0001910f,
-.0012646f,
-.0020047f,
.0203930f,

(((then, if you have a good text editor, remove (most of?) the carriage returns so it's on a smaller amount of lines... i removed carriage returns with crimson editor, recording a 'macro' of joining values in groups of 100... this will make vertical scrolling more convenient for editing the file as it will be 10s of thousands of lines long otherwise))))

..so you have a .txt file that looks like

.0001910f, -.0012646f, -.0020047f, .0203930f,




the process loop for the sem is:

Code: Select all

void Module::sub_process(long buffer_offset, long sampleFrames )
{
    float *in1  = buffer_offset + input1_buffer;
    float *in2  = buffer_offset + input2_buffer;
    float *out1 = buffer_offset + output1_buffer;

    while(--sampleFrames >= 0) {

	if (*in2 > 0.f && gatebuf <= 0.f) {
		inc = pow(2.f, (*in1 * 10.f) - 5.f) * sratio;
		count = 0.f;
	}

	if (count < 9754.f) {
		c = (int)count;
		//d = count - (float)c;

		//o = wave[c] + (wave[c+1] - wave[c]) * d;
		o = wave[c];

		count += inc;
		if (count >= 9754) o = 0.f;
	}

	*out1 = o;

	gatebuf = *in2;

	in1++;
	in2++;

	out1++;
    }
}
simple, isn't it :)

remember, pow(2, 0) is 1.00000f. sratio is 44100.f / samplerate, producing 1.0 at 44100 and 0.5 at 88200 et c.

with a pitch of 5 and a samplerate of 44100, inc is 1, the sample is incremented by 1 for each sample ;)

in this case, my sample is 9753 samples long (array is 9754 samples, with an extra '0' at the end).

linear interpolation is commented out :) and there's undoubtedly faster ways of coding this.. the float to int conversion is the big cpu drain.

(((for faster code inc and count can be done with integers.. instead of using a decimal variable, 'inc' is a large integer, eg. progressing at 4096, then the read position integer is derived from count using a bitshift, like readposition = count >> 12 - this makes the pitch slightly less accurate but can reduce cpu, and is suitable for noisy percussion samples.)))


the only thing that's left is to get the sample data into the sem... as said, i'm not used to declaring preset data. i tried declaring wave[] as a const in the .cpp file, this produced a large cpu spike on the first trigger but fine otherwise.

the best performance i get is to declare a temporary array with the sample data and then pass it to the wave[] array, which is declared in the header. there's no cpu spike, and so far (i've only used a few 10's of thousands of smaple data so far) quick startup.

my header looks like this..

Code: Select all

	void zchannels(); // i make a separate function for writing data to arrays

private:
	float *input1_buffer; // pitch pin
	float *input2_buffer; // gate pin

	float *output1_buffer;

	short int sample; // various list entry pins for my module
	short int mode;
	short int active;

	float samplerate;
	float sratio; //    this is declared as 44100.f / samplerate :)

	int i;
	float o;

	float wave[9754];

	float count;
	float inc;

	int c;
	float d;

	float gatebuf;

	int static_count;
the open function looks like..

Code: Select all

void Module::open()
{
	SEModule_base::open();
	SET_PROCESS_FUNC(Module::sub_process);

	samplerate = getSampleRate();
	sratio = 44100.f / samplerate;

	activebuf = -97; // not used in this tutorial
	zchannels();

	o = 0.f;         // i declare i and o as my all-purpose temporary int and float vaiables

	count = 40000.f;
	inc = 1.f;

	c = 0;
	d = 0.f;

	gatebuf = -97.f;

	getPin(PN_OUTPUT1)->TransmitStatusChange( SampleClock(), ST_RUN);
}



and the zchannels() function looks like this...

Code: Select all

void Module::zchannels() {

float temp1[9754] = {
.019013f, .084689f, .181677f.....
......
......
..... 0.000031f, 0.000061f};

	for (i = 0; i < 9754; i++) {
		wave[i] = temp1[i];
	}
	wave[9754] = 0.f;
}
if you're really new at c++, yes, there is a redundant array declared to pass the data :) i only know so much about c++, and this seems to be the best way to do it :)

i'll post a demo sem later once i've improved the performance (the above is ~2x se native osc during playback due to the float to int conversion, doesn't sleep, is smaller than 1x native osc between playback, so uses about 1x se osc cpu during normal intermittent triggering. you'll also note that pitch is only calculated at sample triggering, this is easy to amend ;) )

once your SEM base code is built, it takes a few minutes of using a text editor to format the sample data, and voila! solid, reliable, and if you tweak the above code as described, fairly cpu efficient :)
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

so, you're putting a waveform (lookup table) in an array of a sem file?
if that's what you're doing then - erm, that's not good (at least, i won't do it like that)
your sem will be big (will have the lookup table inside it)
you'd better use dynamic memory for the array.. and even shared lookups if needed

and looking more at your code, it seems that the Gate will trigger the "sample" to start playback
but if you stop the gate (note-off) the sample will continue..
and also, when that sample finishes - the output will not be zeroed, but it will remain the last sample value of the waveform?
um, and um, is that a looped sample anyway?
does it work?
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

you'll see count is incremented precedent to the eof check, in which case 'o' is set to 0.f (zeroing subsequent signals) so it works (these are percussion samples).

last night i built it to 2 samples, @ 20kb each, the sem is 131kb compiled.. comparatively the osc for perfecto2 is 104kb, and small sems average ~55kb with my compiler. so it hasn't caused any issues :)

i wasn't particularly successful at sussing the mechanics of shared lookups, as such i've never had operational problems w/o them apart from longer load times :) i don't have enough environment knowledge or something, my synth horizon crashes for many people because of my nescient shared lookup table implementation.

afa dynamic memory.. my compiler is borland. there's no valid syntax for deleting memory i'm aware of, eg. ~myarray[];

again, i've made do without this facility for almost three years and it hasn't seemed to be problematic :)

tbh, the only issue i can see with this method is in case there's some internal limit in se or related environments that has a ceiling on module size or et c., which there probably is somewhere. or is there? anyone want to try it with a 5mb rom??
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

i tried an integer implementation and am getting cpu performance of ~.15% during playback now :) that's lower than oscs and 2 pole filters :)

Code: Select all

void Module::sub_process1(long buffer_offset, long sampleFrames )
{
    float *in1  = buffer_offset + input1_buffer;
    float *in2  = buffer_offset + input2_buffer;
    float *out1 = buffer_offset + output1_buffer;
    while(--sampleFrames >= 0) {

	if (*in2 > 0.f && gatebuf <= 0.f) {
		incf = pow(2.f, (*in1 * 10.f) - 5.f) * sratio * 65536.f;
		inc = (int)incf;
		count = 0;
	}
	if (count < 9754*65536) {
		c = count>>16;
		o = wave1[c];

		count += inc;
		if (count >= 9754*65536) o = 0.f;
	}
	*out1 = o;
	gatebuf = *in2;
	in1++;
	in2++;
	out1++;
    }
}
inc and count are now integers, incf is a float :)

the *65536 can of course be condensed down (and applied to the sratio variable declaration)..

if you don't understand this, it's the same as before, except the increment value is multiplied by 65536 and converted to INT, for much faster arithmetic. count is incremented by a larger value, which is reduced for resolution by bitshifting. since count is an unsigned long int and has a maximum value of 2^32, incrementing by 65536 means that these values work for any sample up to 65536 samples in length :) (2^32 / 2^16 = 2^16)

the c>>16 "bitshifts" (moves the binary values to the left or right) the value of c by 16 digits, essentially dividing by 65536.

:)
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

http://www.breathcube.com/temp/samplesem1.zip

8 snare samples (used for rolls/fills in breathcube), almost 2 seconds of sample audio, sem is ~347kb, about twice the size of the samples alone. cpu use is very good at ~.15%, no noticeable load time.

this is great! now pay me money to embed samples :hihi: as sem modules :lol: in synthedit :D i'm finding it takes about 2 or 3 minutes per sample to word process the data.
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

Two quick suggestions...

1) Data compression.
Convert the floats to chars and back to save memory.

2) Speech !
Record your voice (or google the interweb) for individual words
and you have the basis of a cool text to speech sem(s)


Regards
Etric
:)

Post

xoxos wrote:http://www.breathcube.com/temp/samplesem1.zip

8 snare samples (used for rolls/fills in breathcube), almost 2 seconds of sample audio, sem is ~347kb, about twice the size of the samples alone. cpu use is very good at ~.15%, no noticeable load time.

this is great! now pay me money to embed samples :hihi: as sem modules :lol: in synthedit :D i'm finding it takes about 2 or 3 minutes per sample to word process the data.
i might well do that if you can make single cycle wavs into oscillators ?

Post

sample based oscs -
well, i'd have to take a few minutes :hihi: to add extra pins for oscillators, like phase modulation. if you can make the wav length a power of 2 (eg. at 1024, zeros should be on sample 0 and sample 1024) cpu will be lower.

and there's two options.. i can process pitch as INTs, which will have the same low cpu as the drums, but will be quantised... if you do very fine vibrato you may hear it 'jump' between two pitches.

if i compute pitch as a float (normal accuracy), cpu is ~2x, i'd expect ~.4%


voice synthesis -
i thought the same thing a few nights ago for breathcube. i'll keep an ear out for a woman with a nice clear speaking voice :) thanks for the char idea (i'm supposed to be making a database app and learning about such things..)
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

http://www.breathcube.com/temp/samplesem1.zip

i've updated the zip (now roll snares and closed has.. i'll upload the entire set when it's complete.. presuming interest).

modules have linear int interpolation (at a resolution of 1/65536th). they use ~the same cpu in use, especially the hats transpose much better. a little lower quality than float interpolation.
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

"presuming interest" :)
always!
Image

Post

very cool xoxos !!! :love:

Post

yeah,nice!
the examples work perfectly here.:)

Post

http://www.breathcube.com/temp/samplesem1.zip
all the sound categories now.. ch/oh/s/k/cr/cl

the limitation seems to be for ~500kb worth of 44.1/16 data. so only 12 open hats and 4 crash cymbals.

the crash sem has reverse play mode with some code to make the sample continue or restart when retriggered if switching from reverse to forward based on sample position.. if anyone else has a project for which they require four samples.
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post

nice work,they'll be useful.
i'm not sure i need any custom switchable samples,i kinda been doing collections played across the keyboard,like drum or keyboard sample sets.can they be done like that? like polyphonic rompler style?

Post

if you derive a switch statement from the pitch (key) pin, and add another pin for pitch shifting, why not?

(if you potter around, using the bitshifting for INT pitch is faster if your osc loops are powers of 2 samples long).
you come and go, you come and go. amitabha neither a follower nor a leader be tagore "where roads are made i lose my way" where there is certainty, consideration is absent.

Post Reply

Return to “Modular Synthesis”