|
|
@ -31,27 +31,18 @@ namespace Heck { |
|
|
|
|
|
|
|
std::array<Track, Constants::TRACK_COUNT> tracks; |
|
|
|
|
|
|
|
// function prototypes
|
|
|
|
int create_tracks(); |
|
|
|
|
|
|
|
void AudioCallback( |
|
|
|
ld::AudioHandle::InterleavingInputBuffer in, |
|
|
|
ld::AudioHandle::InterleavingOutputBuffer out, |
|
|
|
size_t size); |
|
|
|
|
|
|
|
void midi_realtime_handler(ld::MidiEvent& msg); |
|
|
|
|
|
|
|
struct Clock { |
|
|
|
void advance() |
|
|
|
struct MidiClock { |
|
|
|
public: |
|
|
|
void tick_advance() |
|
|
|
{ |
|
|
|
if (enabled) { |
|
|
|
time++; |
|
|
|
time_inf++; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
void reset() |
|
|
|
{ |
|
|
|
time = 0; |
|
|
|
time_inf = 0; |
|
|
|
}; |
|
|
|
|
|
|
|
void enable(bool enabled) |
|
|
@ -59,17 +50,148 @@ namespace Heck { |
|
|
|
this->enabled = enabled; |
|
|
|
}; |
|
|
|
|
|
|
|
int gettime() |
|
|
|
// 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 time; |
|
|
|
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{ 0 }; |
|
|
|
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{}; |
|
|
|
}; |
|
|
|
|
|
|
|
Clock clock{}; |
|
|
|
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() |
|
|
|
{ |
|
|
@ -105,38 +227,21 @@ namespace Heck { |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
hw.PrintLine("Creating Tracks"); |
|
|
|
create_tracks(); |
|
|
|
|
|
|
|
|
|
|
|
hw.PrintLine("Setting up MIDI"); |
|
|
|
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); |
|
|
|
|
|
|
|
hw.PrintLine("Entering MainLoop"); |
|
|
|
} |
|
|
|
|
|
|
|
int create_tracks() |
|
|
|
{ |
|
|
|
instrument0.init(); |
|
|
|
tracks[0].init(instrument0); |
|
|
|
instrument1.init(); |
|
|
|
tracks[1].init(instrument1); |
|
|
|
instrument2.init(); |
|
|
|
tracks[2].init(instrument2); |
|
|
|
instrument3.init(); |
|
|
|
tracks[3].init(instrument3); |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
// =============================================================================================
|
|
|
|
// RUN
|
|
|
@ -163,15 +268,17 @@ namespace Heck { |
|
|
|
event_log.PushBack(msg); |
|
|
|
switch (msg.srt_type) { |
|
|
|
case daisy::TimingClock: |
|
|
|
clock.advance(); |
|
|
|
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; |
|
|
@ -180,67 +287,82 @@ namespace Heck { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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].instrument->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].instrument->ctl(1, 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); |
|
|
|
switch (msg.type) { |
|
|
|
case ld::MidiMessageType::NoteOn: { |
|
|
|
if (msg.channel >= 0 && msg.channel < Constants::TRACK_COUNT) { |
|
|
|
tracks[msg.channel].instrument->trigger(); |
|
|
|
} |
|
|
|
} break; |
|
|
|
case ld::MidiMessageType::ControlChange: { |
|
|
|
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].instrument->ctl(1, 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; |
|
|
|
} |
|
|
|
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() |
|
|
|
{ |
|
|
|
u32 systick_now = ld::System::GetNow(); |
|
|
|
hw.PrintLine("Entering MainLoop"); |
|
|
|
u32 systick_now{}; |
|
|
|
u32 midi_log_systick_last{}; |
|
|
|
|
|
|
|
bool heartbeat_led_state{ false }; |
|
|
@ -252,16 +374,21 @@ namespace Heck { |
|
|
|
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.gettime(); |
|
|
|
clock_time_new = clock.tick_infinite(); |
|
|
|
if (clock_time_current != clock_time_new) { |
|
|
|
clock_time_current = clock_time_new; |
|
|
|
if (0) { |
|
|
@ -269,6 +396,20 @@ namespace Heck { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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
|
|
|
@ -281,6 +422,7 @@ namespace Heck { |
|
|
|
} else { |
|
|
|
hw.PrintLine("RECORDING OFF"); |
|
|
|
} |
|
|
|
sequencer.recording = seq_recording_current; |
|
|
|
} |
|
|
|
|
|
|
|
if (systick_now - midi_log_systick_last > 5) { |
|
|
@ -303,7 +445,7 @@ namespace Heck { |
|
|
|
case ld::NoteOn: |
|
|
|
case ld::NoteOff: |
|
|
|
case ld::MidiMessageType::ControlChange: { |
|
|
|
if (1) { |
|
|
|
if (0) { |
|
|
|
char outstr[256]; |
|
|
|
char type_str[32]; |
|
|
|
GetMidiTypeAsString(msg, type_str); |
|
|
|