Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
    Shubham Kamthania
    @ikamthania
    Awesome. Now I can hear it. Thanks @Sciss I will try other examples.
    Hanns Holger Rutz
    @Sciss
    Ok cool, I'm glad it's working now. Sorry about the bumpy road, it really needs an intro for people who haven't worked with SuperCollider before.
    Shubham Kamthania
    @ikamthania
    :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,
        LFNoise2.kr(1).mulAdd(0.20, 1.20),
        LFNoise2.kr(1).mulAdd(0.15, 0.15),
      )
      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
    Screenshot from 2020-06-06 13-02-28.png
    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.