Wav file as osc waveform

Hi,

I decided to sign up to the forum as I think it will help me learn a lot more with supercollider.

Im building patches in puredata and wanting to convert to supercollider to understand my ideas in terms of coding.

I’m trying to build a simple oscillator that uses a wavefile as a carrier waveform and a sin wave to modulate it.

The easiest way i can explain my patch in pure data is:

Convert Midi note (0-127) into frequency
Divide the frequency / 10 to give a maximum tune / pitch range of 1.27khz
Add a phasor and sin wave carrier together
Multiply the phasor by the number of samples (21793) in my tabled array which contains my wav file waveform.
Multiply by .2 to reduce volume
Read and write the array to dac

Ive ran through video tutorials on youtube with supercollider and I can do some simple synthdefs etc. but the part im having trouble with is loading a wav file into a tabled array and then using this waveform as my main oscillator waveform.

If anyone could give me some pointers it would be a big help

thanks in advance :slight_smile:

Welcome!

I’ll give some quick pointers to SC equivalents:

  • Convert Midi note (0-127) into frequency: midicps
  • Divide the frequency / 10 to give a maximum tune / pitch range of 1.27khz – normal math operator
  • Add a phasor and sin wave carrier together: Phasor (note that this goes by an increment, not a frequency, so you have to divide by the sample rate, Phasor.ar(rate: frequency * SampleDur.ir, 0, 1)) and SinOsc.ar (like Pd [osc~] but sine rather than cosine)
  • Multiply the phasor by the number of samples (21793) in my tabled array which contains my wav file waveform.
    • Load the file using Buffer.read but this is not part of your SynthDef! It is a language side operation. SC’s division between language and server is likely to confuse you at first. Read the help carefully and ask more questions as you need to.
    • phasor * BufFrames.kr(bufnum)
    • BufRd.ar
  • Multiply by .2 to reduce volume – normal math operator (of course you need to read the buffer before scaling amplitude)
  • Read and write the array to dac: Out.ar

Hope that gets you started.

hjh

Wow, thanks for this! It’s is a big help, I really appreciate the detailed response. Looking forward to giving this a whirl and hopefully get some coding down! :slight_smile:

Hi jamshark70,

I’m running through a lot of the help files and reading up on the library objects and arguements.

I place my file in the sounds file in my Supercollider folder

(

s = Server.local;

b = Buffer.read(s, Platform.resourceDir +/+ “sounds\145775__zesoundresearchinc__909-kick.wav”);

)

I get an error - The system cannot find the file specified

Any pointers?

Thanks in advance;
P :slight_smile:

Use a forward slash, or \\. \ is an escape character so the path you wrote is really sounds145775__zesoundresearchinc__909-kick.wav.

hjh

1 Like

thanks jamshark70 - that worked :smiley:

i had a few other problems but i can read the file now, I also included:

Server.default = s;

I’m really struggling with the order of code - I can only see linearly from using puredata. I’ve attached the patch idea its really simple in PD but progressing to supercollider is obviously a steep learning curve (loving it though) if anyone could be kind enough to show me some pointers on how this would be coded/ordered it would be a big help. I’ve bought a few books to help me along the way and I think supercollider is probably a great start to working with coding music/synths but sometimes watching videos and translating an idea to code can be a different story :slight_smile:

For instance where would midicps be implemented? within the Freq arguement of the osc object?

Thanks in advance

So I’ve done some more reading up on the Phasor and that helped a lot using examples on the supercollider website. I’ve tried to implement that into an FM tutorial I found on youtube. I can hear the kick it clicks for about 3-4 times and nothing.

Is there a way I can set some operations to modulate a few parameters - such as implementing MouseX.

Code is here - I know I’m a total newbie but I’ve only been doing this a few days and its quite difficult to pick up.

b = Buffer.read(s, Platform.resourceDir +/+ “sounds/145775__zesoundresearchinc__909-kick.wav”);

{ BufRd.ar(1, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum))) }.play;

(
SynthDef(\fm, {
arg freq=500, mRatio=1, cRatio=1,
amp=0.2, atk=0.5, rel=3, pan=0;
var car, mod, env, framesInBuffer, trig, rate;
framesInBuffer = BufFrames.kr(b.bufnum);
env = EnvGen.kr(Env.perc(atk, rel),doneAction:2);
mod = SinOsc.ar(freq * mRatio, mul:freq * mRatio * index);
car = Phasor.ar(freq, BufRateScale.kr(b.bufnum), 0, framesInBuffer,) * env * amp;
car = Pan2.ar(car, pan);
BufRd.ar(1, b.bufnum, car);
Out.ar(0, car);
}).add;
)

Synth(\fm, [\freq, 69.midicps]);

My trouble is I’ve come from working in Puredata using sliders. but im wanting to learn code so I can learn DSP and build my own VST when I’ve gained a lot more experience. :slight_smile:

1 Like

I’ve decided to trim this down rather than bolting things from video tutorials and the library and making a whole mess.

I have absolutely no idea whether I’m on the right path with this but here it goes:

From my pure data patch I set my sliders at:

Amp: 0.2
Frequency: 395
Mod Rate: 70
Mod Depth: 65

So from that I have set up 4 arguements to represent them settings.

I then create 2 variables for my Carrier and Modulating oscillators and variable for my framesInBuffer;

My buffer is setup with my audio file.

There’s no Envelope generator as my PD patch doesnt have one - it’s basically just an Oscillator patch.

When I run the code I get a loud fuller kick sound that then goes into a clicking sound and its not at the frequency of my arguement? I would say its more around 1- 3hz.

Some help would be really great as I’m really stuck.

Code here:

b = Buffer.read(s, Platform.resourceDir +/+ “sounds/145775__zesoundresearchinc__909-kick.wav”);

{ BufRd.ar(1, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum))) }.play;

(
SynthDef(\syn, {
arg freq=395, modRate=70, modDepth=65, amp = 0.2;
var car, mod, framesInBuffer;
framesInBuffer = BufFrames.kr(b.bufnum);
mod = SinOsc.ar(modRate, 0, modDepth);
car = Phasor.ar(freq + mod, BufRateScale.kr(b.bufnum), 0, framesInBuffer) * amp;
BufRd.ar(1, b.bufnum, car);
Out.ar(0, car);
}).add;
)

Synth(\syn,);

thanks :slight_smile:

1 Like

Yes, going one step at a time is a good idea.

Note for forum usage: enclose code in triple backticks, not "> " quote tags.

```
code
```

Couple of things:

  • The first argument to Phasor is a trigger, not a frequency, so freq + mod isn’t right.
  • On second thought, though, Phasor in SC is good at increments but it’s harder to get a specific frequency. Maybe I should have suggested LFSaw and work directly in terms of frequency. Pd’s phasor is 0 - 1; SC’s LFSaw is -1 to +1, but range handles that: LFSaw.ar(freq + mod).range(0, framesInBuffer - 1).
  • car is an index into the buffer, but you’re multiplying it by amp – but amp is the volume scaler for the result of BufRd.
  • BufRd is not plugged into anything, so you aren’t even hearing the buffer at all.
  • Eventually, you’ll want to make bufnum a SynthDef argument as well (instead of hardcoding b.bufnum everywhere.

Should be:

// make sure to declare phase too, in "var"
phase = LFSaw.ar(freq + mod).range(0, framesInBuffer - 1);
car = BufRd.ar(1, b.bufnum, phase) * amp;
Out.ar(0, car);

hjh

1 Like

Massive thanks - works a treat! I just tested side by side with PD and it outputs exactly the same. :smiley:

Just another thing - i’m only getting sound from 1 channel. Is it possible to get playback from both?

Yes – you need an array of channels given to Out. The easiest way to have a two-channel array where both items are the same signal is Out.ar(0, car.dup) – that’s the same as Out.ar(0, [car, car]) where the first array element is the left channel and the second is the right (and this is the same as the two signal connections into dac~ in your Pd patch).

hjh

I’m trying another example from the puredata audio examples - this one is a phase modulation oscillator.

I run the same similar arguements as the previous example but I’m having trouble implementing the cosine into it, when I remove the cosine output from the PD patch it sounds the same as my supercollider so I know I may be close - not sure how to add the cosine onto the phasor? (I implemented LFSaw again in place of Phasor).

(
SynthDef(\pmodsyn, {
	arg carFreq=26, modFreq=58, modIndex=233, amp=0.7;
	var car, mod, index, cosine;
	mod = SinOsc.ar(modFreq, 0, modIndex / 100);
	phase = LFSaw.ar(carFreq + mod).range (0, 50);
	car = phase + mod * amp;
	Out.ar(0, car.dup);
}).add;
)

Synth(\pmodsyn,);

PD Patch example:

From my understanding is an LFSaw/Phasor used as a ramp signal that iterates through an array of data which could be a waveform or a soundfile (as previous example?)

thanks,
P

Hi,

I tried implementing cos(phase + mod) but its not working. Is there a way of achieving this with a sinOsc object and delay the starting phase, i tried below but it doesnt work. Do i need to generate a cosine wave y=cos(x) using math operators? Any help would be appreciated

Tried to implement:-

phase = (LFSaw.ar(carFreq + mod, -1);
(
SynthDef(\pmodsyn, {
	arg carFreq=26, modFreq=58, modIndex=233, amp=0.7;
	var car, mod, phase
	mod = SinOsc.ar(modFreq, 0, modIndex / 100);
	phase = (LFSaw.ar(carFreq + mod);
	car = cos(phase + mod) * amp;
	Out.ar(0, car.dup);
}).add;
)

Synth(\pmodsyn,);

thanks in advance :slight_smile:

Sorry, I haven’t read all your steps, just jumping in on the last one.
When you do cos(phase+mod) you are getting the cosine of (phase+mod), where (phase+mod) is an angle in radians. Just to make it clear: if (phase+mod) changes over time, your cos will change over time, effectively generating a signal.
People also do phase modulation with SinOscs with freq=0 (makes me happy)

        SinOsc.ar(0, phaseMod)

Note: not your case, if I understood correctly, but if (phase+mod) should instead be the frequency of a cosine wave, then you can shift a sine wave:

	SinOsc.ar((phase+mod),pi/2)

thanks elgiano - am I right in thinking that it isnt necessary to use phasor or LFSaw in this?

I’ve tried the following but i’m not getting that typical phase modulation sound

(
SynthDef(\pmodsyn, {
	arg carFreq=26, modFreq=57, modIndex=233, amp=0.7;
	var car, mod, phase
	mod = SinOsc.ar(modFreq, 0, modIndex / 100);
	car = SinOsc.ar(0, carFreq + mod) * amp;
	Out.ar(0, car.dup);
}).add;
)

Synth(\pmodsyn,);

From the PD patch i can see there is a modulation index that is ramped from 0-50ms.

The frequency of the modulator is 57hz Sine Oscillator

The modulator is multiplied by the mod index and added to a phasor object and the phasor is iterating through a cosine wave object at a frequency of 26hz.

I set the arguements up for modFreq, modIndex, and carFreq to correspond these values

I then set 3 variables at car, mod and phase

Any clues?

thanks :slight_smile:

Put the carrier frequency in the frequency argument:

(

SynthDef(\pmodsyn, {

arg carFreq=200, modFreq=300, modIndex=200, amp=0.2;

var car, mod, phase;

mod = SinOsc.ar(modFreq, 0, modIndex / 100);

car = SinOsc.ar(carFreq, mod.wrap(-2pi, 2pi)) * amp;

Out.ar(0, car.dup);

}).play;

)

1 Like

I’d suggest to derive it from first principles – one step at a time. Doing too many things at once = confusion.

// so that we can swap out synths easily
p = ProxySpace.new.push;

~a = { |freq = 200|
	var phase = LFSaw.ar(freq);
	phase.dup
};

~a.play(vol: 0.1);

~a = { |freq = 200|
	var phase = LFSaw.ar(freq);
	cos(phase).dup
};

If you look at this on the scope, it isn’t a true cosine – because a full cycle of a cosine requires 0 to 2pi, or -pi to pi, but LFSaw is -1 to +1. To get a cosine, you have to scale the phase:

~a = { |freq = 200|
	var phase = LFSaw.ar(freq) * pi;
	cos(phase).dup
};

Then, adding a modulator does give you that classic phase modulation sound.

~a = { |freq = 200|
	var modIndex = 1,
	mod = SinOsc.ar(freq) * modIndex,
	phase = LFSaw.ar(freq) * pi;
	cos(phase + mod).dup
};

// compare to other approaches

// FM way
// bit of phase shift in the waveform
// but the spectrum analyzer shows basically the same content
// so this formula is compatible with the last one
~a = { |freq = 200|
	var modIndex = 1,
	mod = SinOsc.ar(freq) * modIndex;
	SinOsc.ar(freq * (1 + mod)).dup
};

// Sam's way (least amount of DC offset btw)
// sin(phase + mod) is about the same
~a = { |freq = 200|
	var modIndex = 1,
	mod = SinOsc.ar(freq) * modIndex;
	SinOsc.ar(freq, mod.wrap(-pi, pi)).dup
};

~a.clear;
p.pop;

From the PD patch i can see there is a modulation index that is ramped from 0-50ms.

You do have to adjust the numbers a bit between SC and Pd.

In Pd, you do [phasor~] → [cos~]. [phasor~] is 0…1. [cos~] accepts not radians, but fractions of a cycle. 0.5 → [cos~] in Pd gives you the same answer as cos(pi) in SC.

cos-pd-vs-sc

So anything that you did with phase in Pd, you have to multiply by 2pi to get the same in SC.

Also note that the Pd patch is dividing the modulation index by 100! But you haven’t done that at all in the SC code (so you will get an extremely noisy result). So you need modIndex * (0.01 * 2pi) for full compatibility.

hjh

1 Like

thanks jamshark70 super helpful and detailed response :smiley:

~a.play(vol: 0.1).plot;

Not sure where to use the plot/scope function so I can analyse and get a deeper understanding of what is happening. i tried using .plot here as well but its not working.

I checked the library and it works no problem with the pink noise example

{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.scope;

I made sure its outside the curly parenthesis but still no luck :confused:

thanks,
P