2 3 4 5 6 7 last»

solar cell with loudspeaker

2020-11-24 16:52 other

Here is a simple and useful tool that can be built out of a small solar cell and an active speaker (headphones also work). This tool translates light into audio and will allow you to hear how much certain LED or fluorescent lamps flicker. It can be used to test and figure out the quality of LED lamps, learn about PWM dimming and also be a fun way of generating sounds.

I forgot where I learned this trick - it might have been on electronupdate's YouTube channel.

My solar cell came from a broken calculator and the loudspeaker (roxcore) I had since before. The speaker is active i.e. it includes an amplifier and a battery. I simply glued a 3.5mm audio connector to the back of the solar cell and soldered some wires so that the + and - from the cell directly connect to the TIP and GND of the 3.5mm audio jack. One could also add a capacitor in series to remove the DC offset, but I trust that the speaker has one built-in.

Note: check that your solar panel is not generating too high voltage. Mine outputs ~3V maximum and that is okay. Bigger panels usually output 6V or 12V and that might damage your speaker.

The resulting sound is most often a 50Hz hum, but it can also be much more complex and interesting with harmonics, noises, chirps and clicks. The worse LED or fluorescent light fixture the better the sound. Old tungsten and halogen lamps produce a constant voltage (DC) so the result is only silence.

solar cell and loudspeaker 1

solar cell and loudspeaker 2


f0videoplayerOsc

2020-11-15 14:42 visuals

Making use of some old original Raspberry Pi Model A...

Despite having very little RAM (256MB) the Raspberry Pi Model A from 2012 can play video and sound in high resolution via HDMI. So here is how I set up four of them as independent remote-controlled video players. I used the program OMXplayer and the pyliblo and omxplayer-wrapper Python libraries. This setup will also work great with Raspberry Pi Zero and any other low-end model.

To control the players I use SuperCollider and send Open Sound Control messages, but other OSC capable programs or mobile apps will also work. There is a similar system that is controller via physical buttons (GPIO) described in the /f0blog/cheap-4-channel-videoplayer post.

Instructions for installing

This will require at a minimum a 4 GB SD card and a way to connect the Raspberry Pi to the internet.

Prepare the RPi

  • Burn the Raspberry Pi OS Lite image (2020-08-20-raspios-buster-armhf-lite) to the SD card

  • Enable SSH by creating an empty file in the root directory of the SD card called ssh.

    touch /Volumes/boot/ssh
    
  • Put the SD card in the Raspberry Pi and log in via SSH.

  • Run the following...

    sudo raspi-config #set password and hostname, overclock RPi1 to 800MHz, RPi1 GPU memory = 64
    sudo apt-get update
    sudo apt-get upgrade
    sudo apt-get dist-upgrade
    
  • And run these commands to install the required libraries...

    sudo apt-get install omxplayer libdbus-1-dev python3-pip python3-liblo
    pip3 install omxplayer-wrapper
    

Copy over files

  • On your laptop find a video file and name it video.mp4.
  • Download the Python script from here below (f0videoplayerOsc.py).
  • Put the video and the python file in a directory (here Downloads) and in Terminal type...
    cd ~/Downloads
    scp video.mp4 pi@192.168.1.102:  #edit IP of your Raspberry Pi
    scp f0videoplayerOsc.py pi@192.168.1.102:
    

Autostart

  • Log in to your Raspberry Pi again and type...

    crontab -e
    
  • Add the following to the end of the crontab file...

    @reboot cd /home/pi && python3 f0videoplayerOsc.py video.mp4
    
  • Type ctrl+o and ctrl+x to save and exit.

  • Restart...

    sudo reboot
    

Now the player should launch at boot and start looping a file in the home directory called video.mp4. Send OSC messages to port 9999 to control it (see below).

It is fairly easy to adapt and add new commands to the Python player script. See the omxplayer-wrapper documentation. One can also query and send back information - for example about current position in video.

Test

Here is some SuperCollider code for testing...

n= NetAddr("192.168.1.102", 9999);  //edit IP of your Raspberry Pi
n.sendMsg(\pause);
n.sendMsg(\play);
n.sendMsg(\set_position, 1000);
n.sendMsg(\set_position, 100);
n.sendMsg(\set_position, 10);
n.sendMsg(\hide);
n.sendMsg(\show);
n.sendMsg(\set_dimensions, 100, 100, 200, 200);
n.sendMsg(\set_dimensions, 0, 0, 1920, 1080);  //display dim
n.sendMsg(\set_crop, 100, 100, 200, 200);
n.sendMsg(\set_crop, 0, 0, 640, 360);  //movie dim
n.sendMsg(\set_volume, 0.0);  //0.0-10.0
n.sendMsg(\set_volume, 5.0);
n.sendMsg(\set_volume, 1.0);  //default
n.sendMsg(\set_rate, 1.0);
n.sendMsg(\set_rate, 0.75);
n.sendMsg(\set_rate, 0.5);
n.sendMsg(\set_rate, 0.01);
n.sendMsg(\set_rate, 1.0);  //default

//slow down
(
Routine.run({
	100.do{|i|
		n.sendMsg(\set_rate, i.linlin(0, 99, 1.0, 0.01));
		0.025.wait;
	};
});
)
n.sendMsg(\set_rate, 1.0);

//zoom out
(
Routine.run({
	var num= 100;
    var w= 1920, h= 1080;  //display dim
	num.do{|i|
		n.sendMsg(
			\set_dimensions,
			i.linlin(0, num-1, 0, w/2).asInteger,
			i.linlin(0, num-1, 0, h/2).asInteger,
			i.linlin(0, num-1, w, w/2+1).asInteger,
			i.linlin(0, num-1, h, h/2+1).asInteger
		);
		0.025.wait;
	};
});
)
n.sendMsg(\set_dimensions, 0, 0, 1920, 1080);

//zoom in
(
Routine.run({
	var num= 100;
    var w= 640, h= 360;  //movie dim
	num.do{|i|
		n.sendMsg(
			\set_crop,
			i.linlin(0, num-1, 0, w/2).asInteger,
			i.linlin(0, num-1, 0, h/2).asInteger,
			i.linlin(0, num-1, w, w/2+1).asInteger,
			i.linlin(0, num-1, h, h/2+1).asInteger
		);
		0.025.wait;
	};
});
)
n.sendMsg(\set_crop, 0, 0, 640, 360);

//stop and shut down the Raspberry Pi
n.sendMsg(\shutdown)

References

github.com/redFrik/yetanotheroscmovieplayer

github.com/avilleret/rpi_osc_video_player

Attachments:
f0videoplayerOsc.py

f06dof - Wireless Accelerometer+Gyro OSC

2020-11-03 21:36 electronics

Recently I built a bunch of really cheap and simple wireless sensor circuits that send Open Sound Control messages. It is just two modules next to each other powered from two AA batteries.

The sensor module is the MPU-6050 with 3D accelerometer and gyroscope. The microcontroller is an ESP8266-01. Total cost per unit is around €4,50 (excluding batteries).

There are a few settings like smoothing, update rate and thresholds which can be controlled by sending OSC messages to the ESP module. See the examples.

Attached below are examples for SuperCollider and MaxMSPJitter, schematics and the firmware (PlatformIO).

f06dof circuit

f06dof completed

Attachments:
f06dof-examples.zip
f06dof-schematics.pdf
f06dof-firmware.zip

Tamas 3

2020-08-17 17:11 electronics

Here is a board I built in autumn of 2018. It is for making custom hardware controllers with sliders, buttons and LEDs. This version (v3) communicates via MIDI, Serial or over WiFi using OSC.

(The previous two versions are: /f0blog/tamas and /f0blog/tamas-2)

greybox version 3 pinouts

The board is just a Teensy 3.5 with a TLC5940 LED driver and an optional ESP8266-01 module. There are 16 analogue inputs, 16 digital inputs and 16 PWM outputs. It is powered from USB.

Under the hood it looks like this...

greybox version 3 circuit

Power to the ESP8266-01 is supplied from the Teensy 3V3 pin. Note that this is pushing the limits. The Teensy 3V3 rail is only rated for 250mA and an ESP module can draw quite a bit more than that (current spikes). As a precaution I added big capacitors close to the ESP and it has been working well so far.

When using MIDI the analogue input and PWM output resolutions are 7bits, and over Serial and OSC my firmware is using 8bit resolution. The hardware supports 10 and 12-bit resolutions but I decided that 8 is enough. It will keep the communictation protocol simple and reduce the amount of data being sent.

Attached are several examples for SuperCollider and MaxMSPJitter. They demonstrate how to talk to this box, how to control LEDs and read sensor data via MIDI, WiFi and Serial.

Also attached are the schematics and Teensy and ESP firmware files. In the file tamas3_teensy.ino one can customise debounce time, MIDI note and control numbers, update rate etc. And in the file tamas3_esp.ino there are settings for how the WiFi should behave (soft-access point with DHCP is the default).

Attachments:
grey_box3-examples.zip
tamas3_schematics.pdf
tamas3_firmware.zip

Under the Hood Changes 3

2020-07-01 22:10 other

Big remake of this blog as well as my homepage. I decided to rewrite everything using the wonderful Zola engine www.getzola.org - a static site generator.

On the surface it all should look and function about the same as before. I worked hard to keep backward compatibility and to make sure URLs stay the same. I used aliases and a few .htaccess RewriteRules.

Up until now, my site was a mixture of plain HTML and Drupal (the monster). It took quite some time to change everything over, but I think it was worth the trouble.

Also now my homepage finally should look at least decent on mobile.


Serial Match

2020-04-06 17:37 supercollider

In MaxMSP there is a great object called [match]. It is especially useful for parsing data from a serial port. In SuperCollider there is nothing like that build-in, but one can do the same with the class extension below.

Bascially this method will match an incoming array of specific bytes where nil means match any value. For example port.match([1, 2, nil, 3]); will return a match for 1, 2, 55, 3 but not for 1, 4, 55, 10.

Save the following in a file called extSerial.sc, move the file to your SuperCollider extensions folder and recompile...

//f.olofsson
+SerialPort {
    match {|arr|
        var byte, index= 0;
        var res= arr.copy;
        while({index<arr.size}, {
            byte= this.read;
            if(arr[index].isNil or:{byte==arr[index]}, {
                res[index]= byte;
                index= index+1;
            }, {
                ^nil;
            });
        });
        ^res;
    }
}

To test it upload the following code to an Arduino...

//Arduino test code - send six 10-bit values
#define NUM_ANALOG 6
#define UPDATERATE 20  //ms to wait between each reading

void setup() {
  Serial.begin(57600);
}
void loop() {
  Serial.write(200);  //header
  Serial.write(201);
  for (byte i = 0; i < NUM_ANALOG; i++) {
    int data = analogRead(i);
    Serial.write(data & 255); //lsb
    Serial.write(data >> 8); //msb
  }
  Serial.write(202);  //footer
  delay(UPDATERATE);
}

and then run this code in SuperCollider (adapt serial port name where it says EDIT)...

//SuperCollider test code - read six 10bit values
SerialPort.listDevices;
(
var num_analog= 6;
var port= SerialPort("/dev/cu.SLAB_USBtoUART", 57600);  //EDIT
var arr= [200, 201, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 202];  //200, 201 header, 202 footer and nil the data
r= Routine.run({
    inf.do{|i|
        var res;
        res= port.match(arr);
        if(res.notNil, {
            num_analog.do{|i|
                var lsb= res[i*2+2];
                var msb= res[i*2+3];
                ((msb<<8)+lsb).postln;
            };
        });
    };
});
CmdPeriod.doOnce({port.close});
)

r.stop

Buffer xFader

2020-03-29 13:42 supercollider

This SuperCollider function automates the process of making seamless loops as shown in this animation...

i.e. creating continuous loops from buffers following this recipe...

  1. select a few seconds from the end.
  2. fade out the selection.
  3. cut the selection.
  4. paste the selection at time 0.
  5. select a few seconds from the beginning (usually same length as in #1).
  6. fade in the selection.
  7. mix the two tracks.
(
~xfader= {|inBuffer, duration= 2, curve= -2, action|
    var frames= duration*inBuffer.sampleRate;
    if(frames>inBuffer.numFrames, {
        "xfader: crossfade duration longer than half buffer - clipped.".warn;
    });
    frames= frames.min(inBuffer.numFrames.div(2)).asInteger;
    Buffer.alloc(inBuffer.server, inBuffer.numFrames-frames, inBuffer.numChannels, {|outBuffer|
        inBuffer.loadToFloatArray(action:{|arr|
            var interleavedFrames= frames*inBuffer.numChannels;
            var startArr= arr.copyRange(0, interleavedFrames-1);
            var endArr= arr.copyRange(arr.size-interleavedFrames, arr.size-1);
            var result= arr.copyRange(0, arr.size-1-interleavedFrames);
            interleavedFrames.do{|i|
                var fadeIn= i.lincurve(0, interleavedFrames-1, 0, 1, curve);
                var fadeOut= i.lincurve(0, interleavedFrames-1, 1, 0, 0-curve);
                result[i]= (startArr[i]*fadeIn)+(endArr[i]*fadeOut);
            };
            outBuffer.loadCollection(result, 0, action);
        });
    });
};
)

s.boot;

//edit and load a sound file (or use an already existing buffer)
b.free;  b= Buffer.read(s, "~/Desktop/testnoise.wav".standardizePath);

//evaluate the function to create the seamless cross fade
c= ~xfader.value(b);

//try looping it - should loop smoothly and without discontinuities
d= {PlayBuf.ar(c.numChannels, c, loop:1)}.play;
d.release;

//compare with the input file - this will probably have a hickup
d= {PlayBuf.ar(b.numChannels, b, loop:1)}.play;
d.release;

b.free;
c.free;


//--save to disk example with shorter cross fade and done action function.
b.free;  b= Buffer.read(s, "~/Desktop/testnoise.wav".standardizePath);
c= ~xfader.value(b, 0.5, -3, action:{|buf| ("done with buffer"+b).postln});
c.write("~/Desktop/testnoise-looped.wav".standardizePath);
c.free;
b.free;

The SuperCollider code works with any Buffer containing sound files, live sampled or generated sounds. The duration argument set the cross fade length in seconds and with the curve argument one can set curvature.

Note that duration can not be longer than half the duration of the input buffer and a curve of 0.0 or greater will mean that the amplitude will dip in the middle of the crossfade. So it is recommended to bend the curve a bit to get more of an equal power crossfade. -2.0 to -4.0 seem like sensible values. The function will allocate and return a new Buffer instance and not touch the buffer passed in as an argument.

An action function is optional. The function is asynchronous. Also, note that the resulting file will be shorter than the original because of the crossfade.

Footnote:
I remember learning this trick from Peter Lundén ~20 years ago. He showed it to me on an SGI machine running IRIX, possibly using the Snd sound editor.


ESP Mesh Network with OSC

2020-03-23 19:32 electronics

With the painlessMesh library, it turned out easy to set up a decentralised mesh network for a few ESP8266 modules. The example below shows how I set it up and how to send and receive OpenSoundControl (OSC) messages. The code also works on an ESP32.

  1. Install the painlessMesh and the OSC libraries for Arduino.
  2. Program a few nodes (ESP8266) so that they all run this code...
//req. libraries: OSC, painlessMesh

#include <Arduino.h>
#include <painlessMesh.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCData.h>

#define MESH_NAME "networkname" //EDIT mesh name
#define MESH_PASS "networkpass" //EDIT password
#define MAX_CONN 4 //EDIT ESP32 can have more than ESP8266

#define INPORT 19998 //OSC in port
#define OUTPORT 57120 //OSC out port (SC)

IPAddress outIP;
WiFiUDP Udp;
painlessMesh mesh;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  mesh.init(MESH_NAME, MESH_PASS, 5555, WIFI_AP_STA, 1, 0, MAX_CONN);
  Udp.begin(INPORT);
}

void pingFunc(OSCMessage &inMsg) {
  digitalWrite(LED_BUILTIN, 1 - digitalRead(LED_BUILTIN));  //toggle led
  OSCMessage outmsg("/pong");
  outmsg.add(mesh.getNodeId()); //uint32
  IPAddress ip;
  ip= mesh.getStationIP(); //0.0.0.0 if current base station
  outmsg.add(ip[0]);
  outmsg.add(ip[1]);
  outmsg.add(ip[2]);
  outmsg.add(ip[3]);
  ip= mesh.getAPIP();
  outmsg.add(ip[0]);
  outmsg.add(ip[1]);
  outmsg.add(ip[2]);
  outmsg.add(ip[3]);
  Udp.beginPacket(outIP, OUTPORT);
  outmsg.send(Udp);
  Udp.endPacket();
}

void loop() {
  mesh.update();

  int packetSize= Udp.parsePacket();
  if (packetSize) {
    OSCMessage inMsg;
    while (packetSize--) {
      inMsg.fill(Udp.read());
    }
    if (!inMsg.hasError()) {
      outIP= Udp.remoteIP();
      inMsg.dispatch("/ping", pingFunc);
    }
  }
}
  1. After that power up the nodes and a new WiFi network should show up.
  2. Connect a laptop to the new mesh network and take note of the IP number assigned.
  3. Run the test code below on the laptop. It will broadcast a \ping OSC message and listen for \pong replies.

The test code is for SuperCollider but any program that can send OSC should work.

(
OSCFunc({|msg| msg.postln}, \pong);
NetAddr.broadcastFlag= true;
NetAddr("10.214.190.255", 19998).sendMsg(\ping);  //EDIT laptop ip number but leave 255 as the last number
)

The painlessMesh library will for sure come in handy when I need a network without a WiFi router, or for when trying to cover a larger area.

A major drawback though seems to be that the maximum number of nodes that can be used is really low. Apparently, an ESP8266 can only handle 5 (TCP/IP) connections at the same time and an ESP32 about three times that. And that is not very many.


2 3 4 5 6 7 last»