paper speakers

here's some technical information on a collaboration with visual artist Jenny Michel. it's an sounding art installation that has previously been shown at galerie feldbuschwiesner and städtische galerie wolfsburg, and is now (sept2016) up and running at smac project space in berlin.

'traps', as the piece is called, consists of speakers made out of paper and enameled copper wire. the wire is embedded into the paper in the shape of a coil and two strong magnets are situated on the back. the paper is manually layered and filled with prints and later perforated with cutouts. behind the paper is also a small circuit with a microcontroller, an amplifier and an antenna - all hand soldered dead-bug style. power comes from a shared 5v wall adapter.

traps consists of around 30 individual speakers and they all react on each other via their antennas. they also pick up different electromagnetic fields - but we can't say in exactly which frequency range they are sensitive as the antennas are made with arbitrary lengths and shapes (they're also copper wire).

the sound is synthesized with the microcontroller (attiny45). loudness and quality varies depending on the shape of the speakers - they're all individual - but in general the resulting sounds are quite noisy and piercing. the code was written to create a lot of variation and not just sonify the raw antenna input. so it's more of an interpretation than a direct translation of trapped radio frequency signals to sound as we first had planned. we also wanted harsher electronic sounds as it both fits better with the visual impression and that that type of square wave audio work very well in these non-linear lo-fi speakers.

there are also two modes: if one quickly turn the power on-off-on, the installation will start in 'vernissage mode' and play more sound than if one just turn the power on as normal (look for the eeprom part in the code below to see how it works more in detail).

in total we built 50 circuits and many more paper speakers. though ~30 are enough to fill medium sized gallery.





here's the code...

//f.olofsson 2015-2016
//select ATtiny, 1MHz (internal)
//decreased durMax in two places from 80000 to 8000

#include <EEPROM.h>

int durPause;
unsigned long durMax;
byte pausePer;
void setup() {
  randomSeed(analogRead(3));
  pinMode(4, OUTPUT);
  byte magic = EEPROM.read(0);
  if (magic == 123) { //lots of sound - chaotic opening mode
    durPause = 15000;
    durMax = 8000;
    pausePer = 67;  //percentage
    for (byte i = 0; i < 3; i++) {  //beep three times at startup
      prgStatic(100);
      prgPause(100);
    }
  } else {  //default - soft gallery mode
    durPause = 30000;
    durMax = 8000;
    pausePer = 75;  //percentage
    EEPROM.write(0, 123);
    delay(3000);  //power on for <3sec & then next time mode 1
    prgStatic(1500);  //make a tone at startup to know all working
  }
  EEPROM.write(0, 255);
}
void loop() {
  prgNoise(analogRead(3));
  if (random(100) < pausePer) {
    prgPause(durPause);
  } else {
    unsigned long dur = random(durMax);
    switch (analogRead(3) % 7) {
      case 0:
        prgPause(dur);
        break;
      case 1:
        prgNoise(dur);
        break;
      case 2:
        prgNoise(dur);
        break;
      case 3:
        prgChunks(dur);
        break;
      case 4:
        prgChunks(dur);
        break;
      case 5:
        prgBitbang(dur);
        break;
      case 6:
        prgImpulses(dur);
        break;
    }
  }
}
void prgPause(unsigned long dur) {
  analogWrite(4, 0);
  pinMode(0, OUTPUT);
  digitalWrite(0, 0);
  delay(dur);
  pinMode(0, INPUT);
}
void prgStatic(int dur) {
  analogWrite(4, 127);
  delay(dur);
}
void prgNoise(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
  }
}
void prgNoise2(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
    delay(1);
  }
}
void prgChunks(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val);
  }
}
void prgChunks2(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val / 2);
    analogWrite(4, base);
    delay(val / 2);
  }
}
void prgBitbang(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3 >> 2);
    for (byte i = 0; i < 8; i++) {
      digitalWrite(4, val >> i & 1);
    }
  }
}
void prgImpulses(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    digitalWrite(4, 1);
    digitalWrite(4, 0);
    delay(val);
  }
}

note the use of the lm386 n-4. it is more powerful than the n1, n3 variants.

stine

next week in Bucharest we'll be setting up the subjective frequency transducer for the third time. i described the sound/vibration generating part of this system before but didn't write much about how the controllers work.

so for each sound channel (i.e. each bass transducer) there's a wireless controller that enables the audience to set their preferred frequency. technically it's done with a rotary encoder, a esp8266 wifi module, a mega168 and a big 7-segment lcd. the circuit runs off two AAA batteries.

when someone touches the rotary encoder, the circuit wakes up and starts sending osc messages to a laptop running supercollider. supercollider receives the values, starts playing an oscillator and sends the sound to the corresponding audio channel. when done, sc fades out the oscillator and sends an off message to the circuit and the controller goes back to sleep mode.

i spent quite some time optimizing the microcontroller (atmega168) code. it was hard to both reduce power consumption and still being able to quickly wake up and react on user input as well as on incoming osc messages. it's a common problem with battery powered radio devices.

also getting the esp8266 to handle osc messages was a pain. here and here are some more info and simplified versions of that.

in the end, the code for talking to these circuits in supercollider looked like this:

//sc example: sending. turn off circuit 3 and set it back to initial frequency
~encode= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
~encode.value(3, 0, 0, ~initFreq);
//sc example: receiving. decoding data from the four esp8266
OSCdef(\sti, {|msg, time, addr|
        var id= msg[1]>>24;
        var onoff= (msg[1]>>16)&255;
        var freq= (msg[1]&65280)+(msg[1]&255);
        [\id, id, \onoff, onoff, \freq, freq].post;
}, \sti);

the microcontroller code could still be improved. i'd like it to wake up on both wdt and uart. at the moment the circuit is drawing 22mA average in off+idle state, and 33mA average with display set to '20' which is okey but not optimal. and when sending osc you get current spikes of a few hundred milliamps but there's no way around that.

//f.olofsson 2015-2016

#define ID 3
#define FREQ 0 //start frequency
#define FREQ_MIN 0
#define FREQ_MAX 9999
#define WLAN_SSID "MYNETWORK"
#define WLAN_PASS "MYPASSWORD"
#define WLAN_ADDR "192.168.43.99" //laptop static ip
#define WLAN_PORT 1112
String tag = "/tap"; //incomming osc addy

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

#include <Encoder.h>

Encoder myEnc(3, 2);
float freq = FREQ;  //starting frequency
int freqLast = -999;

byte state = 0;
int enc = 0;
byte dig = 0;
byte cnt = 0;
boolean resp;

uint8_t buf[16];  //osc message

void setup() {
  pinMode(2, INPUT_PULLUP);  //encoder a
  pinMode(3, INPUT_PULLUP);  //encoder b
  pinMode(4, INPUT_PULLUP);  //encoder button
  DDRB = B11111111;  //segments
  DDRC = B00001111;  //digits selector

  //--set up wifi
  Serial.begin(115200);
  Serial.setTimeout(10000);
  resp = Serial.find("ready\r\n");
  progressDot(1);
  Serial.println("AT+CWMODE=1");
  resp = Serial.find("OK\r\n");
  progressDot(2);
  do {
    Serial.print("AT+CWJAP=\"");
    Serial.print(WLAN_SSID);
    Serial.print("\",\"");
    Serial.print(WLAN_PASS);
    Serial.println("\"");
    resp = Serial.find("OK\r\n");
  } while (!resp);
  progressDot(3);
  Serial.println("AT+CIPMUX=1");
  resp = Serial.find("OK\r\n");
  progressDot(4);
  Serial.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial.print(WLAN_ADDR);
  Serial.print("\",57120,");  //supercollider default port
  Serial.print(WLAN_PORT);
  Serial.println(",0");
  resp = Serial.find("OK\r\n");
  Serial.setTimeout(1000);
  displayClear();

  //--osc message
  buf[0] = 47;   // /
  buf[1] = 115;  // s
  buf[2] = 116;  // t
  buf[3] = 105;  // i
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;
  buf[8] = 44;   // ,
  buf[9] = 105;  // i
  buf[10] = 0;
  buf[11] = 0;
  buf[12] = ID;  // a high   (id)
  buf[13] = state; // a low  (onoff)
  buf[14] = 0;   // b high   (freq hi)
  buf[15] = 0;   // b low    (freq lo)

  //--timer
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 32768;  //62.5Hz display updaterate
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS10);  //prescaler divide by 1
  TIMSK1 |= (1 << OCIE1A);
  interrupts();

  //--sleep
  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = 1 << WDP0 | 1 << WDP1;
  WDTCSR |= _BV(WDIE);
}

volatile int f_wdt = 1; //watchdog wakeup
ISR(WDT_vect) {
  if (f_wdt == 0) {
    f_wdt = 1;
  }
}
void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_IDLE);
  sleep_enable();
  sleep_mode();
  sleep_disable();
  power_all_enable();
}

ISR(TIMER1_COMPA_vect) {  //update display periodically
  if (state == 2) {
    displayFreq();
  }
}

void sendOsc() {
  buf[13] = state;
  buf[14] = int(freq) >> 8;
  buf[15] = int(freq) & 255;
  Serial.println("AT+CIPSEND=4,16");
  Serial.find(">");
  Serial.write(buf, sizeof(buf));
  resp = Serial.find("OK\r\n");
}

void loop() {
  dig = 1 - ((PIND >> 4) & 1);  //encoder momentary button
  switch (state) {
    case 2:  //running (display on)
      enc = myEnc.read();
      if (enc != 0) {
        float incStep = enc / 2.0;
        myEnc.write(0);
        freq = max(FREQ_MIN, min(FREQ_MAX, freq + incStep));
        if (int(freq) != freqLast) {
          sendOsc();
          freqLast = int(freq);
        }
      }
      if (dig == 1) {  //TODO: or timeout here?
        state = 3;
      }
      break;
    case 0:  //sleeping (display off)
      f_wdt = 0;
      enterSleep();
      enc = myEnc.read();
      if ((dig == 1) || (enc != 0)) {
        state = 1;
        freq = FREQ; //reset
        sendOsc();
      }
      break;
    case 3:  //turning off when button released
      displayClear();
      if (dig == 0) {
        state = 0;
        sendOsc();
      }
      break;
    case 1:  //turning on when button released
      if ((dig == 0) || (enc != 0)) {
        state = 2;
        myEnc.write(0);
      }
  }

  //--receive osc
  while (Serial.available()) {
    String abc = Serial.readStringUntil('\n');
    if (abc.startsWith("+IPD,4,16:" + tag)) {
      //if(abc[22]==ID) { //optional filter by device ID
      if (abc[23] == 0) {
        displayClear();
        state = 0;
      } else {
        state = 2;
        myEnc.write(0);
      }
      freq = (abc[24] << 8) + abc[25];
    }
  }
}
void displayClear() {
  PORTC = B00001111;
  PORTB = B00000000;
}
void progressDot(byte index) {
  setChr(255, true);
  selDig(index);
}
void displayFreq() {
  int val = freq; //cuts off fraction
  switch (cnt) {
    case 0:
      if (val > 999) {
        setChr((val % 10000) / 1000, false);
      } else {
        setChr(255, false);
      }
      selDig(1);
      cnt = 1;
      break;
    case 1:
      if (val > 99) {
        setChr((val % 1000) / 100, false);
      } else {
        setChr(255, false);
      }
      selDig(2);
      cnt = 2;
      break;
    case 2:
      if (val > 9) {
        setChr((val % 100) / 10, false);
      } else {
        setChr(255, false);
      }
      selDig(3);
      cnt = 3;
      break;
    case 3:
      setChr(val % 10, false);
      selDig(4);
      cnt = 0;
  }
}

void selDig(byte index) {
  switch (index) {
    case 1:
      PORTC = B00001110;
      break;
    case 2:
      PORTC = B00001101;
      break;
    case 3:
      PORTC = B00001011;
      break;
    case 4:
      PORTC = B00000111;
  }
}

void setChr(byte chr, bool dot) {
  switch (chr) {
    case 255:  //clear
      PORTB = B00000000;
      break;
    case 0:
      PORTB = B11111100;
      break;
    case 1:
      PORTB = B01100000;
      break;
    case 2:
      PORTB = B11011010;
      break;
    case 3:
      PORTB = B11110010;
      break;
    case 4:
      PORTB = B01100110;
      break;
    case 5:
      PORTB = B10110110;
      break;
    case 6:
      PORTB = B10111110;
      break;
    case 7:
      PORTB = B11100000;
      break;
    case 8:
      PORTB = B11111110;
      break;
    case 9:
      PORTB = B11100110;
      break;
      /*
        case 10:  //A
        case 11:  //B
        case 12:  //C
        case 13:  //D
        case 14:  //E
        case 15:  //F
        case 16:  //G
        case 17:  //H
      */

  }
  if (dot) {
    PORTB |= B00000001;
  }
}

tamas

here another project. it's a box for replacing some old midi hardware. it can read 16 analog sensors, 16 digital sensors and control 16 leds via pwm. basically it is just a teensy plus a pwm breakout board from sparkfun. to easily access all the pins on the teensy, i also used the excellent Teensy 3.2 Breakout Board R3 by Daniel Gilbert.

attached are schematics, teensy code, and supercollider and maxmsp code for dealing with the serial communication.

tamas00

tamas01

AttachmentSize
Binary Data TamasSerial.sc1.84 KB
Binary Data tamas_test.maxpat84.75 KB
Image icon tamas_teensy_schematics.png184.1 KB
Binary Data tamas_teensy.ino2.07 KB

mp3 speakers

for an upcoming installation we need a few speakers hanging on the walls playing back sound files in a loop. i found the dfplayer mini to work well. it's a small and cheap board that can play soundfiles from a sd-card and it has a built-in 3W amplifier. with a decent 5v batterybank (here 4400mAh) it can run for ~4 days continuously.

mp3speakers

example class

here is a very basic supercollider class i wrote as an example of why and how to write classes.

//save this as MySequencerTrack.sc in your extensions folder and recompile
MySequencerTrack {
        var <steps;
        var <>array;
        *new {|steps= 16|
                ^super.newCopyArgs(steps).init;
        }
        init {
                array= Array.fill(4, {Array.fill(steps, 0)});  //4 here because of the four params: amp, freq, mod, pan
        }

        //array get/set
        amps {^array[0]}
        amps_ {|arr| array[0]= arr}
        freqs {^array[1]}
        freqs_ {|arr| array[1]= arr}
        mods {^array[2]}
        mods_ {|arr| array[2]= arr}
        pans {^array[3]}
        pans_ {|arr| array[3]= arr}

        //single value get/set
        amp {|index| ^array[0][index]}
        amp_ {|index, val| array[0].put(index, val)}
        freq {|index| ^array[1][index]}
        freq_ {|index, val| array[1].put(index, val)}
        mod {|index| ^array[2][index]}
        mod_ {|index, val| array[2].put(index, val)}
        pan {|index| ^array[3][index]}
        pan_ {|index, val| array[3].put(index, val)}
}

and here is some test code for it...

a= MySequencerTrack.new;
a.freqs
a.steps
a.amps= {1.0.rand}!a.steps
a.amps
a.amp(10)  //first one
//and then the same for a.freqs etc

(
s.waitForBoot{
        a.amps= {[0.5, 0.25, 0, 0, 0].choose}!a.steps;
        a.freqs= {[60, 66, 70].choose.midicps}!a.steps;
        a.mods= {1.0.linrand}!a.steps;
        a.pans= {1.0.rand2}!a.steps;
        b= {|freq= 400, amp= 0, mod= 0, pan= 0| Pan2.ar(SinOsc.ar(freq, SinOsc.ar*mod, amp), pan)}.play;
        s.sync;
        r= Routine.run({
                inf.do{
                        a.steps.do{|i|
                                b.set(\freq, a.freq(i), \amp, a.amp(i), \mod, a.mod(i), \pan, a.pan(i));
                                0.125.wait;
                        };
                };
        });
};
)

//and while it is running...  replace freqs
a.freqs= {[52, 66, 70, 80].choose.midicps}!a.steps;
a.amp_(0, 1)  //set first amp to 1.0
a.amps
a.amps= a.amps.rotate(-1)  //rotate amps left
a.freqs= a.freqs.rotate(2) //rotate freqs right
a.freqs= a.freqs+10  //transpose up
a.freqs= a.freqs.scramble //reorder

r.stop
b.free

//now the important thing and why classes are good...
//here we make 10 tracks all 32 values in length...
~mysequencer= {MySequencerTrack(32)}!10;
~mysequencer[0].amps  //amplitudes for first track
~mysequencer[0].amps= {1.0.rand}!a.steps
~mysequencer[0].amps
~mysequencer[0].amp(0)  //first one

(
s.waitForBoot{
10.do{|i|
        var steps= ~mysequencer[i].steps;
        ~mysequencer[i].amps= {[0.5, 0.25, 0, 0, 0, 0, 0, 0].choose}!steps;
        ~mysequencer[i].freqs= {[60, 66, 70, 90].choose.midicps}!steps;
        ~mysequencer[i].mods= {1.0.linrand}!steps;
        ~mysequencer[i].pans= {1.0.rand2}!steps;
};
~synths= {
        {|freq= 400, amp= 0, mod= 0, pan= 0| Pan2.ar(SinOsc.ar(freq, SinOsc.ar*mod, amp/2), pan)}.play;
}!10;
        s.sync;
        r= ~mysequencer.collect{|trk, i|
                var syn= ~synths[i];
                Routine.run({
                        inf.do{|j|
                                var x= j%trk.steps;
                                syn.set(\freq, trk.freq(x), \amp, trk.amp(x), \mod, trk.mod(x), \pan, trk.pan(x));
                                0.125.wait;
                        };
                });
        };
};
)

//while the above is running
~mysequencer[0..7].do{|trk| trk.amps= 0!32}  //mute all tracks except 8&9
~mysequencer[8..9].do{|trk| trk.mods= {4.0.linrand}!32}  //more fmod on tracks 8&9
~mysequencer[0..1].do{|trk, i| trk.freqs= {i+1*150}!32; trk.mods= {0}!32; trk.amps= {|j| [0, 1].wrapAt(i+j)}!32} //renew and add tracks 0&1
~mysequencer[8..9].do{|trk| trk.amps= {0.75.linrand*[1, 0].choose}!32}  //new amps for track 8&9
~mysequencer[8..9].do{|trk| trk.freqs= {8.linrand+1*150}!32}  //new freqs for track 8&9
~mysequencer[6].amps= {0.4}!9++({0}!23); ~mysequencer[6].freqs= {|i| 2**i*50+50}!32; //add arpeggio on track 6

~mysequencer.do{|x| x.freqs= x.freqs*1.1} //transpose all frequencies
~mysequencer.do{|x| x.amps= x.amps.rotate(-3)} //rotate all amps

r.do{|x| x.stop}
~synths.do{|x| x.free}

esp8266 opensound control teensy

today i ported my opensound control esp8266 example for arduino to run on a teensy 3.1.

the version below is a bit simpler but still works the same. it is just to show how to send and receive osc messages directly in sc or max.

teensy code:

//f0 150705 - modified for teensy3 160430
//sending and receiving udp osc with an esp8266
//for teensy + esp8266 with firmare 0.9.5.2
#define WLAN_SSID  "ssid"
#define WLAN_PASS  "pass"
#define WLAN_ADDR  "192.168.1.3" //laptop running sc EDIT
#define ADDR "/tap" //incoming osc addy
#define PORT  1112  //incoming osc port
uint8_t buf[16];
char indata[12];
char inbuffer[256];
char OKrn[] = "OK\r\n";
byte wait_for_esp_response(int timeout, char* term = OKrn) {
  unsigned long t = millis();
  bool found = false;
  int i = 0;
  int len = strlen(term);
  while (millis() < t + timeout) {
    if (Serial1.available()) {
      inbuffer[i++] = Serial1.read();
      if (i >= len) {
        if (strncmp(inbuffer + i - len, term, len) == 0) {
          found = true;
          break;
        }
      }
    }
  }
  inbuffer[i] = 0;
  return found;
}
void setup() {
  //--osc message
  buf[0] = 47;   // /
  buf[1] = 115;  // s
  buf[2] = 116;  // t
  buf[3] = 105;  // i
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;
  buf[8] = 44;   // ,
  buf[9] = 105;  // i
  buf[10] = 0;
  buf[11] = 0;
  buf[12] = 4;   // a
  buf[13] = 3;   // b
  buf[14] = 2;   // c
  buf[15] = 0;   // d
  pinMode(23, OUTPUT);
  Serial.begin(115200);   //usb serial for feedback
  delay(400);
  Serial1.begin(115200);  //teensy hardware pins 0 and 1
  Serial.println("starting");
  Serial.print("hard reset...");
  pinMode(4, OUTPUT);
  delay(10);
  pinMode(4, INPUT);
  Serial.print("ready...");
  boolean resp = wait_for_esp_response(1000, "ready\r\n");
  Serial.println(resp);
  Serial.print("mode1...");
  Serial1.println("AT+CWMODE=1");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.print("connecting...");
  do {
    Serial1.print("AT+CWJAP=\"");
    Serial1.print(WLAN_SSID);
    Serial1.print("\",\"");
    Serial1.print(WLAN_PASS);
    Serial1.println("\"");
    resp = wait_for_esp_response(3000);
    Serial.print(resp);
  } while (!resp);
  Serial.print("\nmux1...");
  Serial1.println("AT+CIPMUX=1");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.print("udp...");
  Serial1.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial1.print(WLAN_ADDR);
  Serial1.print("\",57120,");
  Serial1.print(PORT);
  Serial1.println(",0");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.println("setup done");
}
void loop() {
  if (wait_for_esp_response(1000, "\r\n+IPD,4,16:")) {
    if (wait_for_esp_response(1000, ADDR)) {
      Serial1.readBytes(indata, 12);
      if (wait_for_esp_response(1000)) {
        buf[12] = indata[8] + 1; //add one to incomming values
        buf[13] = indata[9] + 1;
        buf[14] = indata[10] + 1;
        buf[15] = indata[11] + 1;
        Serial.println(int(indata[8]));
        Serial.println(int(indata[9]));
        Serial.println(int(indata[10]));
        Serial.println(int(indata[11]));
        Serial1.println("AT+CIPSEND=4,16");
        if (wait_for_esp_response(1000, "> ")) {
          Serial1.write(buf, sizeof(buf));
          if (wait_for_esp_response(1000)) {
            Serial.println("reply sent!");
          }
        }
      }
    }
  }
}

supercollider code:

(
//--call&response
var send= 0, last= Main.elapsedTime;
OSCdef(\sti, {|msg, time, addr|
        //should receive the values you sent +1
        ([msg[1]>>24, (msg[1]>>16)&255, (msg[1]>>8)&255, msg[1]&255]).post;
        (" % sec since last: %, % sec since sent").format(addr, time-last, time-send).postln;
        last= time;
}, \sti);
n= NetAddr("192.168.1.4", 1112); //esp8266 ip address EDIT
f= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
r= Routine.run({
        inf.do{|i|
                n.sendMsg(\tap, f.value(4, 3, i.asInteger%256, 1));
                send= Main.elapsedTime;
                0.5.wait;
        };
});
)
AttachmentSize
Package icon maxmsp example patch1.56 KB

greenpeace

here's a handy class for supercollider. it's an audio clipping detector loosely based on Batuhan Bozkurt's StageLimiter.

to install it download and extract the zip file into your supercollider extensions folder. then recompile and type...

GreenPeace.activate

now as soon as you play sound that's clipping (i.e. exceeds -1.0 or 1.0), the class will warn you.

to turn it off type...

GreenPeace.deactivate
AttachmentSize
Package icon GreenPeace.zip3.67 KB

Pages

Subscribe to f0blog RSS