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.
 
 

404 lines
12 KiB

#include <array>
#include <memory>
#include "main_osp.hh"
#include "track.hh"
#include "osp_utils.hh"
#include "midiclock.hh"
#include "sequencer.hh"
#include "instr_interface.hh"
#include "instr_kick.hh"
#include "instr_noise.hh"
#include "instr_fm.hh"
#include "instr_grainlet.hh"
#include "instr_hihat.hh"
#include "instr_zosc.hh"
// DTCM_MEM_SECTION
// DMA_BUFFER_MEM_SECTION
// DSY_SDRAM_DATA
// DSY_SDRAM_BSS
float DSY_SDRAM_BSS sdram_block1[4000000];
namespace Heck::OSP {
namespace State {
bool record_mode{ false };
bool clear_mode{ false };
} // namespace State
// =============================================================================================
// INIT
// =============================================================================================
ld::DaisySeed seed{};
ld::Switch but_rec{};
ld::Switch but_clear{};
static ld::MidiUartHandler midi{};
static MidiLoggerBuffered logger_midi{};
Instrument::ZOsc instrument0{};
Instrument::FM instrument1{};
Instrument::Grainlet instrument2{};
Instrument::HiHat instrument3{};
std::array<Track, Constants::TRACK_COUNT> tracks{};
MidiClock clock{};
Sequencer sequencer{};
// function prototypes
void audio_callback(ld::AudioHandle::InputBuffer in, ld::AudioHandle::OutputBuffer out, size_t size);
void midi_realtime_handler(const ld::MidiEvent& msg);
void init_tracks()
{
sequencer.init();
tracks[0].init(instrument0);
tracks[1].init(instrument1);
tracks[2].init(instrument2);
tracks[3].init(instrument3);
}
void init()
{
seed.Configure();
seed.Init(Constants::CPU_BOOST480MHZ);
ld::DaisySeed::StartLog(Constants::Developer::LOG_BLOCKS_BOOT);
but_rec.Init(ld::DaisySeed::GetPin(Constants::Hardware::PIN_BUTTON_RECORD), 0);
but_clear.Init(ld::DaisySeed::GetPin(Constants::Hardware::PIN_BUTTON_CLEAR), 0);
ld::DaisySeed::PrintLine("Setting Blocksize: %i", Constants::AUDIO_BUFFERSIZE);
seed.SetAudioBlockSize(Constants::AUDIO_BUFFERSIZE);
ld::DaisySeed::PrintLine("Setting Samplerate: %i", Constants::AUDIO_SAMPLERATE);
seed.SetAudioSampleRate(Constants::AUDIO_SAMPLERATE);
ld::DaisySeed::PrintLine("Initializing MIDI");
ld::MidiUartHandler::Config midi_config{};
midi.Init(midi_config);
init_tracks();
ld::DaisySeed::PrintLine("Starting MIDI Receive");
midi.StartReceiveRt(&midi_realtime_handler);
midi.Listen();
ld::DaisySeed::PrintLine("Starting Audio");
seed.StartAudio(audio_callback);
}
// =============================================================================================
// RUN
// =============================================================================================
Sig::RingBuffer<30000> buff1{};
Sig::Delay101<30000> dly1{};
volatile float sig_in[Constants::AUDIO_BUFFERSIZE];
volatile float sig_out[Constants::AUDIO_BUFFERSIZE];
volatile float delaytime{};
volatile float delayfb{};
float sig_dly1{};
float sig_osp{};
float sig_bus{};
void process_signals()
{
for (size_t i = 0; i < Constants::AUDIO_BUFFERSIZE; i++) {
sig_osp = tracks[0].nextsample();
sig_osp += tracks[1].nextsample();
sig_osp += tracks[2].nextsample();
sig_osp += tracks[3].nextsample();
sig_bus = sig_osp + sig_in[i];
// dly1.delayfb_ = delayfb;
// dly1.delaytime_ = delaytime;
// sig_bus += dly1.process(sig_bus);
sig_out[i] = sig_bus;
}
}
void audio_callback(ld::AudioHandle::InputBuffer in, ld::AudioHandle::OutputBuffer out, size_t size)
{
for (size_t i = 0; i < size; i++) {
sig_in[i] = in[0][i];
}
process_signals();
for (size_t i = 0; i < size; i++) {
out[0][i] = sig_out[i];
out[1][i] = sig_out[i];
}
}
void midi_realtime_handler(const ld::MidiEvent& msg)
{
if constexpr (Constants::Developer::LOG_MIDI_UART_REALTIME) {
logger_midi.push(msg);
}
switch (msg.srt_type) {
case ld::TimingClock:
clock.tick_advance();
break;
case ld::Start: {
sequencer.reset();
clock.reset();
clock.enable(true);
} break;
case ld::Stop: {
clock.enable(false);
clock.reset();
sequencer.reset();
} break;
case ld::Reset:
break;
case ld::Continue:
break;
}
}
enum class MidiSource : u8 {
UART = 0,
SEQUENCER = 1
};
void midi_dispatch_note(ld::MidiEvent msg, MidiSource source)
{
ld::DaisySeed::PrintLine(
"[%i] MIDI from seq=%i : NOTE - ch: %i, nr: %i, val: %i",
ld::System::GetNow(),
source,
msg.channel,
msg.AsNoteOn().note,
msg.AsNoteOn().velocity);
if (msg.channel >= 0 && msg.channel < Constants::TRACK_COUNT) {
tracks[msg.channel].trigger();
}
}
void midi_dispatch_cc(ld::MidiEvent msg, MidiSource source)
{
const ld::ControlChangeEvent msg_cc = msg.AsControlChange();
ld::DaisySeed::PrintLine(
"[%i] MIDI from seq=%i : CC - ch: %i, nr: %i, val: %i",
ld::System::GetNow(),
source,
msg_cc.channel,
msg_cc.control_number,
msg_cc.value);
if (source == MidiSource::UART) {
if (!State::clear_mode) {
sequencer.midi_in(msg);
} else {
ld::DaisySeed::PrintLine("Clear: ch: %i, cc: %i", msg_cc.channel, msg_cc.control_number);
sequencer.clear_track_cc(msg_cc.channel, msg_cc.control_number);
}
}
const float cc_val_normalized = float(msg_cc.value) / 128.F;
switch (msg_cc.control_number) {
// Pots
case Constants::MIDI_Mapping::TRACK_PITCH:
tracks[msg_cc.channel].instrument->ctl(0, cc_val_normalized);
break;
case Constants::MIDI_Mapping::TRACK_DECAY:
tracks[msg_cc.channel].decay(cc_val_normalized);
break;
case Constants::MIDI_Mapping::TRACK_PARAM1:
tracks[msg_cc.channel].instrument->ctl(2, cc_val_normalized);
break;
case Constants::MIDI_Mapping::TRACK_PARAM2:
tracks[msg_cc.channel].instrument->ctl(3, cc_val_normalized);
break;
case Constants::MIDI_Mapping::TRACK_FILTER:
tracks[msg_cc.channel].filter(cc_val_normalized);
delaytime = cc_val_normalized;
break;
case Constants::MIDI_Mapping::TRACK_DRIVE:
tracks[msg_cc.channel].drive(cc_val_normalized);
delayfb = cc_val_normalized;
break;
case Constants::MIDI_Mapping::TRACK_VOLUME:
tracks[msg_cc.channel].volume(cc_val_normalized);
break;
// Switches
case Constants::MIDI_Mapping::TRACK_ALGO:
tracks[msg_cc.channel].instrument->switch_algo(int(cc_val_normalized * 2.));
break;
case Constants::MIDI_Mapping::TRACK_MODE:
tracks[msg_cc.channel].instrument->switch_mode(int(cc_val_normalized * 2.));
break;
case Constants::MIDI_Mapping::TRACK_FILTERMODE:
tracks[msg_cc.channel].filtermode(cc_val_normalized);
break;
default:
break;
}
}
void midi_dispatch(ld::MidiEvent msg, MidiSource source)
{
switch (msg.type) {
case ld::MidiMessageType::NoteOn:
midi_dispatch_note(msg, source);
break;
case ld::MidiMessageType::ControlChange:
midi_dispatch_cc(msg, source);
break;
default:
// Other MIDI message
// ld::DaisySeed::PrintLine(
// "[%i] UNHANDLED MIDI UNHANDLED MIDI from seq=%i : CC - ch: %i",
// ld::System::GetNow(),
// from_seq,
// msg.channel);
break;
}
}
void midi_from_uart()
{
while (midi.HasEvents()) {
ld::MidiEvent msg = midi.PopEvent();
midi_dispatch(msg, MidiSource::UART);
}
}
void midi_from_sequencer()
{
sequencer.midi_out([](ld::MidiEvent msg) { midi_dispatch(msg, MidiSource::SEQUENCER); });
}
//-------------------------------------------------
// HEARTBEAT
//-------------------------------------------------
void task_heartbeat_func(u32)
{
static bool heartbeat_led_state{ false };
heartbeat_led_state = !heartbeat_led_state;
seed.SetLed(heartbeat_led_state);
}
dz::PeriodicTaskCT<task_heartbeat_func, 100> task_heartbeat{};
//-------------------------------------------------
// MIDI LOGGER
//-------------------------------------------------
void task_logger_midi_print_func(u32)
{
logger_midi.print();
}
dz::PeriodicTaskCT<task_logger_midi_print_func, 5> task_logger_midi_print{};
//-------------------------------------------------
// REC BUTTON
//-------------------------------------------------
bool but_rec_update()
{
but_rec.Debounce();
return but_rec.Pressed();
}
void but_rec_changed(dz::Cache<bool>& cache)
{
bool but_rec_current = cache.read();
if (but_rec_current) {
if (State::clear_mode) {
ld::DaisySeed::PrintLine("CLEAR SEQUENCE");
sequencer.clear_sequence();
} else {
ld::DaisySeed::PrintLine("BUTTON RECORD ON");
State::record_mode = true;
}
} else {
ld::DaisySeed::PrintLine("BUTTON RECORD OFF");
State::record_mode = false;
}
sequencer.recording(but_rec_current);
}
dz::Cache<bool> c_but_rec{ but_rec_update, but_rec_changed };
//-------------------------------------------------
// CLEAR BUTTON
//-------------------------------------------------
bool but_clear_update()
{
but_clear.Debounce();
return but_clear.Pressed();
}
void but_clear_changed(dz::Cache<bool>& cache)
{
bool but_clear_current = cache.read();
if (but_clear_current) {
ld::DaisySeed::PrintLine("BUTTON CLEAR ON");
State::clear_mode = true;
} else {
ld::DaisySeed::PrintLine("BUTTON CLEAR OFF");
State::clear_mode = false;
}
}
dz::Cache<bool> c_but_clear{ but_clear_update, but_clear_changed };
//==========================================================================================
// MAINLOOP
//==========================================================================================
void mainloop()
{
ld::DaisySeed::PrintLine("Entering MainLoop");
u32 uptime_ms{};
int clock_16n_new{};
int clock_16n_current{};
buff1.init(sdram_block1);
dly1.init(&buff1);
while (1) {
uptime_ms = ld::System::GetNow();
clock_16n_new = clock.count_16n();
if (clock_16n_new != clock_16n_current) {
clock_16n_current = clock_16n_new;
if constexpr (Constants::Developer::LOG_CLOCK_16N) {
ld::DaisySeed::PrintLine("16n: %i", clock_16n_current);
}
sequencer.next_step();
midi_from_sequencer();
}
midi_from_uart();
c_but_clear.update_and_notify_change();
c_but_rec.update_and_notify_change();
task_logger_midi_print.run_pending(uptime_ms);
task_heartbeat.run_pending(uptime_ms);
}
}
} // namespace Heck::OSP
int main()
{
Heck::OSP::init();
Heck::OSP::mainloop();
}