Last active
February 19, 2019 03:20
-
-
Save brianfay/f117b0d5094b5b58b8f869f8c922eaac to your computer and use it in GitHub Desktop.
A simple looper for Bela. Press a button attached to P9_12 to start recording. Press again to start playback. Loops until button is pressed again. Additionally plays the input signal, regardless of whether or not the loop is being played. Can control the volume with a nexus ui slider (browser websockets->node server->cpp osc server)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Bela.h> | |
#include <OSCServer.h> | |
#include <OSCClient.h> | |
#include <math.h> | |
#include <digital_gpio_mapping.h> | |
//44100 * 60 * 4 (4 minute loop) | |
#define LOOP_BUFFER_SIZE 10584000 | |
float gLoopBuffer_l[LOOP_BUFFER_SIZE] = {0}; | |
float gLoopBuffer_r[LOOP_BUFFER_SIZE] = {0}; | |
int gLoopWritePtr = 0; | |
int gLoopReadPtr = 1; | |
int gLoopEndPtr = 0; | |
bool is_recording = true; | |
int debounce_frames; | |
OSCServer oscServer; | |
OSCClient oscClient; | |
int localPort = 7562; | |
int remotePort = 7563; | |
const char* remoteIp = "127.0.0.1"; | |
typedef struct | |
{ | |
int pin; | |
uint64_t last_frame_sampled; | |
int state; | |
} Button; | |
Button btn1, btn2, btn3; | |
float volume = 1.0; | |
typedef enum {UNCHANGED, BTN_DOWN, BTN_UP} button_event; | |
/** | |
* Returns whether btn was pressed down, released, or unchanged at the beginning of the current audio block. | |
* Tries to debounce - the button value will be sampled every block until a transition is detected. | |
* When the transition is detected, stops sampling until a certain number of frames equal to the global variable, debounce_frames, have passed. | |
* The assumption is that button values are always stable except when you're pushing it down or releasing it. | |
* This is probably not a universally true assumption. Interference could cause false button reads. | |
*/ | |
button_event detectButtonEvent(BelaContext * context, Button *btn){ | |
if ((context->audioFramesElapsed - btn->last_frame_sampled) > debounce_frames){ | |
int old_state = btn->state; | |
btn->state = digitalRead(context, 0, btn->pin); | |
if (old_state == 0 && btn->state > 0){ | |
btn->last_frame_sampled = context->audioFramesElapsed; | |
return BTN_DOWN; | |
} else if (old_state > 0 && btn->state == 0){ | |
btn->last_frame_sampled = context->audioFramesElapsed; | |
return BTN_UP; | |
} | |
} | |
return UNCHANGED; | |
} | |
bool setup(BelaContext *context, void *userData) | |
{ | |
oscServer.setup(localPort); | |
oscClient.setup(remotePort, remoteIp); | |
//TODO: osc handshake like in example | |
debounce_frames = ceilf(0.05 * context->audioSampleRate); //50 ms delay, 2205 samples at CD rate | |
pinMode(context, 0, P9_12, INPUT); | |
pinMode(context, 0, P9_14, INPUT); | |
pinMode(context, 0, P9_16, INPUT); | |
btn1.pin = P9_12; | |
btn1.last_frame_sampled = 0; | |
btn1.state = 0; | |
btn2.pin = P9_14; | |
btn2.last_frame_sampled = 0; | |
btn2.state = 0; | |
btn3.pin = P9_14; | |
btn3.last_frame_sampled = 0; | |
btn3.state = 0; | |
return true; | |
} | |
//want to listen for set volume request | |
//should this happen on an aux task? | |
//sounds like it can happen on an aux task if desired | |
void render(BelaContext *context, void *userData) | |
{ | |
float new_volume; | |
while(oscServer.messageWaiting()){ | |
oscpkt::Message msg = oscServer.popMessage(); | |
if (msg.match("/set-volume").popFloat(new_volume)){ | |
if(0.0 <= new_volume <= 1.0){ | |
volume = new_volume; | |
} | |
} | |
} | |
float in_l = 0; | |
float in_r = 0; | |
float out_l = 0; | |
float out_r = 0; | |
button_event b_ev1 = detectButtonEvent(context, &btn1); | |
switch(b_ev1){ | |
case BTN_DOWN: | |
if (is_recording){ | |
gLoopEndPtr = gLoopWritePtr; | |
gLoopReadPtr = 0; | |
} else{ | |
gLoopWritePtr = 0; | |
} | |
is_recording = !is_recording; | |
break; | |
case BTN_UP: | |
break; | |
case UNCHANGED: | |
break; | |
} | |
for(unsigned int n = 0; n < context->audioFrames; n++){ | |
in_l = audioRead(context,n,0); | |
in_r = audioRead(context,n,1); | |
audioWrite(context, n, 0, out_l); | |
audioWrite(context, n, 1, out_r); | |
out_l = in_l; | |
out_r = in_r; | |
if(is_recording){ | |
gLoopBuffer_l[gLoopWritePtr] = in_l; | |
gLoopBuffer_r[gLoopWritePtr] = in_r; | |
gLoopWritePtr++; | |
gLoopWritePtr %= LOOP_BUFFER_SIZE; | |
}else{ | |
out_l += gLoopBuffer_l[gLoopReadPtr]; | |
out_r += gLoopBuffer_r[gLoopReadPtr]; | |
gLoopReadPtr++; | |
gLoopReadPtr %= gLoopEndPtr; | |
} | |
audioWrite(context, n, 0, out_l * volume); | |
audioWrite(context, n, 1, out_r * volume); | |
} | |
} | |
void cleanup(BelaContext *context, void *userData) | |
{ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html language="en-US"> | |
<head> | |
<script src="resources/NexusUI.js"></script> | |
<meta charset="utf-8"> | |
<title>Bela Looper</title> | |
</head> | |
<body> | |
<div id="slider"></div> | |
</body> | |
<script src="resources/app.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express') | |
const bodyParser = require('body-parser') | |
const app = express() | |
const port = 3000 | |
const WebSocket = require('ws') | |
const server = require('http').createServer() | |
const osc = require('osc-min') | |
const udp = require('dgram') | |
const sock = udp.createSocket('udp4', function(msg, rinfo){ | |
try { | |
return console.log(osc.fromBuffer(msg)) | |
} catch (error) { | |
return console.log('invalid soc packet') | |
} | |
}); | |
sock.bind(7563); | |
const wss = new WebSocket.Server({server: server}) | |
wss.on('connection', function connection(ws) { | |
ws.on('message', function incoming(message) { | |
let volume = JSON.parse(message).volume | |
let buf | |
buf = osc.toBuffer({address: '/set-volume', | |
args: [volume]}) | |
sock.send(buf, 0, buf.length, 7562, "localhost") | |
}) | |
}) | |
app.use(bodyParser.json()) | |
app.use(express.static('.')) | |
server.on('request', app) | |
server.listen(port, function() {console.log(`listening on ${port}`)}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var slider = new Nexus.Slider('#slider'); | |
let socket = new WebSocket('ws://localhost:3000/'); | |
//socket.onopen = function(){} | |
// | |
//socket.onmessage = function(){} | |
let ts = performance.now(); | |
function sendSetVolume(volume){ | |
var newTs = performance.now(); | |
if((newTs - ts) >= 50){ | |
ts = newTs; | |
socket.send(JSON.stringify({volume: volume})); | |
} | |
} | |
slider.on('change', function(v){sendSetVolume(v)}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment