Final painted Space Rocks

Some photos and videos of the prepped & painted 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.

433MHz antennae
433MHz antennae

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.

Electric plinths
Electric plinths – equal connections

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

XBee network types
XBee network types

Similar to this diagram I drew a while back:

PG02, Cycle 2 - Behind the Scenes - initial individual concept diagram
PG02, Cycle 2 – Behind the Scenes – initial individual concept diagram

Ending up with something akin to this:

Sitraka's node and co-ordinator sketch
Sitraka’s node and co-ordinator sketch

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();
}

Circuitry: Nano, XBee and RSSI

Working on the circuitry for the inside of the Space Rocks. With lots of help from Sitraka Rakotoniaina, we managed to get the code to work using a Nano I/O shield to connect the XBee and the Nano microcontroller. With a little help from a soldering iron and a wire from the RSSI pin (pin 6). Now looking forward to experimenting with this on all 4 circuits.

Here’s the current code, which maps the RSSI signal (essentially the distance between each Space Rock) from the XBees to a value that can effect the Mozzi synthesiser within the code.

/*   ~ Simple Arduino - xBee Receiver sketch ~

  Read an PWM value from Arduino Transmitter to fade an LED
  The receiving message starts with '<' and closes with '>' symbol.
  
  Dev: Michalis Vasilakis // Date:2/3/2016 // Info: www.ardumotive.com // Licence: CC BY-NC-SA                    */

#include <MozziGuts.h>
#include <Oscil.h> // oscillator 
#include <tables/cos2048_int8.h> // table for Oscils to play
#include <AutoMap.h> // maps unpredictable inputs to a range
 
// desired carrier frequency max and min, for AutoMap
const int MIN_CARRIER_FREQ = 22;
const int MAX_CARRIER_FREQ = 440;

// desired intensity max and min, for AutoMap, note they're inverted for reverse dynamics
const int MIN_INTENSITY = 700;
const int MAX_INTENSITY = 10;

AutoMap kMapCarrierFreq(0,1023,MIN_CARRIER_FREQ,MAX_CARRIER_FREQ);
AutoMap kMapIntensity(0,1023,MIN_INTENSITY,MAX_INTENSITY);

const int KNOB_PIN = 0; // set the input for the knob to analog pin 0
const int LDR_PIN = 1; // set the input for the LDR to analog pin 1

Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aCarrier(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModulator(COS2048_DATA);

int mod_ratio = 3; // harmonics
long fm_intensity; // carries control info from updateControl() to updateAudio()

//Constants
const int ledPin = 3; //Led to Arduino pin 3 (PWM)
//Variables:
int ff ; //Value from pot
int pbPin = 7;

//Variables
bool started= false;//True: Message is strated
bool ended  = false;//True: Message is finished 
char incomingByte ; //Variable to store the incoming byte
char msg[3];    //Message - array from 0 to 2 (3 values - PWM - e.g. 240)
byte index;     //Index of array

int rssiDur = 0;
int rssiMapped =0;
bool calib = false;
bool calib_top = false;
int base_value= 0;
int top_value=0;

void setup() {
  //Start the serial communication
  Serial.begin(9600); //Baud rate must be the same as is on xBee module
  pinMode(ledPin, OUTPUT);
  pinMode(6, OUTPUT);
  calib = true;
  calib_top = true;
  startMozzi(); // :))
 pinMode(pbPin, INPUT_PULLUP);
}
void updateControl(){
  // read the knob
  //int knob_value = mozziAnalogRead(KNOB_PIN); // value is 0-1023
  rssiDur = pulseIn(5, LOW, 200);
  rssiMapped = map(rssiDur, 10, 40, 0, 1023);
  
  
  Serial.print("raw RSSI : ");
  Serial.println(rssiDur);
  Serial.print("RSSI mapped : ");
  Serial.println(rssiMapped);
  
  delay(100);
  
  // map the knob to carrier frequency
  int carrier_freq = kMapCarrierFreq(rssiMapped);
  
  //calculate the modulation frequency to stay in ratio
  int mod_freq = carrier_freq * mod_ratio;
  
  // set the FM oscillator frequencies to the calculated values
  aCarrier.setFreq(carrier_freq); 
  aModulator.setFreq(mod_freq);
  
  // read the light dependent resistor on the Analog input pin
  int light_level= mozziAnalogRead(LDR_PIN); // value is 0-1023

  fm_intensity = kMapIntensity(light_level);

}

 int updateAudio(){
  long modulation = fm_intensity * aModulator.next(); 
  return aCarrier.phMod(modulation); // phMod does the FM
}


void loop() {
  
  
  //delay(100);
 

  if(rssiDur != 0){
      digitalWrite(6, HIGH);
    }else{
      digitalWrite(6,LOW);
    }

    audioHook();

    //Serial.println(digitalRead(pbPin));
  //Read the analog value from pot and store it to "value" variable
   ff = digitalRead(pbPin);//analogRead(A0);

  //Map the analog value to pwm value
  //value = map (value, 0, 1023, 0, 255);
  //Send the message:
 Serial.print('<');  //Starting symbol
  Serial.print(ff);//Value from 0 to 255
  Serial.println('>');//Ending symbol
  
    
  /*while (Serial.available()>0){
    //Read the incoming byte
    incomingByte = Serial.read();
    //Start the message when the '<' symbol is received
    if(incomingByte == '<')
    {
     started = true;
     index = 0;
     msg[index] = '\0'; // Throw away any incomplete packet
   }
   //End the message when the '>' symbol is received
   else if(incomingByte == '>')
   {
     ended = true;
     break; // Done reading - exit from while loop!
   }
   //Read the message!
   else
   {
     if(index < 4) // Make sure there is room
     {
       msg[index] = incomingByte; // Add char to array
       index++;
       msg[index] = '\0'; // Add NULL to end
     }
   }
 }
 
 if(started && ended)
 {
   int value = atoi(msg);
   
   //analogWrite(ledPin, value);
   Serial.println(value); //Only for debugging
   if(calib_top){
   if(value == 0){
    top_value = rssiMapped;
    Serial.println(top_value);
    calib_top = false;
   }
   }
   
   index = 0;
   msg[index] = '\0';
   started = false;
   ended = false;
 }*/
}