# oscf

### Overview

`oscf`

is a table-lookup oscillator using floating point
precision and linear interpolation. This is nearly identical
to osc, except that the internal phasor used to
keep track of table position is a floating point value
`SKFLT`

instead of a fixed point integer. What this enables
is oscillator sizes that aren't power-of-two. This allows
usage of waveforms like the Adventure Kid Waveforms.

Compared to fixed point, floating point numerical representation is quite complicated and has some gotchas. The author will attempt to navigate the known gotchas as best as possible.

### Files

`oscf`

tanlges to two files: `oscf.h`

and `oscf.c`

. If
`SK_OSCF_PRIV`

is defined, the struct is exposed.

*<<oscf.h>>=*```
#ifndef SK_OSCF_H
#define SK_OSCF_H
#ifndef SKFLT
#define SKFLT float
#endif
<<typedefs>>
<<funcdefs>>
#ifdef SK_OSCF_PRIV
<<structs>>
#endif
#endif
```

*<<oscf.c>>=*```
#include <math.h>
#define SK_OSCF_PRIV
#include "oscf.h"
<<funcs>>
```

### Struct and Contents

#### Declaration

Everything is wrapped inside of a struct called `sk_osc`

.

*<<typedefs>>=*`typedef struct sk_oscf sk_oscf;`

*<<structs>>=*```
struct sk_oscf {
<<sk_oscf>>
};
```

#### Phasor

The heartbeat of an oscillator is what is known as an
`phasor`

: a ramp signal that goes from 0-1 at a specified
frequency.

Phasor state is stored in a value called `phs`

.

*<<sk_oscf>>=*`SKFLT phs;`

*<<init>>=*`oscf->phs = iphs;`

The `phs`

value gets incremented by a floating point value
`inc`

every sample.

*<<sk_oscf>>=*`SKFLT inc;`

*<<init>>=*`oscf->inc = 0;`

#### Table

The table is stored in a value called `tab`

with a size
`sz`

.

*<<sk_oscf>>=*```
SKFLT *tab;
unsigned long sz;
```

*<<init>>=*```
oscf->tab = tab;
oscf->sz = sz;
```

#### Sampling Rate

A copy of the sampling rate is stored. It is needed in order to compute the incrementor component in the phasor.

*<<sk_oscf>>=*`unsigned int sr;`

*<<init>>=*`oscf->sr = sr;`

### Initialization

oscf is initialized with `sk_oscf_init`

. It expects a
pre-allocated table `tab`

and known size `sz`

, as well
as an initial phase `iphs`

.

*<<funcdefs>>=*```
void sk_oscf_init(sk_oscf *oscf,
unsigned int sr,
SKFLT *tab,
unsigned long sz,
SKFLT iphs);
```

*<<funcs>>=*```
void sk_oscf_init(sk_oscf *oscf,
unsigned int sr,
SKFLT *tab,
unsigned long sz,
SKFLT iphs)
{
<<init>>
}
```

### Frequency

The frequency of oscf can be set with `sk_oscf_freq`

.

*<<funcdefs>>=*`void sk_oscf_freq(sk_oscf *oscf, SKFLT freq);`

*<<funcs>>=*```
void sk_oscf_freq(sk_oscf *oscf, SKFLT freq)
{
oscf->freq = freq;
}
```

freq uses parameter caching to update internal values when the frequency is changed.

*<<sk_oscf>>=*```
SKFLT freq;
SKFLT lfreq;
```

*<<init>>=*```
sk_oscf_freq(oscf, 440);
oscf->lfreq = -1;
```

### Computation

A single sample of audio is computed with `sk_oscf_tick`

.

*<<funcdefs>>=*`SKFLT sk_oscf_tick(sk_oscf *oscf);`

*<<funcs>>=*```
SKFLT sk_oscf_tick(sk_oscf *oscf)
{
SKFLT out;
unsigned long ipos;
SKFLT fpos;
SKFLT x[2];
SKFLT phs;
phs = oscf->phs;
<<update_freq>>
<<get_position>>
<<get_values>>
<<interpolate>>
<<update_phase>>
<<bounds_checking>>
oscf->phs = phs;
return out;
}
```

To begin, the frequency parameter is checked to see if the incrementor needs to be updated.

This is one way to think about computing the increment amount:

The phasor needs to move at a certain rate, which means it needs to go from 0 to 1 over a certain period of time.

Inverting the frequency value gives the cycle time in seconds.

Multiplying by the sampling rate converts it into units of samples.

Inverting it again divides it into equal chunks that add up 1: our incrementor value.

But wait! That's a lot of operations. Back-tracking, this
whole operation can be simplified to: `f/sr`

. where `f`

is
the frequency, and `sr`

is the sampling rate.

*<<update_freq>>=*```
if (oscf->lfreq != oscf->freq) {
oscf->lfreq = oscf->freq;
oscf->inc = oscf->freq / (SKFLT)oscf->sr;
}
```

Next up is to get the table lookup position. There
are two parts. The fractional component `fpos`

and
the integer component `ipos`

. The position is obtained
by scaling the phasor value.

Fun fact: keeping the phasor in a unit range and scaling this way is numerically ideal for floating point.

*<<get_position>>=*```
fpos = phs * oscf->sz;
ipos = floor(fpos);
fpos = fpos - ipos;
```

Table positions at `ipos`

and `ipos + 1`

are retrieved
from `tab`

. If `ipos`

is at the end of the table, it wraps
around.

The phasor is set to be in range 0 and 1 exactly by the time it is here, so there is no reason to normally expect out-of-bounds samples here.

*<<get_values>>=*```
x[0] = oscf->tab[ipos];
if (ipos >= (oscf->sz - 1)) {
x[1] = oscf->tab[0];
} else {
x[1] = oscf->tab[ipos + 1];
}
```

This is your run-of-the-mill linear interpolation. A crossfade in some circles.

*<<interpolate>>=*`out = fpos * x[1] + (1 - fpos) * x[0];`

Update the internal phase state using the incrementor.

*<<update_phase>>=*`phs += oscf->inc;`

Care must be done to ensure
that the phase is within the range 0 and 1. while loops
are used in place of things like `fmod`

because they are
less expensive.

A note that the phase cannot be **exactly** 1, hence the
greather than **or** equal to. (This mistake was figured out
the hard way by the author).

*<<bounds_checking>>=*```
while (phs >= 1) phs--;
while (phs < 0) phs++;
```

### Variation: external phase control

The function `sk_oscf_tick_extphs`

can be used in place
of `sk_oscf_tick`

to compute a sample of of oscf with an
external phasor. This can be used to implement things like
hard sync and phase distortion synthesis. Note that internal
frequency control is disabled in this mode.

*<<funcdefs>>=*`SKFLT sk_oscf_tick_extphs(sk_oscf *oscf, SKFLT phs);`

The function re-uses many of the same codeblocks used
in `sk_oscf_tick`

. The big change is that bounds checking
happens **before** the computation, rather than after. This
allows intentional wrap-around for things like hard sync
oscillation.

*<<funcs>>=*```
SKFLT sk_oscf_tick_extphs(sk_oscf *oscf, SKFLT phs)
{
SKFLT out;
unsigned long ipos;
SKFLT fpos;
SKFLT x[2];
out = 0;
<<bounds_checking>>
<<update_freq>>
<<get_position>>
<<get_values>>
<<interpolate>>
return out;
}
```