Photos of the Ugly Duck exhibition for the Ravensbourne Postgraduate final degree show. 19th-21st September 2018
Tag: sound
Current code for Space Rocks
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"); }
Painted and mounted Space Rocks
ex-XBees
So…it seems that the combination of XBee RSSI data and the Mozzi synth can only produce intermittent or pulsing sound and doesn’t allow for constant tones, as witnessed by the following video demos. All the data that has to be sent from Arduino to the XBee in order to get it to send and receive packets is causing the audio to stutter.
To get the tones required for the final piece, the circuits will need to be created using SX1278 LoRa modules instead. These will connect the four Space Rocks via an additional transmitter, and based around this circuit:
This should guarantee that the Mozzi synths can play continuous tones rather than interrupted pulses. Although I’ll now need to find a way to fit 4+” 433MHz antennaes into the Space Rocks too.

Thinking about networks
Thinking about the network that connects the Space Rocks. Initially I started with the idea of all four rocks connecting equally, which isn’t really possible.

The network will be connected as a star formation, as shown here:

Similar to this diagram I drew a while back:

Ending up with something akin to this:

The co-ordinator would be loaded with one Mozzi synth, and the same synth is installed on the three nodes, with each node changing a different filter (attack, decay and sustain). The code for this looks like so:
Co-ordinator:
// include all libraries #include <XBee.h> #include <SoftwareSerial.h> #include <MozziGuts.h> #include <Oscil.h> #include <EventDelay.h> #include <ADSR.h> #include <tables/sin8192_int8.h> #include <mozzi_rand.h> #include <mozzi_midi.h> // uncomment the following line to enable debug output #define DEBUG // debug macros #ifdef DEBUG #define DEBUG_BEGIN(x) Serial.begin (x) #define DEBUG_PRINT(x) Serial.print (x) #define DEBUG_PRINTLN(x) Serial.println (x) #else #define DEBUG_BEGIN(x) #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #endif // Mozzi control rate in Hz #define CONTROL_RATE 128 Oscil <8192, AUDIO_RATE> aOscil(SIN8192_DATA); // for triggering the envelope EventDelay noteDelay; ADSR <CONTROL_RATE, CONTROL_RATE> envelope; boolean note_is_on = true; byte gain; // XBee packet reception timeout in ms #define XBEE_PACKET_TIMEOUT 250 // RSSI reading timeout in us #define RSSI_READING_TIMEOUT 100 // Arduino pin connections #define RSSI_PIN 5 #define XBEE_DOUT 6 #define XBEE_DIN 7 #define AUDIO_PIN 9 // freqency range bounds in Hz #define ATTACK_LOW 0 #define ATTACK_HIGH 1000 #define DECAY_LOW 0 #define DECAY_HIGH 1000 #define SUSTAIN_LOW 0 #define SUSTAIN_HIGH 1000 // RSSI range bounds int RSSI_LOW = 25; int RSSI_HIGH = 45; int RSSI_LOW_2 = 25; int RSSI_HIGH_2 = 45; int RSSI_LOW_3 = 25; int RSSI_HIGH_3 = 45; // number of samples for moving average filter #define SAMPLES 10 // XBee object instance XBee xbee = XBee(); // address of destination XBee (node address) XBeeAddress64 addr64 = XBeeAddress64(0x0013a200, 0x40A58A5D); // packet to be sent to coordinator uint8_t ping[] = {0xAA, 0xAA}; ZBTxRequest pingRequest = ZBTxRequest(addr64, ping, sizeof(ping)); XBeeResponse response = XBeeResponse(); // create reusable response objects for responses we expect to handle ZBRxResponse rx = ZBRxResponse(); // SoftwareSerial port SoftwareSerial beeSerial(XBEE_DOUT, XBEE_DIN); // Mozzi sine oscillator //Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA); // array to hold RSSI samples for filter float rssiSamples[SAMPLES]; // current position in array int pos = 0; float rssiAttack = 0; float rssiDecay = 0; float rssiSustain = 0; // moving average filter float movingAverage(float* arr, int newValue, int pos) { // update array arr[pos] = newValue; // get array total float total = 0; for (int i = 0; i < SAMPLES; i++) { total += arr[i]; } // get average float average = total / (float)SAMPLES; return (average); } void setup() { randSeed(); // fresh random noteDelay.set(2000); // begin Serial communication with PC DEBUG_BEGIN(9600); // set pin modes pinMode(RSSI_PIN, INPUT); pinMode(AUDIO_PIN, OUTPUT); // initialize array contents to 0 for (int i = 0; i < SAMPLES; i++) { rssiSamples[i] = 0; } // begin Serial communication with XBee beeSerial.begin(9600); // set XBee serial port xbee.setSerial(beeSerial); // start Mozzi startMozzi(CONTROL_RATE); // set output frequency to the lowest value //aSin.setFreq(FREQ_LOW); } unsigned int duration, attack, decay, sustain, release_ms; void updateControl() { // send packet to node xbee.send(pingRequest); // try to receive pong packet if (xbee.readPacket(XBEE_PACKET_TIMEOUT)) { if (xbee.getResponse().isAvailable()) { if (xbee.getResponse().getApiId() == 16) { xbee.getResponse().getZBRxResponse(rx); uint16_t sender = rx.getRemoteAddress16(); //DEBUG_PRINTLN(sender); if (sender == 18431) { // try to read raw RSSI value int rssiRaw = pulseIn(RSSI_PIN, HIGH, RSSI_READING_TIMEOUT); // check if the read was successful if (rssiRaw > 0) { if (rssiRaw <= RSSI_LOW) { RSSI_LOW = rssiRaw; } if (rssiRaw >= RSSI_HIGH) { RSSI_HIGH = rssiRaw; } // if so, map the raw RSSI value to frequency range int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, ATTACK_LOW, ATTACK_HIGH); // filter RSSI value rssiAttack = movingAverage(rssiSamples, rssiMapped, pos); // check array position overflow if (pos == SAMPLES - 1) { pos = 0; } else { pos++; } // print both values to PC for debugging /*DEBUG_PRINT(rssiRaw); DEBUG_PRINT('\t'); DEBUG_PRINT(rssiMapped); DEBUG_PRINT('\t'); DEBUG_PRINTLN(rssiFiltered);*/ } } else if (sender == 24063) { int rssiRaw = pulseIn(RSSI_PIN, HIGH, RSSI_READING_TIMEOUT); // check if the read was successful if (rssiRaw > 0) { if (rssiRaw <= RSSI_LOW_2) { RSSI_LOW_2 = rssiRaw; } if (rssiRaw >= RSSI_HIGH_2) { RSSI_HIGH_2 = rssiRaw; } // if so, map the raw RSSI value to frequency range int rssiMapped = map(rssiRaw, RSSI_LOW_2, RSSI_HIGH_2, DECAY_LOW, DECAY_HIGH); // filter RSSI value rssiDecay = movingAverage(rssiSamples, rssiMapped, pos); // check array position overflow if (pos == SAMPLES - 1) { pos = 0; } else { pos++; } // print both values to PC for debugging /*DEBUG_PRINT(rssiRaw); DEBUG_PRINT('\t'); DEBUG_PRINT(rssiMapped); DEBUG_PRINT('\t'); DEBUG_PRINTLN(rssiFiltered);*/ } } else if (sender == 20479) { int rssiRaw = pulseIn(RSSI_PIN, HIGH, RSSI_READING_TIMEOUT); // check if the read was successful if (rssiRaw > 0) { if (rssiRaw <= RSSI_LOW_3) { RSSI_LOW_3 = rssiRaw; } if (rssiRaw >= RSSI_HIGH_3) { RSSI_HIGH_3 = rssiRaw; } // if so, map the raw RSSI value to frequency range int rssiMapped = map(rssiRaw, RSSI_LOW_3, RSSI_HIGH_3, SUSTAIN_LOW, SUSTAIN_HIGH); // filter RSSI value rssiSustain = movingAverage(rssiSamples, rssiMapped, pos); // check array position overflow if (pos == SAMPLES - 1) { pos = 0; } else { pos++; } // print both values to PC for debugging /*DEBUG_PRINT(rssiRaw); DEBUG_PRINT('\t'); DEBUG_PRINT(rssiMapped); DEBUG_PRINT('\t'); DEBUG_PRINTLN(rssiFiltered);*/ } } } } } if (noteDelay.ready()) { // choose envelope levels byte attack_level = rand(128) + 127; byte decay_level = rand(255); envelope.setADLevels(attack_level, decay_level); // generate a random new adsr parameter value in milliseconds int r = rand(1000) - rand(1000); unsigned int new_value = abs(r); release_ms = new_value; // randomly choose one of the adsr parameters and set the new value /*switch (rand(4)){ case 0: attack = new_value; break; case 1: decay = new_value; break; case 2: sustain = new_value; break; case 3: release_ms = new_value; break; }*/ attack = rssiAttack; decay = rssiDecay; sustain = rssiSustain; envelope.setTimes(attack, decay, sustain, release_ms); envelope.noteOn(); byte midi_note = rand(107) + 20; aOscil.setFreq((int)mtof(midi_note)); DEBUG_PRINT("ATTACK - "); DEBUG_PRINTLN(attack); DEBUG_PRINT("DECAY - "); DEBUG_PRINTLN(decay); DEBUG_PRINT("SUSTAIN - "); DEBUG_PRINTLN(sustain); // print to screen /*Serial.print("midi_note\t"); Serial.println(midi_note); Serial.print("attack_level\t"); Serial.println(attack_level); Serial.print("decay_level\t"); Serial.println(decay_level); Serial.print("attack\t\t"); Serial.println(attack); Serial.print("decay\t\t"); Serial.println(decay); Serial.print("sustain\t\t"); Serial.println(sustain); Serial.print("release\t\t"); Serial.println(release_ms); Serial.println();*/ noteDelay.start(attack + decay + sustain + release_ms); } envelope.update(); gain = envelope.next(); // this is where it's different to an audio rate envelope } int updateAudio() { return (int) (gain * aOscil.next()) >> 8; } void loop() { audioHook(); }
And the three nodes:
// include all libraries #include <XBee.h> #include <SoftwareSerial.h> #include <MozziGuts.h> #include <Oscil.h> // oscillator template #include <tables/sin2048_int8.h> // sine table for oscillator #include <RollingAverage.h> #include <ControlDelay.h> // uncomment the following line to enable debug output #define DEBUG // debug macros #ifdef DEBUG #define DEBUG_BEGIN(x) Serial.begin (x) #define DEBUG_PRINT(x) Serial.print (x) #define DEBUG_PRINTLN(x) Serial.println (x) #else #define DEBUG_BEGIN(x) #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #endif unsigned int echo_cells_1 = 32; unsigned int echo_cells_2 = 60; unsigned int echo_cells_3 = 127; // Mozzi control rate in Hz ControlDelay <128, int> kDelay; // 2seconds // 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); // use: RollingAverage <number_type, how_many_to_average> myThing RollingAverage <int, 32> kAverage; // how_many_to_average has to be power of 2 int averaged; float rssiFiltered = 512; // XBee packet reception timeout in ms #define XBEE_PACKET_TIMEOUT 250 // RSSI reading timeout in us #define RSSI_READING_TIMEOUT 100 // Arduino pin connections #define RSSI_PIN 5 #define XBEE_DOUT 6 #define XBEE_DIN 7 #define AUDIO_PIN 9 // freqency range bounds in Hz #define FREQ_LOW 512 #define FREQ_HIGH 1023 // RSSI range bounds int RSSI_LOW = 11; int RSSI_HIGH = 20; // number of samples for moving average filter #define SAMPLES 10 // XBee object instance XBee xbee = XBee(); // address of destination XBee (coordinator address) XBeeAddress64 addr64 = XBeeAddress64(0x0013a200, 0x4176E94F); // packet to be sent to coordinator uint8_t pong[] = {0xAA, 0xAA}; ZBTxRequest pongRequest = ZBTxRequest(addr64, pong, sizeof(pong)); XBeeResponse response = XBeeResponse(); // create reusable response objects for responses we expect to handle ZBRxResponse rx = ZBRxResponse(); // SoftwareSerial port SoftwareSerial beeSerial(XBEE_DOUT, XBEE_DIN); // Mozzi sine oscillator Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA); // array to hold RSSI samples for filter float rssiSamples[SAMPLES]; // current position in array int pos = 0; // moving average filter float movingAverage(float* arr, int newValue, int pos) { // update array arr[pos] = newValue; // get array total float total = 0; for (int i = 0; i < SAMPLES; i++) { total += arr[i]; } // get average float average = total / (float)SAMPLES; return (average); } void setup() { // begin Serial communication with PC DEBUG_BEGIN(9600); // set pin modes pinMode(RSSI_PIN, INPUT); pinMode(AUDIO_PIN, OUTPUT); // initialize array contents to 0 for (int i = 0; i < SAMPLES; i++) { rssiSamples[i] = 0; } // begin Serial communication with XBee beeSerial.begin(9600); // set XBee serial port xbee.setSerial(beeSerial); // start Mozzi kDelay.set(echo_cells_1); startMozzi(); // set output frequency to the lowest value //aSin.setFreq(FREQ_LOW); } void updateControl() { // try to receive ping packet with 250 ms timeout if (xbee.readPacket(XBEE_PACKET_TIMEOUT)) { if (xbee.getResponse().isAvailable()) { //DEBUG_PRINTLN(xbee.getResponse().getApiId()); if (xbee.getResponse().getApiId() == 16) { xbee.getResponse().getZBRxResponse(rx); uint16_t sender = rx.getRemoteAddress16(); //DEBUG_PRINTLN(sender); } } // try to read raw RSSI value with 100 us timeout int rssiRaw = pulseIn(RSSI_PIN, LOW, RSSI_READING_TIMEOUT); // check if the read was successful if (rssiRaw > 0) { // if so, map the raw RSSI value to frequency range if (rssiRaw <= RSSI_LOW) { RSSI_LOW = rssiRaw; } if (rssiRaw >= RSSI_HIGH) { RSSI_HIGH = rssiRaw; } int rssiMapped = map(rssiRaw, RSSI_LOW, RSSI_HIGH, FREQ_LOW, FREQ_HIGH); // filter RSSI value rssiFiltered = movingAverage(rssiSamples, rssiMapped, pos); // check array position overflow if (pos == SAMPLES - 1) { pos = 0; } else { pos++; } // print both values to PC for debugging /*DEBUG_PRINT(rssiRaw); DEBUG_PRINT('\t'); DEBUG_PRINT(rssiMapped); DEBUG_PRINT('\t'); DEBUG_PRINTLN(rssiFiltered);*/ DEBUG_PRINT(rssiFiltered); DEBUG_PRINT(" - "); DEBUG_PRINT(RSSI_LOW); DEBUG_PRINT(" - "); DEBUG_PRINTLN(RSSI_HIGH); //rssiFiltered); // adjust sine wave frequency //aSin.setFreq(rssiFiltered); } } averaged = kAverage.next(rssiFiltered); aSin0.setFreq(averaged); aSin1.setFreq(kDelay.next(averaged)); aSin2.setFreq(kDelay.read(echo_cells_2)); aSin3.setFreq(kDelay.read(echo_cells_3)); // send packet to coordinator xbee.send(pongRequest); // delay(2000); } int updateAudio() { return 3 * ((int)aSin0.next() + aSin1.next() + (aSin2.next() >> 1) + (aSin3.next() >> 2)) >> 3; //return aSin.next(); } void loop() { pong[0] = 100 >> 8 & 0xff; pong[1] = 100 & 0xff; audioHook(); }
We Are Here exhibition catalogue preview
Here’s a sneak preview of the We Are Here exhibition catalogue.
Two more XBees talking
Space Rocks demonstration video
Installation proof-of-concept video.
Node and co-ordinator: two synths talking
Playing with Mozzi & XBee RSSI values to manipulate sound
Noise Generation. Playing with Mozzi & XBee RSSI values to manipulate sound. Note sound changes as RSSI feed connected to pin D5.