Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Looping Unquantized Drum Machine #78

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions patch/DrumLooper/DrumLooper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#include "daisysp.h"
#include "daisy_patch.h"
#include <string>

using namespace daisy;
using namespace daisysp;

#define MAX_SIZE (1000 * 60) // 1 minute at 1000Hz

DaisyPatch patch;

Oscillator osc;
WhiteNoise noise;

AdEnv kckPitchEnv;
AdEnv volEnvs[3];

bool first = true; //first loop (sets length)
bool rec = false; //currently recording

int pos = 0;
bool DSY_SDRAM_BSS buf[3][MAX_SIZE];
int mod = MAX_SIZE;
int len = 0;
float drywet = 0;

int recChan = 0;

void UpdateControls();

void UpdateEnvs()
{
for (int chn = 0; chn < 3; chn++)
{
if (buf[chn][pos])
{
volEnvs[chn].Trigger();
if (chn == 0)
{
kckPitchEnv.Trigger();
}
}
}
}

static void AudioCallback(float **in, float **out, size_t size)
{
UpdateControls();

for (size_t i = 0; i < size; i ++){
float outs[3];

osc.SetFreq(kckPitchEnv.Process());
osc.SetAmp(volEnvs[0].Process());
outs[0] = osc.Process();

float noise_out = noise.Process();
outs[1] = noise_out * volEnvs[1].Process();

outs[2]= noise_out * volEnvs[2].Process();

float mix = 0.f;
float mixLevels[] = {.5f, .1f, .05f};

for (size_t chn = 0; chn < 3; chn++)
{
out[chn][i] = outs[chn];
mix += mixLevels[chn] * outs[chn];
}

out[3][i] = mix;
}
}

void UpdateOled();

void InitEnvs(float samplerate)
{
for (int i = 0; i < 3; i++)
{
volEnvs[i].Init(samplerate);
volEnvs[i].SetTime(ADENV_SEG_ATTACK, .01);
volEnvs[i].SetCurve(-4);
}

//This envelope will control the kick oscillator's pitch
//Note that this envelope is much faster than the volume
kckPitchEnv.Init(samplerate);
kckPitchEnv.SetTime(ADENV_SEG_ATTACK, .02);
kckPitchEnv.SetMax(200);
kckPitchEnv.SetCurve(-4);
}

void ResetBuffer()
{
rec = false;
first = true;
pos = 0;
len = 0;

for (int chn = 0; chn < 3; chn++){
for(int i = 0; i < mod; i++)
{
buf[chn][i] = false;
}
}

mod = MAX_SIZE;
}


int main(void)
{
float samplerate;
patch.Init(); // Initialize hardware (daisy seed, and patch)
samplerate = patch.AudioSampleRate();

//Initialize oscillator for kickdrum
osc.Init(samplerate);
osc.SetWaveform(Oscillator::WAVE_TRI);

//Initialize noise
noise.Init();

patch.display.Fill(false);

InitEnvs(samplerate);

//Init loop stuff
ResetBuffer();

patch.StartAdc();
patch.StartAudio(AudioCallback);
while(1)
{
UpdateOled();
}
}

void UpdateOled()
{
patch.display.Fill(false);

patch.display.SetCursor(0,0);
std::string str = rec ? "rec" : "play";
str = (first && !rec) ? "ready" : str;
char* cstr = &str[0];
patch.display.WriteString(cstr, Font_7x10, true);

patch.display.SetCursor(0,25);
str = recChan == 0 ? "Kick" : "";
str = recChan == 1 ? "Snare": str;
str = recChan == 2 ? "Hat " : str;
patch.display.WriteString(cstr, Font_7x10, true);

patch.display.Update();
}
void UpdateControls()
{
patch.UpdateAnalogControls();
patch.DebounceControls();

//encoder pressed
if(patch.encoder.RisingEdge())
{
//set loop len
if(first && rec)
{
first = false;
mod = len;
len = 0;
pos = 0;
}

rec = !rec;
}

//encoder held
if(patch.encoder.TimeHeldMs() >= 1000)
{
ResetBuffer();
}

//encoder turned
recChan += patch.encoder.Increment();
recChan = (recChan % 3 + 3) % 3;

//parameters
volEnvs[0].SetTime(ADENV_SEG_DECAY, patch.controls[0].Process() * 3);

volEnvs[1].SetTime(ADENV_SEG_DECAY, patch.controls[2].Process());
volEnvs[2].SetTime(ADENV_SEG_DECAY, patch.controls[3].Process());

kckPitchEnv.SetMin((patch.controls[1].Process() * 4 + 1) * 20);

//gate in
if (patch.gate_input[0].Trig())
{
if(rec || first)
{
buf[recChan][pos] = true;
rec = true;
}
}

//if we're making our first loop
if(first && rec)
{
len++;
//automatic looptime
if(len >= MAX_SIZE)
{
first = false;
mod = MAX_SIZE;
len = 0;
pos = 0;
}
}

//we want this to happen before we update the pos
UpdateEnvs();

//the only situation in which we don't increment is when
//we're waiting for the first recording
if(!(first && !rec))
{
pos++;
pos %= mod;
//EOC
dsy_gpio_write(&patch.gate_output, pos == 0);
}
}
14 changes: 14 additions & 0 deletions patch/DrumLooper/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Project Name
TARGET = DrumLooper

# Sources
CPP_SOURCES = DrumLooper.cpp

# Library Locations
LIBDAISY_DIR = ../../libdaisy
DAISYSP_DIR = ../../DaisySP

# Core location, and generic Makefile.
SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core
include $(SYSTEM_FILES_DIR)/Makefile

53 changes: 53 additions & 0 deletions patch/DrumLooper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Description
Free looping drum machine. Record unquantized drums on three different channels.
The first recording sets the loop length. The recording can be started by either a trigger or pressing the encoder.
After the first loop, turn record back on to record more drums. There is an end of cycle output available to sync other gear.
Try different trigger inputs! Drum pads, switches, and sequencers are all good ideas!

# Controls

| Control | Description | Comment |
| --- | --- | --- |
| Ctrl 1 | Kick Decay Time | |
| Ctrl 2 | Kick Pitch | |
| Ctrl 3 | Snare Decay Time | |
| Ctrl 4 | Hat Decay Time | |
| Encoder Turn | Select Channel | |
| Encoder Press | Record | The first loop sets the length. |
| Encoder long press | Reset looper | |
| Gate In 1 | Trigger to record | |
| Gate Out | End of Cycle | |
| Audio Out 1 | Kick Out | |
| Audio Out 2 | Snare Out |
| Audio Out 3 | Hat Out | |
| Audio Out 4 | Mix Out | |

# Diagram
<img src="https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/patch/DrumLooper/resources/DrumLooper.png" alt="DrumLooper.png" style="width: 100%;"/>

# Code Snippet
```cpp
for (size_t i = 0; i < size; i ++){
float outs[3];

osc.SetFreq(kckPitchEnv.Process());
osc.SetAmp(volEnvs[0].Process());
outs[0] = osc.Process();

float noise_out = noise.Process();
outs[1] = noise_out * volEnvs[1].Process();

outs[2]= noise_out * volEnvs[2].Process();

float mix = 0.f;
float mixLevels[] = {.5f, .1f, .05f};

for (size_t chn = 0; chn < 3; chn++)
{
out[chn][i] = outs[chn];
mix += mixLevels[chn] * outs[chn];
}

out[3][i] = mix;
}
```