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 Detuened 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");
}

Using my own samples with Mozzi

Have finally cracked getting my own samples into Mozzi. The sound clips need to be VERY short (around 0.5 seconds) or they are too big for the Nano. I also tried mono to see if that saved space, but then there was no sound at all. I really raised the volume of this sample and the clipping isn’t too noticeable compared to the previously-encoded version , as it clips because of the processing anyway.

Here’s the code:

/*  Example of playing a sampled sound,
    using Mozzi sonification library.
  
    Demonstrates one-shot samples scheduled
    with EventDelay.
  
    Circuit: Audio output on digital pin 9 on a Uno or similar, or
    DAC/A14 on Teensy 3.1, or 
    check the README or http://sensorium.github.com/Mozzi/
  
    Mozzi help/discussion/announcements:
    https://groups.google.com/forum/#!forum/mozzi-users
  
    Tim Barrass 2012, CC by-nc-sa.
*/

#include <MozziGuts.h>
#include <Sample.h> // Sample template
#include <samples/alienwave.h>
#include <EventDelay.h>

#define CONTROL_RATE 64

// use: Sample <table_size, update_rate> SampleName (wavetable)
Sample <alienwave_NUM_CELLS, AUDIO_RATE> aSample(alienwave_DATA);

// for scheduling sample start
EventDelay kTriggerDelay;

void setup(){
  startMozzi(CONTROL_RATE);
  aSample.setFreq((float) alienwave_SAMPLERATE / (float) alienwave_NUM_CELLS); // play at the speed it was recorded
  kTriggerDelay.set(1500); // 1500 msec countdown, within resolution of CONTROL_RATE
}


void updateControl(){
  if(kTriggerDelay.ready()){
    aSample.start();
    kTriggerDelay.start();
  }
}


int updateAudio(){
  return (int) aSample.next();
}


void loop(){
  audioHook();
}

With thanks to Tim Barass and his very helpful forum for help getting this working.

Decoding the project – languages, code and ciphers

Below is a gallery of images collected during research into ideas around a ‘universal’ language and codes, partly inspired by my exposure to cuneiform at the British Museum, and research into the Aricebo Message.

Firstly, hobo signs, or the ‘hobo code’.

“Sometime in the mid-to-late 1800s, poor men, typically those finding difficulty finding jobs, took to the rails. These migratory workers hopped onto trains, riding illegally (but for free) in freight cars, bouncing around the country looking for work. For reasons lost to history, these people became known as “hobos.” They developed a less than sterling reputation, disregarding the law and often running afoul of those who nonetheless offered them accommodation. Further, they often lived the life of loners — you stayed where you were until the work dried up, moving on and leaving any friends or fellow vagabonds behind.

But this life of solitude didn’t mean that you didn’t look out for your fellow hobo. In fact, these transient workers found a way to help each other out — a series of glyphs known as the “hobo code.

Hobos would write this code with chalk or coal to provide directions, information, and warnings to others in “the brotherhood”. A symbol would indicate “turn right here”, “beware of hostile railroad police”, “dangerous dog”, “food available here”.”

The first image shows some common hobo signs.

Next I also looked again at cuneiform, and how it evolved from pictographs to more abstract representation of characters. This also made me think about how language is evolving again to a more visual, and therefore universal, way of communicating (via memes, emojis and video-based storytelling). Particularly interesting was this article – Emojis Are Just the Next Stage of Language Evolution – which among other things discusses the concept that emojis are ideograms, representing ideas or concepts that are independent of a specific human language. An ideogram or ideograph (from Greek ἰδέα idéa “idea” and γράφω gráphō “to write”) is a graphic symbol that represents an idea or concept, independent of any particular language, and specific words or phrases. Some ideograms are comprehensible only by familiarity with prior convention; others convey their meaning through pictorial resemblance to a physical object, and thus may also be referred to as pictograms. Although the article does also make the point that emojis aren’t completely universal, as different cultures interpret the same symbol differently. For example, “there’s also plenty of room for cultural interpretation, even in these little icons. Japanese emoji users have a preference for those that convey feelings with eyes, whilst Western cultures favour those expressing emotions with the mouth. So although the language-neutral emoji may seem to be an international cipher, there’s room for cultural nuances.”

Cuneiform script  is one of the earliest systems of writing, was invented by the Sumerians. It is distinguished by its wedge-shaped marks on clay tablets, made by means of a blunt reed for a stylus. The name cuneiform itself simply means “wedge shaped”.

During the  critical thinking group workshop, we discussed braille as a form of non-visual, tactile communication. This started me thinking about the surface of the Space Rock objects being created, and whether messages could be coded into them using this form of language.

[Braille] characters have rectangular blocks called cells that have tiny bumps called raised dots. The number and arrangement of these dots distinguish one character from another. Since the various braille alphabets originated as transcription codes for printed writing, the mappings (sets of character designations) vary from language to language, and even within one; in English Braille there are three levels of encoding: Grade 1 – a letter-by-letter transcription used for basic literacy; Grade 2 – an addition of abbreviations and contractions; and Grade 3 – various non-standardised personal stenography.

Braille cells are not the only thing to appear in braille text. There may be embossed illustrations and graphs, with the lines either solid or made of series of dots, arrows, bullets that are larger than braille dots, etc. A full Braille cell includes six raised dots arranged in two columns, each having three dots. The dot positions are identified by numbers from one to six. 64 solutions are possible using one or more dots. A cell can be used to represent a letter, number, punctuation mark, or even a word.

In the face of screen reader software, braille usage has declined. However, because it teaches spelling and punctuation, braille education remains important for developing reading skills among blind and visually impaired children, and braille literacy correlates with higher employment rates.

This also made me think about morse code, a form of communication simple to create, being a series of repetitive tones with different spacing (silences) between.

Morse code is a method of transmitting text information as a series of on-off tones, lights, or clicks that can be directly understood by a skilled listener or observer without special equipment.

And here’s the morse code sent into space with the Voyager Golden Record. For some reason combined with the sound of ship horns. The morse code message spells out Per aspera ad astra, a popular Latin phrase meaning “through hardship to the stars”.

I also looked at the Enigma machine, used by the German army for coded communications during the Second World War. During my research, I found a way to make my own paper model to crack Enigma codes. Visually, the Zygalski sheets used in initial Enigma code-breaking are intriguing.

The method of Zygalski sheets was a cryptologic technique used by the Polish Cipher Bureau before and during World War II, and during the war also by British cryptologists at Bletchley Park, to decrypt messages enciphered on German Enigma machines.

The Zygalski-sheet apparatus takes its name from Polish Cipher Bureau mathematician–cryptologist Henryk Zygalski, who invented it about October 1938. Zygalski’s device comprised a set of 26 perforated sheets for each of the, initially, six possible sequences for inserting the three rotors into the Enigma machine’s scrambler.[1] Each sheet related to the starting position of the left (slowest-moving) rotor.

The 26 × 26 matrix represented the 676 possible starting positions of the middle and right rotors and was duplicated horizontally and vertically: a–z, a–y. The sheets were punched with holes in the positions that would allow a “female” to occur.

The first set was completed in late December 1939. On 28 December part of the second set was delivered to the Polish cryptologists,[7] who had by then escaped from German-overrun Poland to PC Bruno outside Paris, France. The remaining sheets were completed on 7 January 1940,[8] and were couriered by Alan Turing to France shortly thereafter.[7] “With their help,” writes Rejewski, “we continued solving Enigma daily keys.”[3] The sheets were used by the Poles to make the first wartime decryption of an Enigma message, on 17 January 1940.

In May 1940, the Germans once again completely changed the procedure for enciphering message keys (with the exception of a Norwegian network). As a result, Zygalski’s sheets were of no use…