Skip to content

Instantly share code, notes, and snippets.

@thomask77
Last active October 10, 2018 19:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomask77/9b87389fe6e69f733d525be96f2f96a0 to your computer and use it in GitHub Desktop.
Save thomask77/9b87389fe6e69f733d525be96f2f96a0 to your computer and use it in GitHub Desktop.
/**
.__ .___ _____
_____|__| __| _// | |_____ ___ _________
/ ___/ |/ __ |/ | |\__ \\ \/ /\_ __ \
___________ \___ \| / /_/ / ^ // __ \\ /__| |_\/___________
/____ >__\____ \____ |(____ /\_/ |__|
\/ \/ |__| \/
Copyright (c)2008 Thomas Kindler <mail_sid4avr@t-kindler.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version. See http://www.gnu.org/licenses/.
*/
#include "sid4avr.h"
#ifdef __AVR_ARCH__
# include <avr/pgmspace.h>
#else
#define PROGMEM
#define __LPM_word(x) (*(x))
#define __LPM(x) (*(x))
#endif
// Control register bits
//
#define CTRL_NOISE 0x80
#define CTRL_SQUARE 0x40
#define CTRL_SAW 0x20
#define CTRL_TRI 0x10
#define CTRL_TEST 0x08
#define CTRL_RING 0x04
#define CTRL_SYNC 0x02
#define CTRL_GATE 0x01
// Mode register bits
//
#define MODE_3OFF 0x80
#define MODE_HP 0x40
#define MODE_BP 0x20
#define MODE_LP 0x10
// Envelope generator state
//
#define ENV_ATTACK 0
#define ENV_DCYSTN 1
#define ENV_RELEASE 2
// Envelope timing table
//
#define ENV_PERIOD(ms) (((uint32_t)ms * F_SAMPLE / 1000 + 255)/256)
static const uint16_t envtab[] PROGMEM = {
ENV_PERIOD( 2), ENV_PERIOD( 8),
ENV_PERIOD( 16), ENV_PERIOD( 24),
ENV_PERIOD( 38), ENV_PERIOD( 56),
ENV_PERIOD( 68), ENV_PERIOD( 80),
ENV_PERIOD( 100), ENV_PERIOD( 250),
ENV_PERIOD( 500), ENV_PERIOD( 800),
ENV_PERIOD( 1000), ENV_PERIOD( 3000),
ENV_PERIOD( 5000), ENV_PERIOD( 8000)
};
// Master volume table
//
static const uint8_t voltab[] PROGMEM = {
0, 6, 11, 17, 23, 28, 34, 40,
46, 41, 57, 63, 68, 74, 80, 85
};
struct voice {
// Parameters
//
uint16_t freq;
uint8_t pw;
uint8_t ctrl;
uint16_t attack;
uint16_t decay;
uint16_t release;
uint8_t sustain;
// Oscillator
//
uint16_t acc;
uint8_t noise;
// Envelope generator
//
uint8_t env_state;
uint8_t env_volume;
uint8_t env_hold_zero;
uint16_t env_counter1;
uint16_t env_period1;
uint8_t env_counter2;
uint8_t env_period2;
};
static struct voice voice[3];
static uint16_t rand = 0xffff;
static uint8_t filt, mode;
static uint8_t volume;
static uint8_t cutoff, rez;
static int16_t band, low;
/**
* Fixed point 16x8 multiplication
*/
#ifdef __AVR_HAVE_MUL__
static inline int16_t mulsu16x8(int16_t opA, uint8_t opB)
{
int16_t out;
asm volatile (
"mulsu %B1, %2 \n\t"
"movw %A0, r0 \n\t"
"mul %A1, %2 \n\t"
"add %A0, r1 \n\t"
"clr __zero_reg__ \n\t"
"adc %B0, __zero_reg__ \n\t"
: "=&r" (out) // output
: "a" (opA), "a" (opB) // input
);
return out;
}
#else
# define mulsu16x8(opA, opB) (((opA) * (opB)) >> 8)
#endif
/**
* Fixed point 8x8 multiplication
*
*/
#ifdef __AVR_HAVE_MUL__
static inline uint8_t mul8x8(uint8_t opA, uint8_t opB)
{
int8_t out;
asm volatile (
"mul %1, %2 \n\t"
"mov %0, r1 \n\t"
"clr __zero_reg__ \n\t"
: "=&r" (out) // output
: "r" (opA), "r" (opB) // input
);
return out;
}
#else
# define mul8x8(opA, opB) (((opA) * (opB)) >> 8)
#endif
uint8_t sid_render()
{
int16_t outf = 0, outo = 0;
// Use only one 16 bit noise generator for all channels.
// (The LFSR period is 2^15-1 because 2^16-1 would need 4
// taps without any noticeable advantage)
//
rand = (rand << 1) | (((rand >> 14) ^ (rand >> 13)) & 1);
struct voice *v = voice;
for (int i = 0; i < 3; i++, v++) {
if (v->ctrl & CTRL_TEST)
v->acc = 0;
// for speed reasons, use a 16 bit phase accumulator
//
uint16_t next = v->acc + v->freq;
if ((v->acc ^ next) & 0x800)
v->noise = rand & 0xff;
// synchronize the next channel if enabled
//
const uint8_t sync_dest = i == 2 ? 0 : i + 1;
if (voice[sync_dest].ctrl & CTRL_SYNC)
if (!(v->acc & 0x8000) && (next & 0x8000))
voice[sync_dest].acc = 0;
v->acc = next;
// combine the selected waveforms
//
uint8_t outv = 0xff;
uint8_t saw = v->acc >> 8;
if (v->ctrl & CTRL_NOISE)
outv &= v->noise;
if (v->ctrl & CTRL_SQUARE)
outv &= saw > v->pw ? 0 : 255;
if (v->ctrl & CTRL_SAW)
outv &= saw;
if (v->ctrl & CTRL_TRI) {
uint8_t tri = saw << 1;
if (saw & 0x80)
tri ^= 0xff;
// look at the previous channel for ring modulation
//
if (v->ctrl & CTRL_RING)
if (voice[i ? i - 1 : 2].acc & 0x8000)
tri ^= 0xff;
outv &= tri;
}
if (++v->env_counter1 >= v->env_period1) {
v->env_counter1 = 0;
if (v->env_state == ENV_ATTACK || ++v->env_counter2 >= v->env_period2) {
v->env_counter2 = 0;
if (!v->env_hold_zero) {
if (v->env_state == ENV_ATTACK) {
if (++v->env_volume == 0xff) {
v->env_state = ENV_DCYSTN;
v->env_period1 = v->decay;
}
}
else if (v->env_volume != v->sustain || v->env_state == ENV_RELEASE) {
v->env_volume--;
}
if (!v->env_volume && v->env_state == ENV_RELEASE) {
v->env_hold_zero = 1;
v->env_period2 = 1;
}
else {
// do a binary search for period2
//
v->env_period2 =
v->env_volume < 55
? v->env_volume < 15
? v->env_volume < 7
? 30
: 16
: v->env_volume < 27
? 8
: 4
: v->env_volume < 94
? 2
: 1;
}
}
}
}
outv = mul8x8(outv, v->env_volume);
if (filt & (1 << i))
outf += outv;
else if (i < 2 || !(mode & MODE_3OFF))
outo += outv;
}
int16_t high = outf - low - mulsu16x8(band, rez);
band += mulsu16x8(high, cutoff);
low += mulsu16x8(band, cutoff);
if (mode & MODE_LP)
outo += low;
if (mode & MODE_BP)
outo += band;
if (mode & MODE_HP)
outo += high;
if (outo < 0)
outo = 0;
else if (outo > 768) // Yeah, 768.
outo = 768;
return mulsu16x8(outo, volume);
}
void sid_poke(uint8_t reg, uint8_t data)
{
struct voice *v = voice;
if (reg <= 20) {
while (reg > 6) {
v++;
reg -= 7;
}
}
switch (reg) {
#if (F_SAMPLE >= (31250 + 15625)/2)
case 0: v->freq = (v->freq & 0xffe0) | (data >> 3); break;
case 1: v->freq = (v->freq & 0x001f) | (data << 5); break;
#else
case 0:
v->freq = (v->freq & 0xffc0) | (data >> 2);
break;
case 1:
v->freq = (v->freq & 0x003f) | (data << 6);
break;
#endif
case 2:
v->pw = (v->pw & 0xf0) | (data >> 4);
break;
case 3:
v->pw = (v->pw & 0x0f) | (data << 4);
break;
case 4:
if (!(v->ctrl & 1) && (data & 1)) {
v->env_state = ENV_ATTACK;
v->env_period1 = v->attack;
v->env_hold_zero = 0;
}
else if ((v->ctrl & 1) && !(data & 1)) {
v->env_state = ENV_RELEASE;
v->env_period1 = v->release;
}
v->ctrl = data;
break;
case 5:
v->attack = __LPM_word(&envtab[data >> 4]);
v->decay = __LPM_word(&envtab[data & 0x0f]);
if (v->env_state == ENV_ATTACK)
v->env_period1 = v->attack;
else if (v->env_state == ENV_DCYSTN)
v->env_period1 = v->decay;
break;
case 6:
v->sustain = (data >> 4) | ((data >> 4) << 4);
v->release = __LPM_word(&envtab[data & 0x0f]);
if (v->env_state == ENV_RELEASE)
v->env_period1 = v->release;
break;
case 22:
cutoff = mul8x8(data, 240) + 16;
break;
case 23:
rez = 255 - mul8x8(data & 0xf0, 160);
filt = data;
break;
case 24:
mode = data;
// Why is this necessary?
//
if (!(mode & 0x70))
mode |= 0x70;
volume = __LPM(&voltab[data & 0x0f]);
break;
}
}
/**
.__ .___ _____
_____|__| __| _// | |_____ ___ _________
/ ___/ |/ __ |/ | |\__ \\ \/ /\_ __ \
___________ \___ \| / /_/ / ^ // __ \\ /__| |_\/___________
/____ >__\____ \____ |(____ /\_/ |__|
\/ \/ |__| \/
Copyright (c)2008 Thomas Kindler <mail_sid4avr@t-kindler.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version. See http://www.gnu.org/licenses/.
*/
#ifndef SID4AVR_H
#define SID4AVR_H
#include <stdint.h>
// #define F_SAMPLE 15625
#define F_SAMPLE 31250
extern void sid_poke(uint8_t reg, uint8_t data);
extern uint8_t sid_render();
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment