related to digital signal processing
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 that maps time values, 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 suddenly increasing rate
will not just lead to changing the steepness of the slope, but will create a 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/frequency 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
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, summing would ideally be made with error compensation, like kahan summation, because y
will be constantly added to. still, imprecision will increase. alternatively, and more stable, one could try to find a way to reset y
to exact 0 at the right points
an example in scheme
(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)))
another example in c that is based on sample count offsets as integers, to be able to be exact and reset the phase easily. the result can also be used for sine lookup tables
// accumulate an integer phase and reset it after cycles. uint32_t sp_cheap_phase_96(uint32_t current, uint32_t change) { uint32_t result; result = current + change; return (96000 <= result) ? (result % 96000) : result; } // accumulate an integer phase with change given as a float value. // change must be a positive value and is rounded to the next larger integer uint32_t sp_cheap_phase_96_float(uint32_t current, double change) { return sp_cheap_phase_96(current, (uint32_t)(change) + ((uint32_t)(change) < change)); }
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" (apply (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)))))
i have looked into creating a function (t, rate), a time offset and a rate factor, to draw a slope with varying steepness, but this seemed unnecessarily complicated, needed multiple state values, and also took a lot of floating point summation. new slope segments needed 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 it seems to conflate modulation and phase generation and is as complicated