Last active
October 10, 2018 19:00
-
-
Save thomask77/9b87389fe6e69f733d525be96f2f96a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
.__ .___ _____ | |
_____|__| __| _// | |_____ ___ _________ | |
/ ___/ |/ __ |/ | |\__ \\ \/ /\_ __ \ | |
___________ \___ \| / /_/ / ^ // __ \\ /__| |_\/___________ | |
/____ >__\____ \____ |(____ /\_/ |__| | |
\/ \/ |__| \/ | |
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
.__ .___ _____ | |
_____|__| __| _// | |_____ ___ _________ | |
/ ___/ |/ __ |/ | |\__ \\ \/ /\_ __ \ | |
___________ \___ \| / /_/ / ^ // __ \\ /__| |_\/___________ | |
/____ >__\____ \____ |(____ /\_/ |__| | |
\/ \/ |__| \/ | |
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