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.
445 lines
14 KiB
445 lines
14 KiB
// Hecks perkons extension
|
|
#include <array>
|
|
#include <memory>
|
|
|
|
#include "globals.hh"
|
|
#include "track.hh"
|
|
#include "utils.hh"
|
|
#include "midiclock.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{};
|
|
ld::Switch but_clear{};
|
|
|
|
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 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::LOG_BLOCKS_BOOT);
|
|
|
|
but_rec.Init(hw.GetPin(Constants::Hardware::PIN_BUTTON_RECORD), 0);
|
|
but_clear.Init(hw.GetPin(Constants::Hardware::PIN_BUTTON_CLEAR), 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 but_record_new{ false };
|
|
bool but_record_current{ false };
|
|
|
|
bool but_clear_new{ false };
|
|
bool but_clear_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;
|
|
}
|
|
|
|
clock_bar_new = clock.count_bar();
|
|
if (clock_bar_new != clock_bar_current) {
|
|
clock_bar_current = clock_bar_new;
|
|
if (Constants::Developer::LOG_CLOCK_BAR) {
|
|
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;
|
|
if (Constants::Developer::LOG_CLOCK_16N) {
|
|
hw.PrintLine("16n: %i", clock_16n_current);
|
|
}
|
|
sequencer.next_step();
|
|
}
|
|
|
|
sequencer_midi_handler();
|
|
midi_async_handler();
|
|
|
|
// REC
|
|
but_rec.Debounce();
|
|
but_record_new = but_rec.Pressed();
|
|
if (but_record_current != but_record_new) {
|
|
but_record_current = but_record_new;
|
|
if (but_record_current) {
|
|
hw.PrintLine("BUTTON RECORD ON");
|
|
} else {
|
|
hw.PrintLine("BUTTON RECORD OFF");
|
|
}
|
|
sequencer.recording = but_record_current;
|
|
}
|
|
|
|
// CLEAR
|
|
but_clear.Debounce();
|
|
but_clear_new = but_clear.Pressed();
|
|
if (but_clear_current != but_clear_new) {
|
|
but_clear_current = but_clear_new;
|
|
if (but_clear_current) {
|
|
hw.PrintLine("BUTTON CLEAR ON");
|
|
} else {
|
|
hw.PrintLine("BUTTON CLEAR OFF");
|
|
sequencer.clear_sequence();
|
|
}
|
|
}
|
|
|
|
if (systick_now - midi_log_systick_last > 5) {
|
|
midi_log_systick_last = systick_now;
|
|
if (!event_log.IsEmpty()) {
|
|
auto msg = event_log.PopFront();
|
|
switch (msg.type) {
|
|
case ld::MidiMessageType::SystemRealTime: {
|
|
if (Constants::Developer::LOG_MIDI_REALTIME) {
|
|
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 (Constants::Developer::LOG_MIDI_NOTESANDCC) {
|
|
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();
|
|
}
|