4. VM Components

4.1. Main Struct

<<typedefs>>=
typedef struct seqvm seqvm;
<<main_struct>>=
struct seqvm {
<<seqvm>>
};

4.2. Cell

Commands are contained inside of cells, called seqvm_cell.

<<typedefs>>=
typedef struct seqvm_cell seqvm_cell;
<<structs>>=
struct seqvm_cell {
    unsigned char cmd;
    unsigned p[3];
};

Initialize with seqvm_cell_init.

<<funcdefs>>=
void seqvm_cell_init(seqvm_cell *c);
<<funcs>>=
void seqvm_cell_init(seqvm_cell *c)
{
    c->cmd = SEQVM_NONE;
    c->p[0] = 0;
    c->p[1] = 0;
    c->p[2] = 0;
}

4.3. Cell Pool

An array of cells with size.

<<seqvm>>=
seqvm_cell *pool;
int size;
<<init>>=
svm->pool = pool;
svm->size = size;

{
    int i;
    for (i = 0; i < svm->size; i++) {
        seqvm_cell_init(&svm->pool[i]);
    }
}

The pool can be retrieved with seqvm_get_pool.

<<funcdefs>>=
seqvm_cell* seqvm_get_pool(seqvm *svm);
<<funcs>>=
seqvm_cell* seqvm_get_pool(seqvm *svm)
{
    return svm->pool;
}

4.4. Channel

Output signals are written to something known as a channel, or seqvm_chan.

<<typedefs>>=
typedef struct seqvm_chan seqvm_chan;

An output channel has 4 parameters: sequence, tick, gate, and aux.

<<structs>>=
struct seqvm_chan {
    int val;
    int tick;
    int gate;
    int aux;
};

val stores the sequence value. This should be fed into a table lookup algorithm to get converted into a note.

tick is a flag that tells seqvm to send a tick signal when a clock tick happens. This gets reset every block. Ticks can be fed into triggerable envelope generators for more percussive sounds.

gate sends a gate signal, and is generally used to indicate if a note is on or off.

aux doesn't do anything yet, but will be reserved for later.

Initialize with seqvm_chan_init.

<<funcdefs>>=
void seqvm_chan_init(seqvm_chan *chan);
<<funcs>>=
void seqvm_chan_init(seqvm_chan *chan)
{
    chan->val = 0;
    chan->tick = 0;
    chan->gate = 0;
    chan->aux = 0;
}

4.5. Channel Bank

A set of 4 channels makes a channel bank, known as seqvm_chanbank.

<<typedefs>>=
typedef struct seqvm_chanbank seqvm_chanbank;
<<structs>>=
struct seqvm_chanbank {
    seqvm_chan chan[4];
};

Initialize with seqvm_chanbank_init.

<<funcdefs>>=
void seqvm_chanbank_init(seqvm_chanbank *bnk);
<<funcs>>=
void seqvm_chanbank_init(seqvm_chanbank *bnk)
{
    int i;
    for (i = 0; i < 4; i++) seqvm_chan_init(&bnk->chan[i]);
}

There are two channel banks, known as main and next. This is done to ensure sample-precision for clocked output signals.

<<seqvm>>=
seqvm_chanbank main;
seqvm_chanbank next;
<<init>>=
seqvm_chanbank_init(&svm->main);
seqvm_chanbank_init(&svm->next);

When a tick happens inside a block of audio, the state is split between main and next. Everything before posuses main, and everything after uses next. At the next block after the tick, main is updated to be the state in next.

Explicitely update the two with svm_update_states. The block position blkpos is where a tick would happen in the block.

Get the appropriate channel from a channel bank using seqvm_get_chan.

<<funcdefs>>=
seqvm_chan * seqvm_get_chan(seqvm *svm, int chan, int pos);
<<funcs>>=
seqvm_chan * seqvm_get_chan(seqvm *svm, int chan, int pos)
{
    if (pos < svm->blkpos || svm->blkpos < 0) {
        return &svm->main.chan[chan];
    }

    return &svm->next.chan[chan];
}

4.6. Register

Registers are integer values addressable via index. These can be read and written to in the commands.

<<seqvm>>=
int r[16];
<<init>>=
{
    int i;
    for (i = 0; i < 16; i++) svm->r[i] = 0;
}

4.7. Program Position

The current program position is stored in a variable called pos. It starts at 0 at startup.

<<seqvm>>=
int pos;
<<init>>=
svm->pos = 0;

4.8. Block Position

In order to keep track of clocks with sample precision, a block position is stored in the VM. This is where the tick happens inside the computed block of audio rendered.

This adds a sparseness limitation to seqvm, only one clock signal event can happen per block. For most needs, this is acceptable.

blkpos is initialized to be negative in order to indicate no event has happened.

<<seqvm>>=
int blkpos;
<<init>>=
svm->blkpos = -1;

4.9. Halt Flag

A halt flag is used to tell the VM to stop computing.

<<seqvm>>=
int halt;
<<init>>=
svm->halt = 0;

4.10. Random Number Generator

An internal random number generator is implemented for portability purposes. It is a pretty typical LCG.

<<seqvm>>=
unsigned long rng;

Can be initialized with seqvm_srand.

<<funcdefs>>=
void seqvm_srand(seqvm *svm, unsigned long val);
<<funcs>>=
void seqvm_srand(seqvm *svm, unsigned long val)
{
    svm->rng = val;
}

Initalized to be 0 by default.

<<init>>=
seqvm_srand(svm, 0);

A random number is produce with seqvm_rand.

<<funcdefs>>=
unsigned long seqvm_rand(seqvm *svm);

A pretty standard LGC process.

<<funcs>>=
unsigned long seqvm_rand(seqvm *svm)
{
    unsigned long rng;

    rng = (1103515245 * svm->rng + 12345) % SEQVM_RANDMAX;
    svm->rng = rng;

    return rng;
}
<<macros>>=
#define SEQVM_RANDMAX 2147483648



prev | home | next