supercollider

buffer xfader

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 soundfile (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 soundfiles, 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 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 a SGI machine running Irix, possibly using the Snd sound editor.

AttachmentSize
Image icon xfader-audacity.gif947.7 KB

building supercollider for piCore linux

These instructions show how to build, package and install SuperCollider, SC3-plugins and jackd for piCore - a variant of TinyCoreLinux for the Raspberry Pi.

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

* a SD card - 2 Gb is plenty
* a Raspberry pi - here a RPi 3B v1.2
* an ethernet cable
* a router with internet connection
* a laptop - here running macOS

//--preparation

* download piCore-9.0.3.zip for armv7 (or piCore-9.0.3.zip for armv6 - see notes below)
* burn the zip file to the SD card using for example balenaEtcher
* put the SD card in the RPi and connect ethernet and 5V power

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

arp -a  #figure out which IP address the RPi has (here 192.168.1.13)
ssh tc@192.168.1.13  #pass: piCore

sudo fdisk -u /dev/mmcblk0  #then press the following keys in order to delete and recreate partition2
 p  #check start of partition2 - usually 77824 (or 195693)
 d
 2
 n
 p
 2
 77824  #enter start of partition2 from above
 <RET>  #type return to accept suggestion
 w
sudo reboot

ssh-keygen -R 192.168.1.13  #remove the ssh keys to be able to log in again
ssh tc@192.168.1.13  #pass: piCore

sudo resize2fs /dev/mmcblk0p2  #resize partition2

//--jackd

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

#download and install build dependencies
tce-load -wil cmake compiletc python squashfs-tools libudev-dev libsndfile-dev readline-dev libsamplerate-dev fftw-dev git

#download and compile jackd
cd /tmp
git clone git://github.com/jackaudio/jack2 --depth 1
cd jack2
wget https://waf.io/waf-2.0.12
chmod +x waf-2.0.12
./waf-2.0.12 configure --alsa
./waf-2.0.12 build
sudo ./waf-2.0.12 install > /tmp/jack2_tmp.list

#create the jackd tcz extension package
cd /tmp
cat jack2_tmp.list | grep "/usr/local/" | grep -v "/share/man/\|.h \|.pc " | awk '{print $3}' > jack2.list
tar -T /tmp/jack2.list -czvf /tmp/jack2.tar.gz
mkdir /tmp/pkg && cd /tmp/pkg
tar -xf /tmp/jack2.tar.gz
cd ..
mksquashfs pkg/ jack2.tcz
sudo mv jack2.tcz ~
rm -rf /tmp/pkg
tce-load -i ~/jack2.tcz
jackd  #check that it is working

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

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

//--supercollider

Assuming jackd is installed like above...

#download and compile supercollider
cd /tmp
git clone --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 -DNO_AVAHI=ON -DCMAKE_C_FLAGS="-lncurses" -DCMAKE_CXX_FLAGS="-lncurses" ..
make
sudo make install
cat install_manifest.txt | grep -v "/usr/local/include/\|/usr/local/share/pixmaps/\|/usr/local/share/mime/" > /tmp/sc.list

#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

//--sc3-plugins

Assuming supercollider is installed like above...

#download and compile sc3-plugins
cd /tmp
git clone --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/ ..
make
sudo make install
cat install_manifest.txt | grep -v "/HelpSource\|.html\|/Help" > /tmp/scplugs.list

#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 three tcz packages are created and downloaded 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).

//--restart and install

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

* burn the zip file to the SD card using for example balenaEtcher
* put the SD card in the RPi and connect ethernet and 5V power

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

arp -a  #figure out which IP address the RPi has (here 192.168.1.13)
ssh-keygen -R 192.168.1.13  #remove the ssh keys to be able to log in again
ssh tc@192.168.1.13  #pass: piCore

sudo fdisk -u /dev/mmcblk0  #then press the following keys in order to delete and recreate partition2
 p  #check start of partition2 - usually 77824 (or 195693)
 d
 2
 n
 p
 2
 77824  #enter start of partition2 from above
 <RET>  #type return to accept suggestion
 w
sudo reboot

ssh-keygen -R 192.168.1.13  #remove the ssh keys to be able to log in again
ssh tc@192.168.1.13  #pass: piCore

sudo resize2fs /dev/mmcblk0p2  #resize partition2

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

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

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

Back on the Raspberry Pi...

cd ~
mv jack2.tcz supercollider.tcz sc3-plugins.tcz /mnt/mmcblk0p2/tce/optional/
echo jack2.tcz >> /mnt/mmcblk0p2/tce/onboot.lst
echo supercollider.tcz >> /mnt/mmcblk0p2/tce/onboot.lst
echo sc3-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

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

nano /opt/bootlocal.sh  #add the following lines to the end
        echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
        /home/tc/autostart.sh &

#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

* for RPi1 and RPi Zero you should probably get the armv6 version.
* for RPi2 and newer get the armv7 version (even though the files seem identical).
* running the make command with flag -j3 will usually just result in a out-of-memory freeze.
* avahi is not activated because libavahi-client-dev is not available for piCore - maybe later.
* waf-2.0.12 seems to be the newest version that can build jack2.
* jackd and supercollider will be running as root when autostarting.
* the start up time from applying power to supercollider is making sound is ~20 seconds.
* after the first backup (filetool.sh -b) the ssh-keygen -R will not be needed any longer.
* cpu benchmarks are more or less the same as for running sc under raspbian (see here)

//TODO: usb soundcard

update 200401: simplified by using install_manifest.txt

hid to osc

A Human Interface Device (HID) to Open Sound Control (OSC) converter for macOS written in Python3.

The program can send OSC to any IP and port but by default it will send to 127.0.0.1 (localhost) on port 57120 (SuperCollider).

Here is a binary build... HIDtoOSC.zip (macOS, 64bit, 5.3mb)

To run it start Terminal.app and type...

cd ~/Downloads
./HIDtoOSC

That should list available HID devices on your system. After that you will probably see a warning that it failed to open the default device.

So to open and start reading data from any of your own devices you will need to give the correct vendor id and product id as arguments.

usage: HIDtoOSC [-h] [-V] [--vid VID] [--pid PID] [--ip IP] [--port PORT]
                [--rate RATE] [--debug]

optional arguments:
  -h, --help     show this help message and exit
  -V, --version  show program version
  --vid VID      set HID vendor id
  --pid PID      set HID product id
  --ip IP        set OSC destination IP
  --port PORT    set OSC destination port
  --rate RATE    update rate in milliseconds
  --debug        post incoming HID data

 
example - usb keyboard

Here is an example that show how to connect to an external generic usb keyboard with the vendor and product id 6700, 2.

NOTE: for security reasons sudo is needed when accessing keyboards but not for accessing most other devices (gamepads, joysticks, mice)

Some times you will need to run the program a few times before the HID is claimed. Stop the program with ctrl+c

In the example the key A is pressed and released, but all HID will use their own data format. The --debug flag makes the program print out the incoming data.

 
receiving osc

In SuperCollider we can receive the data as an OSC message. Test it by running the line...

OSCFunc.trace;

Here is what the example above generates...

OSC Message Received:
        time: 75956.941459501
        address: a NetAddr(127.0.0.1, 56073)
        recvPort: 57120
        msg: [ /hid, 6700, 2, Int8Array[ 0, 0, 4, 0, 0, 0, 0, 0 ] ]

example - usb mouse

And here is another example connecting to an optical mouse. (no sudo needed)

This mouse example sends OSC to port 9999 and the data format is slightly different than in the keyboard example above.

 
building

If you do not trust the binary above (and you should not - specially when running it with sudo) you can run the python code directly.

homebrew and python3 is required.

First install some libraries...

brew install hidapi liblo

Next create an virtual environment...

cd ~/python3
mkdir HIDtoOSC && cd HIDtoOSC
python3 -m venv env
source env/bin/activate  #to later leave the virtual environment type: deactivate

Then install some python libraries...

pip install pyliblo
pip install hid pyusb
pip install pyinstaller

Finally copy the main python program below, put it in a file called HIDtoOSC.py and test it with for example python HIDtoOSC.py -h (stop with ctrl+c)

#f.olofsson 2019

# required python libraries: pyliblo, hid, pyusb
# example: sudo python HIDtoOSC.py --vid 6700 --pid 2 --port 12002 --debug
# note: macOS must be running this as root if the device is a keyboard

import sys, argparse
import usb.core, hid, liblo

#--settings
version= 0.1

#--arguments
parser= argparse.ArgumentParser()
parser.add_argument('-V', '--version', action= 'store_true', help= 'show program version')
parser.add_argument('--vid', type= int, dest= 'vid', help= 'set HID vendor id')
parser.add_argument('--pid', type= int, dest= 'pid', help= 'set HID product id')
parser.add_argument('--ip', dest= 'ip', help= 'set OSC destination IP')
parser.add_argument('--port', type= int, dest= 'port', help= 'set OSC destination port')
parser.add_argument('--rate', type= int, dest= 'rate', help= 'update rate in milliseconds')
parser.add_argument('--debug', dest= 'debug', action= 'store_true', help= 'post incoming HID data')

parser.set_defaults(vid= 1452)
parser.set_defaults(pid= 517)
parser.set_defaults(ip= '127.0.0.1')
parser.set_defaults(port= 57120)
parser.set_defaults(rate= 100)
parser.set_defaults(debug= False)
args= parser.parse_args()

if args.version:
  sys.exit('HIDtoOSC version %s'%version)

print('Available HID devices:')
for d in hid.enumerate():
  print('  device: %s, %s'%(d['manufacturer_string'], d['product_string']))
  print('  vid: %s, pid: %s'%(d['vendor_id'], d['product_id']))

def main():
  dev= usb.core.find(idVendor= args.vid, idProduct= args.pid)

  if dev is None:
    sys.exit('Could not find device %s, %s'%(args.vid, args.pid))

  for config in dev:
    for i in range(config.bNumInterfaces):
      if dev.is_kernel_driver_active(i):
        dev.detach_kernel_driver(i)

  try:
    dev.set_configuration()
  except usb.core.USBError as e:
    sys.exit('Could not set configuration: %s'%str(e))

  endpoint= dev[0][(0, 0)][0]
  try:
    dev= hid.Device(args.vid, args.pid)
    print('Device %s, %s open'%(args.vid, args.pid))
  except:
    sys.exit('Could not open device %s, %s'%(args.vid, args.pid))

  print(endpoint)
  target= (args.ip, args.port)
  noerror= True
  while noerror:
    try:
      data= dev.read(endpoint.wMaxPacketSize, args.rate)
    except:
      noerror= False
      try:
        dev.close()
        print('Closed the hid device')
      except:
        pass

    if data:
      if args.debug:
        print([x for x in data])
      try:
        msg= liblo.Message('/hid', args.vid, args.pid, ('b', data))
        liblo.send(target, msg)
      except:
        print('WARNING: Could not send osc to %s'%(target, ))

if __name__=='__main__':
  main()

Later you can build your own binary with the command...

pyinstaller HIDtoOSC.py --onefile --hidden-import=libhidapi --add-binary='/usr/local/lib/libhidapi.dylib:.'

featureCreep

Software written for and in collaboration with violinist George Kentros. Premiered at after work - this violin must die, Fylkingen Stockholm 30 mar 2019.

The program is written in SuperCollider with additional features like iPad control and video mixer made in MaxMSPJitter.

Main interface

The main window with its eight tracks (configurable) looks like this...

and here you can set an amplitude threshold for the microphone and arm tracks to automatically detect and capture sounds.

With the waveform selection (dark grey areas) you select what part of the captured sound you want your different players to use.

There are many players - all with different behaviours...
slow, medium, repetitive, scanner, sweep, nervous, fastRhythmic, slowRhythmic, limpRhythmic, fast, drill, jump, plain, half, third, quarter, pingpong, crawl, wave, waveHalf, pattern1, pattern2, pattern3, round, sync

Player behaviours include in which direction and how fast to progress in the captured sound buffer, when to play for how long, with which envelope and how loud, how much transposition, play in which audio channel etc etc.
Some of these players are using granular synthesis techniques while others are scratching and jumping around in the sound buffer in more or less unpredictable ways. hopefully the names listed here above will give a hint of what players do to the sound.

All players are pieces of code. For example this the code for the scanner player...

SynthDef(\scanner, {|buf, amp= 1, start= 0, end= 1|
        var dur= BufDur.kr(buf);
        var pha= LFNoise2.kr(In.kr(100)+0.1)+1/2;
        var pos= pha*(end-start)+start;
        var add= LFNoise2.kr(1);
        var fre= In.kr(101).linexp(0, 1, 1.1, 18)+add*SinOsc.kr(0.1).max(0.5);
        var snd= TGrains.ar(
                2,
                Impulse.ar(fre),
                buf,
                1+LFNoise2.kr(1, 0.05),
                pos*dur,
                0.75,
                LFNoise2.kr(0.06, 0.5),
                amp.lag(0.5)
        );
        SendReply.kr(Impulse.kr(\updateRate.kr+1), '/pos', pos);
        Out.ar(\syncBus.ir(40)+\index.ir, K2A.ar(pha));
        snd= CompanderD.ar(snd, \compressorThresh.kr, 0.5, 0.5, 0.005, 0.01, EnvGate());
        Out.ar(\out.kr, snd);
}).add;

and most players will look very similar to this. But there are also a few special ones like sync which just matches the playback position of the player in the track above.

In the main window one can also set track volume, bypass any global effects and read/write soundfiles.

The computer keyboard may be used for shortcuts quickly arming, playing and selecting what each track should play.

Global control

There is also an additional control gui with global volume, effects, automation and record...

The three sliders delay, sustainer, distortion are just simple wet/dry control for global effects (that can be bypassed for each track using the bp button), while the other sliders freeze, fire, industry, chaos, air are more complex and control a bunch of effects and behaviours. For example freeze will take the sound from all players and smear it out by removing transients and pitch shifting copies of the sound up/down in octaves.

When the automation button is clicked, the program itself can start and stop tracks, change effects and vary a lot of other parameters in the code. The idea here is that the software should be able to run independently and generate interesting variations over long periods of time. Also it should never go completely silent nor go wild and overload everything.

Remote iPad interface

All the above can be viewed and controlled remotely from a tablet or a phone using a web browser. We programmed a look-alike gui patch with MaxMSPJitter and the mira externals. This patch talks to the SuperCollider interface over Open Sound Control.

The interface here is even more ugly - specially the waveform display because the sound data had to be downsampled and kept at a low resolution to reduce wifi network traffic. Also it was hard to fit everything on a single screen as all widgets had to be quite large and not too close to each other to be usable on an iPad.

Our software also includes a video mixer for camera and movie playback (not shown here).

sc: fixed number of decimals

Here's a quick function for displaying float numbers as strings in SuperCollider.

(
~fixDec= {|val, numDecimals= 2|  //float to string with fixed number of decimals
        var str= val.round(0.1**numDecimals).asString;
        var num= str.size-str.indexOf($.)-1;
        str.extend(str.size+numDecimals-num, $0);
};
)

//test examples
~fixDec.value(0.1, 3)
-> 0.100
~fixDec.value(0.12345, 3)
-> 0.123

//rounds internally.  compare:
~fixDec.value(0.191, 2)
-> 0.19
~fixDec.value(0.197, 2)
-> 0.20

//can deal with negative values
~fixDec.value(-2pi, 4)
-> -6.2832

//and integers
~fixDec.value(10000, 4)
-> 10000.0000

~fixDec.value(10000, 0)
-> 10000.

f0mid

Wireless MIDI <-> OSC bridge using an ESP8266-01. This circuit is extremely cheap to build. Schematics, Arduino code and examples for SuperCollider below.

I'm using the great Arduino MIDI Library that allows for both sending and receiving a multitude of MIDI messages including sysex, system realtime and time code messages. My Arduino code just converts all these to/from OSC and send or broadcast them over WiFi network.

Note: sending MIDI over WiFi UDP is generally a bad idea. There will be delays, glitches and even lost messages (hanging notes). This is specially problematic for MIDI time code (sync) messages. That said, in many situations this is ok and in my tests with simple note on/off messages + bend and control, things seem to work just fine.

f0mid

The circuit takes in 5V and then the regulator steps this down to 3.3V. Notice the huge 220uF capacitor that's needed to provide power for the ESP8266 during its infamous current draw spikes.

f0mid_schematics

SuperCollider example code...

//http://fortyseveneffects.github.io/arduino_midi_library/a00041.html
//http://fortyseveneffects.github.io/arduino_midi_library/a00043.html

OSCFunc.trace(true, true);
OSCFunc.trace(false);

n= NetAddr("f0mid.local", 18120);  //ip of esp8266
n.sendMsg(\ip, 192, 168, 1, 99);  //receiver ip (laptop - by default this is x.x.x.255 (broadcast))
n.sendMsg(\port, 57120);  //set receiver port (by default this is 57120)

n.sendMsg(\thru, 0);  //off
n.sendMsg(\thru, 1);  //full (default)
n.sendMsg(\thru, 2);  //same channel
n.sendMsg(\thru, 3);  //different channel

n.sendMsg(\noteOn, 66, 127, 1);  //(note, velo, chan)
n.sendMsg(\noteOff, 66, 0, 1);  //(note, velo, chan)
n.sendMsg(\afterTouchPoly, 50, 60, 3);  //poly pressure (note, press, chan)
n.sendMsg(\controlChange, 1, 64, 3);  //(num, val, chan)
n.sendMsg(\programChange, 10, 4);  //(num, chan)  note the -1 offset
n.sendMsg(\afterTouchChannel, 40, 2);  //(press, chan)
n.sendMsg(\pitchBend, -8000, 1);  //(bend, chan)  -8192 - 8191
n.sendMsg(\sysEx, 240, 14, 5, 0, 5, 247);  //(sysex) - 240 a b c d e ... 247

//realtime
(
var clock= 0xf8;  //248
var start= 0xfa;  //250
var continue= 0xfb;  //251
var stop= 0xfc;  //252
Routine.run({
        n.sendMsg(\realTime, start);
        100.do{
                n.sendMsg(\realTime, clock);
                0.02.wait;
        };
        n.sendMsg(\realTime, stop);
        1.wait;
        n.sendMsg(\realTime, continue);
        100.do{
                n.sendMsg(\realTime, clock);
                0.02.wait;
        };
        n.sendMsg(\realTime, stop);
});
)
n.sendMsg(\realTime, 0xfe);  //active sensing
n.sendMsg(\realTime, 0xff);  //system reset

n.sendMsg(\songPosition, 100);
n.sendMsg(\songSelect, 3);
n.sendMsg(\tuneRequest);

n.sendMsg(\beginNrpn, 10, 3);  //(number, channel)
n.sendMsg(\nrpnDecrement, 40, 3);  //(amount, channel)
n.sendMsg(\nrpnIncrement, 30, 3);  //(amount, channel)
n.sendMsg(\endNrpn, 3);  //(channel)

n.sendMsg(\beginRpn, 10, 4);  //(number, channel)
n.sendMsg(\rpnDecrement, 40, 4);  //(amount, channel)
n.sendMsg(\rpnIncrement, 30, 4);  //(amount, channel)
n.sendMsg(\endRpn, 4);  //(channel)

//--simple midi synth example
(
s.latency= 0.02;
s.waitForBoot{
        var busBend= Bus.control(s);
        var busCF= Bus.control(s);
        var busRQ= Bus.control(s);
        var busVol= Bus.control(s);
        var busPan= Bus.control(s);
        busBend.value= 0;
        busCF.value= 1000;
        busRQ.value= 0.5;
        busVol.value= 0.5;
        busPan.value= 0;
        SynthDef(\note, {|freq= 400, amp= 0.5, gate= 1, busBend, busCF, busRQ, busVol, busPan|
                var env= EnvGen.ar(Env.adsr(0.01, 1, 0.85, 0.1), gate, amp, doneAction:2);
                var bend= In.kr(busBend).lag(0.01);
                var cf= In.kr(busCF).lag(0.01);
                var rq= In.kr(busRQ).lag(0.01);
                var vol= In.kr(busVol).lag(0.01);
                var pan= In.kr(busPan).lag(0.01);
                var src= BLowPass4.ar(VarSaw.ar((freq+bend).midicps), cf, rq);
                OffsetOut.ar(0, Pan2.ar(src*env, pan, vol));
        }).add;
        d= ();
        OSCdef(\f0mid, {|msg|
                switch(msg[1],
                        \activeSensing, {},
                        \noteOn, {
                                d.at(msg[2]).set(\gate, 0);
                                d.put(msg[2], Synth(\note, [
                                        \freq, msg[2],
                                        \amp, msg[3].lincurve(0, 127, 0, 0.75, 4),
                                        \busBend, busBend,
                                        \busCF, busCF,
                                        \busRQ, busRQ,
                                        \busVol, busVol,
                                        \busPan, busPan
                                ]));
                        },
                        \noteOff, {
                                d.at(msg[2]).set(\gate, 0);
                                d.put(msg[2], nil);
                        },
                        \pitchBend, {
                                busBend.value= msg[2]/8192;
                        },
                        \controlChange, {
                                switch(msg[2],
                                        1, {
                                                busCF.value= msg[3].linexp(0, 127, 400, 4000);
                                        },
                                        7, {
                                                busVol.value= msg[3].lincurve(0, 127, 0, 1, 0);
                                        },
                                        10, {
                                                busPan.value= msg[3].linlin(0, 127, -1, 1);
                                        },
                                        74, {
                                                busRQ.value= msg[3].linlin(0, 127, 2, 0.1);
                                        },
                                        {("todo control: "+msg).postln}
                                );
                        },
                        {("todo command: "+msg).postln}
                );
        }, \f0mid);
        CmdPeriod.doOnce({
                busBend.free;
                busCF.free;
                busRQ.free;
                busVol.free;
                busPan.free;
        });
};
)

//mtc - receive
(
var a= MIDISMPTEAssembler({|time, format, dropFrame, srcID|
        [time, format, dropFrame, srcID].postln;
        //time.postln;
});
OSCdef(\f0mid, {|msg, time, addr|
        var chan, valu;
        if(msg[1]==\mtcQF, {
                chan= msg[2].rightShift(4);  //nibble high
                valu= msg[2].bitAnd(15);  //nibble low
                if(chan==7, {
                        valu= switch(valu,
                                6, {valu= 96},  //30fps
                                4, {valu= 64},  //30fps drop
                                2, {valu= 32},  //25fps
                                0, {valu= 0}     //24fps
                        );
                });
                a.value(addr.addr.bitAnd(255), chan, valu);
        });
}, \f0mid);
)

//mtc - send (kind of works - wslib quark required)
(
var startSec= 0;
var t= Main.elapsedTime-startSec;
var a= SMPTE(0, 30);
Routine.run({
        var chan= 0, valu= 0;
        inf.do{
                a.newSeconds(Main.elapsedTime-t);
                switch(chan,
                        0, {valu= a.frames.asInteger.bitAnd(15)},
                        1, {valu= a.frames.asInteger.rightShift(4)},
                        2, {valu= a.seconds.asInteger.bitAnd(15)},
                        3, {valu= a.seconds.asInteger.rightShift(4)},
                        4, {valu= a.minutes.asInteger.bitAnd(15)},
                        5, {valu= a.minutes.asInteger.rightShift(4)},
                        6, {valu= a.hours.asInteger.bitAnd(15)},
                        7, {
                                valu= a.hours.asInteger.bitAnd(1).rightShift(4);
                                switch(a.fps,
                                        30, {valu= valu.bitOr(6)},  //30fps
                                        //30fps drop not supported
                                        25, {valu= valu.bitOr(2)},  //25fps
                                        //24, {valu= valu.bitOr(0)}     //24fps
                                );
                        }
                );
                n.sendMsg(\mtcQF, chan.leftShift(4)+valu.bitAnd(15));
                chan= chan+1;
                if(chan==8, {
                        chan= 0;
                });
                (1/(a.fps*4)).wait;
        };
});
)

update 180421: added soft access-point option as well as OSC commands for setting the receiver IP and port
update 181211: removed soft access-point and simplified WiFi setup with the wifimanager library

AttachmentSize
Package icon f0mid_firmware.zip3.92 KB

rpi audio codec

Here's how to set up the proto WM8731 based audio codec module from MikroElektronika and use it with SuperCollider on a Raspberry Pi 3.

Power off the RPi and connect the proto board to the RPi with jump wires like this...

proto               raspberry
-----                -----
sck           ->    rpi 12
miso         ->    rpi 38
mosi         ->    rpi 40
adcl+dacl  ->    rpi 35  //both proto pins go to the same rpi pin
sda           ->    rpi 3
scl            ->    rpi 5
3.3v         ->    rpi 1
gnd          ->    rpi 6

See pinout diagram for help with the RPi GPIO numbering.

Power on the RPi and open a terminal and type...

sudo nano /boot/config.txt

Find and uncomment the first line and add the second...

#dtparam=audio=on
dtoverlay=audioinjector-wm8731-audio

Press ctrl+o to save and ctrl+x to exit.

sudo reboot

Again open a terminal and type...

alsamixer

First press F5 to show all controls, then...
* enable item 'Mic' (space)
* set item 'Mic Boost' to 100 (up arrow key)
* enable item 'Playback Deemphasis' (m key)
* disable item 'ADC High Pass Filter' (m key)
* set item 'Input Mux' to Mic (arrow keys)
* enable item 'Output Mixer HiFi' (m key)

alsamixer-proto

Now you should be able to start jackd with for example...

jackd -P75 -dalsa -dhw:0 -r48000 -p256 -n2

and get decent in/out sound with only 5.3ms latency.

solar powered supercollider

Here's how to run SuperCollider on power coming from the sun...

The main component is a Raspberry Pi Zero with WiFi that at startup creates a wireless access point, starts jackd+SuperCollider and launches a default sound patch.
To play around with the system and change the default sound log on to the access point with a laptop and start livecoding SuperCollider via the terminal or use the standard SC-IDE via VNC. One can for example also set up a couple of OSC responders and let friends log on with their phones to control sounds.

Front...
solarpoweredsupercollider02

Nack...
solarpoweredsupercollider01

The connections are pretty straightforward...

solarpanel -> dcdc converter -> battery -> rpi0 -> soundcard -> amplifier -> speaker(s)

The DC-DC converter is taking the higher voltage coming out of the solar panel (~6V) and turns it into a stable 5V. This is then either charging the battery, or directly powering the Raspberry Pi Zero. Note that the amplifier also needs 5V and here I have taken that from pins 4 and 6 on the RPi.

The powerbank battery is optional and can be omitted but then the solar panel will have to stay in the sun at all times - else the system will turn off or reboot when the power from the panel drops. The battery acts like a reservoir for when clouds are passing by but not only that - it also lets the system be used for a couple of hours in the evening.

Material/modules needed:

* rpi zero w
* 8gb micro sd card
* 5v usb powerbank (best if it can charge and output power at the same time)
* 6v 6watt solar panel ( www.adafruit.com/product/1525 )
* dc-dc converter ( www.olimex.com/Products/Power/DCDC6-16-TO5 )
* usb sound adapter
* pam8403 stereo amplifier module
* two full range speakers
* wooden board, double adhesive tape + various cables and screws

Download Raspbian Jessie (here jessie desktop 2017-07-05-raspbian-jessie.zip) and burn it onto the SD card with balenaEtcher.

Do the usual setup (change default password, activate SSH), optionally activate VNC and then install supercolliderStandaloneRPI1.

To set up a WiFi access point do the following (basically the same as this)...

* sudo apt-get install dnsmasq hostapd
* sudo systemctl stop dnsmasq
* sudo systemctl stop hostapd
* sudo nano /etc/dhcpcd.conf  #and add...
        denyinterfaces wlan0
* sudo nano /etc/network/interfaces  #and make sure wlan0 looks like...
        allow-hotplug wlan0
        iface wlan0 inet static
            address 192.168.4.1
            netmask 255.255.255.0
            network 192.168.4.0
* sudo service dhcpcd restart
* sudo ifdown wlan0
* sudo ifup wlan0
* sudo nano /etc/dnsmasq.conf  #and add the following...
        interface=wlan0
        dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
* sudo nano /etc/hostapd/hostapd.conf  #and add the following...
        interface=wlan0
        driver=nl80211
        ssid=solarsc
        hw_mode=g
        channel=7
        wmm_enabled=0
        macaddr_acl=0
        auth_algs=1
        ignore_broadcast_ssid=0
        wpa=2
        wpa_passphrase=mypass12345
        wpa_key_mgmt=WPA-PSK
        wpa_pairwise=TKIP
        rsn_pairwise=CCMP
* sudo nano /etc/default/hostapd  #and change to the following...
        DAEMON_CONF="/etc/hostapd/hostapd.conf"
* sudo service hostapd start
* sudo service dnsmasq start

Last change the file mycode.scd and add this default sound (tweet0340)...

s.waitForBoot{
        play{a=SinOscFB;Mix(AllpassN ar:a.ar(midicps(Duty.ar(c=a.ar(1/[12,8])+3/24,0,Dseq([0,8,5,1,5,4,5]*round(c*18),inf))+60),c*2)/4)}// #SuperCollider
};

If it is distorting try lowering the volume in alsamixer.

Pages

Subscribe to RSS - supercollider