Commit 1da1646c authored by Jacob Vosmaer's avatar Jacob Vosmaer

Consolidate assigners into crowbx.c

parent bf40d446
PRG = crowbx
OBJ = crowbx.o midi.o dac.o gate.o poly.o mono.o list.o vibrato.o poly2.o clamp_add.o pitch.o pitch_env.o vibrato_rate.o voice.o
OBJ = crowbx.o midi.o dac.o list.o vibrato.o clamp_add.o pitch.o pitch_env.o vibrato_rate.o voice.o
MCU_TARGET = atmega328p
OPTIMIZE = -O3
......
......@@ -5,12 +5,11 @@
#include "array_size.h"
#include "dac.h"
#include "gate.h"
#include "list.h"
#include "midi.h"
#include "mono.h"
#include "notes.h"
#include "pitch.h"
#include "pitch_env.h"
#include "poly.h"
#include "poly2.h"
#include "uart.h"
#include "vibrato.h"
#include "voice.h"
......@@ -63,14 +62,27 @@ ISR(USART_RX_vect) {
uint8_t uart_read(uint8_t *data) { return uart_buffer_pop(data); }
void poly_init(void);
void poly_note_on(uint8_t note);
void poly_note_off(uint8_t note);
void poly2_init(void);
void poly2_note_on(uint8_t note);
void poly2_note_off(uint8_t note);
void mono_init(void);
void mono_note_on(uint8_t note);
void mono_note_off(uint8_t note);
void mono_control_change(uint8_t ctl, uint8_t val);
struct {
void (*init)(void);
void (*note_on)(uint8_t n);
void (*note_off)(uint8_t n);
void (*control_change)(uint8_t ctl, uint8_t val);
} programs[] = {
{poly_init, poly_note_on, poly_note_off, poly_control_change},
{poly2_init, poly2_note_on, poly2_note_off, poly2_control_change},
{poly_init, poly_note_on, poly_note_off, 0},
{poly2_init, poly2_note_on, poly2_note_off, 0},
{mono_init, mono_note_on, mono_note_off, mono_control_change},
};
......@@ -157,7 +169,9 @@ void handle_cc(uint8_t cc, uint8_t val) {
break;
}
programs[current_program].control_change(cc, val);
if (programs[current_program].control_change) {
programs[current_program].control_change(cc, val);
}
}
int main(void) {
......@@ -200,3 +214,273 @@ int main(void) {
return 0;
}
struct {
uint8_t avail_array[NUM_VOICES];
uint8_t in_use_array[NUM_VOICES];
struct list avail;
struct list in_use;
uint8_t current_note[NUM_VOICES];
} poly = {
.avail = {.array = poly.avail_array, .sup = NUM_VOICES},
.in_use = {.array = poly.in_use_array, .sup = NUM_VOICES},
};
uint8_t poly_acquire(void) {
if (l_empty(&poly.avail)) {
l_pushl(&poly.avail, l_popr(&poly.in_use));
}
uint8_t v = l_popr(&poly.avail);
l_pushl(&poly.in_use, v);
return v;
}
void poly_release(uint8_t v) {
if (l_contains(&poly.in_use, v)) {
l_delete(&poly.in_use, v);
l_pushl(&poly.avail, v);
}
}
void poly_init(void) {
l_flush(&poly.in_use);
l_flush(&poly.avail);
for_each_voice (v) {
l_pushl(&poly.avail, v);
poly.current_note[v] = NUM_NOTES;
}
}
void poly_note_on(uint8_t key) {
if (key >= NUM_NOTES) {
return;
}
uint8_t v = poly_acquire();
if (v >= NUM_VOICES) {
return;
}
pitch_set_note(v, key, 0);
gate_on(v);
poly.current_note[v] = key;
}
void poly_note_off(uint8_t key) {
if (key >= NUM_NOTES) {
return;
}
for_each_voice (v) {
if (poly.current_note[v] == key) {
poly.current_note[v] = NUM_NOTES;
poly_release(v);
gate_off(v);
}
}
}
struct {
uint8_t available_voices_array[NUM_VOICES];
uint8_t note_queue_array[NUM_NOTES];
struct list available_voices;
struct list note_queue;
uint8_t current_note[NUM_VOICES];
} poly2 = {
.available_voices = {.array = poly2.available_voices_array,
.sup = ARRAY_SIZE(poly2.available_voices_array)},
.note_queue = {.array = poly2.note_queue_array,
.sup = ARRAY_SIZE(poly2.note_queue_array)},
};
void poly2_init(void) {
l_flush(&poly2.note_queue);
l_flush(&poly2.available_voices);
for_each_voice (v) {
poly2.current_note[v] = poly2.note_queue.sup;
l_pushl(&poly2.available_voices, v);
}
}
void poly2_assign_available_voices(void) {
for (; !l_empty(&poly2.available_voices);) {
uint8_t v = l_popr(&poly2.available_voices);
uint8_t note = l_popr(&poly2.note_queue);
if (note == poly2.note_queue.sup) {
l_pushr(&poly2.available_voices, v);
return;
}
gate_on(v);
pitch_set_note(v, note, 0);
poly2.current_note[v] = note;
}
}
void poly2_note_on(uint8_t note) {
if (note >= poly2.note_queue.sup) {
return;
}
l_pushl(&poly2.note_queue, note);
poly2_assign_available_voices();
}
void poly2_note_off(uint8_t note) {
if (note >= poly2.note_queue.sup) {
return;
}
l_delete(&poly2.note_queue, note);
for_each_voice (v) {
if (poly2.current_note[v] == note) {
poly2.current_note[v] = poly2.note_queue.sup;
gate_off(v);
l_pushl(&poly2.available_voices, v);
}
}
poly2_assign_available_voices();
}
static uint8_t _gate_retrig[NUM_VOICES];
enum {
// RETRIG_DELAY should be as low as possible to reduce MIDI latency, but
// high enough to allow the hardware envelope generators in the CrowBX
// to
// reset. I know 0x3f to be too low.
RETRIG_DELAY = 0x5f,
};
void gate_init(void) {
for_each_voice (v) { gate_off(v); }
}
void gate_on_legato(uint8_t v) {
struct gate *g = gate(v);
if (!g) {
return;
}
*(g->port) |= g->mask;
}
void gate_off(uint8_t v) {
struct gate *g = gate(v);
if (!g) {
return;
}
_gate_retrig[v] = 0;
*(g->port) &= ~(g->mask);
}
void gate_on(uint8_t v) {
if (v >= NUM_VOICES) {
return;
}
gate_off(v);
_gate_retrig[v] = RETRIG_DELAY;
}
void gate_update(uint8_t delta) {
for_each_voice (v) {
if (_gate_retrig[v] == 0) {
continue;
}
if (delta < _gate_retrig[v]) {
_gate_retrig[v] -= delta;
continue;
}
_gate_retrig[v] = 0;
gate_on_legato(v);
pitch_env_trigger(v);
}
}
struct {
uint8_t held_notes_array[NUM_NOTES];
struct list held_notes;
const float detune_factor[MAX_VOICES];
uint8_t detune_amount;
uint8_t stacked_voices;
} mono = {
.held_notes = {.array = mono.held_notes_array,
.sup = ARRAY_SIZE(mono.held_notes_array)},
.detune_factor = {1.2, -1.2, 0.6, -0.6, 0.9, -0.9, 0.3, -0.3},
};
uint8_t mono_active_voices(void) {
if (mono.stacked_voices && mono.stacked_voices < NUM_VOICES) {
return mono.stacked_voices;
} else {
return NUM_VOICES;
}
}
void mono_init(void) { l_flush(&mono.held_notes); }
void mono_set_pitch(void) {
if (l_empty(&mono.held_notes)) {
return;
}
uint8_t current_note = l_last(&mono.held_notes);
for (uint8_t v = 0; v < mono_active_voices(); v++) {
int16_t detune = mono.detune_factor[v] * mono.detune_amount;
pitch_set_note(v, current_note, detune);
}
}
void mono_note_on(uint8_t n) {
if (n >= NUM_NOTES) {
return;
}
l_pushr(&mono.held_notes, n);
mono_set_pitch();
for (uint8_t v = 0; v < mono_active_voices(); v++) {
gate_on(v);
}
}
void mono_note_off(uint8_t n) {
if (n >= NUM_NOTES) {
return;
}
if (l_last(&mono.held_notes) != n) {
l_delete(&mono.held_notes, n);
return;
}
l_delete(&mono.held_notes, n);
mono_set_pitch();
void (*f)(uint8_t) = l_empty(&mono.held_notes) ? gate_off : gate_on;
for (uint8_t v = 0; v < mono_active_voices(); v++) {
f(v);
}
}
void mono_control_change(uint8_t ctl, uint8_t val) {
switch (ctl) {
case CC_DETUNE:
mono.detune_amount = val;
break;
case CC_STACKED_VOICES:
mono.stacked_voices = val;
for (uint8_t v = mono_active_voices(); v < NUM_VOICES; v++) {
gate_off(v);
}
break;
}
}
#include "gate.h"
#include "pitch.h"
#include "voice.h"
static uint8_t _gate_retrig[NUM_VOICES];
enum {
// RETRIG_DELAY should be as low as possible to reduce MIDI latency, but
// high enough to allow the hardware envelope generators in the CrowBX
// to
// reset. I know 0x3f to be too low.
RETRIG_DELAY = 0x5f,
};
void gate_init(void) {
for_each_voice (v) { gate_off(v); }
}
void gate_on_legato(uint8_t v) {
struct gate *g = gate(v);
if (!g) {
return;
}
*(g->port) |= g->mask;
}
void gate_off(uint8_t v) {
struct gate *g = gate(v);
if (!g) {
return;
}
_gate_retrig[v] = 0;
*(g->port) &= ~(g->mask);
}
void gate_on(uint8_t v) {
if (v >= NUM_VOICES) {
return;
}
gate_off(v);
_gate_retrig[v] = RETRIG_DELAY;
}
void gate_update(uint8_t delta) {
for_each_voice (v) {
if (_gate_retrig[v] == 0) {
continue;
}
if (delta < _gate_retrig[v]) {
_gate_retrig[v] -= delta;
continue;
}
_gate_retrig[v] = 0;
gate_on_legato(v);
pitch_env_trigger(v);
}
}
#include "mono.h"
#include "gate.h"
#include "list.h"
#include "midi.h"
#include "notes.h"
#include "pitch.h"
#include "voice.h"
#include <util/delay.h>
INIT_LIST(held_notes, NUM_NOTES);
const float detune_factor[] = {1.2, -1.2, 0.6, -0.6, 0.9, -0.9, 0.3, -0.3};
static uint8_t detune_amount;
static uint8_t stacked_voices;
uint8_t active_voices(void) {
if (stacked_voices && stacked_voices < NUM_VOICES) {
return stacked_voices;
} else {
return NUM_VOICES;
}
}
void mono_init(void) { l_flush(held_notes); }
void mono_set_pitch(void) {
if (l_empty(held_notes)) {
return;
}
uint8_t current_note = l_last(held_notes);
for (uint8_t v = 0; v < active_voices(); v++) {
int16_t detune = detune_factor[v] * detune_amount;
pitch_set_note(v, current_note, detune);
}
}
void mono_note_on(uint8_t n) {
if (n >= NUM_NOTES) {
return;
}
l_pushr(held_notes, n);
mono_set_pitch();
for (uint8_t v = 0; v < active_voices(); v++) {
gate_on(v);
}
}
void mono_note_off(uint8_t n) {
if (n >= NUM_NOTES) {
return;
}
if (l_last(held_notes) != n) {
l_delete(held_notes, n);
return;
}
l_delete(held_notes, n);
mono_set_pitch();
void (*f)(uint8_t) = l_empty(held_notes) ? gate_off : gate_on;
for (uint8_t v = 0; v < active_voices(); v++) {
f(v);
}
}
void mono_control_change(uint8_t ctl, uint8_t val) {
switch (ctl) {
case CC_DETUNE:
detune_amount = val;
break;
case CC_STACKED_VOICES:
stacked_voices = val;
for (uint8_t v = active_voices(); v < NUM_VOICES; v++) {
gate_off(v);
}
break;
}
}
#ifndef MONO_H
#define MONO_H
#include <stdint.h>
void mono_init(void);
void mono_note_on(uint8_t note);
void mono_note_off(uint8_t note);
void mono_control_change(uint8_t ctl, uint8_t val);
#endif
#include "poly.h"
#include "gate.h"
#include "list.h"
#include "notes.h"
#include "pitch.h"
#include "voice.h"
static struct {
uint8_t avail_array[NUM_VOICES];
uint8_t in_use_array[NUM_VOICES];
struct list avail;
struct list in_use;
} round_robin = {
.avail = {.array = round_robin.avail_array, .sup = NUM_VOICES},
.in_use = {.array = round_robin.in_use_array, .sup = NUM_VOICES},
};
void round_robin_init(void) {
l_flush(&round_robin.in_use);
l_flush(&round_robin.avail);
for_each_voice (v) { l_pushl(&round_robin.avail, v); }
}
uint8_t round_robin_acquire(void) {
if (l_empty(&round_robin.avail)) {
l_pushl(&round_robin.avail, l_popr(&round_robin.in_use));
}
uint8_t v = l_popr(&round_robin.avail);
l_pushl(&round_robin.in_use, v);
return v;
}
void round_robin_release(uint8_t v) {
if (l_contains(&round_robin.in_use, v)) {
l_delete(&round_robin.in_use, v);
l_pushl(&round_robin.avail, v);
}
}
static uint8_t current_note[NUM_VOICES];
void poly_init(void) {
round_robin_init();
for_each_voice (v) { current_note[v] = NUM_NOTES; }
}
void poly_note_on(uint8_t key) {
if (key >= NUM_NOTES) {
return;
}
uint8_t v = round_robin_acquire();
if (v >= NUM_VOICES) {
return;
}
pitch_set_note(v, key, 0);
gate_on(v);
current_note[v] = key;
}
void poly_note_off(uint8_t key) {
if (key >= NUM_NOTES) {
return;
}
for_each_voice (v) {
if (current_note[v] == key) {
current_note[v] = NUM_NOTES;
round_robin_release(v);
gate_off(v);
}
}
}
void poly_control_change(uint8_t ctl, uint8_t val) {
(void)ctl;
(void)val;
}
#ifndef POLY_H
#define POLY_H
#include <stdint.h>
void poly_init(void);
void poly_note_on(uint8_t note);
void poly_note_off(uint8_t note);
void poly_control_change(uint8_t ctl, uint8_t val);
#endif
#include "poly2.h"
#include "gate.h"
#include "list.h"
#include "notes.h"
#include "pitch.h"
#include "voice.h"
uint8_t current_note[NUM_VOICES];
INIT_LIST(available_voices, NUM_VOICES);
INIT_LIST(note_queue, NUM_NOTES);
void poly2_init(void) {
l_flush(note_queue);
l_flush(available_voices);
for_each_voice (v) {
current_note[v] = NUM_NOTES;
l_pushl(available_voices, v);
}
}
void poly2_assign_available_voices(void) {
for (; !l_empty(available_voices);) {
uint8_t v = l_popr(available_voices);
uint8_t note = l_popr(note_queue);
if (note == note_queue->sup) {
l_pushr(available_voices, v);
return;
}
gate_on(v);
pitch_set_note(v, note, 0);