## Where communities thrive

• Join over 1.5M+ people
• Join over 100K+ communities
• Free without limits
##### Activity
ShubhamK
@ishubham-k
:thumbsup:
bmott
@Bmottomus
Hey - is there a 'correct' way to set overtones and their respective weights with ScalaCollider? I've done it in the below way, but it feels like a hacky way to achieve my goal
  var summation: GE = SinOsc.ar(220.0)
(2 to 16 ).zipWithIndex.foreach{
case (ot, idx) => {
summation = summation + (SinOsc.ar(220.0 * ot) * weights(idx))
}
}
Hanns Holger Rutz
@Sciss

@Bmottomus Yes sure; so let's say your complete example is

play {
val f0 = 220.0
var summation: GE = SinOsc.ar(f0)
val weights = Seq.tabulate(15)(i => 1.0 / (i + 1))
(2 to 16 ).zipWithIndex.foreach{
case (ot, idx) => {
summation = summation + (SinOsc.ar(f0 * ot) * weights(idx))
}
}
summation * 0.2
}

That is, weights are 1.0, 0.5, 0.333, .... You could write that more concisely as

play {
val f0 = 220.0 // fudamental
val n = 16 // number of partials
val sum = Mix.tabulate(n) { i =>
val j = i + 1
SinOsc.ar(f0 * j) * 1.0 / j
}
sum * 0.2
}

Or use multi-channel expansion followed by a Mix:

play {
val f0 = 220.0 // fudamental
val n = 16 // number of partials
val f = (1 to n).map(_ * f0)
val w = (1 to n).map(_.reciprocal)
val sum = Mix(SinOsc.ar(f) * w)
sum * 0.2
}
Are you familiar with multi-channel-expansion? (where you pass a sequence of values into a paramter, and that then creates a sequence of UGens)
Mix.tabulate(n)(...) is kind of the same as Mix(Seq.tabulate(n)(...))
bmott
@Bmottomus
No, wasn't aware I could do that - this is awesome / exactly what I was looking for!! Appreciate the detailed examples - this makes sense, thanks!
Hanns Holger Rutz
@Sciss
:thumbsup:
bmott
@Bmottomus
Hey - is it possible to create an envelope based off an AudioControlProxy? Something like Env.Step(...) but instead of a Seq[GE] a Proxy which expands to a Seq[Float] like below?
  SynthDef.recv("envl") {
Out.ar(0,
PanAz.ar(2,
SinOsc.ar(220) *
EnvGen.ar(Env.step("levels".ar(Seq(1f,0f,2f,0f)), "durs".ar(Seq(1f,1f,1f,1f)))),
orient = 0.5 )
)
}
Synth.play("envl", Seq(
"levels" -> Seq(1f, 0f, 1f, 0f),
"durs" -> Seq(1f, 1f, 1f, 1f),
)
Hanns Holger Rutz
@Sciss

@Bmottomus yes, there is a bit of mismatch between unexpanded multi-channel UGen and explicit requirement for Seq[GE], which comes from the fact that the envelope generator must have a static number of elements. This would be the workaround if there wasn't another bug:

SynthDef.recv("envl") {
val lvl   = "levels".ar(Seq(1f,0f,2f,0f))
val dur   = "durs"  .ar(Seq(1f,1f,1f,1f))
val lvlSq = Seq.tabulate(4)(lvl.out)
val durSq = Seq.tabulate(4)(dur.out)
val eg    = EnvGen.ar(Env.step(lvlSq, durSq))

Out.ar(0, Pan2.ar(SinOsc.ar(441) * eg))
}

So you still need to statically know that the number of controls is four.

Unfortunately, it seems that Env.step is buggy, it swaps levels and durations. So you have to create the envelope by hand:

SynthDef.recv("envl") {
val lvl   = "levels".ar(Seq(1f,0f,2f,0f))
val dur   = "durs"  .ar(Seq(1f,1f,1f,1f))
val lvlSq = Seq.tabulate(4)(lvl.out)
val durSq = Seq.tabulate(4)(dur.out)
val env   = new Env(lvlSq.head, (durSq zip lvlSq).map { case (d, l) =>
new Env.Segment(d, l, Curve.step)
})
val eg    = EnvGen.ar(env)

Out.ar(0, Pan2.ar(SinOsc.ar(441) * eg))
}

Synth.play("envl", Seq(
"levels" -> Seq(1f, 0f, 1f, 0f),
"durs"   -> Seq(1f, 1f, 1f, 1f),
))

(The bug is noted)

Hanns Holger Rutz
@Sciss
I have released new versions of ScalaCollider (1.28.5) and ScalaCollider-Swing (1.41.6) that contain the fix for Env.step among other things. So the first example should work now.
bmott
@Bmottomus
Make sense - didn't realize that the envelope required static inputs. Appreciate the example code again!
Hanns Holger Rutz
@Sciss
You could use DemandEnvGen for a more complex case where you don't know the number of segments in advance.
Hanns Holger Rutz
@Sciss
Here's a little fun synth by Luka Princic translated:
play {
val h = HenonN.ar(5000,
)
val lo  = 40
val hi  = LFNoise2.kr(0.1).linLin(-1, 1, 1000, 10000)
val f   = (h * 0.6).linLin(-1, 1, lo, hi)
val p   = (Pulse.ar(f) * 0.2).ring3(0.5).clip2(0.8)
val dry = LeakDC.ar(p)
val vrb = Greyhole.ar(dry, dry, feedback = 0.2, diff = 1, delayTime = 0.6) // needs sc3plugins installed
dry + vrb * 0.7
}
bmott
@Bmottomus
Hey - any chance you have examples for how to use buffer.sine1-3? For sine1 I can't figure out from the method description if the partials parameter expects overtones-as-multiples (i.e. 1-N). For sine2 I'm wondering if the partials tuples are effectively overtones.zip(weights)
Hanns Holger Rutz
@Sciss

@Bmottomus yes, they are integer multiples, because it's filling a wavetable, so naturally there are only integer overtones.

val b = Buffer.alloc(s, 512, 1)
b.sine1((1 to 4).map(_.reciprocal), normalize = true, wavetable = true, clear = true)
val x = play { Osc.ar(b.id, 200) * 0.2 }

Sadly, we currently don't have freq scope, but you could record and look at a spectrogram.

So the argument is, as the doc says, the relative amplitudes of the partials (here fundamental 1.0, up to third overtone of 1.0/4)
Hanns Holger Rutz
@Sciss
For sine2 you indicate which partials to use; again wavetable, so only integer numbers make sense (indeed I'm not sure why it accepts float at all).
b.sine2(Seq((1, 1.0f), (3, 0.5f)), true, true, true)  // fundamental at 1.0 amplitude, second overtone (third partial) at 0.5 or -6 dB
So yes, it's pairs of freq multipliers with weights, or "overtones.zip(weights)".
And for sine3 you have triplets with third component being the phase for each given partial.
Hanns Holger Rutz
@Sciss
Here's a hackish spectral plot (no axis labels), that works if you use ScalaCollider-Swing:
val bRec = Buffer.alloc(s, 16384, 1)
val b = Buffer.alloc(s, 512, 1)
b.sine1((1 to 4).map(_.reciprocal), normalize = true, wavetable = true, clear = true)

(play {
val sig = Osc.ar(b.id, 200)
RecordBuf.ar(sig, bRec.id, loop = 0, doneAction = freeSelf)
Pan2.ar(sig * 0.2)
}).onEnd {
bRec.getData().foreach { sig =>
val f = de.sciss.dsp.Fourier(bRec.numFrames)
val sigA = (sig :+ 0f :+ 0f).toArray
f.realForward(sigA)
val spect = sigA.iterator.grouped(2).map { case Seq(re, im) => (re.squared + im.squared).sqrt.ampDb } .toVector
scala.swing.Swing.onEDT(spect.take(1024).plot())
}
}
Showing the first four partials (linear frequency scale) with decreasing amplitudes (decibels)
Hanns Holger Rutz
@Sciss
Hanns Holger Rutz
@Sciss
ScalaCollider-Swing v1.41.7 has been published which includes a new oscilloscope. FreqScope is something that will probably be added in one of the next versions.
ShubhamK
@ishubham-k
Tried demo for supercollider wasm. Super cool. :thumbsup:
Hanns Holger Rutz
@Sciss

thanks! I forgot to announce it here, so here's the demo link: https://www.sciss.de/temp/scalacollider.js/

Working now on the possibility to export Mellite workspaces so they can be directly read in the browser (this still needs a few things, but I expect that to work in the next few weeks)