The OSP is a hackable, open source drum machine that accidentally got very, very close to the erica perkons hd-01, which is real beauty of instrument design that i truly love, except for its unhackable closed source nature. So, we new need gnu one.
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

// 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();
}