Skip to content
Snippets Groups Projects
Commit b5bb5db3 authored by David Huss's avatar David Huss :speech_balloon:
Browse files

Basic multi-playhead functionality

parent 8bf8c32e
Branches
Tags
No related merge requests found
...@@ -380,7 +380,7 @@ void loop() { ...@@ -380,7 +380,7 @@ void loop() {
// Delaytime is in samples // Delaytime is in samples
if (!isnan(p5)) { lfo_amount = p5; } if (!isnan(p5)) { lfo_amount = p5; }
if (!isnan(p6)) { delaytime = 100.0f + p6 * 23900.0f; } if (!isnan(p6)) { delaytime = 100.0f + p6 * 23900.0f; }
if (!isnan(p7)) { reverbmix = p7; Serial.print("Reverb was not NaN: "); Serial.println(reverbmix); } if (!isnan(p7)) { reverbmix = p7; }
// Render the UI (frame rate limited by UI_MAX_FPS in ui.h) // Render the UI (frame rate limited by UI_MAX_FPS in ui.h)
// double start = millis(); // double start = millis();
......
#ifndef Looper_h #pragma once
#define Looper_h
#include "luts.h"
namespace atoav { namespace atoav {
...@@ -17,267 +18,395 @@ enum RecStartMode { ...@@ -17,267 +18,395 @@ enum RecStartMode {
REC_START_MODE_LAST, REC_START_MODE_LAST,
}; };
enum PlaybackState {
PLAYBACK_STATE_STOPPED,
PLAYBACK_STATE_LOOP,
PLAYBACK_STATE_MULTILOOP,
PLAYBACK_STATE_MIDI,
PLAYBACK_STATE_LAST,
};
enum RecordingState {
REC_STATE_EMPTY,
REC_STATE_RECORDING,
REC_STATE_OVERDUBBING,
REC_STATE_ERASING,
REC_STATE_NONE,
REC_STATE_LAST,
};
// =================================================
// = H E A D =
// =================================================
class Head {
public:
Head();
void activate();
void deactivate();
bool isActive();
void setPosition(float value);
void setIncrement(float value);
void incrementBy(float value);
void update();
float read();
float increment = 1.0f;
private:
bool active = true;
float position = 0.0f;
};
Head::Head() {
}
void Head::activate() {
this->active = true;
}
void Head::deactivate() {
this->active = false;
}
bool Head::isActive() {
return active;
}
void Head::setPosition(float value) {
this->position = value;
}
void Head::setIncrement(float value) {
this->increment = value;
}
void Head::incrementBy(float value) {
this->position += value;
}
void Head::update() {
this->position += this->increment;
}
float Head::read() {
return this->position;
}
// =================================================
// = L O O P E R =
// =================================================
class Looper { class Looper {
public: public:
void Init(float *buf, size_t length) { Looper();
void Init(float *buf, size_t buf_size);
void SetRecord();
void SetOverdub();
void SetErase();
void SetStopWriting();
bool isWriting();
void ResetRecHead();
void SetLoop(float loop_start_time, float loop_length_time);
void Record(float in);
float Process();
float* getBuffer();
size_t getBufferLength();
void setRecPitchMode(RecPitchMode mode);
void setRecStartMode(RecStartMode mode);
float GetPlayhead();
float* GetPlayheads();
float GetRecHead();
bool toggleRecMode();
void setRecModeFull();
void setRecModeLoop();
void setRecModeFullShot();
void setRecModeLoopShot();
void setPlaybackSpeed(float increment);
void addToPlayhead(float value);
float loop_start_f = 0.0f;
float loop_length_f = 1.0f;
RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL;
RecStartMode rec_start_mode = REC_START_MODE_BUFFER;
PlaybackState playback_state = PLAYBACK_STATE_LOOP;
RecordingState recording_state = REC_STATE_EMPTY;
private:
static const size_t kFadeLength = 200;
static const size_t kMinLoopLength = 2 * kFadeLength;
float* buffer;
size_t buffer_length = 0;
Head playheads[9];
Head rec_head;
size_t loop_start = 0;
size_t loop_length = 48000;
bool stop_after_recording = false;
bool stay_within_loop = true;
};
Looper::Looper() {}
void Looper::Init(float *buf, size_t buf_size) {
buffer = buf; buffer = buf;
buffer_length = length; buffer_length = buf_size;
// Reset buffer contents to zero
memset(buffer, 0, sizeof(float) * buffer_length); memset(buffer, 0, sizeof(float) * buffer_length);
} }
RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL; void Looper::SetRecord() {
RecStartMode rec_start_mode = REC_START_MODE_BUFFER; recording_state = REC_STATE_RECORDING;
ResetRecHead();
rec_head.activate();
}
void Looper::SetOverdub() {
recording_state = REC_STATE_OVERDUBBING;
ResetRecHead();
rec_head.activate();
}
void Looper::SetErase() {
recording_state = REC_STATE_ERASING;
ResetRecHead();
rec_head.activate();
}
void Looper::SetStopWriting() {
recording_state = REC_STATE_NONE;
rec_head.deactivate();
}
void SetRecording(bool is_recording, bool is_overdub) { bool Looper::isWriting() {
this->is_overdub = is_overdub; return recording_state == REC_STATE_RECORDING
this->is_recording = is_recording || is_overdub; || recording_state == REC_STATE_OVERDUBBING
//Initialize recording head position on start || recording_state == REC_STATE_ERASING;
// if (rec_env_pos_inc <= 0 && is_recording) { }
if (is_recording) {
// rec_head = (loop_start + play_head) % buffer_length; void Looper::ResetRecHead() {
if (isWriting()) {
switch (rec_start_mode) { switch (rec_start_mode) {
case REC_START_MODE_LOOP: case REC_START_MODE_LOOP:
rec_head = (loop_start) % buffer_length; rec_head.setPosition(loop_start % buffer_length);
break; break;
case REC_START_MODE_BUFFER: case REC_START_MODE_BUFFER:
rec_head = 0.0f; rec_head.setPosition(0.0f);
break; break;
case REC_START_MODE_PLAYHEAD: case REC_START_MODE_PLAYHEAD:
rec_head = fmod(loop_start + play_head, float(buffer_length)); rec_head.setPosition(fmod(loop_start + playheads[0].read(), float(buffer_length)));
break; break;
} }
is_empty = false;
} }
// When record switch changes state it effectively
// sets ramp to rising/falling, providing a
// fade in/out in the beginning and at the end of
// the recorded region.
rec_env_pos_inc = is_recording ? 1 : -1;
} }
void SetLoop(const float loop_start_time, const float loop_length_time) { void Looper::SetLoop(float loop_start_time, float loop_length_time) {
if (!isnan(loop_start_time)) { if (!isnan(loop_start_time)) {
loop_start_f = loop_start_time; loop_start_f = loop_start_time;
// Set the start of the next loop loop_start = static_cast<size_t>(loop_start_time * (buffer_length - 1));
pending_loop_start = static_cast<size_t>(loop_start_time * (buffer_length - 1));
// If the current loop start is not set yet, set it too
if (!is_loop_set) loop_start = pending_loop_start;
} }
if (!isnan(loop_length_time)) { if (!isnan(loop_length_time)) {
loop_length_f = loop_length_time; loop_length_f = loop_length_time;
// Set the length of the next loop loop_length = max(kMinLoopLength, static_cast<size_t>(loop_length_time * buffer_length));
pendingloop_length = max(kMinLoopLength, static_cast<size_t>(loop_length_time * buffer_length));
// CHECK if this is truly good
// loop_length = pendingloop_length;
// loop_length = pendingloop_length;
//If the current loop length is not set yet, set it too
if (!is_loop_set) loop_length = pendingloop_length;
} }
is_loop_set = true;
} }
void Record(float in) { void Looper::Record(float in) {
// Calculate iterator position on the record level ramp. // Overwrite/Add/Erase the buffer depending on the mode
if (rec_env_pos_inc > 0 && rec_env_pos < kFadeLength switch (recording_state) {
|| rec_env_pos_inc < 0 && rec_env_pos > 0) { case REC_STATE_RECORDING:
rec_env_pos += rec_env_pos_inc; buffer[int(rec_head.read())] = in;
} break;
// If we're in the middle of the ramp - record to the buffer. case REC_STATE_OVERDUBBING:
if (rec_env_pos > 0) { buffer[int(rec_head.read())] += in;
// Calculate fade in/out break;
float rec_attenuation = static_cast<float>(rec_env_pos) / static_cast<float>(kFadeLength); case REC_STATE_ERASING:
if (this->is_overdub) { buffer[int(rec_head.read())] = 0.0f;
buffer[int(rec_head)] += in * rec_attenuation; break;
} else {
buffer[int(rec_head)] = in * rec_attenuation + buffer[int(rec_head)] * (1.f - rec_attenuation);
} }
// Set recording pitch mode // Advance the recording head if needed
if (isWriting()) {
// Set Recording head increment depending on the mode
switch (rec_pitch_mode) { switch (rec_pitch_mode) {
case REC_PITCH_MODE_NORMAL: case REC_PITCH_MODE_NORMAL:
rec_head += 1.0f; rec_head.setIncrement(1.0f);
break; break;
case REC_PITCH_MODE_UNPITCHED: case REC_PITCH_MODE_UNPITCHED:
rec_head += playback_increment; rec_head.setIncrement(playheads[0].increment);
break; break;
case REC_PITCH_MODE_PITCHED: case REC_PITCH_MODE_PITCHED:
if (playback_increment != 0.0) { if (playheads[0].increment != 0.0) {
rec_head += 1.0f/playback_increment; rec_head.setIncrement(1.0f/playheads[0].increment);
} }
break; break;
} }
// Increment recording head
rec_head.update();
// Different recording modes // Limit the position of the rec head depending on the active mode
if (!stop_after_recording) { if (!stop_after_recording) {
if (!stay_within_loop) { if (!stay_within_loop) {
// record into whole buffer // record into whole buffer
rec_head = fmod(rec_head, float(buffer_length)); rec_head.setPosition(fmod(rec_head.read(), float(buffer_length)));
} else { } else {
// Limit rec head to stay inside the loop // Limit rec head to stay inside the loop
rec_head = fmod(rec_head, float(loop_start + loop_length)); rec_head.setPosition(fmod(rec_head.read(), float(loop_start + loop_length)));
rec_head = max(float(loop_start), rec_head); rec_head.setPosition(max(float(loop_start), rec_head.read()));
} }
} else { } else {
// Stop at end (either end of buffer or end of loop)
if (!stay_within_loop) { if (!stay_within_loop) {
if (rec_head > buffer_length) { SetRecording(false, false); } if (rec_head.read() > buffer_length) { SetStopWriting(); }
} else { } else {
if (rec_head > loop_start + loop_length) { SetRecording(false, false); } if (rec_head.read() > loop_start + loop_length) { SetStopWriting(); }
} }
} }
if (rec_head > buffer_length) { // Ensure the Rec-Head is never without bounds, even when running backwards
rec_head = 0.0f; if (rec_head.read() > buffer_length) {
} else if (rec_head < 0) { rec_head.setPosition(0.0f);
rec_head = buffer_length; } else if (rec_head.read() < 0) {
rec_head.setPosition(buffer_length);
} }
} }
} }
float Process() { float Looper::Process() {
// Early return if the buffer is empty // Early return if buffer is empty or not playing to save performance
if (is_empty) { if (recording_state == REC_STATE_EMPTY
|| playback_state == PLAYBACK_STATE_STOPPED) {
return 0; return 0;
} }
// Variables for the Playback from the Buffer // Deactivate all playheads except first if playback state is Loop
float attenuation = 1; switch (playback_state) {
float output = 0; case PLAYBACK_STATE_LOOP:
playheads[0].activate();
// Calculate fade in/out for (size_t i=1; i<9; i++) {
if (play_head < kFadeLength) { playheads[i].deactivate();
attenuation = static_cast<float>(play_head) / static_cast<float>(kFadeLength);
} }
else if (play_head >= loop_length - kFadeLength) { break;
attenuation = static_cast<float>(loop_length - play_head) / static_cast<float>(kFadeLength); case PLAYBACK_STATE_MULTILOOP:
for (size_t i=0; i<9; i++) {
playheads[i].activate();
}
break;
case PLAYBACK_STATE_STOPPED:
for (size_t i=0; i<9; i++) {
playheads[i].deactivate();
} }
break;
}
double mix = 0.0;
for (size_t i=0; i<9; i++) {
// Skip inactive playheads
if (!playheads[i].isActive()) continue;
// Ensure we are actually inside the buffer // Ensure we are actually inside the buffer
auto play_pos = int(loop_start + play_head) % buffer_length; int play_pos = int(loop_start + playheads[i].read()) % buffer_length;
// Read from the buffer // Read from the buffer
output = buffer[play_pos] * attenuation; mix += buffer[play_pos];
// Advance playhead by the increment // Advance the playhead
play_head += playback_increment; playheads[i].update();
// Ensure the playhead stays within bounds of the loop // Ensure the playhead stays within bounds of the loop
if (play_head >= loop_length) { if ((playheads[i].read()) >= loop_length) {
loop_start = pending_loop_start; playheads[i].setPosition(0.0f);
loop_length = pendingloop_length; } else if (playheads[i].read() <= 0.0f) {
play_head = 0; playheads[i].setPosition(loop_length);
} else if (play_head <= 0) { }
loop_start = pending_loop_start; }
loop_length = pendingloop_length; return saturate(mix);
play_head = loop_length;
} }
// Return the attenuated signal float Looper::GetPlayhead() {
return output * attenuation; return float(playheads[0].read()) / float(buffer_length);
} }
float GetPlayhead() { float* Looper::GetPlayheads() {
return float(play_head) / float(buffer_length); static float playhead_positions[9];
for (size_t i=0; i<9; i++) {
playhead_positions[i] = float(playheads[i].read()) / float(buffer_length);
Serial.print(playhead_positions[i]);
Serial.print(" ");
}
Serial.println("");
return playhead_positions;
} }
float GetRecHead() { float Looper::GetRecHead() {
return float(rec_head) / float(buffer_length); return float(rec_head.read()) / float(buffer_length);
} }
bool toggleRecMode() { bool Looper::toggleRecMode() {
stay_within_loop = !stay_within_loop; stay_within_loop = !stay_within_loop;
return stay_within_loop; return stay_within_loop;
} }
void setRecModeFull() { void Looper::setRecModeFull() {
stay_within_loop = false; stay_within_loop = false;
stop_after_recording = false; stop_after_recording = false;
} }
void setRecModeLoop() { void Looper::setRecModeLoop() {
stay_within_loop = true; stay_within_loop = true;
stop_after_recording = false; stop_after_recording = false;
} }
void setRecModeFullShot() { void Looper::setRecModeFullShot() {
stay_within_loop = false; stay_within_loop = false;
stop_after_recording = true; stop_after_recording = true;
} }
void setRecModeLoopShot() { void Looper::setRecModeLoopShot() {
stay_within_loop = true; stay_within_loop = true;
stop_after_recording = true; stop_after_recording = true;
} }
void Looper::setPlaybackSpeed(float increment) {
void setPlaybackSpeed(float increment) { switch (playback_state) {
playback_increment = increment; case PLAYBACK_STATE_LOOP:
playheads[0].setIncrement(increment);
break;
case PLAYBACK_STATE_MULTILOOP:
playheads[0].setIncrement(increment);
for (size_t i=1; i<9; i++) {
playheads[i].setIncrement(increment + increment/(1+i));
} }
break;
void addToPlayhead(float value) {
play_head += value;
} }
float* getBuffer() {
return buffer;
} }
size_t getBufferLength() { void Looper::addToPlayhead(float value) {
return buffer_length; switch (playback_state) {
case PLAYBACK_STATE_LOOP:
playheads[0].incrementBy(value);
break;
case PLAYBACK_STATE_MULTILOOP:
playheads[0].incrementBy(value);
for (size_t i=1; i<9; i++) {
playheads[i].incrementBy(value + value/(1+i));
}
break;
}
} }
bool isRecording() { float* Looper::getBuffer() {
return is_recording; return buffer;
} }
bool isOverdubbing() { size_t Looper::getBufferLength() {
return is_overdub; return buffer_length;
} }
void setRecPitchMode(RecPitchMode mode) { void Looper::setRecPitchMode(RecPitchMode mode) {
rec_pitch_mode = mode; rec_pitch_mode = mode;
} }
void setRecStartMode(RecStartMode mode) { void Looper::setRecStartMode(RecStartMode mode) {
rec_start_mode = mode; rec_start_mode = mode;
} }
float loop_length_f = 1.0f;
float loop_start_f = 0.0f;
private:
static const size_t kFadeLength = 200;
static const size_t kMinLoopLength = 2 * kFadeLength;
float* buffer;
size_t buffer_length = 0;
size_t loop_length = 48000;
size_t pendingloop_length = 0;
size_t loop_start = 0;
size_t pending_loop_start = 0;
float play_head = 0.0f;
float rec_head = 0.0f;
float playback_increment = 1.0f;
size_t rec_env_pos = 0;
int32_t rec_env_pos_inc = 0;
bool is_empty = true;
bool is_loop_set = false;
bool stay_within_loop = false;
bool is_overdub = false;
bool is_recording = false;
bool stop_after_recording = false;
};
}; // namespace atoav }; // namespace atoav
\ No newline at end of file
#endif
\ No newline at end of file
...@@ -101,12 +101,12 @@ class Ui { ...@@ -101,12 +101,12 @@ class Ui {
GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0, "START RECORDING AT\n\nSTART--->Start of the\n Buffer\nLOOP---->Start of the\n Loop\nPLAYH--->Position of\n the Playhead"), GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0, "START RECORDING AT\n\nSTART--->Start of the\n Buffer\nLOOP---->Start of the\n Loop\nPLAYH--->Position of\n the Playhead"),
}), }),
ButtonGrid((int) UI_MODE_PLAY_MENU, { ButtonGrid((int) UI_MODE_PLAY_MENU, {
GridButton("LOOP", &button_1, false), GridButton(" ", &button_1, false),
GridButton("PLAY\nMENU", &button_2, true), GridButton("PLAY\nMENU", &button_2, true),
GridButton("ACTIVE\nSUM\nRING", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0), GridButton("ACTIVE\nSUM\nRING", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0),
GridButton("DRUNK", &button_4, false), GridButton("STOP\nLOOP\nMULTI\nMIDI", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1),
GridButton("GRAIN", &button_5, false), GridButton(" ", &button_5, false),
GridButton("SHOT", &button_6, false), GridButton(" ", &button_6, false),
}), }),
ButtonGrid((int) UI_MODE_TRIGGER_MENU, { ButtonGrid((int) UI_MODE_TRIGGER_MENU, {
GridButton("MIDI\nTRIG.", &button_1, false), GridButton("MIDI\nTRIG.", &button_1, false),
...@@ -415,25 +415,22 @@ class Ui { ...@@ -415,25 +415,22 @@ class Ui {
if (ui_mode == UI_MODE_PLAY_MENU && last_ui_mode != UI_MODE_PLAY_MENU) { if (ui_mode == UI_MODE_PLAY_MENU && last_ui_mode != UI_MODE_PLAY_MENU) {
int n = 1; int n = 1;
// Show the setting of the current buffer
button_grids[n].grid_buttons_[3].active = (int) activeLooper()->playback_state ;
// Setup button Grid // Setup button Grid
Button* home_button = setupButtonGrid(n); Button* home_button = setupButtonGrid(n);
// button_1.onPress([this, n](){
// });
button_3.onPress([this, n](){ button_3.onPress([this, n](){
button_grids[n].grid_buttons_[2].next(); button_grids[n].grid_buttons_[2].next();
buffer_summing_mode = (BufferSummingMode) button_grids[n].grid_buttons_[2].active; buffer_summing_mode = (BufferSummingMode) button_grids[n].grid_buttons_[2].active;
}); });
// button_4.onPress([this, n](){
// button_4.onPress([this, n](){
// }); button_grids[n].grid_buttons_[3].next();
// button_5.onPress([this, n](){ // 0 is stop so we add one, check looper.h for definition of enum
// activeLooper()->playback_state = (atoav::PlaybackState) (button_grids[n].grid_buttons_[3].active);
// }); });
// button_6.onPress([this, n](){
//
// });
// Store the last ui mode, for the check on top // Store the last ui mode, for the check on top
last_ui_mode = ui_mode; last_ui_mode = ui_mode;
...@@ -573,14 +570,38 @@ class Ui { ...@@ -573,14 +570,38 @@ class Ui {
} }
// Draw Playhead // Draw Playhead
switch (activeLooper()->playback_state) {
case atoav::PLAYBACK_STATE_LOOP:
{
int x_playhead = int(activeLooper()->GetPlayhead() * display.width()) + x_start_loop;
display.drawFastVLine(x_playhead, 6, 24, SH110X_WHITE);
break;
}
case atoav::PLAYBACK_STATE_MULTILOOP:
{
float* playheads = activeLooper()->GetPlayheads();
int x_playhead = 0;
for (size_t i=0; i<9; i++) {
x_playhead = int(playheads[i] * display.width()) + x_start_loop;
int h = 6 + i*3;
display.drawFastVLine(x_playhead, h, 3, SH110X_WHITE);
}
break;
}
case atoav::PLAYBACK_STATE_MIDI:
{
int x_playhead = int(activeLooper()->GetPlayhead() * display.width()) + x_start_loop; int x_playhead = int(activeLooper()->GetPlayhead() * display.width()) + x_start_loop;
display.drawLine(x_playhead, 6, x_playhead, 24, SH110X_WHITE); display.drawFastVLine(x_playhead, 6, 24, SH110X_WHITE);
break;
}
}
// Draw Recording Indicator and Recording Head // Draw Recording Indicator and Recording Head
if (recording_state == REC_STATE_RECORDING) { if (recording_state == REC_STATE_RECORDING) {
// Draw Rec Head // Draw Rec Head
int x_rec_head = int(activeLooper()->GetRecHead() * display.width()); int x_rec_head = int(activeLooper()->GetRecHead() * display.width());
display.drawLine(x_rec_head, 10, x_rec_head, bottom, SH110X_WHITE); display.drawFastVLine(x_rec_head, 10, bottom, SH110X_WHITE);
display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE); display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE);
// Record sign // Record sign
display.fillRect(0, 0, 13, 13, SH110X_WHITE); display.fillRect(0, 0, 13, 13, SH110X_WHITE);
...@@ -593,7 +614,7 @@ class Ui { ...@@ -593,7 +614,7 @@ class Ui {
if (recording_state == REC_STATE_OVERDUBBING) { if (recording_state == REC_STATE_OVERDUBBING) {
// Draw Rec Head // Draw Rec Head
int x_rec_head = int(activeLooper()->GetRecHead() * display.width()); int x_rec_head = int(activeLooper()->GetRecHead() * display.width());
display.drawLine(x_rec_head, 10, x_rec_head, bottom, SH110X_WHITE); display.drawFastVLine(x_rec_head, 10, bottom, SH110X_WHITE);
display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE); display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE);
// Overdub sign (a "plus") // Overdub sign (a "plus")
...@@ -620,7 +641,7 @@ class Ui { ...@@ -620,7 +641,7 @@ class Ui {
// Activate recording and set the waveform cache to dirty // Activate recording and set the waveform cache to dirty
void activateRecording() { void activateRecording() {
if (recording_state != REC_STATE_RECORDING) { if (recording_state != REC_STATE_RECORDING) {
activeLooper()->SetRecording(true, false); activeLooper()->SetRecord();
waveform_cache_dirty = true; waveform_cache_dirty = true;
recording_state = REC_STATE_RECORDING; recording_state = REC_STATE_RECORDING;
} }
...@@ -646,7 +667,7 @@ class Ui { ...@@ -646,7 +667,7 @@ class Ui {
if (recording_state != REC_STATE_OVERDUBBING) { if (recording_state != REC_STATE_OVERDUBBING) {
waveform_cache_dirty = true; waveform_cache_dirty = true;
recording_state = REC_STATE_OVERDUBBING; recording_state = REC_STATE_OVERDUBBING;
activeLooper()->SetRecording(true, true); activeLooper()->SetOverdub();
} }
} }
...@@ -654,7 +675,7 @@ class Ui { ...@@ -654,7 +675,7 @@ class Ui {
void stopRecording() { void stopRecording() {
if (recording_state != REC_STATE_NOT_RECORDING) { if (recording_state != REC_STATE_NOT_RECORDING) {
recording_state = REC_STATE_NOT_RECORDING; recording_state = REC_STATE_NOT_RECORDING;
activeLooper()->SetRecording(false, false); activeLooper()->SetStopWriting();
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment