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 the network that connects the Space Rocks. Initially I started with the idea of all four rocks connecting equally, which isn’t really possible.
Electric plinths – equal connections
The network will be connected as a star formation, as shown here:
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#defineDEBUG// debug macros#ifdefDEBUG#defineDEBUG_BEGIN(x)Serial.begin(x)#defineDEBUG_PRINT(x)Serial.print(x)#defineDEBUG_PRINTLN(x)Serial.println(x)#else#defineDEBUG_BEGIN(x)#defineDEBUG_PRINT(x)#defineDEBUG_PRINTLN(x)#endif// Mozzi control rate in Hz#defineCONTROL_RATE128Oscil<8192,AUDIO_RATE>aOscil(SIN8192_DATA);// for triggering the envelopeEventDelaynoteDelay;ADSR<CONTROL_RATE,CONTROL_RATE>envelope;booleannote_is_on=true;bytegain;// XBee packet reception timeout in ms#defineXBEE_PACKET_TIMEOUT250// RSSI reading timeout in us#defineRSSI_READING_TIMEOUT100// Arduino pin connections#defineRSSI_PIN5#defineXBEE_DOUT6#defineXBEE_DIN7#defineAUDIO_PIN9// freqency range bounds in Hz#defineATTACK_LOW0#defineATTACK_HIGH1000#defineDECAY_LOW0#defineDECAY_HIGH1000#defineSUSTAIN_LOW0#defineSUSTAIN_HIGH1000// RSSI range boundsintRSSI_LOW=25;intRSSI_HIGH=45;intRSSI_LOW_2=25;intRSSI_HIGH_2=45;intRSSI_LOW_3=25;intRSSI_HIGH_3=45;// number of samples for moving average filter#defineSAMPLES10// XBee object instanceXBeexbee=XBee();// address of destination XBee (node address)XBeeAddress64addr64=XBeeAddress64(0x0013a200,0x40A58A5D);// packet to be sent to coordinatoruint8_tping[]={0xAA,0xAA};ZBTxRequestpingRequest=ZBTxRequest(addr64,ping,sizeof(ping));XBeeResponseresponse=XBeeResponse();// create reusable response objects for responses we expect to handleZBRxResponserx=ZBRxResponse();// SoftwareSerial portSoftwareSerialbeeSerial(XBEE_DOUT,XBEE_DIN);// Mozzi sine oscillator//Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);// array to hold RSSI samples for filterfloatrssiSamples[SAMPLES];// current position in arrayintpos=0;floatrssiAttack=0;floatrssiDecay=0;floatrssiSustain=0;// moving average filterfloatmovingAverage(float*arr,intnewValue,intpos){// update arrayarr[pos]=newValue;// get array totalfloattotal=0;for(inti=0;i<SAMPLES;i++){total+=arr[i];}// get averagefloataverage=total/(float)SAMPLES;return(average);}voidsetup(){randSeed();// fresh randomnoteDelay.set(2000);// begin Serial communication with PCDEBUG_BEGIN(9600);// set pin modespinMode(RSSI_PIN,INPUT);pinMode(AUDIO_PIN,OUTPUT);// initialize array contents to 0for(inti=0;i<SAMPLES;i++){rssiSamples[i]=0;}// begin Serial communication with XBeebeeSerial.begin(9600);// set XBee serial portxbee.setSerial(beeSerial);// start MozzistartMozzi(CONTROL_RATE);// set output frequency to the lowest value//aSin.setFreq(FREQ_LOW);}unsignedintduration,attack,decay,sustain,release_ms;voidupdateControl(){// send packet to nodexbee.send(pingRequest);// try to receive pong packetif(xbee.readPacket(XBEE_PACKET_TIMEOUT)){if(xbee.getResponse().isAvailable()){if(xbee.getResponse().getApiId()==16){xbee.getResponse().getZBRxResponse(rx);uint16_tsender=rx.getRemoteAddress16();//DEBUG_PRINTLN(sender);if(sender==18431){// try to read raw RSSI valueintrssiRaw=pulseIn(RSSI_PIN,HIGH,RSSI_READING_TIMEOUT);// check if the read was successfulif(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 rangeintrssiMapped=map(rssiRaw,RSSI_LOW,RSSI_HIGH,ATTACK_LOW,ATTACK_HIGH);// filter RSSI valuerssiAttack=movingAverage(rssiSamples,rssiMapped,pos);// check array position overflowif(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);*/}}elseif(sender==24063){intrssiRaw=pulseIn(RSSI_PIN,HIGH,RSSI_READING_TIMEOUT);// check if the read was successfulif(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 rangeintrssiMapped=map(rssiRaw,RSSI_LOW_2,RSSI_HIGH_2,DECAY_LOW,DECAY_HIGH);// filter RSSI valuerssiDecay=movingAverage(rssiSamples,rssiMapped,pos);// check array position overflowif(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);*/}}elseif(sender==20479){intrssiRaw=pulseIn(RSSI_PIN,HIGH,RSSI_READING_TIMEOUT);// check if the read was successfulif(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 rangeintrssiMapped=map(rssiRaw,RSSI_LOW_3,RSSI_HIGH_3,SUSTAIN_LOW,SUSTAIN_HIGH);// filter RSSI valuerssiSustain=movingAverage(rssiSamples,rssiMapped,pos);// check array position overflowif(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 levelsbyteattack_level=rand(128)+127;bytedecay_level=rand(255);envelope.setADLevels(attack_level,decay_level);// generate a random new adsr parameter value in millisecondsintr=rand(1000)-rand(1000);unsignedintnew_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();bytemidi_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}intupdateAudio(){return(int)(gain*aOscil.next())>>8;}voidloop(){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#defineDEBUG// debug macros#ifdefDEBUG#defineDEBUG_BEGIN(x)Serial.begin(x)#defineDEBUG_PRINT(x)Serial.print(x)#defineDEBUG_PRINTLN(x)Serial.println(x)#else#defineDEBUG_BEGIN(x)#defineDEBUG_PRINT(x)#defineDEBUG_PRINTLN(x)#endifunsignedintecho_cells_1=32;unsignedintecho_cells_2=60;unsignedintecho_cells_3=127;// Mozzi control rate in HzControlDelay<128,int>kDelay;// 2seconds// oscils to compare bumpy to averaged control inputOscil<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> myThingRollingAverage<int,32>kAverage;// how_many_to_average has to be power of 2intaveraged;floatrssiFiltered=512;// XBee packet reception timeout in ms#defineXBEE_PACKET_TIMEOUT250// RSSI reading timeout in us#defineRSSI_READING_TIMEOUT100// Arduino pin connections#defineRSSI_PIN5#defineXBEE_DOUT6#defineXBEE_DIN7#defineAUDIO_PIN9// freqency range bounds in Hz#defineFREQ_LOW512#defineFREQ_HIGH1023// RSSI range boundsintRSSI_LOW=11;intRSSI_HIGH=20;// number of samples for moving average filter#defineSAMPLES10// XBee object instanceXBeexbee=XBee();// address of destination XBee (coordinator address)XBeeAddress64addr64=XBeeAddress64(0x0013a200,0x4176E94F);// packet to be sent to coordinatoruint8_tpong[]={0xAA,0xAA};ZBTxRequestpongRequest=ZBTxRequest(addr64,pong,sizeof(pong));XBeeResponseresponse=XBeeResponse();// create reusable response objects for responses we expect to handleZBRxResponserx=ZBRxResponse();// SoftwareSerial portSoftwareSerialbeeSerial(XBEE_DOUT,XBEE_DIN);// Mozzi sine oscillatorOscil<SIN2048_NUM_CELLS,AUDIO_RATE>aSin(SIN2048_DATA);// array to hold RSSI samples for filterfloatrssiSamples[SAMPLES];// current position in arrayintpos=0;// moving average filterfloatmovingAverage(float*arr,intnewValue,intpos){// update arrayarr[pos]=newValue;// get array totalfloattotal=0;for(inti=0;i<SAMPLES;i++){total+=arr[i];}// get averagefloataverage=total/(float)SAMPLES;return(average);}voidsetup(){// begin Serial communication with PCDEBUG_BEGIN(9600);// set pin modespinMode(RSSI_PIN,INPUT);pinMode(AUDIO_PIN,OUTPUT);// initialize array contents to 0for(inti=0;i<SAMPLES;i++){rssiSamples[i]=0;}// begin Serial communication with XBeebeeSerial.begin(9600);// set XBee serial portxbee.setSerial(beeSerial);// start MozzikDelay.set(echo_cells_1);startMozzi();// set output frequency to the lowest value//aSin.setFreq(FREQ_LOW);}voidupdateControl(){// try to receive ping packet with 250 ms timeoutif(xbee.readPacket(XBEE_PACKET_TIMEOUT)){if(xbee.getResponse().isAvailable()){//DEBUG_PRINTLN(xbee.getResponse().getApiId());if(xbee.getResponse().getApiId()==16){xbee.getResponse().getZBRxResponse(rx);uint16_tsender=rx.getRemoteAddress16();//DEBUG_PRINTLN(sender);}}// try to read raw RSSI value with 100 us timeoutintrssiRaw=pulseIn(RSSI_PIN,LOW,RSSI_READING_TIMEOUT);// check if the read was successfulif(rssiRaw>0){// if so, map the raw RSSI value to frequency rangeif(rssiRaw<=RSSI_LOW){RSSI_LOW=rssiRaw;}if(rssiRaw>=RSSI_HIGH){RSSI_HIGH=rssiRaw;}intrssiMapped=map(rssiRaw,RSSI_LOW,RSSI_HIGH,FREQ_LOW,FREQ_HIGH);// filter RSSI valuerssiFiltered=movingAverage(rssiSamples,rssiMapped,pos);// check array position overflowif(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 coordinatorxbee.send(pongRequest);// delay(2000);}intupdateAudio(){return3*((int)aSin0.next()+aSin1.next()+(aSin2.next()>>1)+(aSin3.next()>>2))>>3;//return aSin.next();}voidloop(){pong[0]=100>>8&0xff;pong[1]=100&0xff;audioHook();}
Managed to get the XBees talking to each other, following these steps. The video shows two completely unconnnected Arduino circuits. The one on the right (coordinator) sends the reading from the potentiometer to fade the light on the board on the left (receiver).
A rough guide to how all of the required electronics (XBee, Arduino Nano, speaker, sensor and – hopefully – a USB power bank) might fit into the four Space Rock shapes. All items drawn to scale.
Space Rock circuitSpace Rock profiles with space for circuit and power bank