Here’s the current code for each Space Rock and the transmitter that they connect to. The code allows the synth programmed into each Rock to change based on the relative distance to the transmitter.
Vaporum (‘Small whale’)
Uses the Control Echo Theremin (Mozzi) synth
// include all libraries
#include <LoRaLib.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <ControlDelay.h>
#include <RollingAverage.h>
// uncomment the following line for debug output
// NOTE: debug output will slow down Arduino!
//#define DEBUG
#ifdef DEBUG
#define DEBUG_BEGIN(x) Serial.begin (x)
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINT_DEC(x) Serial.print (x, DEC)
#define DEBUG_PRINT_HEX(x) Serial.print (x, HEX)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINT_STR(x) Serial.print (F(x))
#define DEBUG_PRINTLN_STR(x) Serial.println (F(x))
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINT_DEC(x)
#define DEBUG_PRINT_HEX(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT_STR(x)
#define DEBUG_PRINTLN_STR(x)
#endif
// Mozzi control rate in Hz
#define CONTROL_RATE 512
// RSSI range bounds
#define RSSI_LOW -60
#define RSSI_HIGH -30
// freqency range bounds in Hz
#define FREQ_LOW 0
#define FREQ_HIGH 1000
// SX1278 object instance
SX1278 lora = new LoRa;
// rolling average filter
RollingAverage <float, 32> kAverage;
// theremin effect echo cells
unsigned int echo_cells_1 = 32;
unsigned int echo_cells_2 = 60;
unsigned int echo_cells_3 = 127;
// 2 second delay
ControlDelay <128, int> kDelay;
// oscils to compare bumpy to averaged control input
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin0(SIN2048_DATA);
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin1(SIN2048_DATA);
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin2(SIN2048_DATA);
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin3(SIN2048_DATA);
void setup(){
// start Serial port for debugging
DEBUG_BEGIN(115200);
// initialize SX1278
int state = lora.begin();
DEBUG_PRINTLN(state);
// set LoRa bandwidth
state = lora.setBandwidth(250);
DEBUG_PRINTLN(state);
// set interrupt action
lora.setDio0Action(setFlag);
// start listening for LoRa transmissions
lora.startReceive();
// start Mozzi
startMozzi(CONTROL_RATE);
// set echo cells
kDelay.set(echo_cells_1);
}
// flag to indicate that a packet was received
volatile bool receivedFlag = false;
// disable interrupt when it's not needed
volatile bool enableInterrupt = true;
void setFlag(void) {
// check if the interrupt is enabled
if(!enableInterrupt) {
return;
}
// we got a packet, set the flag
receivedFlag = true;
}
void updateControl(){
// check if packet was received
if(receivedFlag) {
// disable the interrupt service routine while processing the data
enableInterrupt = false;
// reset flag
receivedFlag = false;
// read packet and update RSSI reading
String str;
lora.readData(str);
// get raw RSSI value
int rssiRaw = lora.lastPacketRSSI;
// map raw RSSI value to frequency range
int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, FREQ_LOW, FREQ_HIGH);
// filter frequency value
float freqFiltered = kAverage.next(rssiMapped);
// print RSSI values for debugging
DEBUG_PRINT(rssiRaw);
DEBUG_PRINT('\t');
DEBUG_PRINT(rssiMapped);
DEBUG_PRINT('\t');
DEBUG_PRINTLN(freqFiltered);
// set new frequency
aSin0.setFreq(freqFiltered);
aSin1.setFreq(kDelay.next(freqFiltered));
aSin2.setFreq(kDelay.read(echo_cells_2));
aSin3.setFreq(kDelay.read(echo_cells_3));
// enable interrupt again
enableInterrupt = true;
}
}
int updateAudio(){
return 3 * ((int)aSin0.next() + aSin1.next() + (aSin2.next() >> 1) + (aSin3.next() >> 2)) >> 3;
}
void loop(){
audioHook();
}
Cognitum (‘Razor clam’)
Uses the Detuned Beats Wash (Mozzi) synth
// include all libraries
#include <LoRaLib.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/cos8192_int8.h>
#include <mozzi_rand.h>
#include <mozzi_midi.h>
#include <mozzi_fixmath.h>
#include <RollingAverage.h>
// uncomment the following line for debug output
// NOTE: debug output will slow down Arduino!
//#define DEBUG
#ifdef DEBUG
#define DEBUG_BEGIN(x) Serial.begin (x)
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINT_DEC(x) Serial.print (x, DEC)
#define DEBUG_PRINT_HEX(x) Serial.print (x, HEX)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINT_STR(x) Serial.print (F(x))
#define DEBUG_PRINTLN_STR(x) Serial.println (F(x))
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINT_DEC(x)
#define DEBUG_PRINT_HEX(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT_STR(x)
#define DEBUG_PRINTLN_STR(x)
#endif
// Mozzi control rate in Hz
#define CONTROL_RATE 512
// RSSI range bounds
#define RSSI_LOW -60
#define RSSI_HIGH -30
// freqency range bounds in Hz
#define FREQ_LOW 0
#define FREQ_HIGH 1000
// SX1278 object instance
SX1278 lora = new LoRa;
// rolling average filter
RollingAverage <float, 32> kAverage;
// harmonics
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos1(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos2(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos3(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos4(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos5(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos6(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos7(COS8192_DATA);
// duplicates but slightly off frequency for adding to originals
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos1b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos2b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos3b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos4b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos5b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos6b(COS8192_DATA);
Oscil<COS8192_NUM_CELLS, AUDIO_RATE> aCos7b(COS8192_DATA);
// base pitch frequencies in 24n8 fixed int format (for speed later)
Q16n16 f1,f2,f3,f4,f5,f6,f7;
Q16n16 variation() {
// 32 random bits & with 524287 (b111 1111 1111 1111 1111)
// gives between 0-8 in Q16n16 format
return (Q16n16) (xorshift96() & 524287UL);
}
void setup(){
// start Serial port for debugging
DEBUG_BEGIN(115200);
// initialize SX1278
int state = lora.begin();
DEBUG_PRINTLN(state);
// set LoRa bandwidth
state = lora.setBandwidth(250);
DEBUG_PRINTLN(state);
// set interrupt action
lora.setDio0Action(setFlag);
// start listening for LoRa transmissions
lora.startReceive();
// start Mozzi
startMozzi(CONTROL_RATE);
// select base frequencies using mtof (midi to freq) and fixed-point numbers
f1 = Q16n16_mtof(Q16n0_to_Q16n16(48));
f2 = Q16n16_mtof(Q16n0_to_Q16n16(74));
f3 = Q16n16_mtof(Q16n0_to_Q16n16(64));
f4 = Q16n16_mtof(Q16n0_to_Q16n16(77));
f5 = Q16n16_mtof(Q16n0_to_Q16n16(67));
f6 = Q16n16_mtof(Q16n0_to_Q16n16(57));
f7 = Q16n16_mtof(Q16n0_to_Q16n16(60));
// set Oscils with chosen frequencies
aCos1.setFreq_Q16n16(f1);
aCos2.setFreq_Q16n16(f2);
aCos3.setFreq_Q16n16(f3);
aCos4.setFreq_Q16n16(f4);
aCos5.setFreq_Q16n16(f5);
aCos6.setFreq_Q16n16(f6);
aCos7.setFreq_Q16n16(f7);
// set frequencies of duplicate oscillators
aCos1b.setFreq_Q16n16(f1 + variation());
aCos2b.setFreq_Q16n16(f2 + variation());
aCos3b.setFreq_Q16n16(f3 + variation());
aCos4b.setFreq_Q16n16(f4 + variation());
aCos5b.setFreq_Q16n16(f5 + variation());
aCos6b.setFreq_Q16n16(f6 + variation());
aCos7b.setFreq_Q16n16(f7 + variation());
}
// flag to indicate that a packet was received
volatile bool receivedFlag = false;
// disable interrupt when it's not needed
volatile bool enableInterrupt = true;
void setFlag(void) {
// check if the interrupt is enabled
if(!enableInterrupt) {
return;
}
// we got a packet, set the flag
receivedFlag = true;
}
void updateControl(){
// check if packet was received
if(receivedFlag) {
// disable the interrupt service routine while processing the data
enableInterrupt = false;
// reset flag
receivedFlag = false;
// read packet and update RSSI reading
String str;
lora.readData(str);
// get raw RSSI value
int rssiRaw = lora.lastPacketRSSI;
// map raw RSSI value to frequency range
int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, FREQ_LOW, FREQ_HIGH);
// filter frequency value
float freqFiltered = kAverage.next(rssiMapped);
// print RSSI values for debugging
DEBUG_PRINT(rssiRaw);
DEBUG_PRINT('\t');
DEBUG_PRINT(rssiMapped);
DEBUG_PRINT('\t');
DEBUG_PRINTLN(freqFiltered);
// change frequencies of the b oscillators
switch(lowByte(xorshift96()) & 7) {
case 0:
aCos1b.setFreq_Q16n16(f1 + float_to_Q16n16(freqFiltered) + variation());
break;
case 1:
aCos2b.setFreq_Q16n16(f2 + float_to_Q16n16(freqFiltered) + variation());
break;
case 2:
aCos3b.setFreq_Q16n16(f3 + float_to_Q16n16(freqFiltered) + variation());
break;
case 3:
aCos4b.setFreq_Q16n16(f4 + float_to_Q16n16(freqFiltered) + variation());
break;
case 4:
aCos5b.setFreq_Q16n16(f5 + float_to_Q16n16(freqFiltered) + variation());
break;
case 5:
aCos6b.setFreq_Q16n16(f6 + float_to_Q16n16(freqFiltered) + variation());
break;
case 6:
aCos7b.setFreq_Q16n16(f7 + float_to_Q16n16(freqFiltered) + variation());
break;
}
// enable interrupt again
enableInterrupt = true;
}
}
int updateAudio(){
int asig =
aCos1.next() + aCos1b.next() +
aCos2.next() + aCos2b.next() +
aCos3.next() + aCos3b.next() +
aCos4.next() + aCos4b.next() +
aCos5.next() + aCos5b.next() +
aCos6.next() + aCos6b.next() +
aCos7.next() + aCos7b.next();
return asig >> 3;
}
void loop(){
audioHook();
}
Undarum (‘Wide shell’)
Uses the Knob Light (Mozzi) synth
// include all libraries
#include <LoRaLib.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/cos2048_int8.h>
#include <RollingAverage.h>
// uncomment the following line for debug output
// NOTE: debug output will slow down Arduino!
//#define DEBUG
#ifdef DEBUG
#define DEBUG_BEGIN(x) Serial.begin (x)
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINT_DEC(x) Serial.print (x, DEC)
#define DEBUG_PRINT_HEX(x) Serial.print (x, HEX)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINT_STR(x) Serial.print (F(x))
#define DEBUG_PRINTLN_STR(x) Serial.println (F(x))
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINT_DEC(x)
#define DEBUG_PRINT_HEX(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT_STR(x)
#define DEBUG_PRINTLN_STR(x)
#endif
// Mozzi control rate in Hz
#define CONTROL_RATE 512
// RSSI range bounds
#define RSSI_LOW -60
#define RSSI_HIGH -30
// freqency range bounds in Hz
#define FREQ_LOW 400
#define FREQ_HIGH 800
// SX1278 object instance
SX1278 lora = new LoRa;
// rolling average filter
RollingAverage <float, 32> kAverage;
// Mozzi oscillators
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aCarrier(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModulator(COS2048_DATA);
// harmonics
int mod_ratio = 3;
// carries control info from updateControl() to updateAudio()
long fm_intensity;
void setup(){
// start Serial port for debugging
DEBUG_BEGIN(115200);
// initialize SX1278
int state = lora.begin();
DEBUG_PRINTLN(state);
// set LoRa bandwidth
state = lora.setBandwidth(250);
DEBUG_PRINTLN(state);
// set interrupt action
lora.setDio0Action(setFlag);
// start listening for LoRa transmissions
lora.startReceive();
// start Mozzi
startMozzi(CONTROL_RATE);
}
// flag to indicate that a packet was received
volatile bool receivedFlag = false;
// disable interrupt when it's not needed
volatile bool enableInterrupt = true;
void setFlag(void) {
// check if the interrupt is enabled
if(!enableInterrupt) {
return;
}
// we got a packet, set the flag
receivedFlag = true;
}
void updateControl(){
// check if packet was received
if(receivedFlag) {
// disable the interrupt service routine while processing the data
enableInterrupt = false;
// reset flag
receivedFlag = false;
// read packet and update RSSI reading
String str;
lora.readData(str);
// get raw RSSI value
int rssiRaw = lora.lastPacketRSSI;
// map raw RSSI value to frequency range
int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, FREQ_LOW, FREQ_HIGH);
// filter frequency value
float freqFiltered = kAverage.next(rssiMapped);
// print RSSI values for debugging
DEBUG_PRINT(rssiRaw);
DEBUG_PRINT('\t');
DEBUG_PRINT(rssiMapped);
DEBUG_PRINT('\t');
DEBUG_PRINTLN(freqFiltered);
// set new frequency
aCarrier.setFreq(freqFiltered);
aModulator.setFreq(freqFiltered * mod_ratio);
fm_intensity = freqFiltered;
// enable interrupt again
enableInterrupt = true;
}
}
int updateAudio(){
long modulation = fm_intensity * aModulator.next();
return aCarrier.phMod(modulation);
}
void loop(){
audioHook();
}
Marginis (‘Shape 4’)
Uses the Sine Wave Pulse (Mozzi) synth
// include all libraries
#include <LoRaLib.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <Smooth.h>
#include <RollingAverage.h>
// uncomment the following line for debug output
// NOTE: debug output will slow down Arduino!
//#define DEBUG
#ifdef DEBUG
#define DEBUG_BEGIN(x) Serial.begin (x)
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINT_DEC(x) Serial.print (x, DEC)
#define DEBUG_PRINT_HEX(x) Serial.print (x, HEX)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINT_STR(x) Serial.print (F(x))
#define DEBUG_PRINTLN_STR(x) Serial.println (F(x))
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINT_DEC(x)
#define DEBUG_PRINT_HEX(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT_STR(x)
#define DEBUG_PRINTLN_STR(x)
#endif
// Mozzi control rate in Hz
#define CONTROL_RATE 512
// RSSI range bounds
#define RSSI_LOW -60
#define RSSI_HIGH -30
// freqency range bounds in Hz
#define FREQ_LOW 400
#define FREQ_HIGH 800
// SX1278 object instance
SX1278 lora = new LoRa;
// rolling average filter
RollingAverage <float, 32> kAverage;
// Mozzi sine oscillator
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
// target gain for smoothing, toggle between 255/0
byte gain = 0;
// Smoothing filter, higher number = smoother output
Smooth <long> aSmoothGain(0.9995);
void setup() {
// start Serial port for debugging
DEBUG_BEGIN(115200);
// initialize SX1278
int state = lora.begin();
DEBUG_PRINTLN(state);
// set LoRa bandwidth
state = lora.setBandwidth(250);
DEBUG_PRINTLN(state);
// set interrupt action
lora.setDio0Action(setFlag);
// start listening for LoRa transmissions
lora.startReceive();
// start Mozzi
startMozzi(CONTROL_RATE);
// set output frequency to the lowest value
aSin.setFreq(FREQ_LOW);
}
// flag to indicate that a packet was received
volatile bool receivedFlag = false;
// disable interrupt when it's not needed
volatile bool enableInterrupt = true;
void setFlag(void) {
// check if the interrupt is enabled
if(!enableInterrupt) {
return;
}
// we got a packet, set the flag
receivedFlag = true;
}
// variable to save starting timestamp
unsigned long start = 0;
// delay length in microseconds
unsigned long delayMicros = FREQ_LOW * 1000;
void updateControl() {
// check if packet was received
if(receivedFlag) {
// disable the interrupt service routine while processing the data
enableInterrupt = false;
// reset flag
receivedFlag = false;
// read packet and update RSSI reading
String str;
lora.readData(str);
// get raw RSSI value
int rssiRaw = lora.lastPacketRSSI;
// map raw RSSI value to frequency range
int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, FREQ_LOW, FREQ_HIGH);
// filter frequency value
float freqFiltered = kAverage.next(rssiMapped);
// print RSSI values for debugging
DEBUG_PRINT(rssiRaw);
DEBUG_PRINT('\t');
DEBUG_PRINT(rssiMapped);
DEBUG_PRINT('\t');
DEBUG_PRINTLN(freqFiltered);
// set new frequency
aSin.setFreq(freqFiltered);
// set new delay length
delayMicros = (FREQ_HIGH - freqFiltered) * 1000;
// check if it's time to pulse
if(mozziMicros() - start >= delayMicros) {
// save current time
start = mozziMicros();
// flip 0/255
gain = 255 - gain;
}
// enable interrupt again
enableInterrupt = true;
}
}
int updateAudio(){
return (aSmoothGain.next(gain) * aSin.next()) >> 8 ;
}
void loop() {
audioHook();
}
And the code for the transmitter, which links the four Space Rocks together.
// include library
#include <LoRaLib.h>
// SX1278 object instance
SX1278 lora = new LoRa;
void setup() {
// start Serial port for debugging
Serial.begin(115200);
// initialize SX1278
Serial.println(lora.begin());
// set LoRa bandwidth
Serial.println(lora.setBandwidth(250));
// set output power to limit jamming
Serial.println(lora.setOutputPower(5));
}
void loop() {
// transmit packets as fast as we can
lora.transmit("WeAreHere");
}