phase generation

an oscillator can be made of two parts: a phase generator and an amplitude generator. for a sinusoid, the output of the phase generator would be the input to a sine function, and the sine function would be the amplitude generator. phases repeat with the cycle of the wave, and may look like a sawtooth wave - increasing in value, then abruptly restarting.

to modulate the phase, a modulating or messenger phase can be multiplied with or added to the carrier phase

sin(t * messenger(t))

this might be enough for message transmission via frequency modulation when the change by messenger is small. a downside is, that when the change by messenger becomes larger, strong phase discontinuities occur. this comes from the fact that if a phase cycle is an increasing slope like f(x) = rate * x, then increasing rate will not just lead to changing the steepness of the slope, but will create a sudden change to the level of a slope that started with this rate from zero. for example, with values increasing like 1 2 3 4, when messenger changes from 1 to 3, a sudden rise occurs in the series, like 1 2 3 4 15 18 21 24. this sudden rise leads to a discontinuous phase shift to a point that is not a multiple of the cycle length

continuous modulation

to keep a phase continuous, the phase value must only ever increase, except for the restart of the cycle after the end. when the frequency changes, the phase should continue with different speed, to create a stretch or compression of the wave over time and frequency change. addition bounded to the phase size is one solution for that. it continues from a previous phase value and uses a value for the progression speed instead of the frequency, which instead corresponds to the steepness or angle of the phase generator output which is more difficult to use. with floating point values the sums should be made with error compensation like kahan summation because y is constantly added to. alternatively, one could try to find a way to reset y to exact 0 regularly

(define (phase y change phase-size)
  "number number number -> number
   phase generator that allows for high resolution modulation including non-linear transitions
   y: previous result or another starting value to continue from
   change: how fast the phase should progress
   phase-size: value at which the cycle should repeat
   example: (sp-phase 0.0 (/ (* 2 sp-pi) 200) (* 2 sp-pi))"
   (let (y (+ change y)) (if (< phase-size y) (+ y (- phase-size)) y)))

cycle-aligned modulation

it is possible to only allow a change of frequency at the beginnig of cycles. a downside of that is that it doesnt create higher resolution transitions between cycles. waves of different frequencies are attached to each other without smooth interpolation. this creates sudden changes at the seams.

here is an example implementation that starts uses width and height only at the beginning of phases

(define (phase-cycle width height state)
  "integer integer false/previous-result -> (result _ ...):state
   a linear phase generator that uses the given width and height only if it doesnt interrupt an active cycle.
   this keeps phases continuous and cycles phase aligned but doesnt create higher resolution transitions between cycles"
    (l (x active-width active-height)
      (if (= x active-width) (list 0 0 width height)
        (list (* (+ 1 x) (/ active-height active-width)) (+ 1 x) active-width active-height)))
    (or (and state (tail state)) (list 0 width height))))


a sawtooth wave generator can be used as a phase generator, if the output doesnt go below zero. for example, the following triangle wave function can be applied as (triangle x 100 0 (* 2 pi))

(define (triangle x a b height)
  (let (remainder (modulo x (+ a b)))
    (if (< remainder a)
      (* remainder (/ height a))
      (* (- b (- remainder a)) (/ height b)))))

other considerations

ive looked into creating a function that uses an x (or t) offset value and a rate factor to draw what is basically a slope with varying steepness but this seemed unnecessarily complicated, needed multiple state values and also took a lot of floating point summation. for this, new slope segments need to be constantly translated to cross at the endpoint of the previous segment. an alternative function that uses width and height instead of rate, that can then calculate an endpoint, and uses an interpolation function to get points between start and end would perhaps be an improvement over the previous one but seems to conflate modulation and phase generation and is as complicated