Thinking about networks

Sitraka's node and co-ordinator sketch

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