2018-09-18 16:19:10 +00:00
|
|
|
#include "ATMlib.h"
|
|
|
|
|
|
|
|
#ifndef AB_ALTERNATE_WIRING
|
|
|
|
ATMLIB_CONSTRUCT_ISR(OCR4A)
|
|
|
|
#else
|
|
|
|
ATMLIB_CONSTRUCT_ISR(OCR4A,OCR4D)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
byte trackCount;
|
|
|
|
byte tickRate;
|
|
|
|
const word *trackList;
|
|
|
|
const byte *trackBase;
|
|
|
|
uint8_t pcm __attribute__((used)) = 128;
|
|
|
|
bool half __attribute__((used));
|
|
|
|
|
|
|
|
byte ChannelActiveMute = 0b11110000;
|
|
|
|
// ||||||||
|
|
|
|
// |||||||└-> 0 channel 0 is muted (0 = false / 1 = true)
|
|
|
|
// ||||||└--> 1 channel 1 is muted (0 = false / 1 = true)
|
|
|
|
// |||||└---> 2 channel 2 is muted (0 = false / 1 = true)
|
|
|
|
// ||||└----> 3 channel 3 is muted (0 = false / 1 = true)
|
|
|
|
// |||└-----> 4 channel 0 is Active (0 = false / 1 = true)
|
|
|
|
// ||└------> 5 channel 1 is Active (0 = false / 1 = true)
|
|
|
|
// |└-------> 6 channel 2 is Active (0 = false / 1 = true)
|
|
|
|
// └--------> 7 channel 3 is Active (0 = false / 1 = true)
|
|
|
|
|
|
|
|
//Imports
|
|
|
|
extern uint16_t cia;
|
|
|
|
|
|
|
|
// Exports
|
|
|
|
osc_t osc[4];
|
|
|
|
|
|
|
|
|
|
|
|
const word noteTable[64] PROGMEM = {
|
|
|
|
0,
|
|
|
|
262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
|
|
|
|
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
|
|
|
|
1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,
|
|
|
|
2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
|
|
|
|
4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902,
|
|
|
|
8372, 8870, 9397,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct ch_t {
|
|
|
|
const byte *ptr;
|
|
|
|
byte note;
|
|
|
|
|
|
|
|
// Nesting
|
|
|
|
word stackPointer[7];
|
|
|
|
byte stackCounter[7];
|
|
|
|
byte stackTrack[7]; // note 1
|
|
|
|
byte stackIndex;
|
|
|
|
byte repeatPoint;
|
|
|
|
|
|
|
|
// Looping
|
|
|
|
word delay;
|
|
|
|
byte counter;
|
|
|
|
byte track;
|
|
|
|
|
|
|
|
// External FX
|
|
|
|
word freq;
|
|
|
|
byte vol;
|
|
|
|
|
|
|
|
// Volume & Frequency slide FX
|
|
|
|
char volFreSlide;
|
|
|
|
byte volFreConfig;
|
|
|
|
byte volFreCount;
|
|
|
|
|
|
|
|
// Arpeggio or Note Cut FX
|
|
|
|
byte arpNotes; // notes: base, base+[7:4], base+[7:4]+[3:0], if FF => note cut ON
|
|
|
|
byte arpTiming; // [7] = reserved, [6] = not third note ,[5] = retrigger, [4:0] = tick count
|
|
|
|
byte arpCount;
|
|
|
|
|
|
|
|
// Retrig FX
|
|
|
|
byte reConfig; // [7:2] = , [1:0] = speed // used for the noise channel
|
|
|
|
byte reCount; // also using this as a buffer for volume retrig on all channels
|
|
|
|
|
|
|
|
// Transposition FX
|
|
|
|
char transConfig;
|
|
|
|
|
|
|
|
// Tremolo or Vibrato FX
|
|
|
|
byte treviDepth;
|
|
|
|
byte treviConfig;
|
|
|
|
byte treviCount;
|
|
|
|
|
|
|
|
// Glissando FX
|
|
|
|
char glisConfig;
|
|
|
|
byte glisCount;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
ch_t channel[4];
|
|
|
|
|
|
|
|
uint16_t read_vle(const byte **pp) {
|
|
|
|
word q = 0;
|
|
|
|
byte d;
|
|
|
|
do {
|
|
|
|
q <<= 7;
|
|
|
|
d = pgm_read_byte(*pp++);
|
|
|
|
q |= (d & 0x7F);
|
|
|
|
} while (d & 0x80);
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline const byte *getTrackPointer(byte track) {
|
|
|
|
return trackBase + pgm_read_word(&trackList[track]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ATMsynth::play(const byte *song) {
|
2018-09-20 22:32:21 +00:00
|
|
|
TIMSK4 = 0b00000000;// ensure interrupt is disabled
|
2018-09-18 16:19:10 +00:00
|
|
|
cia_count = 1;
|
|
|
|
// cleanUp stuff first
|
|
|
|
memset(channel, 0, sizeof(channel));
|
|
|
|
ChannelActiveMute = 0b11110000;
|
|
|
|
|
|
|
|
// Initializes ATMsynth
|
|
|
|
// Sets sample rate and tick rate
|
|
|
|
tickRate = 25;
|
|
|
|
cia = 15625 / tickRate;
|
|
|
|
// Sets up the ports, and the sample grinding ISR
|
|
|
|
|
|
|
|
osc[3].freq = 0x0001; // Seed LFSR
|
|
|
|
channel[3].freq = 0x0001; // xFX
|
|
|
|
|
|
|
|
TCCR4A = 0b01000010; // Fast-PWM 8-bit
|
|
|
|
TCCR4B = 0b00000001; // 62500Hz
|
|
|
|
OCR4C = 0xFF; // Resolution to 8-bit (TOP=0xFF)
|
|
|
|
OCR4A = 0x80;
|
|
|
|
#ifdef AB_ALTERNATE_WIRING
|
|
|
|
TCCR4C = 0b01000101;
|
|
|
|
OCR4D = 0x80;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Load a melody stream and start grinding samples
|
|
|
|
// Read track count
|
|
|
|
trackCount = pgm_read_byte(song++);
|
|
|
|
// Store track list pointer
|
|
|
|
trackList = (word*)song;
|
|
|
|
// Store track pointer
|
|
|
|
trackBase = (song += (trackCount << 1)) + 4;
|
|
|
|
// Fetch starting points for each track
|
|
|
|
for (unsigned n = 0; n < 4; n++) {
|
|
|
|
channel[n].ptr = getTrackPointer(pgm_read_byte(song++));
|
|
|
|
}
|
2018-09-20 22:23:05 +00:00
|
|
|
TIMSK4 = 0b00000100;// enable interrupt as last
|
2018-09-18 16:19:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop playing, unload melody
|
|
|
|
void ATMsynth::stop() {
|
|
|
|
TIMSK4 = 0; // Disable interrupt
|
|
|
|
memset(channel, 0, sizeof(channel));
|
|
|
|
ChannelActiveMute = 0b11110000;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start grinding samples or Pause playback
|
|
|
|
void ATMsynth::playPause() {
|
|
|
|
TIMSK4 = TIMSK4 ^ 0b00000100; // toggle disable/enable interrupt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Toggle mute on/off on a channel, so it can be used for sound effects
|
|
|
|
// So you have to call it before and after the sound effect
|
|
|
|
void ATMsynth::muteChannel(byte ch) {
|
|
|
|
ChannelActiveMute += (1 << 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
void ATMsynth::unMuteChannel(byte ch) {
|
|
|
|
ChannelActiveMute &= (~(1 << 0 ));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute__((used))
|
|
|
|
void ATM_playroutine() {
|
|
|
|
ch_t *ch;
|
|
|
|
|
|
|
|
// for every channel start working
|
|
|
|
for (byte n = 0; n < 4; n++)
|
|
|
|
{
|
|
|
|
ch = &channel[n];
|
|
|
|
|
|
|
|
// Noise retriggering
|
|
|
|
if (ch->reConfig) {
|
|
|
|
if (ch->reCount >= (ch->reConfig & 0x03)) {
|
|
|
|
osc[n].freq = pgm_read_word(¬eTable[ch->reConfig >> 2]);
|
|
|
|
ch->reCount = 0;
|
|
|
|
}
|
|
|
|
else ch->reCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Apply Glissando
|
|
|
|
if (ch->glisConfig) {
|
|
|
|
if (ch->glisCount >= (ch->glisConfig & 0x7F)) {
|
|
|
|
if (ch->glisConfig & 0x80) ch->note -= 1;
|
|
|
|
else ch->note += 1;
|
|
|
|
if (ch->note < 1) ch->note = 1;
|
|
|
|
else if (ch->note > 63) ch->note = 63;
|
|
|
|
ch->freq = pgm_read_word(¬eTable[ch->note]);
|
|
|
|
ch->glisCount = 0;
|
|
|
|
}
|
|
|
|
else ch->glisCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Apply volume/frequency slides
|
|
|
|
if (ch->volFreSlide) {
|
|
|
|
if (!ch->volFreCount) {
|
|
|
|
int16_t vf = ((ch->volFreConfig & 0x40) ? ch->freq : ch->vol);
|
|
|
|
vf += (ch->volFreSlide);
|
|
|
|
if (!(ch->volFreConfig & 0x80)) {
|
|
|
|
if (vf < 0) vf = 0;
|
|
|
|
else if (ch->volFreConfig & 0x40) if (vf > 9397) vf = 9397;
|
|
|
|
else if (!(ch->volFreConfig & 0x40)) if (vf > 63) vf = 63;
|
|
|
|
}
|
|
|
|
(ch->volFreConfig & 0x40) ? ch->freq = vf : ch->vol = vf;
|
|
|
|
}
|
|
|
|
if (ch->volFreCount++ >= (ch->volFreConfig & 0x3F)) ch->volFreCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Apply Arpeggio or Note Cut
|
|
|
|
if (ch->arpNotes && ch->note) {
|
|
|
|
if ((ch->arpCount & 0x1F) < (ch->arpTiming & 0x1F)) ch->arpCount++;
|
|
|
|
else {
|
|
|
|
if ((ch->arpCount & 0xE0) == 0x00) ch->arpCount = 0x20;
|
|
|
|
else if ((ch->arpCount & 0xE0) == 0x20 && !(ch->arpTiming & 0x40) && (ch->arpNotes != 0xFF)) ch->arpCount = 0x40;
|
|
|
|
else ch->arpCount = 0x00;
|
|
|
|
byte arpNote = ch->note;
|
|
|
|
if ((ch->arpCount & 0xE0) != 0x00) {
|
|
|
|
if (ch->arpNotes == 0xFF) arpNote = 0;
|
|
|
|
else arpNote += (ch->arpNotes >> 4);
|
|
|
|
}
|
|
|
|
if ((ch->arpCount & 0xE0) == 0x40) arpNote += (ch->arpNotes & 0x0F);
|
|
|
|
ch->freq = pgm_read_word(¬eTable[arpNote + ch->transConfig]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Apply Tremolo or Vibrato
|
|
|
|
if (ch->treviDepth) {
|
|
|
|
int16_t vt = ((ch->treviConfig & 0x40) ? ch->freq : ch->vol);
|
|
|
|
vt = (ch->treviCount & 0x80) ? (vt + ch->treviDepth) : (vt - ch->treviDepth);
|
|
|
|
if (vt < 0) vt = 0;
|
|
|
|
else if (ch->treviConfig & 0x40) if (vt > 9397) vt = 9397;
|
|
|
|
else if (!(ch->treviConfig & 0x40)) if (vt > 63) vt = 63;
|
|
|
|
(ch->treviConfig & 0x40) ? ch->freq = vt : ch->vol = vt;
|
|
|
|
if ((ch->treviCount & 0x1F) < (ch->treviConfig & 0x1F)) ch->treviCount++;
|
|
|
|
else {
|
|
|
|
if (ch->treviCount & 0x80) ch->treviCount = 0;
|
|
|
|
else ch->treviCount = 0x80;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ch->delay) {
|
|
|
|
if (ch->delay != 0xFFFF) ch->delay--;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
do {
|
|
|
|
byte cmd = pgm_read_byte(ch->ptr++);
|
|
|
|
if (cmd < 64) {
|
|
|
|
// 0 … 63 : NOTE ON/OFF
|
|
|
|
if (ch->note = cmd) ch->note += ch->transConfig;
|
|
|
|
ch->freq = pgm_read_word(¬eTable[ch->note]);
|
|
|
|
if (!ch->volFreConfig) ch->vol = ch->reCount;
|
|
|
|
if (ch->arpTiming & 0x20) ch->arpCount = 0; // ARP retriggering
|
|
|
|
}
|
|
|
|
else if (cmd < 160) {
|
|
|
|
// 64 … 159 : SETUP FX
|
|
|
|
switch (cmd - 64) {
|
|
|
|
case 0: // Set volume
|
|
|
|
ch->vol = pgm_read_byte(ch->ptr++);
|
|
|
|
ch->reCount = ch->vol;
|
|
|
|
break;
|
|
|
|
case 1: case 4: // Slide volume/frequency ON
|
|
|
|
ch->volFreSlide = pgm_read_byte(ch->ptr++);
|
|
|
|
ch->volFreConfig = (cmd - 64) == 1 ? 0x00 : 0x40;
|
|
|
|
break;
|
|
|
|
case 2: case 5: // Slide volume/frequency ON advanced
|
|
|
|
ch->volFreSlide = pgm_read_byte(ch->ptr++);
|
|
|
|
ch->volFreConfig = pgm_read_byte(ch->ptr++);
|
|
|
|
break;
|
|
|
|
case 3: case 6: // Slide volume/frequency OFF (same as 0x01 0x00)
|
|
|
|
ch->volFreSlide = 0;
|
|
|
|
break;
|
|
|
|
case 7: // Set Arpeggio
|
|
|
|
ch->arpNotes = pgm_read_byte(ch->ptr++); // 0x40 + 0x03
|
|
|
|
ch->arpTiming = pgm_read_byte(ch->ptr++); // 0x40 (no third note) + 0x20 (toggle retrigger) + amount
|
|
|
|
break;
|
|
|
|
case 8: // Arpeggio OFF
|
|
|
|
ch->arpNotes = 0;
|
|
|
|
break;
|
|
|
|
case 9: // Set Retriggering (noise)
|
|
|
|
ch->reConfig = pgm_read_byte(ch->ptr++); // RETRIG: point = 1 (*4), speed = 0 (0 = fastest, 1 = faster , 2 = fast)
|
|
|
|
break;
|
|
|
|
case 10: // Retriggering (noise) OFF
|
|
|
|
ch->reConfig = 0;
|
|
|
|
break;
|
|
|
|
case 11: // ADD Transposition
|
|
|
|
ch->transConfig += (char)pgm_read_byte(ch->ptr++);
|
|
|
|
break;
|
|
|
|
case 12: // SET Transposition
|
|
|
|
ch->transConfig = pgm_read_byte(ch->ptr++);
|
|
|
|
break;
|
|
|
|
case 13: // Transposition OFF
|
|
|
|
ch->transConfig = 0;
|
|
|
|
break;
|
|
|
|
case 14: case 16: // SET Tremolo/Vibrato
|
|
|
|
ch->treviDepth = pgm_read_word(ch->ptr++);
|
|
|
|
ch->treviConfig = pgm_read_word(ch->ptr++) + ((cmd - 64) == 14 ? 0x00 : 0x40);
|
|
|
|
break;
|
|
|
|
case 15: case 17: // Tremolo/Vibrato OFF
|
|
|
|
ch->treviDepth = 0;
|
|
|
|
break;
|
|
|
|
case 18: // Glissando
|
|
|
|
ch->glisConfig = pgm_read_byte(ch->ptr++);
|
|
|
|
break;
|
|
|
|
case 19: // Glissando OFF
|
|
|
|
ch->glisConfig = 0;
|
|
|
|
break;
|
|
|
|
case 20: // SET Note Cut
|
|
|
|
ch->arpNotes = 0xFF; // 0xFF use Note Cut
|
|
|
|
ch->arpTiming = pgm_read_byte(ch->ptr++); // tick amount
|
|
|
|
break;
|
|
|
|
case 21: // Note Cut OFF
|
|
|
|
ch->arpNotes = 0;
|
|
|
|
break;
|
|
|
|
case 92: // ADD tempo
|
|
|
|
tickRate += pgm_read_byte(ch->ptr++);
|
|
|
|
cia = 15625 / tickRate;
|
|
|
|
break;
|
|
|
|
case 93: // SET tempo
|
|
|
|
tickRate = pgm_read_byte(ch->ptr++);
|
|
|
|
cia = 15625 / tickRate;
|
|
|
|
break;
|
|
|
|
case 94: // Goto advanced
|
|
|
|
for (byte i = 0; i < 4; i++) channel[i].repeatPoint = pgm_read_byte(ch->ptr++);
|
|
|
|
break;
|
|
|
|
case 95: // Stop channel
|
|
|
|
ChannelActiveMute = ChannelActiveMute ^ (1 << (n + 4));
|
|
|
|
ch->vol = 0;
|
|
|
|
ch->delay = 0xFFFF;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (cmd < 224) {
|
|
|
|
// 160 … 223 : DELAY
|
|
|
|
ch->delay = cmd - 159;
|
|
|
|
} else if (cmd == 224) {
|
|
|
|
// 224: LONG DELAY
|
|
|
|
ch->delay = read_vle(&ch->ptr) + 65;
|
|
|
|
} else if (cmd < 252) {
|
|
|
|
// 225 … 251 : RESERVED
|
|
|
|
} else if (cmd == 252 || cmd == 253) {
|
|
|
|
// 252 (253) : CALL (REPEATEDLY)
|
|
|
|
byte new_counter = cmd == 252 ? 0 : pgm_read_byte(ch->ptr++);
|
|
|
|
byte new_track = pgm_read_byte(ch->ptr++);
|
|
|
|
|
|
|
|
if (new_track != ch->track) {
|
|
|
|
// Stack PUSH
|
|
|
|
ch->stackCounter[ch->stackIndex] = ch->counter;
|
|
|
|
ch->stackTrack[ch->stackIndex] = ch->track; // note 1
|
|
|
|
ch->stackPointer[ch->stackIndex] = ch->ptr - trackBase;
|
|
|
|
ch->stackIndex++;
|
|
|
|
ch->track = new_track;
|
|
|
|
}
|
|
|
|
|
|
|
|
ch->counter = new_counter;
|
|
|
|
ch->ptr = getTrackPointer(ch->track);
|
|
|
|
} else if (cmd == 254) {
|
|
|
|
// 254 : RETURN
|
|
|
|
if (ch->counter > 0 || ch->stackIndex == 0) {
|
|
|
|
// Repeat track
|
|
|
|
if (ch->counter) ch->counter--;
|
|
|
|
ch->ptr = getTrackPointer(ch->track);
|
|
|
|
//asm volatile (" jmp 0"); // reboot
|
|
|
|
} else {
|
|
|
|
// Check stack depth
|
|
|
|
if (ch->stackIndex == 0) {
|
|
|
|
// Stop the channel
|
|
|
|
ch->delay = 0xFFFF;
|
|
|
|
} else {
|
|
|
|
// Stack POP
|
|
|
|
ch->stackIndex--;
|
|
|
|
ch->ptr = ch->stackPointer[ch->stackIndex] + trackBase;
|
|
|
|
ch->counter = ch->stackCounter[ch->stackIndex];
|
|
|
|
ch->track = ch->stackTrack[ch->stackIndex]; // note 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (cmd == 255) {
|
|
|
|
// 255 : EMBEDDED DATA
|
|
|
|
ch->ptr += read_vle(&ch->ptr);
|
|
|
|
}
|
|
|
|
} while (ch->delay == 0);
|
|
|
|
|
|
|
|
if (ch->delay != 0xFFFF) ch->delay--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(ChannelActiveMute & (1 << n))) {
|
|
|
|
if (n == 3) {
|
|
|
|
// Half volume, no frequency for noise channel
|
|
|
|
osc[n].vol = ch->vol >> 1;
|
|
|
|
} else {
|
|
|
|
osc[n].freq = ch->freq;
|
|
|
|
osc[n].vol = ch->vol;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if all channels are inactive, stop playing or check for repeat
|
|
|
|
|
|
|
|
if (!(ChannelActiveMute & 0xF0))
|
|
|
|
{
|
|
|
|
byte repeatSong = 0;
|
|
|
|
for (byte j = 0; j < 4; j++) repeatSong += channel[j].repeatPoint;
|
|
|
|
if (repeatSong) {
|
|
|
|
for (byte k = 0; k < 4; k++) {
|
|
|
|
channel[k].ptr = getTrackPointer(channel[k].repeatPoint);
|
|
|
|
channel[k].delay = 0;
|
|
|
|
}
|
|
|
|
ChannelActiveMute = 0b11110000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memset(channel, 0, sizeof(channel));
|
|
|
|
TIMSK4 = 0; // Disable interrupt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|