« 1 2 3 4 5 6 7 »

Building SuperCollider for piCore Linux 2

2021-07-27 14:32 supercollider

This is an updated version of /f0blog/building-supercollider-for-picore-linux/ which demonstrates how to build, package and install SuperCollider and sc3-plugins for piCore - the Raspberry Pi port of TinyCoreLinux.

The instructions here below are for piCore12.0 (32-bit) while the old post was for piCore-9.0.3. The main difference between the two is that we no longer need to build jack2 ourselves. jack and jack-dev are now included in the piCore repository. This version also compiles SuperCollider with avahi support.

introduction

piCore has many advantages over the common Raspbian system. It will boot a lot faster, is extremely light-weight and is easy to customise. And because the whole system always resides in RAM, SD card wear is minimal.

Its immutable-by-default design means one can unplug the power to the Raspberry Pi without performing and waiting for a proper shutdown nor risking corrupting the SD card. It also allows one to experiment without being afraid of messing up. A simple reboot will take the system back to a known state. For changes to be persistent, one must deliberately write them to the SD card (using the command filetool.sh -b).

Some drawbacks are that piCore is more advanced to install and configure and that much common Linux software is missing from the built-in package manager (one will have to compile it oneself - hence this guide).

requirements

preparation

On the laptop, open a terminal and run the following commands:

ping 192.168.1.255  #first broadcast ping (stop with ctrl+c)
arp -a  #figure out which IP address the RPi has (here 192.168.1.13)
ssh tc@192.168.1.13  #default password: piCore

sudo fdisk -u /dev/mmcblk0  #then press the following keys in order to delete and recreate partition2
 p  #note start of partition2 - StartLBA - usually 139264
 d
 2
 n
 p
 2
 139264  #enter start of partition2 from above
 <RET>  #type return to accept suggestion
 w  #write

filetool.sh -b
sudo reboot

ssh tc@192.168.1.13  #pass: piCore

sudo resize2fs /dev/mmcblk0p2  #resize partition2

install dependencies

Assuming piCore is now installed and partition2 resized like above...

#download and install build dependencies
cd /tmp
tce-load -wil cmake compiletc squashfs-tools libudev-dev libsndfile-dev readline-dev libsamplerate-dev avahi-dev jack-dev git
wget http://tinycorelinux.net/9.x/armv6/tcz/fftw-dev.tcz  #from old repository
tce-load -il fftw-dev
sudo /usr/sbin/cache-clear

building SuperCollider

Assuming all dependencies above are installed...

#download and compile SuperCollider
cd /tmp
git clone --branch main --single-branch --recurse-submodules https://github.com/supercollider/supercollider.git
cd supercollider
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE="Release" -DBUILD_TESTING=OFF -DSUPERNOVA=OFF -DNATIVE=ON -DSC_IDE=OFF -DNO_X11=ON -DSC_QT=OFF -DSC_ED=OFF -DSC_EL=OFF -DSC_VIM=ON -DINSTALL_HELP=OFF -DSC_ABLETON_LINK=OFF -DCMAKE_C_FLAGS="-lncursesw" -DCMAKE_CXX_FLAGS="-lncursesw" -DCURSES_LIBRARY="/usr/local/lib/libncursesw.so" -DCURSES_INCLUDE_PATH="/usr/local/lib/" ..
make
sudo make install
cat install_manifest.txt | grep -v "/usr/local/include/\|/usr/local/share/pixmaps/\|/usr/local/share/mime/" > /tmp/sc.list
make clean

#create the SuperCollider tcz extension package
cd /tmp
tar -T /tmp/sc.list -czvf /tmp/sc.tar.gz
mkdir /tmp/pkg && cd /tmp/pkg
tar -xf /tmp/sc.tar.gz
cd ..
mksquashfs pkg/ supercollider.tcz
sudo mv supercollider.tcz ~
rm -rf /tmp/pkg
tce-load -i ~/supercollider.tcz
sclang -h  #just to check that it is working

On the laptop, open another terminal window and download the resulting compressed SuperCollider package:

scp tc@192.168.1.13:supercollider.tcz ~/Downloads  #pass: piCore

building sc3-plugins

Assuming SuperCollider is installed like above...

#download and compile sc3-plugins
cd /tmp
git clone --branch main --single-branch --recursive https://github.com/supercollider/sc3-plugins.git
cd sc3-plugins
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE="Release" -DSUPERNOVA=OFF -DNATIVE=ON -DSC_PATH=../../supercollider/ ..
sudo /usr/sbin/cache-clear
make
sudo make install
cat install_manifest.txt | grep -v "/HelpSource\|.html\|/Help" > /tmp/scplugs.list
make clean

#create the sc3-plugins tcz extension package
cd /tmp
tar -T /tmp/scplugs.list -czvf /tmp/scplugs.tar.gz --exclude=*/HelpSource --exclude=*.html --exclude=*/Help
mkdir /tmp/pkg && cd /tmp/pkg
tar -xf /tmp/scplugs.tar.gz
cd ..
mksquashfs pkg/ sc3-plugins.tcz
sudo mv sc3-plugins.tcz ~
rm -rf /tmp/pkg

On the laptop, open another terminal window and download the resulting compressed sc3-plugins package:

scp tc@192.168.1.13:sc3-plugins.tcz ~/Downloads  #pass: piCore

Now that the two tcz packages are created and saved to the laptop, we can erase the SD card and start afresh. (It is possible to continue working with the same piCore install, but unused build dependencies would waste some space).

Install instructions

(for future installs you can skip all of the above and start here assuming you have kept the .tcz packages)

On the laptop, open a terminal and run the following commands:

ping 192.168.1.255  #first broadcast ping (stop with ctrl+c)
arp -a  #figure out which IP address the RPi has (here 192.168.1.13)
ssh-keygen -R 192.168.1.13  #resets the ssh keys to be able to log in again
ssh tc@192.168.1.13  #default password: piCore

sudo fdisk -u /dev/mmcblk0  #then press the following keys in order to delete and recreate partition2
 p  #note start of partition2 - StartLBA - usually 139264
 d
 2
 n
 p
 2
 139264  #enter start of partition2 from above
 <RET>  #type return to accept suggestion
 w  #write

filetool.sh -b
sudo reboot

ssh tc@192.168.1.13  #pass: piCore

sudo resize2fs /dev/mmcblk0p2  #resize partition2

#install dependencies
tce-load -wi alsa alsa-utils jack libsamplerate libavahi libudev readline git nano

On the laptop, open another terminal window and upload the compressed packages:

cd ~/Downloads
scp supercollider.tcz sc3-plugins.tcz tc@192.168.1.13:

Back on the Raspberry Pi...

cd ~
mv supercollider.tcz sc3-plugins.tcz /mnt/mmcblk0p2/tce/optional/
echo -e "supercollider.tcz\nsc3-plugins.tcz" >> /mnt/mmcblk0p2/tce/onboot.lst
echo -e "\nsudo /usr/local/sbin/alsactl -f /home/tc/mysound.state restore" >> /opt/bootlocal.sh

#autostart - optional
nano autostart.sh  #add the following lines
  #!/bin/sh
  jackd -P75 -p16 -dalsa -dhw:0 -r44100 -p1024 -n3 &
  sclang /home/tc/mycode.scd
chmod +x autostart.sh
echo -e "/home/tc/autostart.sh &" >> /opt/bootlocal.sh

nano mycode.scd  #add the following lines
  s.waitForBoot{
    {SinOsc.ar([400, 404], 0, 0.5)}.play;
  };

nano /opt/bootlocal.sh  #change from ondemand to performance in line 10
  echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

#IMPORTANT - make the changes permanent
filetool.sh -b

sudo reboot

The piCore system should now have SuperCollider installed and (optionally) start at boot.

volume

To adjust the volume log in and run the following commands...

alsamixer  #set volume with arrow keys, ESC to exit
alsactl -f /home/tc/mysound.state store  #save in custom alsa settings file
filetool.sh -b  #make permanent

notes


Impulse Train in SuperCollider

2021-07-24 11:44 supercollider

In SuperCollider there are no readily available UGens for creating a fixed number of single sample impulses. One could cobble something together using Demand rate UGens...

(
c = {|dur = 1, num = 8|
  Duty.ar(Dseq([SampleDur.ir, dur / num - SampleDur.ir], inf), 0, Dseq([1, 0], num));
}.play(fadeTime: 0);
)

or another variant could be to use a combination of Sweep and Changed...

(
c= {|dur = 1, num = 8|
  Changed.ar(Sweep.ar(0, num / dur).ceil.min(num));
}.play(fadeTime:0);
)

but there is a more flexible technique using an envelope. The big win doing it like this is that we can play with the envelope's curvature and get different shapes.

//impulse train with curvature
(
c = {|
  t_go, // trigger
  dur = 1, // total duration in seconds (also frequency)
  cur = 0, // curvature or 'shape'. 0 = pulses at regular intervals
  num = 8, // number of impulses per duration
  amp = 1|
  var env = EnvGen.ar(Env(#[0, 0, 1], [0, dur], cur), t_go);
  var train = Changed.ar(ceil(env * num)) * amp;
  // train = train * (1 - env); // optional decreasing amplitude
  // train = train * env; // optional - increasing amplitude
  train;
}.play;
)
c.set(\t_go, 1);
c.set(\t_go, 1, \cur, 3, \num, 18); // start slow and go faster
c.set(\t_go, 1, \cur, -2, \num, 18); // slower and slower
c.set(\amp, 0.2);
c.set(\t_go, 1, \cur, 0, \dur, 0.5, \num, 200); // 200 for half a second = 400 Hz

The trick to converting an envelope into discrete impulses is to make the linear 0-1 ramp into whole number steps (scale up with * num and run it through ceil) and then trigger an impulse each time the step changes (using the Changed UGen). Then multiplying the train of impulses with the original envelope is optional. The extra multiplication will fade in or fade out the amplitude over the given duration.

The plot below shows two short bursts. There are 10 impulses spread over 200 samples at a sample rate of 44.1KHz. The top one has its curvature set to 4 and thereby the impulse train gets exponentially faster. In the bottom plot, the curvature is the inverse -4 and thereby the train starts fast and then slows down.

plot of two different impulse trains

And here is the same (top) impulse train again but showing the process from the curved envelope, via the discrete steps and to the resulting impulses.

impulses from an envelope

solar cell with loudspeaker

2020-11-24 16:52 other

Here is a very 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

Copy over files

Autostart

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...

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

« 1 2 3 4 5 6 7 »