You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
483 lines
15 KiB
483 lines
15 KiB
// Hecks perkons extension
|
|
#include <array>
|
|
#include <memory>
|
|
|
|
#include "globals.hh"
|
|
#include "track.hh"
|
|
#include "utils.hh"
|
|
#include "instr_abstract.hh"
|
|
#include "instr_kick.hh"
|
|
#include "instr_noise.hh"
|
|
#include "instr_fm.hh"
|
|
#include "instr_grainlet.hh"
|
|
|
|
namespace ld = daisy;
|
|
namespace dsp = daisysp;
|
|
|
|
|
|
namespace Heck {
|
|
// =============================================================================================
|
|
// INIT
|
|
// =============================================================================================
|
|
ld::DaisySeed hw{};
|
|
ld::Switch but_rec{};
|
|
|
|
static ld::MidiUartHandler midi{};
|
|
static ld::FIFO<ld::MidiEvent, 128> event_log{};
|
|
|
|
Instrument::FM instrument0{};
|
|
Instrument::Grainlet instrument1{};
|
|
Instrument::FM instrument2{};
|
|
Instrument::Grainlet instrument3{};
|
|
|
|
std::array<Track, Constants::TRACK_COUNT> tracks;
|
|
|
|
struct MidiClock {
|
|
public:
|
|
void tick_advance()
|
|
{
|
|
if (enabled) {
|
|
time_inf++;
|
|
}
|
|
};
|
|
|
|
void reset()
|
|
{
|
|
time_inf = 0;
|
|
};
|
|
|
|
void enable(bool enabled)
|
|
{
|
|
this->enabled = enabled;
|
|
};
|
|
|
|
// get time
|
|
|
|
int tick_infinite()
|
|
{
|
|
return time_inf;
|
|
}
|
|
|
|
int tick_4n()
|
|
{
|
|
return time_inf % PPQ;
|
|
}
|
|
|
|
int tick_1n()
|
|
{
|
|
return time_inf % (PPQ * 4);
|
|
}
|
|
|
|
int count_8n()
|
|
{
|
|
return std::floor(tick_1n() / 12);
|
|
}
|
|
|
|
int count_16n()
|
|
{
|
|
return std::floor(tick_1n() / 6);
|
|
}
|
|
|
|
int count_bar()
|
|
{
|
|
return std::floor(time_inf / 96.);
|
|
}
|
|
|
|
private:
|
|
static constexpr int PPQ = 24;
|
|
bool enabled{ true };
|
|
int time_inf{ 0 };
|
|
};
|
|
|
|
struct Sequencer {
|
|
|
|
Sequencer()
|
|
{
|
|
clear_sequence();
|
|
}
|
|
|
|
// its a MIDI CC step sequencer
|
|
constexpr static int MIDI_MAX = 127;
|
|
constexpr static int STEP_COUNT = 16;
|
|
constexpr static int TRACK_COUNT = 16;
|
|
|
|
using Step = std::array<int, MIDI_MAX>;
|
|
using Track = std::array<Step, STEP_COUNT>;
|
|
using Sequence = std::array<Track, TRACK_COUNT>;
|
|
|
|
|
|
void reset()
|
|
{
|
|
step_current = 0;
|
|
}
|
|
|
|
void next_step()
|
|
{
|
|
step_current++;
|
|
if(step_current >= STEP_COUNT) {
|
|
step_current = 0;
|
|
}
|
|
}
|
|
|
|
void clear_track(int track_nr)
|
|
{
|
|
for (int step = 0; step < STEP_COUNT; step++) {
|
|
for (int cc_nr = 0; cc_nr < MIDI_MAX; cc_nr++) {
|
|
sequence[track_nr][step][cc_nr] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void clear_track_cc(int track_nr, int cc_nr)
|
|
{
|
|
for (int step = 0; step < STEP_COUNT; step++) {
|
|
sequence[track_nr][step][cc_nr] = -1;
|
|
}
|
|
}
|
|
|
|
void clear_sequence()
|
|
{
|
|
for (int track = 0; track < TRACK_COUNT; track++) {
|
|
clear_track(track);
|
|
}
|
|
}
|
|
|
|
void midi_in(ld::MidiEvent& msg)
|
|
{
|
|
if (recording) {
|
|
ld::ControlChangeEvent cc = msg.AsControlChange();
|
|
sequence[cc.channel][step_current][cc.control_number] = cc.value;
|
|
}
|
|
}
|
|
|
|
std::vector<ld::MidiEvent> midi_out()
|
|
{
|
|
std::vector<ld::MidiEvent> ret{};
|
|
for (int track = 0; track < TRACK_COUNT; track++) {
|
|
for (int cc_nr = 0; cc_nr < MIDI_MAX; cc_nr++) {
|
|
int cc_val = sequence[track][step_current][cc_nr];
|
|
if (cc_val != -1) {
|
|
ld::MidiEvent ev{};
|
|
ev.channel = track;
|
|
ev.type = ld::ControlChange;
|
|
ev.data[0] = cc_nr;
|
|
ev.data[1] = cc_val;
|
|
ret.push_back(ev);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int step_current{ 0 };
|
|
bool recording{ false };
|
|
Sequence sequence{};
|
|
};
|
|
|
|
MidiClock clock{};
|
|
Sequencer sequencer{};
|
|
|
|
// function prototypes
|
|
void AudioCallback(
|
|
ld::AudioHandle::InterleavingInputBuffer in,
|
|
ld::AudioHandle::InterleavingOutputBuffer out,
|
|
size_t size);
|
|
|
|
void midi_realtime_handler(ld::MidiEvent& msg);
|
|
|
|
|
|
void init_tracks()
|
|
{
|
|
tracks[0].init(instrument0);
|
|
tracks[1].init(instrument1);
|
|
tracks[2].init(instrument2);
|
|
tracks[3].init(instrument3);
|
|
}
|
|
|
|
void init()
|
|
{
|
|
hw.Configure();
|
|
hw.Init();
|
|
hw.StartLog(Constants::Developer::BOOT_WAIT_FOR_LOG);
|
|
|
|
but_rec.Init(hw.GetPin(28), 0);
|
|
|
|
hw.PrintLine("Setting Blocksize: %i", Constants::BUFFERSIZE);
|
|
hw.SetAudioBlockSize(Constants::BUFFERSIZE);
|
|
|
|
hw.PrintLine("Setting Samplerate: %i", Constants::SAMPLERATE);
|
|
switch (Constants::SAMPLERATE) {
|
|
case 8000:
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_8KHZ);
|
|
break;
|
|
case 16000:
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_16KHZ);
|
|
break;
|
|
case 32000:
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_32KHZ);
|
|
break;
|
|
case 48000:
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_48KHZ);
|
|
break;
|
|
case 96000:
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_48KHZ);
|
|
break;
|
|
default:
|
|
hw.PrintLine("Samplerate not supported, fallback to 48000");
|
|
hw.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_48KHZ);
|
|
break;
|
|
}
|
|
|
|
hw.PrintLine("Initializing MIDI");
|
|
ld::MidiUartHandler::Config midi_config{};
|
|
midi.Init(midi_config);
|
|
midi.realtime_callback = &midi_realtime_handler;
|
|
|
|
init_tracks();
|
|
|
|
hw.PrintLine("Starting MIDI Receive");
|
|
midi.StartReceive();
|
|
midi.Listen();
|
|
|
|
hw.PrintLine("Starting Audio");
|
|
hw.StartAudio(AudioCallback);
|
|
}
|
|
|
|
|
|
// =============================================================================================
|
|
// RUN
|
|
// =============================================================================================
|
|
|
|
void AudioCallback(
|
|
ld::AudioHandle::InterleavingInputBuffer in,
|
|
ld::AudioHandle::InterleavingOutputBuffer out,
|
|
size_t size)
|
|
{
|
|
float sig_out{};
|
|
for (size_t i = 0; i < size; i += 2) {
|
|
for (int i = 0; i < Constants::TRACK_COUNT; i++) {
|
|
sig_out += tracks[i].nextsample();
|
|
}
|
|
sig_out *= 0.1;
|
|
out[i] = sig_out;
|
|
out[i + 1] = sig_out;
|
|
}
|
|
}
|
|
|
|
void midi_realtime_handler(ld::MidiEvent& msg)
|
|
{
|
|
event_log.PushBack(msg);
|
|
switch (msg.srt_type) {
|
|
case daisy::TimingClock:
|
|
clock.tick_advance();
|
|
break;
|
|
case daisy::Start: {
|
|
sequencer.reset();
|
|
clock.reset();
|
|
clock.enable(true);
|
|
} break;
|
|
case daisy::Stop: {
|
|
clock.enable(false);
|
|
clock.reset();
|
|
sequencer.reset();
|
|
} break;
|
|
case daisy::Reset:
|
|
break;
|
|
case daisy::Continue:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void tracks_midi_in(ld::MidiEvent msg)
|
|
{
|
|
switch (msg.type) {
|
|
case ld::MidiMessageType::NoteOn: {
|
|
if (msg.channel >= 0 && msg.channel < Constants::TRACK_COUNT) {
|
|
tracks[msg.channel].trigger();
|
|
}
|
|
} break;
|
|
case ld::MidiMessageType::ControlChange: {
|
|
sequencer.midi_in(msg);
|
|
ld::ControlChangeEvent cc = msg.AsControlChange();
|
|
float val_normalized = cc.value / 127.;
|
|
switch (cc.control_number) {
|
|
// Pots
|
|
case Constants::MIDI_Mapping::TRACK_PITCH:
|
|
tracks[cc.channel].instrument->ctl(0, val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_DECAY:
|
|
tracks[cc.channel].decay(val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_PARAM1:
|
|
tracks[cc.channel].instrument->ctl(2, val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_PARAM2:
|
|
tracks[cc.channel].instrument->ctl(3,val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_FILTER:
|
|
tracks[cc.channel].filter(val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_DRIVE:
|
|
tracks[cc.channel].drive(val_normalized);
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_VOLUME:
|
|
tracks[cc.channel].volume(val_normalized);
|
|
break;
|
|
// Switches
|
|
case Constants::MIDI_Mapping::TRACK_MODE1:
|
|
tracks[cc.channel].instrument->switch_mode1(int(val_normalized * 2.));
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_MODE2:
|
|
tracks[cc.channel].instrument->switch_mode2(int(val_normalized * 2.));
|
|
break;
|
|
case Constants::MIDI_Mapping::TRACK_FILTERMODE:
|
|
tracks[cc.channel].filtermode(val_normalized);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} break;
|
|
default: {
|
|
// Other MIDI message
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void midi_async_handler()
|
|
{
|
|
while (midi.HasEvents()) {
|
|
ld::MidiEvent msg = midi.PopEvent();
|
|
event_log.PushBack(msg);
|
|
tracks_midi_in(msg);
|
|
}
|
|
}
|
|
|
|
void sequencer_midi_handler()
|
|
{
|
|
std::vector<ld::MidiEvent> queue = sequencer.midi_out();
|
|
for (ld::MidiEvent msg : queue) {
|
|
tracks_midi_in(msg);
|
|
}
|
|
}
|
|
|
|
void mainloop()
|
|
{
|
|
hw.PrintLine("Entering MainLoop");
|
|
u32 systick_now{};
|
|
u32 midi_log_systick_last{};
|
|
|
|
bool heartbeat_led_state{ false };
|
|
u32 heartbeat_systick_last{};
|
|
|
|
bool seq_recording_new{ false };
|
|
bool seq_recording_current{ false };
|
|
|
|
int clock_time_new{};
|
|
int clock_time_current{};
|
|
|
|
int clock_bar_new{};
|
|
int clock_bar_current{};
|
|
|
|
int clock_16n_new{};
|
|
int clock_16n_current{};
|
|
|
|
|
|
while (1) {
|
|
// Get current values for state
|
|
// if update is different
|
|
// Update state
|
|
// handle change
|
|
systick_now = ld::System::GetNow();
|
|
|
|
clock_time_new = clock.tick_infinite();
|
|
if (clock_time_current != clock_time_new) {
|
|
clock_time_current = clock_time_new;
|
|
if (0) {
|
|
hw.PrintLine("CLOCK:%i", clock_time_current);
|
|
}
|
|
}
|
|
|
|
clock_bar_new = clock.count_bar();
|
|
if (clock_bar_new != clock_bar_current) {
|
|
clock_bar_current = clock_bar_new;
|
|
// hw.PrintLine("Bar: %i", clock_bar_current);
|
|
}
|
|
|
|
clock_16n_new = clock.count_16n();
|
|
if (clock_16n_new != clock_16n_current) {
|
|
clock_16n_current = clock_16n_new;
|
|
// hw.PrintLine("16n: %i", clock_16n_current);
|
|
sequencer.next_step();
|
|
}
|
|
|
|
sequencer_midi_handler();
|
|
midi_async_handler();
|
|
|
|
// REC
|
|
but_rec.Debounce();
|
|
seq_recording_new = but_rec.Pressed();
|
|
if (seq_recording_current != seq_recording_new) {
|
|
seq_recording_current = seq_recording_new;
|
|
if (seq_recording_current) {
|
|
hw.PrintLine("RECORDING ON");
|
|
} else {
|
|
hw.PrintLine("RECORDING OFF");
|
|
}
|
|
sequencer.recording = seq_recording_current;
|
|
}
|
|
|
|
if (systick_now - midi_log_systick_last > 5) {
|
|
midi_log_systick_last = systick_now;
|
|
if (!event_log.IsEmpty()) {
|
|
auto msg = event_log.PopFront();
|
|
// hw.PrintLine("fsfs");
|
|
switch (msg.type) {
|
|
case ld::MidiMessageType::SystemRealTime: {
|
|
if (0) {
|
|
char outstr[256];
|
|
char rttype_str[32];
|
|
GetMidiRTTypeAsString(msg, rttype_str);
|
|
sprintf(outstr, "RT: type: %s\n", rttype_str);
|
|
if (msg.srt_type != daisy::TimingClock) {
|
|
hw.PrintLine("%s", outstr);
|
|
}
|
|
}
|
|
} break;
|
|
case ld::NoteOn:
|
|
case ld::NoteOff:
|
|
case ld::MidiMessageType::ControlChange: {
|
|
if (0) {
|
|
char outstr[256];
|
|
char type_str[32];
|
|
GetMidiTypeAsString(msg, type_str);
|
|
sprintf(
|
|
outstr,
|
|
"time-last:\t%ld\ttype: %s\tChannel: %d\tData MSB: "
|
|
"%d\tData LSB: %d\n",
|
|
systick_now,
|
|
type_str,
|
|
msg.channel,
|
|
msg.data[0],
|
|
msg.data[1]);
|
|
hw.PrintLine("%s", outstr);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (systick_now - heartbeat_systick_last > 500) {
|
|
heartbeat_systick_last = systick_now;
|
|
heartbeat_led_state = !heartbeat_led_state;
|
|
hw.SetLed(heartbeat_led_state);
|
|
}
|
|
}
|
|
}
|
|
} // namespace Heck
|
|
|
|
|
|
int main()
|
|
{
|
|
Heck::init();
|
|
Heck::mainloop();
|
|
}
|