overtone 2026-01-24

Found/fixed a bug, but it required a fix in potemkin, so we'll see if we can get that merged and released • https://github.com/clj-commons/potemkin/pull/82 • https://github.com/overtone/overtone/pull/595 I got a half a mind to vendor/shade the bits of potemkin that we use into overtone

❤️ 3

Seems like the potemkin PR got merged, can the overtone PR be completed now as well?

Here's a fun synth to play with, loosely inspired by the Yamaha YM2612 found in the Sega Genesis. Lots of great FM sounds you can get out of this.

(definst ym2612-mode0
  "Approximation of the Yamaha YM2612 used in the Sega Genesis. The YM2612 has 4
  'Operators' (sine oscillators), which can either acts as 'carrier' (output
  signal) or modulator, phase modulating another operator. This synth models
  mode 0, where all four oscillators are 'stacked', each modulating the next
  one. Parameters match the range the YM2612 used. e.g. 0-127, 0-32 etc. This
  makes it easy to map these to MIDI CC values.

  Note that the fourth operator is the carrier, which is modulated by op3, etc.
  So you want to tweak `:l3`/`:m3` before moving on to `:l2` and `:l1`.

  The default has operators 1/2/3 (the modulators) at minimum volume, so you get
  a clean sine wave. Decrease the value of :l3/:l2/:l1 (in that order) and
  you'll get richer timbres. Similarly set :m3/:m2/:m1 to change the frequency
  multiplier for each operator, and get other timbres as well.

  Operator 1 (the start of the chain) can be set to feed back into itself,
  leading to noise/crunch."
  [;; conventional synth params
   freq 130.80
   amp 1.0
   gate 1.0
   ;; feedback
   fb 0.0

   ;; Operators
   ;; --- Levels (Total Level) ---
   ;; 0 is max volume (0dB), 127 is silence (-95.25dB)
   l1 {:doc "op1 level (modulator)" :min 0 :max 127 :default 127}
   l2 {:doc "op2 level (modulator)" :min 0 :max 127 :default 127}
   l3 {:doc "op3 level (modulator)" :min 0 :max 127 :default 127}
   l4 {:doc "op4 level (carrier)" :min 0 :max 127 :default 0}

   ;; --- Multipliers ---
   ;; 0 maps to 0.5, 1-15 map to integers 1-15
   m1 {:doc "op1 freq multiplier" :min 0 :max 15 :default 1}
   m2 {:doc "op2 freq multiplier" :min 0 :max 15 :default 1}
   m3 {:doc "op3 freq multiplier" :min 0 :max 15 :default 1}
   m4 {:doc "op4 freq multiplier" :min 0 :max 15 :default 1}

   ;; --- Attack Rates ---
   ;; 31 is instant, 0 is approx 10+ seconds
   a1 {:doc "op1 attack rate" :min 0 :max 31 :default 31}
   a2 {:doc "op2 attack rate" :min 0 :max 31 :default 31}
   a3 {:doc "op3 attack rate" :min 0 :max 31 :default 31}
   a4 {:doc "op4 attack rate" :min 0 :max 31 :default 31}

   ;; --- Decay Rates ---
   ;; 31 is fastest decay, 0 is approx 10+ seconds
   d1 {:doc "op1 decay rate" :min 0 :max 31 :default 31}
   d2 {:doc "op2 decay rate" :min 0 :max 31 :default 31}
   d3 {:doc "op3 decay rate" :min 0 :max 31 :default 31}
   d4 {:doc "op4 decay rate" :min 0 :max 31 :default 31}

   ;; --- Sustain Levels ---
   ;; 0 is max volume (peak), 127 is ~silence
   s1 {:doc "op1 sustain level" :min 0 :max 127 :default 0}
   s2 {:doc "op2 sustain level" :min 0 :max 127 :default 0}
   s3 {:doc "op3 sustain level" :min 0 :max 127 :default 0}
   s4 {:doc "op4 sustain level" :min 0 :max 127 :default 0}

   ;; --- Release Rates ---
   ;; 31 is fastest release, 0 is approx 10+ seconds
   r1 {:doc "op1 release rate" :min 0 :max 31 :default 31}
   r2 {:doc "op2 release rate" :min 0 :max 31 :default 31}
   r3 {:doc "op3 release rate" :min 0 :max 31 :default 31}
   r4 {:doc "op4 release rate" :min 0 :max 31 :default 31}

   ;; detune
   dt1 0.0 dt2 0.0 dt3 0.0 dt4 0.0

   ;; phase scaling constant, you shouldn't have to mess with this normally. It
   ;; influences how sensitive m1/m2/m3/m4 are
   pm-scale 4.0]
  (let [env1 (adsr (lin-exp a1 0 31 10.0 0.001)
                   (lin-exp d1 0 31 10.0 0.001)
                   (lin-lin s1 0 127 1.0 0.0)
                   (lin-exp r1 0 31 10.0 0.001))
        env2 (adsr (lin-exp a2 0 31 10.0 0.001)
                   (lin-exp d2 0 31 10.0 0.001)
                   (lin-lin s2 0 127 1.0 0.0)
                   (lin-exp r2 0 31 10.0 0.001))
        env3 (adsr (lin-exp a3 0 31 10.0 0.001)
                   (lin-exp d3 0 31 10.0 0.001)
                   (lin-lin s3 0 127 1.0 0.0)
                   (lin-exp r3 0 31 10.0 0.001))
        env4 (adsr (lin-exp a4 0 31 10.0 0.001)
                   (lin-exp d4 0 31 10.0 0.001)
                   (lin-lin s4 0 127 1.0 0.0)
                   (lin-exp r4 0 31 10.0 0.001))
        m1'  (+ m1 (* 0.5 (< m1 1))) ;; 0=0.5, higher -> 1=1
        m2'  (+ m2 (* 0.5 (< m2 1)))
        m3'  (+ m3 (* 0.5 (< m3 1)))
        m4'  (+ m4 (* 0.5 (< m4 1)))
        op1  (* (dbamp (* -0.75 l1)) pm-scale
                (fb-sine-n (* (+ m1' dt1) freq) (* fb (/ Math/PI 7.0)))
                (env-gen env1))
        op2  (* (dbamp (* -0.75 l2)) pm-scale (sin-osc (* (+ m2' dt2) freq) op1) (env-gen env2))
        op3  (* (dbamp (* -0.75 l3)) pm-scale (sin-osc (* (+ m3' dt3) freq) op2) (env-gen env3))
        op4  (* amp (dbamp (* -0.75 l4)) (sin-osc (* m4' freq) op3) (env-gen env4 gate :action FREE))]
    op4))

❤️ 6

Why dt4 is orphane?

Looks good! Why are the oscillator levels in reverse (0 being max and 127 min)?

it's just how the chip works

I see. IMHO, the interface could improve on that, as it seems to run counter to the expectations from a user perspective.

I don't remember exactly how I got to this, I was going down a OPL rabbit hole at the time. The idea is pretty simple though, you have "operators", which means a sin-osc + envelope. An operator either goes directly to out, or it phase-modulates another operator. For each operator there's a level control, and a frequency multiplier (either 1/2 or integer 1,2,3,4,etc). Different Yamaha chips have different amounts of operators, and you can route them in different ways. In this case they're routed in series, each phase-modulating the next. It's also common to have 2x2 setup, with two going to output and the other two modulating the first two. In the chip used in the Sega Genesis one of the operators also had a feedback control, so that's that fb-sine-n, this gives you more noisy/grungy options. Most other stuff that's here like all the lin-lin and lin-exp and dbamp is about trying to make the parameters behave similarly, so in theory you can look at original OPL patch sheets and recreate some of those sounds, but it doesn't quite match anyway. I should do a simplified version that uses ranges that are more what you would expect in overtone or other modern systems.

1

@plexus What's the process of creating this synth? e.g., did you implement a spec or transliterate from sclang?

I have no reference points other than the Sega Genesis has a sound chip, and somehow this code appeared 🙂

Might add this as a built-in in overtone