diff --git a/CMakeLists.txt b/CMakeLists.txt index 432de06c668..b0c202f1105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,7 +164,7 @@ else() # ARM platforms do not have SSE set(SSE2 OFF) endif() -set(AUDIOAPI "default" CACHE STRING "Audio API to use (one of {default,coreaudio,jack,portaudio})") +set(AUDIOAPI "default" CACHE STRING "Audio API to use (one of {default,coreaudio,jack,portaudio,bela})") if (AUDIOAPI STREQUAL jack) # here we check for JACK metadata API diff --git a/HelpSource/Classes/AnalogIn.schelp b/HelpSource/Classes/AnalogIn.schelp new file mode 100644 index 00000000000..a788318a4ec --- /dev/null +++ b/HelpSource/Classes/AnalogIn.schelp @@ -0,0 +1,50 @@ +class:: AnalogIn +summary:: Read data from an analog input of the BELA board +related:: Classes/AnalogOut, Classes/DigitalIn, Classes/DigitalOut, Classes/DigitalIO +categories:: UGens>BELA + + +Description:: + +Reads analog data from an analog input of the BELA board. + +note:: +This UGen only works on BeLa +:: + +classmethods:: + +method::ar + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. This value can be modulated at audiorate. + +argument::mul + +argument::add + +method::kr + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. + +argument::mul + +argument::add + +Examples:: + +code:: +// modulate frequency of a sine oscillator + +( +SynthDef("help-AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( 0 ).exprange( 200, 5000 ), 0, 0.1 ) + ) +}).play; +) +:: + diff --git a/HelpSource/Classes/AnalogOut.schelp b/HelpSource/Classes/AnalogOut.schelp new file mode 100644 index 00000000000..37b6e4a4c09 --- /dev/null +++ b/HelpSource/Classes/AnalogOut.schelp @@ -0,0 +1,55 @@ +class:: AnalogOut +summary:: Write data to an analog output of the BELA board +related:: Classes/AnalogIn, Classes/DigitalIn, Classes/DigitalOut, Classes/DigitalIO +categories:: UGens>BELA + + +Description:: + +Writes analog data to an analog output of the BELA board. + +note:: +This UGen only works on BeLa +:: + +classmethods:: + +method::ar + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. This value can be modulated at audiorate. + +argument::output + +Value to write out to the pin. + +argument::mul + +argument::add + +method::kr + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. + +argument::output + +Value to write out to the pin. + +argument::mul + +argument::add + +Examples:: + +code:: +// write a sine oscillator's output to a pin + +( +SynthDef("help-AnalogOut",{ arg out=0; + AnalogOut.ar( 0 , SinOsc.ar( 10 ) ); +}).play; +) +:: diff --git a/HelpSource/Classes/DigitalIO.schelp b/HelpSource/Classes/DigitalIO.schelp new file mode 100644 index 00000000000..7cfaccdd2f5 --- /dev/null +++ b/HelpSource/Classes/DigitalIO.schelp @@ -0,0 +1,72 @@ +class:: DigitalIO +summary:: Read or write data to a digital pin of the BELA board +related:: Classes/AnalogIn, Classes/AnalogOut, Classes/DigitalIn, Classes/DigitalOut +categories:: UGens>BELA + + +Description:: + +Read or write digital data from or to a digital pin. The pin number of this UGen can be modulated, as well as its I/O mode. + +note:: +This UGen only works on BeLa. +:: + +note:: +If you do not need to change the pin mode or the pin, you should use the UGen link::Classes/DigitalIn:: or link::Classes/DigitalOut:: +:: + +classmethods:: + +method::ar + +The output of this UGen is always the last value read when the digital pin was an input. + +argument::digitalPin + +Digital pin number to write to. Pin numbers begin at 0. This value can be modulated at audiorate. + +argument::output + +Value to write out to the pin - the value will be 1 when the argument is larger than 0, otherwise 0. This value can be modulated at audio rate. + +argument::pinMode + +Value to write out to the pin - the pin will be an input when the argument is smaller than 0.5, otherwise an output. This value can be modulated at audiorate. + +argument::mul + +argument::add + +method::kr + +The output of this UGen is always the last value read when the digital pin was an input. + +argument::digitalPin + +Digital pin number to write to. Pin numbers begin at 0. + +argument::output + +Value to write out to the pin - the value will be 1 when the argument is larger than 0, otherwise 0. + +argument::pinMode + +Value to write out to the pin - the pin will be an input when the argument is smaller than 0.5, otherwise an output. + +argument::mul + +argument::add + +Examples:: + +code:: +// write a sine oscillator's output to a pin, and read the pin value at other times + +( +SynthDef("help-DigitalIO",{ arg out=0; + DigitalIO.ar( 0, SinOsc.ar( 10 ),LFPulse.kr( 0.1 ) ).poll; +}).play; +) + +:: diff --git a/HelpSource/Classes/DigitalIn.schelp b/HelpSource/Classes/DigitalIn.schelp new file mode 100644 index 00000000000..1adc44f9459 --- /dev/null +++ b/HelpSource/Classes/DigitalIn.schelp @@ -0,0 +1,55 @@ +class:: DigitalIn +summary:: Read data from a digital input of the BELA board +related:: Classes/AnalogIn, Classes/AnalogOut, Classes/DigitalOut, Classes/DigitalIO +categories:: UGens>BELA + + +Description:: + +Reads digital data from an digital input of the BELA board. + +note:: +This UGen only works on BeLa. +:: + +note:: +If you want to modulate the pin number, you should use the UGen link::Classes/DigitalIO:: +:: + +classmethods:: + +method::ar + +argument::digitalPin + +Digital pin number to read. Pin numbers begin at 0. This value cannot be modulated. + +argument::mul + +argument::add + +method::kr + +argument::digitalPin + +Digital pin number to read. Pin numbers begin at 0. This value cannot be modulated. + +argument::mul + +argument::add + + +Examples:: + +code:: +// turn on and off a sine oscillator + +( +SynthDef("help-DigitalIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( 500, 0, 0.1 * DigitalIn.ar( 0 ) ) + ) +}).play; +) +:: + diff --git a/HelpSource/Classes/DigitalOut.schelp b/HelpSource/Classes/DigitalOut.schelp new file mode 100644 index 00000000000..d5d59131cc4 --- /dev/null +++ b/HelpSource/Classes/DigitalOut.schelp @@ -0,0 +1,71 @@ +class:: DigitalOut +summary:: Write data to a digital input of the BELA board +related:: Classes/AnalogIn, Classes/AnalogOut, Classes/DigitalIn, Classes/DigitalIO +categories:: UGens>BELA + + +Description:: + +Write digital data to a digital output of the BELA board. + +note:: +This UGen only works on BeLa. +:: + +note:: +If you want to modulate the pin number, you should use the UGen link::Classes/DigitalIO:: +:: + +classmethods:: + +method::ar + +argument::digitalPin + +Digital pin number to write to. Pin numbers begin at 0. This value cannot be modulated. + +argument::output + +Value to write out to the pin - the value will be 1 when the argument is larger than 0, otherwise 0. + +argument::writeMode + +Mode of writing to the output, this can be 0 (using Bela's DigitalWrite and only when value changes) or 1 (using Bela's DigitalWriteOnce). This value cannot be modulated. + +argument::mul + +argument::add + + +method::kr + +argument::digitalPin + +Digital pin number to write to. Pin numbers begin at 0. This value cannot be modulated. + +argument::output + +Value to write out to the pin - the value will be 1 when the argument is larger than 0, otherwise 0. + +argument::writeMode + +Mode of writing to the output, this can be 0 (using Bela's DigitalWrite and only when value changes) or 1 (using Bela's DigitalWriteOnce). This value cannot be modulated. + +argument::mul + +argument::add + + +Examples:: + +code:: +// write a sine oscillator's output to a pin + +( +SynthDef("help-DigitalOut",{ arg out=0; + DigitalOut.ar( 0, SinOsc.ar( 10 ) ); +}).play; +) + +:: + diff --git a/HelpSource/Classes/MultiplexAnalogIn.schelp b/HelpSource/Classes/MultiplexAnalogIn.schelp new file mode 100644 index 00000000000..0ed49fc4095 --- /dev/null +++ b/HelpSource/Classes/MultiplexAnalogIn.schelp @@ -0,0 +1,58 @@ +class:: MultiplexAnalogIn +summary:: Read data from an analog input of the BELA board +related:: Classes/AnalogIn, Classes/AnalogOut, Classes/DigitalIn, Classes/DigitalOut, Classes/DigitalIO +categories:: UGens>BELA + + +Description:: + +Reads analog data from a multiplexed analog input of the BELA board, with the additional Multiplexer board. + +note:: +This UGen only works on BeLa +:: + +classmethods:: + +method::ar + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. This value can be modulated at audiorate. + +argument::muxChannel + +Multiplex channel to read. Pin numbers begin at 0. This value can be modulated at audiorate. + +argument::mul + +argument::add + +method::kr + +argument::analogPin + +Analog pin number to read. Pin numbers begin at 0. + +argument::muxChannel + +Multiplex channel to read. Pin numbers begin at 0. + +argument::mul + +argument::add + +Examples:: + +code:: +// modulate frequency of a sine oscillator + +( +SynthDef("help-MultiplexAnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( MultiplexAnalogIn.ar( 0, 1 ).exprange( 200, 5000 ), 0, 0.1 ) + ) +}).play; +) +:: + diff --git a/README_BELA.md b/README_BELA.md new file mode 100644 index 00000000000..17cedb40eca --- /dev/null +++ b/README_BELA.md @@ -0,0 +1,359 @@ +This code will build on Bela image 3.0 and above (requires libbela and xenomai 3). +====================================== + +See [SuperCollider-on-Bela](https://github.com/BelaPlatform/Bela/wiki/SuperCollider-on-Bela) for general information on SuperCollider on Bela. + +Compiling SuperCollider scsynth on Bela +======================================= + +See [README.md](README.md) for the main SuperCollider readme. + +This file is Dan's, Marije's and Giulio's notes about compiling SC on [Bela](http://bela.io) platform. + +This branch contains that plus other modifications to get the SC source code master branch building. +The main addition in this branch is a **Xenomai/Bela audio driver for scsynth**, to use Bela's ultra-low-latency audio thread *instead* of jack/portaudio, and **plugins to access the analog and digital channels of the Bela-cape** + +> *NOTE:* This guide assumes you have the [Bela image v0.3.0](https://github.com/BelaPlatform/bela-image-builder/releases/tag/v0.3.0) or later. + +> *NOTE:* You need to get the latest version of the Bela code in order for Supercollider to compile. + +All of the commands here are to be executed *on the Bela device itself*. Normally you would SSH to it from a computer connected by USB, in order to do the following stuff. + +Preparation +=========== + +Make sure the system time on the board is up to date. This is ensured by simply opening the Bela IDE in a web browser and loading the page, or from the console, if the board is connected to the internet: + + dpkg-reconfigure tzdata + ntpdate pool.ntp.org + date # make sure this gives the right result + +or from the console, if the board is NOT connected to the internet, running from the host something like + + ssh -tt -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@192.168.7.2 "sudo date -s \"`date '+%Y%m%d %T %z'`\"" + +Get the source code +=================== + +My modified source code is in this git branch here. If your Bela is connected to the network you can grab it directly: + + cd ~/ + git clone --recursive -b bela https://github.com/BelaPlatform/supercollider.git + cd supercollider + +Otherwise, `git clone` it on your computer and `scp` it over. + +### On Bela image v0.2.x(Debian Wheezy): + +This version of Supercollider will not compile on this image. Please update your image: https://github.com/BelaPlatform/bela-image-builder/releases + +### On Bela image v0.3.x(Debian Stretch): + +Before we compile, here are two optional steps to make your workflow faster + +1. installing `ccache` makes repeated builds faster, if you have spare disk space for it. It's especially helpful if you're going to be changing the cmake build scripts. + +``` +apt-get install ccache # requires internet access +mkdir /root/.ccache +echo "cache_dir = '/extrabela/ccache'" >> ~/.ccache/ccache.conf +``` + +2. alternatively, use `distcc` to make all your builds faster by off-loading the actual compilation to your host computer. You need to: +* install a cross-compiler for gcc-6.3 or clang 3.9 on your host (e.g.: [this](http://files.bela.io/gcc/arm-bela-linux-gnueabihf.zip) or download clang 3.9 from the LLVM website for Mac or a `g++-6.3-arm-linux-gnueabihf` package for your Linux distro). +* then follow instructions here to setup a working distcc environment https://forum.bela.io/d/724-distcc-distributed-compilation-with-bela +* on the host, launch `distccd` with something like `distccd --verbose --no-detach --daemon --allow 192.168.7.2 --log-level error --log-file ~/distccd.log` (and then `tail ~/distccd.log` for errors) +* if you get an error during compilation where it cannot find some stdlib includes (e.g.: `#include `), it may well be that `cmake` is trying to force `clang` to use `libc++` (with `-stdlib=libc++`). You can override this by editing your distcc-... executable and adding `-stdlib=libstdc++` at the end of the `$@` line. +* then on the board run the following before the `cmake` commands below: + +``` +export DISTCC_HOSTS="192.168.7.1" +export CC="distcc-clang" # or other as appropriate, see forum post above +export CXX="distcc-clang++" # or other as appropriate, see forum post above +``` + +NOTE: make sure you don't pass `-march=native` to the compiler when using `distcc`, or it will compile natively. Therefore, make sure you do NOT pass `-DNATIVE=ON` to `cmake`, as per below + +Then here's how to build: + + mkdir ~/supercollider/build + cd ~/supercollider/build + +Several options here: + + # here's the command WITHOUT ccache + cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DNOVA_SIMD=ON -DSSE=OFF -DSSE2=OFF -DINSTALL_HELP=OFF -DNO_X11=ON -DSC_QT=OFF -DSC_IDE=OFF -DSC_EL=OFF -DSC_ED=OFF -DSC_VIM=OFF -DSC_HIDAPI=OFF -DSUPERNOVA=OFF -DNO_AVAHI=ON -DNATIVE=ON -DENABLE_TESTSUITE=OFF -DAUDIOAPI=bela + + # or here's the command WITH ccache + cmake .. -DCMAKE_C_COMPILER=/usr/lib/ccache/clang -DCMAKE_CXX_COMPILER=/usr/lib/ccache/clang-3.9 -DNOVA_SIMD=ON -DSSE=OFF -DSSE2=OFF -DNO_X11=ON -DINSTALL_HELP=OFF -DSC_QT=OFF -DSC_IDE=OFF -DSC_EL=OFF -DSC_ED=OFF -DSC_VIM=OFF -DSC_HIDAPI=OFF -DSUPERNOVA=OFF -DNO_AVAHI=ON -DNATIVE=ON -DENABLE_TESTSUITE=OFF -DAUDIOAPI=bela + + # or here's the command WITH distcc (it will infer the compilers from the `export CC CXX` above + cmake .. -DNOVA_SIMD=ON -DSSE=OFF -DSSE2=OFF -DNO_X11=ON -DINSTALL_HELP=OFF -DSC_QT=OFF -DSC_IDE=OFF -DSC_EL=OFF -DSC_ED=OFF -DSC_VIM=OFF -DSC_HIDAPI=OFF -DSUPERNOVA=OFF -DNO_AVAHI=ON -DENABLE_TESTSUITE=OFF -DAUDIOAPI=bela + make + +The `make` step will take a little while, about 30 minutes when using plain `gcc` or `ccache` (you can try `make -j2` or `make -j3` with `distcc`), more like 10 minutes when using `distcc`. It seems it is stuck for a long time at compiling the `BinaryOpUGens.cpp`, but it will get past that. + +Next we install: + + make install + +### Cross-compiling + +On a Linux machine: + +- you need a built bela-image-builder, or just copy the whole filesystem from the baord you want to build for +- download a cross compiler, e.g., for Bela Images v0.3.x : http://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/arm-linux-gnueabihf/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf.tar.xz +- create a file with the cross-toolchain details, e.g.: `~/Toolchain-arm-linux-gnueabihf.cmake` which contains something like this (edit the `SYSROOT` and `GCC_BASE` variables) + +```` +SET(CMAKE_SYSTEM_NAME Linux) +SET(CMAKE_SYSTEM_VERSION 1) + +set(CMAKE_SYSTEM_PROCESSOR arm ) +SET(CMAKE_LIBRARY_ARCHITECTURE arm-linux-gnueabihf) + +# where is the target environment +SET(SYSROOT "$ENV{HOME}/bela-image-builder-stretch/rootfs/") + +# RPATH - a list of directories which is linked into the executable, +# supported on most UNIX systems. It is ignored if RUNPATH is present. +set(FLAGS "${FLAGS} -Wl,-rpath-link,${SYSROOT}/lib/arm-linux-gnueabihf") +set(FLAGS "${FLAGS} -Wl,-rpath-link,${SYSROOT}/usr/lib/arm-linux-gnueabihf") +set(FLAGS "${FLAGS} -Wl,-rpath-link,${SYSROOT}/usr/local/lib") +set(RPATH_FLAGS ${FLAGS}) + +# specify the cross compiler +set(GCC_BASE $ENV{HOME}/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-) +SET(CMAKE_C_COMPILER ${GCC_BASE}gcc) +SET(CMAKE_CXX_COMPILER ${GCC_BASE}g++) +SET(CMAKE_ASM_COMPILER ${GCC_BASE}as) +SET(CMAKE_RANLIB ${CROSS_COMPILER}ranlib) +UNSET(CMAKE_C_FLAGS CACHE) +UNSET(CMAKE_CXX_FLAGS CACHE) + +#link_libraries("-no-pie") +set(COMMON_FLAGS "-no-pie -fno-pie") +set(CMAKE_EXE_LINKER_FLAGS "-no-pie") +set(CMAKE_SHARED_LINKER_FLAGS "-no-pie") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS} ${RPATH_FLAGS}" CACHE STRING "c++ flags" FORCE) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS} ${RPATH_FLAGS}" CACHE STRING "c flags" FORCE) + +set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS}" CACHE INTERNAL "c link flags" FORCE) +set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS}" CACHE INTERNAL "c++ link flags" FORCE) + +SET(CMAKE_SYSROOT ${SYSROOT}) +SET(CMAKE_FIND_ROOT_PATH ${SYSROOT}) + +# search for programs in the build host directories +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # for xeno-config an bela-config we have to manually override this there +# for libraries and headers in the target directories +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +``` +- create a couple of symlinks to make things easier: +``` +ln -s ~/bela-image-builder-stretch/rootfs/usr/xenomai /usr/xenomai +ln -s ~/bela-image-builder-stretch/rootfs/lib/arm-linux-gnueabihf/ /lib/arm-linux-gnueabihf +``` +- configure: +``` +cd supercollider +mkdir build && cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-arm-linux-gnueabihf.cmake -DNOVA_SIMD=ON -DSSE=OFF -DSSE2=OFF -DINSTALL_HELP=OFF -DNO_X11=ON -DSC_QT=OFF -DSC_IDE=OFF -DSC_EL=OFF -DSC_ED=OFF -DSC_VIM=OFF -DSC_HIDAPI=OFF -DSUPERNOVA=OFF -DNO_AVAHI=ON -DENABLE_TESTSUITE=OFF -DAUDIOAPI=bela -DSC_ABLETON_LINK=ON -DCMAKE_INSTALL_PREFIX=/usr/ +``` +- build: +``` +make -j20 +``` +- create a debian package to be installed on the board: +``` +cpack -G DEB -D CPACK_PACKAGE_CONTACT="Your Name " -D CPACK_DEBIAN_PACKAGE_ARCHITECTURE="armhf" -D CPACK_CMAKE_GENERATOR=Ninja +``` +(the `CPACK_CMAKE_GENERATOR=Ninja` is a trick to prevent the `preinstall` target from rebuilding the whole thing slower (see [here](https://stackoverflow.com/a/57530945/2958741)). + +Running it +========== + +Just run the executable like this: + + scsynth -u 57110 -z 16 + +The `-u` flag tells it which UDP port to listen on, and with the `-z` flag we choose scsynth's internal blocksize. We need to do this because scsynth's default internal buffer size (64) is bigger than the hardware buffer size (16), so dividing hardware by internal returned 0 buffers per callback. To make it run, you need to add the command-line argument "-z 16" (or presumably make the hardware buffer size bigger). + +So now you should have scsynth running on the device. You should be able to send OSC commands to it from SuperCollider running on your main computer: + + // These commands are to be run in SUPERCOLLIDER running on your MAIN computer. (I guess you could run them on the device too if you wanted.) + Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); + s.initTree; + s.startAliveThread; + SynthDef("funsound", { Out.ar(0, 0.5 * Pan2.ar(SinOsc.ar(LFNoise1.kr(2).exprange(100, 1000)), LFNoise1.kr(2))) }).add; + x = Synth("funsound"); + SynthDef("bish", { Out.ar(0, PinkNoise.ar * EnvGen.ar(Env.perc, Impulse.kr(2))) }).add; + y = Synth("bish"); + + // then when you want to stop the sounds: + x.free; + y.free; + + // You could use this to test mic input - be careful of feedback! + SynthDef("mic", { Out.ar(0, SoundIn.ar([0,1])) }).add; + z = Synth("mic"); + z.free; + +BELA I/O's +========== + +I/O support for the Bela is implemented. + +The startup flag ```-J``` defines how many analog input channels will be enabled, the startup flag ```-K``` how many analog output channels will be enabled, the startup flag ```-G``` how many digital channels will be enabled; by default all are set to 0. + +So for all analog and digital channels to be enabled run scsynth like this: + + scsynth -u 57110 -z 16 -J 8 -K 8 -G 16 + +To use the analog channels all as audio I/O + + scsynth -u 57110 -z 16 -J 8 -K 8 -G 16 -i 10 -o 10 + +This will start scsynth with 10 inputs and outputs, inputs/outputs 2 - 9 are the analog pins + +To use the analog channels all via the UGens only: + + scsynth -u 57110 -z 16 -J 8 -K 8 -G 16 -i 2 -o 2 + +This will start scsynth with 2 audio inputs and outputs, the analog I/O will only be accessible through UGens, but are all enabled. + +If you want higher sample rates of the analog I/O, you can set the number of channels to 4; the number of available channels is then 4. + + scsynth -u 57110 -z 16 -J 4 -K 4 -G 16 -i 2 -o 2 + +The amount of analog inputs and outputs actually used will be rounded to a multiple of 4, so the actual options are 0, 4 or 8 analog channels. This is because in SuperCollider we cannot sample the analog channels faster than audio rate (right now). + +![Channel explanation](bela_analog_audio_io.png) + +The ```ServerOptions``` class has appropriate variables to set the command line arguments, so you can set them with (but also see the comment below): + + s.options.numAnalogInChannels = 8; + s.options.numAnalogOutChannels = 8; + s.options.numDigitalChannels = 16; + + +The UGens ```AnalogIn```, ```AnalogOut```, ```DigitalIn```, ```DigitalOut```, ```DigitalIO``` give access to the pins; they all have helpfiles with examples of usage. + + +Examples +====================================================== + +Example files are available in the folder ```examples/bela```, and will be installed to ```/usr/local/share/SuperCollider/examples/bela```. + + +Running scsynth *and* sclang +====================================================== + +You can start the server as normal from the language. To set the settings for the analog I/O you should set them to some reasonable values. The defaults are to not pass the flags to scsynth. + + s = Server.default; + + s.options.numAnalogInChannels = 8; + s.options.numAnalogOutChannels = 8; + s.options.numDigitalChannels = 16; + + s.options.blockSize = 16; + s.options.numInputBusChannels = 2; + s.options.numOutputBusChannels = 2; + + s.waitForBoot({ + "THE SERVER IS BOOTED! Start of my actually interesting code".postln; + }); + +Alternatively, you can start scsynth manually, and then connect to it from a separate instance of sclang. + +So make one connection and start scsynth: + + scsynth -u 57110 -z 16 -J 8 -K 8 -G 16 -i 2 -o 2 + +And another to start sclang: + + sclang examples/bela/bela_example_analogin_2.scd + +Options Overview +---------------------------- + +Here is a breakdown of the options for running *scsynth* and how to set them up with either *scsynth* or *sclang* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
paramscsynthsclang
audio computation block size-z # s.options.blockSize = 16;
number analog input channels enabled [0, 4, 8]-J #s.options.numAnalogInChannels = 0;
number analog output channels enabled [0, 4, 8]-K #s.options.numAnalogOutChannels = 0;
number digital channels enabled-G #s.options.numDigitalChannels = 16;
number of input buffer channels-i #s.options.numInputBusChannels = 2;
number of output buffer channels-o #s.options.numOutputBusChannels = 2;
+ + +Monitoring its performance +====================================================== +Here's a tip on how to check CPU load and "mode switches" for the running program, to make sure it's running properly in realtime etc. (A "mode switch" is something to be avoided: it means the code is dropping out of the Xenomai real-time execution and into normal Linux mode, which can be caused by certain operations in the realtime thread.) + + watch -n 0.5 cat /proc/xenomai/sched/stat + +which produces output like: + +
+Every 0.5s: cat /proc/xenomai/sched/stat                                                                                                               bela: Fri Jan 11 00:39:12 2019
+
+CPU  PID    MSW        CSW        XSC        PF    STAT       %CPU  NAME
+  0  0      0          835043     0          0     00018000   79.0  [ROOT]
+  0  14390  5          5          14         1     000480c0    0.0  scsynth
+  0  14404  34655      69562      69517      0     00048042    4.4  mAudioSyncSignalTask
+  0  14405  1          69782      104649     0     00048046   14.3  bela-audio
+  0  0      0          744555     0          0     00000000    0.8  [IRQ16: [timer]]
+  0  0      0          34899      0          0     00000000    1.0  [IRQ181: rtdm_pruss_irq_irq]
+
+ +the "MSW" column indicates mode switches; this number should NEVER increase in the bela-audio thread. It is fine if it increases on a task that runs occasionally, but keep in mind that each mode switch carries an additional overhead. + +Optional: Bonus level: Even more plugins (sc3-plugins) +====================================================== + +SuperCollider comes with a built-in set of UGen plugins but there's an extra set in the community **sc3-plugins** project. So if you want, you can also install those: + + cd /extrabela + git clone --recursive https://github.com/supercollider/sc3-plugins.git + cd sc3-plugins + mkdir build + cd build + cmake -DSC_PATH=~/supercollider -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc-4.8 -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++-4.8 -DCMAKE_C_FLAGS="-march=armv7-a -mtune=cortex-a8 -mfloat-abi=hard -mfpu=neon -O2" -DCMAKE_CPP_FLAGS="-march=armv7-a -mtune=cortex-a8 -mfloat-abi=hard -mfpu=neon -O2" .. + make + make install + +These are basically just the instructions from the README of [the sc3-plugins project](https://github.com/supercollider/sc3-plugins/). diff --git a/SCClassLibrary/Common/Audio/bela/BELAUGens.sc b/SCClassLibrary/Common/Audio/bela/BELAUGens.sc new file mode 100644 index 00000000000..79bd9fa7629 --- /dev/null +++ b/SCClassLibrary/Common/Audio/bela/BELAUGens.sc @@ -0,0 +1,94 @@ +/* + * BELAUGens to access the analog and digital I/O + * created by nescivi, (c) 2016 + * https://www.nescivi.eu + */ + +/* input: id of analog pin to read; can be modulated at audiorate + * output: value of analog analogPin + */ +MultiplexAnalogIn : UGen { + signalRange { ^\unipolar } + + *ar { arg analogPin = 0, muxChannel=0, mul=1.0, add=0.0; + ^this.multiNew('audio', analogPin, muxChannel ).madd(mul,add) + } + *kr { arg analogPin = 0, muxChannel=0, mul=1.0, add=0.0; + ^this.multiNew('control', analogPin, muxChannel ).madd(mul,add) + } +} + + +/* input: id of analog pin to read; can be modulated at audiorate + * output: value of analog analogPin + */ +AnalogIn : UGen { + signalRange { ^\unipolar } + + *ar { arg analogPin = 0, mul=1.0, add=0.0; + ^this.multiNew('audio', analogPin ).madd(mul,add) + } + *kr { arg analogPin = 0, mul=1.0, add=0.0; + ^this.multiNew('control', analogPin ).madd(mul,add) + } +} + +/* input 1: id of analog pin to read; can be modulated at audiorate + * input 2: value to write out + * output: none + */ +AnalogOut : UGen { + *ar { arg analogPin = 0, output=0, mul=1.0, add=0.0; + ^this.multiNew('audio', analogPin, output ).madd(mul,add) + } + *kr { arg analogPin = 0, output=0, mul=1.0, add=0.0; + ^this.multiNew('control', analogPin, output ).madd(mul,add) + } + numOutputs { ^0 } + writeOutputSpecs {} +} + +/* input: id of digital pin to read; cannot be modulated + * output: value of digital pin + */ +DigitalIn : UGen { + signalRange { ^\unipolar } + + *ar { arg digitalPin = 0, mul=1.0, add=0.0; + ^this.multiNew('audio', digitalPin ).madd(mul,add) + } + *kr { arg digitalPin = 0, mul=1.0, add=0.0; + ^this.multiNew('control', digitalPin ).madd(mul,add) + } +} + +/* input 1: id of digital pin to read; cannot be modulated + * input 2: value to write out + * output: none + */ +DigitalOut : UGen { + *ar { arg digitalPin = 0, output=0, writeMode=0, mul=1.0, add=0.0; + ^this.multiNew('audio', digitalPin, output, writeMode ).madd(mul,add) + } + *kr { arg digitalPin = 0, output=0, writeMode=0, mul=1.0, add=0.0; + ^this.multiNew('control', digitalPin, output, writeMode ).madd(mul,add) + } + numOutputs { ^0 } + writeOutputSpecs {} +} + +/* input 1: id of digital pin to read; cannot be modulated + * input 2: value to write out + * input 3: pin mode ( < 0.5 = input, otherwise output) + * output: value of digital pin (last read value) + */ +DigitalIO : UGen { + signalRange { ^\unipolar } + + *ar { arg digitalPin = 0, output=0, pinMode=0, mul=1.0, add=0.0; + ^this.multiNew('audio', digitalPin, output, pinMode ).madd(mul,add) + } + *kr { arg digitalPin = 0, output=0, pinMode=0, mul=1.0, add=0.0; + ^this.multiNew('control', digitalPin, output, pinMode ).madd(mul,add) + } +} diff --git a/SCClassLibrary/Common/Control/Server.sc b/SCClassLibrary/Common/Control/Server.sc index ed3ec6b7dd1..3cf4ca868ba 100644 --- a/SCClassLibrary/Common/Control/Server.sc +++ b/SCClassLibrary/Common/Control/Server.sc @@ -58,6 +58,19 @@ ServerOptions { var <>safetyClipThreshold; + // extension for BELA + var <>numAnalogInChannels; + var <>numAnalogOutChannels; + var <>numDigitalChannels; + var <>headphoneLevel; + var <>pgaGainLeft; + var <>pgaGainRight; + var <>speakerMuted; + var <>dacLevel; + var <>adcLevel; + var <>numMultiplexChannels; + var <>belaPRU; + *initClass { defaultValues = IdentityDictionary.newFrom( ( @@ -101,6 +114,17 @@ ServerOptions { recBufSize: nil, bindAddress: "127.0.0.1", safetyClipThreshold: 1.26 // ca. 2 dB + numAnalogInChannels: 2, + numAnalogOutChannels: 2, + numDigitalChannels: 16, + headphoneLevel: -6, + pgaGainLeft: 10, + pgaGainRight: 10, + speakerMuted: 0, + dacLevel: 0, + adcLevel: 0, + numMultiplexChannels: 0, + belaPRU: 1, ) ) } @@ -229,6 +253,40 @@ ServerOptions { }); if (thisProcess.platform.name === \osx && Server.program.asString.endsWith("supernova").not && safetyClipThreshold.notNil, { o = o ++ " -s " ++ safetyClipThreshold; + + // additions for BELA + if (numAnalogInChannels.notNil, { + o = o ++ " -J " ++ numAnalogInChannels; + }); + if (numAnalogOutChannels.notNil, { + o = o ++ " -K " ++ numAnalogOutChannels; + }); + if (numDigitalChannels.notNil, { + o = o ++ " -G " ++ numDigitalChannels; + }); + if (headphoneLevel.notNil, { + o = o ++ " -Q " ++ headphoneLevel; + }); + if (pgaGainLeft.notNil, { + o = o ++ " -X " ++ pgaGainLeft; + }); + if (pgaGainRight.notNil, { + o = o ++ " -Y " ++ pgaGainRight; + }); + if (speakerMuted.notNil, { + o = o ++ " -s " ++ speakerMuted; + }); + if (dacLevel.notNil, { + o = o ++ " -x " ++ dacLevel; + }); + if (adcLevel.notNil, { + o = o ++ " -y " ++ adcLevel; + }); + if (numMultiplexChannels.notNil, { + o = o ++ " -g " ++ numMultiplexChannels; + }); + if (belaPRU.notNil, { + o = o ++ " -T " ++ belaPRU; }); ^o } diff --git a/bela_analog_audio_io.png b/bela_analog_audio_io.png new file mode 100644 index 00000000000..88befd3e40f Binary files /dev/null and b/bela_analog_audio_io.png differ diff --git a/cmake_modules/FindBela.cmake b/cmake_modules/FindBela.cmake new file mode 100644 index 00000000000..2581e815937 --- /dev/null +++ b/cmake_modules/FindBela.cmake @@ -0,0 +1,79 @@ +# - Try to find Bela (BeagleRT) +# Once done this will define +# +# BELA_FOUND - system has bela +# BELA_INCLUDE_DIRS - the bela include directory +# BELA_SOURCES - the bela source files to compile in +# BELA_LIBRARIES - Link these too please +# BELA_DEFINITIONS - Compiler switches required for using bela +# +# Copyright (c) 2008 Andreas Schneider +# Modified for other libraries by Lasse Kärkkäinen +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +if (BELA_CFLAGS AND BELA_CXXFLAGS AND BELA_LDFLAGS) + # in cache already + set(BELA_FOUND TRUE) +else (BELA_CFLAGS AND BELA_CXXFLAGS AND BELA_LDFLAGS) + # Bela comes with its own ...-config program to get configuration flags + if (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + # if cross compiling, we want to find this program only from the sysroot + set(CACHED ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + endif (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + find_program(BELA_CONFIG + NAMES + bela-config + PATHS + /root/Bela/resources/bin + $ENV{BELA_ROOT}/resources/bin + /usr/local/bin + /usr/bin + ) + if (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + # restore the previous value + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${CACHED}) + endif (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) +message("Searching for BELA man: config ${BELA_CONFIG}") + + if (BELA_CONFIG) + execute_process(COMMAND ${BELA_CONFIG} --defines OUTPUT_VARIABLE BELA_DEFINITIONS) + string(STRIP "${BELA_DEFINITIONS}" BELA_DEFINITIONS) + execute_process(COMMAND ${BELA_CONFIG} --includes OUTPUT_VARIABLE BELA_INCLUDE_DIRS) + string(STRIP "${BELA_INCLUDE_DIRS}" BELA_INCLUDE_DIRS) + execute_process(COMMAND ${BELA_CONFIG} --libraries OUTPUT_VARIABLE BELA_LIBRARIES) + string(STRIP "${BELA_LIBRARIES}" BELA_LIBRARIES) + execute_process(COMMAND ${BELA_CONFIG} --cflags OUTPUT_VARIABLE BELA_C_FLAGS) + string(STRIP "${BELA_C_FLAGS}" BELA_C_FLAGS) + execute_process(COMMAND ${BELA_CONFIG} --cxxflags OUTPUT_VARIABLE BELA_CXX_FLAGS) + string(STRIP "${BELA_CXX_FLAGS}" BELA_CXX_FLAGS) + SET(BELA_CXX_FLAGS "${BELA_CXX_FLAGS} -DBELA_DONT_INCLUDE_UTILITIES") + endif (BELA_CONFIG) + + if (BELA_CONFIG) + set(BELA_FOUND TRUE) + endif (BELA_CONFIG) + + if (BELA_FOUND) + if (NOT BELA_FIND_QUIETLY) + execute_process(COMMAND ${BELA_CONFIG} --prefix OUTPUT_VARIABLE BELA_PREFIX) + message(STATUS "Found Bela: ${BELA_PREFIX}") + message(STATUS "BELA_DEFINITIONS: ${BELA_DEFINITIONS}") + message(STATUS "BELA_INCLUDE_DIRS: ${BELA_INCLUDE_DIRS}") + message(STATUS "BELA_LIBRARIES: ${BELA_LIBRARIES}") + message(STATUS "BELA_C_FLAGS: ${BELA_C_FLAGS}") + message(STATUS "BELA_CXX_FLAGS: ${BELA_CXX_FLAGS}") + endif (NOT BELA_FIND_QUIETLY) + else (BELA_FOUND) + if (BELA_FIND_REQUIRED) + message(FATAL_ERROR "Could not find BELA") + endif (BELA_FIND_REQUIRED) + endif (BELA_FOUND) +# show the BELA_ variables only in the advanced view +mark_as_advanced(BELA_CFLAGS BELA_CXXFLAGS BELA_LDFLAGS) + +endif (BELA_CFLAGS AND BELA_CXXFLAGS AND BELA_LDFLAGS) diff --git a/cmake_modules/FindXenomai.cmake b/cmake_modules/FindXenomai.cmake new file mode 100644 index 00000000000..89c03f9dab4 --- /dev/null +++ b/cmake_modules/FindXenomai.cmake @@ -0,0 +1,156 @@ +# - Try to find xenomai +# Once done this will define +# +# XENOMAI_FOUND - system has xenomai +# XENOMAI_INCLUDE_DIRS - the xenomai include directory +# XENOMAI_LIBRARIES - Link these to use xenomai +# XENOMAI_DEFINITIONS - Compiler switches required for using xenomai +# +# Copyright (c) 2008 Andreas Schneider +# Modified for other libraries by Lasse Kärkkäinen +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +# https://cmake.org/Wiki/CMakeMacroParseArguments + +MACRO(PARSE_ARGUMENTS prefix arg_names option_names) + SET(DEFAULT_ARGS) + FOREACH(arg_name ${arg_names}) + SET(${prefix}_${arg_name}) + ENDFOREACH(arg_name) + FOREACH(option ${option_names}) + SET(${prefix}_${option} FALSE) + ENDFOREACH(option) + + SET(current_arg_name DEFAULT_ARGS) + SET(current_arg_list) + FOREACH(arg ${ARGN}) + SET(larg_names ${arg_names}) + LIST(FIND larg_names "${arg}" is_arg_name) + IF (is_arg_name GREATER -1) + SET(${prefix}_${current_arg_name} ${current_arg_list}) + SET(current_arg_name ${arg}) + SET(current_arg_list) + ELSE (is_arg_name GREATER -1) + SET(loption_names ${option_names}) + LIST(FIND loption_names "${arg}" is_option) + IF (is_option GREATER -1) + SET(${prefix}_${arg} TRUE) + ELSE (is_option GREATER -1) + SET(current_arg_list ${current_arg_list} ${arg}) + ENDIF (is_option GREATER -1) + ENDIF (is_arg_name GREATER -1) + ENDFOREACH(arg) + SET(${prefix}_${current_arg_name} ${current_arg_list}) +ENDMACRO(PARSE_ARGUMENTS) + +# LIST_FILTER( [ ...] +# [OUTPUT_VARIABLE ]) +# Removes items from which do not match any of the specified +# regular expressions. An optional argument OUTPUT_VARIABLE +# specifies a variable in which to store the matched items instead of +# updating +# As regular expressions can not be given to macros (see bug #5389), we pass +# variable names whose content is the regular expressions. +# Note that this macro requires PARSE_ARGUMENTS macro, available here: +# http://www.cmake.org/Wiki/CMakeMacroParseArguments +MACRO(LIST_FILTER) + PARSE_ARGUMENTS(LIST_FILTER "OUTPUT_VARIABLE" "" ${ARGV}) + # Check arguments. + LIST(LENGTH LIST_FILTER_DEFAULT_ARGS LIST_FILTER_default_length) + IF(${LIST_FILTER_default_length} EQUAL 0) + MESSAGE(FATAL_ERROR "LIST_FILTER: missing list variable.") + ENDIF(${LIST_FILTER_default_length} EQUAL 0) + IF(${LIST_FILTER_default_length} EQUAL 1) + MESSAGE(FATAL_ERROR "LIST_FILTER: missing regular expression variable.") + ENDIF(${LIST_FILTER_default_length} EQUAL 1) + # Reset output variable + IF(NOT LIST_FILTER_OUTPUT_VARIABLE) + SET(LIST_FILTER_OUTPUT_VARIABLE "LIST_FILTER_internal_output") + ENDIF(NOT LIST_FILTER_OUTPUT_VARIABLE) + SET(${LIST_FILTER_OUTPUT_VARIABLE}) + # Extract input list from arguments + LIST(GET LIST_FILTER_DEFAULT_ARGS 0 LIST_FILTER_input_list) + LIST(REMOVE_AT LIST_FILTER_DEFAULT_ARGS 0) + FOREACH(LIST_FILTER_item ${${LIST_FILTER_input_list}}) + FOREACH(LIST_FILTER_regexp_var ${LIST_FILTER_DEFAULT_ARGS}) + FOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}}) + IF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp}) + LIST(APPEND ${LIST_FILTER_OUTPUT_VARIABLE} ${LIST_FILTER_item}) + ENDIF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp}) + ENDFOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}}) + ENDFOREACH(LIST_FILTER_regexp_var) + ENDFOREACH(LIST_FILTER_item) + # If OUTPUT_VARIABLE is not specified, overwrite the input list. + IF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output") + SET(${LIST_FILTER_input_list} ${${LIST_FILTER_OUTPUT_VARIABLE}}) + ENDIF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output") +ENDMACRO(LIST_FILTER) + + +if (XENOMAI_LIRARIES AND XENOMAI_INCLUDE_DIRS AND XENOMAI_DEFINITIONS) + # in cache already + set(XENOMAI_FOUND TRUE) +else (XENOMAI_LIRARIES AND XENOMAI_INCLUDE_DIRS AND XENOMAI_DEFINITIONS) + # Xenomai comes with its own ...-config program to get cflags and ldflags + if (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + # if cross compiling, we want to find this program only from the sysroot + set(CACHED ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + endif (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + find_program(XENOMAI_XENO_CONFIG + NAMES + xeno-config + PATHS + ${_XENOMAI_INCLUDEDIR} + /usr/xenomai/bin + /usr/local/bin + /usr/bin + ) + if (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + # restore the previous value + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${CACHED}) + endif (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + + if (XENOMAI_XENO_CONFIG) + set(XENOMAI_FOUND TRUE) + execute_process(COMMAND ${XENOMAI_XENO_CONFIG} --skin=posix --cflags OUTPUT_VARIABLE XENOMAI_CFLAGS) + string(STRIP "${XENOMAI_CFLAGS}" XENOMAI_CFLAGS) + # use grep to separate out defines and includes + execute_process( + COMMAND bash -c "A= ; for a in ${XENOMAI_CFLAGS}; do echo $a | grep -q \"\\-D.*\" && A=\"$A $a\"; done; echo $A" + OUTPUT_VARIABLE XENOMAI_DEFINITIONS) + string(STRIP "${XENOMAI_DEFINITIONS}" XENOMAI_DEFINITIONS) + + execute_process( + COMMAND bash -c "A= ; for a in ${XENOMAI_CFLAGS}; do echo $a | grep -q \"\\-I.*\" && A=\"$A `echo $a|sed s/-I//`;\"; done; echo $A" + OUTPUT_VARIABLE XENOMAI_INCLUDE_DIRS) + string(STRIP "${XENOMAI_INCLUDE_DIRS}" XENOMAI_INCLUDE_DIRS) + + execute_process( + COMMAND ${XENOMAI_XENO_CONFIG} --skin=posix --ldflags --no-auto-init + COMMAND sed "s/-Wl,@.*wrappers//g" + OUTPUT_VARIABLE XENOMAI_LIBRARIES) + string(STRIP "${XENOMAI_LIBRARIES}" XENOMAI_LIBRARIES) + endif (XENOMAI_XENO_CONFIG) + + if (XENOMAI_FOUND) + if (NOT XENOMAI_FIND_QUIETLY) + execute_process(COMMAND ${XENOMAI_XENO_CONFIG} --prefix OUTPUT_VARIABLE XENOMAI_PREFIX) + message(STATUS "Found xenomai: ${XENOMAI_PREFIX}") + message(STATUS "XENOMAI_LIBRARIES: ${XENOMAI_LIBRARIES}") + message(STATUS "XENOMAI_INCLUDE_DIRS: ${XENOMAI_INCLUDE_DIRS}") + message(STATUS "XENOMAI_DEFINITIONS: ${XENOMAI_DEFINITIONS}") + endif (NOT XENOMAI_FIND_QUIETLY) + else (XENOMAI_FOUND) + if (XENOMAI_FIND_REQUIRED) + message(FATAL_ERROR "Could not find XENOMAI") + endif (XENOMAI_FIND_REQUIRED) + endif (XENOMAI_FOUND) +# show the XENOMAI_ variables only in the advanced view +mark_as_advanced(XENOMAI_LIRARIES XENOMAI_INCLUDE_DIRS XENOMAI_DEFINITIONS) + +endif (XENOMAI_LIRARIES AND XENOMAI_INCLUDE_DIRS AND XENOMAI_DEFINITIONS) diff --git a/examples/bela/bela_example_analogin.scd b/examples/bela/bela_example_analogin.scd new file mode 100644 index 00000000000..88960c18663 --- /dev/null +++ b/examples/bela/bela_example_analogin.scd @@ -0,0 +1,25 @@ +s = Server.default; + +s.options.numAnalogInChannels = 8; +s.options.numAnalogOutChannels = 8; +s.options.numDigitalChannels = 16; + +s.options.blockSize = 16; +s.options.numInputBusChannels = 2; +s.options.numOutputBusChannels = 2; + +s.options.postln; + +s.waitForBoot({ + // modulate frequency of a sine oscillator + ( + SynthDef("help-AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( DC.ar( 0 ) ).exprange( 200, 5000 ), 0, 0.1 ) + ) + }).send(s); + ); + + s.sync; + Synth.new("help-AnalogIn", target: s); +}); diff --git a/examples/bela/bela_example_analogin_2.scd b/examples/bela/bela_example_analogin_2.scd new file mode 100644 index 00000000000..fb509ffb307 --- /dev/null +++ b/examples/bela/bela_example_analogin_2.scd @@ -0,0 +1,20 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +fork{ + s.sync; + + ( + SynthDef("help-AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( DC.ar( 0 ) ).exprange( 200, 5000 ), 0, 0.1 ) + ) + }).send(s); + ); + + s.sync; + Synth.new("help-AnalogIn", target: s).postln; +}; + +s.freeAll; \ No newline at end of file diff --git a/examples/bela/bela_example_analogout.scd b/examples/bela/bela_example_analogout.scd new file mode 100644 index 00000000000..afd727908ec --- /dev/null +++ b/examples/bela/bela_example_analogout.scd @@ -0,0 +1,16 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +fork{ + s.sync; + + ( + SynthDef("help-AnalogOut",{ arg out=0; + AnalogOut.ar( DC.ar( 0 ), SinOsc.ar( 10, 0, 0.5, 0.5 ) ); + }).send(s); + ); + + s.sync; + Synth.new("help-AnalogOut", target: s).postln; +}; diff --git a/examples/bela/bela_example_digital.scd b/examples/bela/bela_example_digital.scd new file mode 100644 index 00000000000..72a0765f3fc --- /dev/null +++ b/examples/bela/bela_example_digital.scd @@ -0,0 +1,18 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +fork{ + s.sync; + + ( + SynthDef("help-DigitalIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( 500, 0, 0.1 * DigitalIn.ar( 0 ) ) + ) + }).send(s); + ); + + s.sync; + Synth.new("help-DigitalIn", target: s).postln; +}; diff --git a/examples/bela/bela_example_digitalio.scd b/examples/bela/bela_example_digitalio.scd new file mode 100644 index 00000000000..c0231d5d85d --- /dev/null +++ b/examples/bela/bela_example_digitalio.scd @@ -0,0 +1,16 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +fork{ + s.sync; + + ( + SynthDef("help-DigitalIO",{ arg out=0; + DigitalIO.ar( DC.ar( 0 ), SinOsc.ar( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; + }).send(s); + ); + + s.sync; + Synth.new("help-DigitalIO", target: s).postln; +}; diff --git a/examples/bela/bela_example_digitalout.scd b/examples/bela/bela_example_digitalout.scd new file mode 100644 index 00000000000..522306fa389 --- /dev/null +++ b/examples/bela/bela_example_digitalout.scd @@ -0,0 +1,16 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +fork{ + s.sync; + + ( + SynthDef("help-DigitalOut",{ arg out=0; + DigitalOut.ar( 0, SinOsc.ar( 10 ) ); + }).send(s); + ); + + s.sync; + Synth.new("help-DigitalOut", target: s).postln; +}; diff --git a/examples/bela/bela_start_scsynth.scd b/examples/bela/bela_start_scsynth.scd new file mode 100644 index 00000000000..16860ecdd3d --- /dev/null +++ b/examples/bela/bela_start_scsynth.scd @@ -0,0 +1,21 @@ +s = Server.default; + +s.options.numAnalogInChannels = 8; +s.options.numAnalogOutChannels = 8; +s.options.numDigitalChannels = 16; + +s.options.pgaGainLeft = 4; +s.options.pgaGainRight = 5; +s.options.headphoneLevel = -8; +s.options.speakerMuted = 0; +s.options.dacLevel = -5; +s.options.adcLevel = -3; +s.options.numMultiplexChannels = 0; + +s.options.blockSize = 16; +s.options.numInputBusChannels = 2; +s.options.numOutputBusChannels = 2; + +s.options.postln; + +s.boot; diff --git a/examples/bela/bela_start_scsynth_2.scd b/examples/bela/bela_start_scsynth_2.scd new file mode 100644 index 00000000000..aeb26d865de --- /dev/null +++ b/examples/bela/bela_start_scsynth_2.scd @@ -0,0 +1,22 @@ +s = Server.default; + +s.options.numAnalogInChannels = 8; +s.options.numAnalogOutChannels = 8; +s.options.numDigitalChannels = 16; + +s.options.pgaGainLeft = 4; +s.options.pgaGainRight = 5; +s.options.headphoneLevel = -8; +s.options.speakerMuted = 1; +s.options.dacLevel = -5; +s.options.adcLevel = -3; +s.options.numMultiplexChannels = 6; +s.options.belaPRU = 1; + +s.options.blockSize = 16; +s.options.numInputBusChannels = 2; +s.options.numOutputBusChannels = 2; + +s.options.postln; + +s.boot; diff --git a/examples/bela/bela_test_cases.scd b/examples/bela/bela_test_cases.scd new file mode 100644 index 00000000000..d1209ad7e8e --- /dev/null +++ b/examples/bela/bela_test_cases.scd @@ -0,0 +1,351 @@ +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + + +/// AnalogIn + +// ak +( +SynthDef("AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( 0 ).poll.exprange( 200, 5000 ), 0, 0.1 ) + ) +}).send(s); +); + +a = Synth.new("AnalogIn", target: s).postln; +a.free; + +// ak +( +SynthDef("AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( + Stepper.kr(Impulse.kr(1), 0, 0, 7, 1 ).poll + ).poll.exprange( 200, 5000 ), 0, 0.1 ) + ) +}).send(s); +); + +a = Synth.new("AnalogIn", target: s).postln; +a.free; + +// kk +( +SynthDef("AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.kr( + 0 + ).poll.exprange( 200, 5000 ), 0, 0.1 ) + ) +}).send(s); +); + +a = Synth.new("AnalogIn", target: s).postln; +a.free; + +// aa +( +SynthDef("AnalogIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( AnalogIn.ar( Stepper.ar(Impulse.ar(1), 0, 0, 7, 1 ).poll ).poll.exprange( 200, 5000 ), 0, 0.1 ) + ) +}).send(s); +); + +a = Synth.new("AnalogIn", target: s).postln; +a.free; + + +/// AnalogOut + +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + + +// aaa +( +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.ar( DC.ar( 0 ), SinOsc.ar( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free + +// aka +( +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.ar( 0, SinOsc.ar( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free; + +// aak +( +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.ar( DC.ar( 0 ), SinOsc.kr( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free + +// kk +( // diverted to kk - AnalogOut becomes .kr effectively +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.ar( 0, SinOsc.kr( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free; + + +// kk +( +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.kr( 0, SinOsc.kr( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free; + +// kk + warning +( +SynthDef("AnalogOut",{ arg out=0; + AnalogOut.kr( 0, SinOsc.ar( 10, 0, 0.5, 0.5 ) ); +}).send(s); +); + +a = Synth.new("AnalogOut", target: s).postln; +a.free; + + + +// DigitalIn + +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + + +// a +( +SynthDef("DigitalIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( 500, 0, 0.1 * DigitalIn.ar( 0 ) ) + ) +}).send(s); +); + +a = Synth.new("DigitalIn", target: s).postln; +a.free; + +// k +( +SynthDef("DigitalIn",{ arg out=0; + Out.ar(out, + SinOsc.ar( 500, 0, 0.1 * DigitalIn.kr( 0 ) ) + ) +}).send(s); +); + +a = Synth.new("DigitalIn", target: s).postln; +a.free; + + + +// DigitalOut + +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + +// a +( +SynthDef("DigitalOut",{ arg out=0; + DigitalOut.ar( 0, SinOsc.ar( 10 ) ); +}).send(s); +); + +a = Synth.new("DigitalOut", target: s).postln; +a.free; + +// k + warning +( +SynthDef("DigitalOut",{ arg out=0; + DigitalOut.kr( 0, SinOsc.ar( 10 ) ); +}).send(s); +); + +a = Synth.new("DigitalOut", target: s).postln; +a.free; + + +// k +( +SynthDef("DigitalOut",{ arg out=0; + DigitalOut.kr( 0, SinOsc.kr( 10 ) ); +}).send(s); +); + +a = Synth.new("DigitalOut", target: s).postln; +a.free; + +// k + warning +( +SynthDef("DigitalOut",{ arg out=0; + DigitalOut.ar( 0, SinOsc.kr( 10 ) ); +}).send(s); +); + +a = Synth.new("DigitalOut", target: s).postln; +a.free; + + +// a-once +( +SynthDef("DigitalOut",{ arg out=0; + DigitalOut.ar( 0, SinOsc.ar( 10 ), 1 ); +}).send(s); +); + +a = Synth.new("DigitalOut", target: s).postln; +a.free; + + + + +// DigitalIO + + +Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110)); +s.initTree; +s.startAliveThread; + + +( // aaaa once - everything audio rate +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( DC.ar( 0 ), SinOsc.ar( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + +( // aaak once - pinmode control rate +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( DC.ar( 0 ), SinOsc.ar( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // aaka once - output value control rate +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( DC.ar( 0 ), SinOsc.kr( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // aakk once - output value control rate, pinmode control rate +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( DC.ar( 0 ), SinOsc.kr( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // akaa once - pin changed at control rate, rest audio +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( 0, SinOsc.ar( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // akak once - pin changed at control rate, output value at audio rate, mode at control rate +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( 0, SinOsc.ar( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + +( // ak once - pin changed at control rate, output control rate, pinmode control +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( 0, SinOsc.kr( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // akka once - pin changed at control rate, output control rate, pinmode control +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.ar( 0, SinOsc.kr( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + + +( // kk - warning +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.kr( 0, SinOsc.kr( 10 ), K2A.ar( LFPulse.kr( 0.1 ) ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + +( // kk - warning +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.kr( 0, SinOsc.ar( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // kk - warning +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.kr( Stepper.ar( Impulse.ar(1), 0, 7, 1 ), SinOsc.kr( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + + +( // kk +SynthDef("DigitalIO",{ arg out=0; + DigitalIO.kr( 0, SinOsc.kr( 10 ), LFPulse.kr( 0.1 ) ).poll; +}).send(s); +); + +a = Synth.new("DigitalIO", target: s).postln; +a.free; + +// +s.freeAll; \ No newline at end of file diff --git a/include/plugin_interface/SC_World.h b/include/plugin_interface/SC_World.h index c768385d78d..9d68c3d0096 100644 --- a/include/plugin_interface/SC_World.h +++ b/include/plugin_interface/SC_World.h @@ -21,6 +21,10 @@ #pragma once +#ifdef BELA +# include "Bela.h" +#endif + #include "SC_Types.h" #include "SC_Rate.h" #include "SC_SndBuf.h" @@ -103,6 +107,22 @@ struct World { nova::padded_rw_spinlock* mAudioBusLocks; nova::spin_lock* mControlBusLock; #endif + +#ifdef BELA + BelaContext* mBelaContext; + // uint32 mBelaAnalogChannels; + uint32 mBelaAnalogInputChannels; + uint32 mBelaAnalogOutputChannels; + uint32 mBelaDigitalChannels; + float mBelaHeadphoneLevel; + float mBelaPGAGainLeft; + float mBelaPGAGainRight; + bool mBelaSpeakerMuted; + float mBelaDACLevel; + float mBelaADCLevel; + uint32 mBelaNumMuxChannels; + uint32 mBelaPRU; +#endif }; inline SndBuf* World_GetBuf(struct World* inWorld, uint32 index) { diff --git a/include/server/SC_WorldOptions.h b/include/server/SC_WorldOptions.h index 024bab3941f..af1b1cbc9ae 100644 --- a/include/server/SC_WorldOptions.h +++ b/include/server/SC_WorldOptions.h @@ -84,6 +84,21 @@ struct WorldOptions { const char* mRestrictedPath = nullptr; int mSharedMemoryID = 0; + +#ifdef BELA + // uint32 mBelaAnalogChannels; + uint32 mBelaAnalogInputChannels; + uint32 mBelaAnalogOutputChannels; + uint32 mBelaDigitalChannels; + float mBelaHeadphoneLevel; + float mBelaPGAGainLeft; + float mBelaPGAGainRight; + bool mBelaSpeakerMuted; + float mBelaDACLevel; + float mBelaADCLevel; + uint32 mBelaNumMuxChannels; + uint32 mBelaPRU; +#endif }; struct SndBuf; diff --git a/lang/CMakeLists.txt b/lang/CMakeLists.txt index 56142d61704..6141e8b8e73 100644 --- a/lang/CMakeLists.txt +++ b/lang/CMakeLists.txt @@ -116,7 +116,7 @@ if(AUDIOAPI STREQUAL "default") endif(APPLE) endif() -if(NOT AUDIOAPI MATCHES "^(jack|coreaudio|portaudio)$") +if(NOT AUDIOAPI MATCHES "^(jack|coreaudio|portaudio|bela)$") message(FATAL_ERROR "Unrecognised audio API: ${AUDIOAPI}") endif() diff --git a/server/plugins/BELAUGens.cpp b/server/plugins/BELAUGens.cpp new file mode 100644 index 00000000000..235797d576b --- /dev/null +++ b/server/plugins/BELAUGens.cpp @@ -0,0 +1,1386 @@ +/* + SuperCollider real time audio synthesis system + Copyright (c) 2002 James McCartney. All rights reserved. + http://www.audiosynth.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + * BELA I/O UGens created by nescivi, (c) 2016 + * https://www.nescivi.eu + */ + +// #include + +#include + +#include "Bela.h" +// These functions are provided by xenomai +int rt_printf(const char* format, ...); +int rt_fprintf(FILE* stream, const char* format, ...); + +#include "SC_PlugIn.h" + +static InterfaceTable* ft; + +struct MultiplexAnalogIn : public Unit { + // TODO: can we remove this ? +}; + + +struct AnalogIn : public Unit { + // TODO: can we remove this ? +}; + +struct AnalogOut : public Unit { + // TODO: can we remove this ? +}; + +// static digital pin, static function (in) +struct DigitalIn : public Unit { + int mDigitalPin; +}; + +// static digital pin, static function (out) - uses DigitalWrite and a check whether value changed +struct DigitalOut : public Unit { + int mDigitalPin; + int mLastOut; +}; + +// static digital pin, static function (out) - uses DigitalWriteOnce +struct DigitalOutA : public Unit { + int mDigitalPin; + int mLastOut; +}; + +// flexible digital pin, flexible function (in or out) +struct DigitalIO : public Unit { + int mLastDigitalIn; + int mLastDigitalOut; +}; + +/* +struct BelaScope : public Unit +{ +}; + +struct BelaScopeChannel : public Unit +{ + int mScopeChannel; +}; +*/ + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void MultiplexAnalogIn_next_aaa(MultiplexAnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* fin = IN(0); // analog in pin, can be modulated + float* fmux = IN(1); // mux channel, can be modulated + float* out = ZOUT(0); + int analogPin = 0; + int muxChannel = 0; + float analogValue = 0; + + // context->audioFrames should be equal to inNumSamples + // for(unsigned int n = 0; n < context->audioFrames; n++) { + for (unsigned int n = 0; n < inNumSamples; n++) { + analogPin = (int)fin[n]; + muxChannel = (int)fmux[n]; + if ((analogPin < 0) || (analogPin >= context->analogInChannels) || (muxChannel < 0) + || (muxChannel > context->multiplexerChannels)) { + rt_printf("MultiplexAnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + rt_printf("MultiplexAnalogIn warning: muxChannel must be between %i and %i, it is %i \n", 0, + context->multiplexerChannels, muxChannel); + } else { + analogValue = multiplexerAnalogRead( + context, analogPin, muxChannel); // is there something like NI? analogReadNI(context, 0, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("MultiPlexed AnalogValue = %.3f\n", analogValue); + // } + } + *++out = analogValue; + } +} + +void MultiplexAnalogIn_next_aak(MultiplexAnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* fin = IN(0); // analog in pin, can be modulated + int muxChannel = (float)IN0(1); + float* out = ZOUT(0); + int analogPin = 0; + float analogValue = 0; + + // context->audioFrames should be equal to inNumSamples + // for(unsigned int n = 0; n < context->audioFrames; n++) { + for (unsigned int n = 0; n < inNumSamples; n++) { + analogPin = (int)fin[n]; + if ((analogPin < 0) || (analogPin >= context->analogInChannels) || (muxChannel < 0) + || (muxChannel > context->multiplexerChannels)) { + rt_printf("MultiplexAnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + rt_printf("MultiplexAnalogIn warning: muxChannel must be between %i and %i, it is %i \n", 0, + context->multiplexerChannels, muxChannel); + } else { + analogValue = multiplexerAnalogRead( + context, analogPin, muxChannel); // is there something like NI? analogReadNI(context, 0, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("MultiPlexed AnalogValue = %.3f\n", analogValue); + // } + } + *++out = analogValue; + } +} + +void MultiplexAnalogIn_next_aka(MultiplexAnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (float)IN0(0); + float* fmux = IN(1); // mux channel, can be modulated + float* out = ZOUT(0); + int muxChannel = 0; + float analogValue = 0; + + // context->audioFrames should be equal to inNumSamples + // for(unsigned int n = 0; n < context->audioFrames; n++) { + for (unsigned int n = 0; n < inNumSamples; n++) { + muxChannel = (int)fmux[n]; + if ((analogPin < 0) || (analogPin >= context->analogInChannels) || (muxChannel < 0) + || (muxChannel > context->multiplexerChannels)) { + rt_printf("MultiplexAnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + rt_printf("MultiplexAnalogIn warning: muxChannel must be between %i and %i, it is %i \n", 0, + context->multiplexerChannels, muxChannel); + } else { + analogValue = multiplexerAnalogRead( + context, analogPin, muxChannel); // is there something like NI? analogReadNI(context, 0, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("MultiPlexed AnalogValue = %.3f\n", analogValue); + // } + } + *++out = analogValue; + } +} + +void MultiplexAnalogIn_next_akk(MultiplexAnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (float)IN0(0); + int muxChannel = (float)IN0(1); + float* out = ZOUT(0); + float analogValue = 0; + + if ((analogPin < 0) || (analogPin >= context->analogInChannels) || (muxChannel < 0) + || (muxChannel > context->multiplexerChannels)) { + rt_printf("MultiplexAnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + rt_printf("MultiplexAnalogIn warning: muxChannel must be between %i and %i, it is %i \n", 0, + context->multiplexerChannels, muxChannel); + for (unsigned int n = 0; n < inNumSamples; n++) { + *++out = 0; + } + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + analogValue = multiplexerAnalogRead( + context, analogPin, muxChannel); // is there something like NI? analogReadNI(context, 0, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("MultiPlexed AnalogValue = %.3f\n", analogValue); + // } + *++out = analogValue; + } + } +} + +void MultiplexAnalogIn_next_kkk(MultiplexAnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (float)IN0(0); + int muxChannel = (float)IN0(1); + + if ((analogPin < 0) || (analogPin >= context->analogInChannels)) { + rt_printf("MultiplexAnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + ZOUT0(0) = 0.0; + } else if ((muxChannel < 0) || (muxChannel > context->multiplexerChannels)) { + rt_printf("MultiplexAnalogIn warning: muxChannel must be between %i and %i, it is %i \n", 0, + context->multiplexerChannels, muxChannel); + ZOUT0(0) = 0.0; + } else { + ZOUT0(0) = multiplexerAnalogRead( + context, analogPin, muxChannel); // is there something like NI? analogReadNI(context, 0, analogPin); + } +} + +void MultiplexAnalogIn_Ctor(MultiplexAnalogIn* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + if (context->analogFrames == 0 || context->analogFrames > context->audioFrames) { + rt_printf("MultiplexAnalogIn Error: the UGen needs BELA analog enabled, with 4 or 8 channels\n"); + return; + } + if (context->multiplexerChannels == 0) { + rt_printf("MultiplexAnalogIn Error: the UGen needs BELA Multiplexer Capelet enabled\n"); + return; + } + + // initiate first sample + MultiplexAnalogIn_next_kkk(unit, 1); + // set calculation method + if (unit->mCalcRate == calc_FullRate) { + if (INRATE(0) == calc_FullRate) { + if (INRATE(1) == calc_FullRate) { + SETCALC(MultiplexAnalogIn_next_aaa); + } else { + // rt_printf("AnalogIn: aa\n"); + SETCALC(MultiplexAnalogIn_next_aak); + } + } else { + if (INRATE(1) == calc_FullRate) { + SETCALC(MultiplexAnalogIn_next_aka); + } else { + // rt_printf("AnalogIn: ak\n"); + SETCALC(MultiplexAnalogIn_next_akk); + } + } + } else { + if ((INRATE(0) == calc_FullRate) || (INRATE(1) == calc_FullRate)) { + rt_printf("MultiplexAnalogIn warning: output rate is control rate, so cannot change analog pin or " + "multiplex channel at audio rate\n"); + } + // rt_printf("AnalogIn: kk\n"); + SETCALC(MultiplexAnalogIn_next_kkk); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void AnalogIn_next_aa(AnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* fin = IN(0); // analog in pin, can be modulated + float* out = ZOUT(0); + int analogPin = 0; + float analogValue = 0; + + // context->audioFrames should be equal to inNumSamples + // for(unsigned int n = 0; n < context->audioFrames; n++) { + for (unsigned int n = 0; n < inNumSamples; n++) { + analogPin = (int)fin[n]; + // analogPin = sc_clip( analogPin, 0.0, context->analogInChannels ); + if ((analogPin < 0) || (analogPin >= context->analogInChannels)) { + rt_printf("AnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogInChannels, analogPin); + } else { + analogValue = analogReadNI(context, n, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("AnalogValue = %.3f\n", analogValue); + // } + } + *++out = analogValue; + } +} + +void AnalogIn_next_ak(AnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (float)IN0(0); + float* out = ZOUT(0); + float analogValue = 0; + + if ((analogPin < 0) || (analogPin >= context->analogInChannels)) { + rt_printf("AnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, context->analogInChannels, + analogPin); + for (unsigned int n = 0; n < inNumSamples; n++) { + *++out = 0; + } + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + analogValue = analogReadNI(context, n, analogPin); + // if(analogPin == 0) + // { + // static int count = 0; + // count++; + // if(count % 20000 == 0) + // rt_printf("AnalogValue = %.3f\n", analogValue); + // } + *++out = analogValue; + } + } +} + + +void AnalogIn_next_kk(AnalogIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (float)IN0(0); + + if ((analogPin < 0) || (analogPin >= context->analogInChannels)) { + rt_printf("AnalogIn warning: analog pin must be between %i and %i, it is %i \n", 0, context->analogInChannels, + analogPin); + ZOUT0(0) = 0.0; + } else { + ZOUT0(0) = analogReadNI(context, 0, analogPin); + } +} + +void AnalogIn_Ctor(AnalogIn* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + if (context->analogFrames == 0 || context->analogFrames > context->audioFrames) { + rt_printf("AnalogIn Error: the UGen needs BELA analog enabled, with 4 or 8 channels\n"); + return; + } + + // initiate first sample + AnalogIn_next_kk(unit, 1); + // set calculation method + if (unit->mCalcRate == calc_FullRate) { + if (INRATE(0) == calc_FullRate) { + // rt_printf("AnalogIn: aa\n"); + SETCALC(AnalogIn_next_aa); + } else { + // rt_printf("AnalogIn: ak\n"); + SETCALC(AnalogIn_next_ak); + } + } else { + if (INRATE(0) == calc_FullRate) { + rt_printf("AnalogIn warning: output rate is control rate, so cannot change analog pin at audio rate\n"); + } + // rt_printf("AnalogIn: kk\n"); + SETCALC(AnalogIn_next_kk); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void AnalogOut_next_aaa(AnalogOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* fin = IN(0); // analog in pin, can be modulated + float* in = IN(1); + + int analogPin = 0; + float newinput = 0; + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + analogPin = (int)fin[n]; + if ((analogPin < 0) || (analogPin >= context->analogOutChannels)) { + rt_printf("AnalogOut warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogOutChannels, analogPin); + } else { + newinput = in[n]; // read next input sample + analogWriteOnceNI(context, n, analogPin, newinput); + } + } +} + +void AnalogOut_next_aka(AnalogOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (int)IN0(0); // analog in pin, can be modulated + float* in = IN(1); + + float newinput = 0; + if ((analogPin < 0) || (analogPin >= context->analogOutChannels)) { + rt_printf("AnalogOut warning: analog pin must be between %i and %i, it is %i \n", 0, context->analogOutChannels, + analogPin); + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + newinput = in[n]; // read next input sample + analogWriteOnceNI(context, n, analogPin, newinput); + } + } +} + +void AnalogOut_next_aak(AnalogOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* fin = IN(0); // analog in pin, can be modulated + float in = IN0(1); + + int analogPin = 0; + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + analogPin = (int)fin[n]; + if ((analogPin < 0) || (analogPin >= context->analogOutChannels)) { + rt_printf("AnalogOut warning: analog pin must be between %i and %i, it is %i \n", 0, + context->analogOutChannels, analogPin); + } else { + analogWriteOnceNI(context, n, analogPin, in); + } + } +} + + +void AnalogOut_next_kk(AnalogOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int analogPin = (int)IN0(0); // analog in pin, can be modulated + float in = IN0(1); + + if ((analogPin < 0) || (analogPin >= context->analogOutChannels)) { + rt_printf("AnalogOut warning: analog pin must be between %i and %i, it is %i \n", 0, context->analogOutChannels, + analogPin); + } else { + analogWriteNI(context, 0, analogPin, in); + } +} + +void AnalogOut_Ctor(AnalogOut* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + if (context->analogFrames == 0) { + rt_printf("AnalogOut Error: the UGen needs BELA analog enabled\n"); + return; + } + + // initiate first sample + AnalogOut_next_kk(unit, 1); + + if (unit->mCalcRate == calc_FullRate) { // ugen running at audio rate; + if (INRATE(0) == calc_FullRate) { // pin changed at audio rate + if (INRATE(1) == calc_FullRate) { // output changed at audio rate + SETCALC(AnalogOut_next_aaa); + // rt_printf("AnalogOut: aaa\n"); + } else { + SETCALC(AnalogOut_next_aak); + // rt_printf("AnalogOut: aak\n"); + } + } else { // pin changed at control rate + if (INRATE(1) == calc_FullRate) { // output changed at audio rate + SETCALC(AnalogOut_next_aka); + // rt_printf("AnalogOut: aka\n"); + } else { // analog output only changes at control rate anyways + rt_printf("AnalogOut warning: inputs are control rate, so AnalogOut is also running at control rate\n"); + // rt_printf("AnalogOut: kk\n"); + SETCALC(AnalogOut_next_kk); + } + } + } else { // ugen at control rate + if ((INRATE(0) == calc_FullRate) || (INRATE(1) == calc_FullRate)) { + rt_printf("AnalogOut warning: output rate is control rate, so cannot change inputs at audio rate\n"); + } + // rt_printf("AnalogOut: kk\n"); + SETCALC(AnalogOut_next_kk); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void DigitalIn_next_a(DigitalIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = unit->mDigitalPin; + int digitalValue; + float* out = ZOUT(0); + + // context->audioFrames should be equal to inNumSamples + // for(unsigned int n = 0; n < context->audioFrames; n++) { + for (unsigned int n = 0; n < inNumSamples; n++) { + digitalValue = digitalRead(context, n, pinid); // read the value of the button + *++out = (float)digitalValue; + } +} + +void DigitalIn_next_k(DigitalIn* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = unit->mDigitalPin; + int digitalValue = digitalRead(context, 0, pinid); // read the value of the button + ZOUT0(0) = (float)digitalValue; +} + +void DigitalIn_next_dummy_a(DigitalIn* unit, int inNumSamples) { + float* out = ZOUT(0); + + for (unsigned int n = 0; n < inNumSamples; n++) { + *++out = 0.0; + } +} + +void DigitalIn_next_dummy_k(DigitalIn* unit, int inNumSamples) { ZOUT0(0) = 0.0; } + +void DigitalIn_Ctor(DigitalIn* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + float fDigitalIn = ZIN0(0); // digital in pin -- cannot change after construction + unit->mDigitalPin = (int)fDigitalIn; + // unit->mDigitalPin = (int) sc_clip( fDigitalIn, 0., 15.0 ); + if ((unit->mDigitalPin < 0) || (unit->mDigitalPin >= context->digitalChannels)) { + rt_printf("DigitalIn warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + unit->mDigitalPin); + // initiate first sample + if (unit->mCalcRate == calc_FullRate) { // ugen running at audio rate; + DigitalIn_next_dummy_a(unit, 1); + } else { + DigitalIn_next_dummy_k(unit, 1); + } + } else { + pinMode(context, 0, unit->mDigitalPin, INPUT); + // initiate first sample + DigitalIn_next_k(unit, 1); + // set calculation method + if (unit->mCalcRate == calc_FullRate) { // ugen running at audio rate; + SETCALC(DigitalIn_next_a); + // rt_printf("DigitalIn: a\n"); + } else { + SETCALC(DigitalIn_next_k); + // rt_printf("DigitalIn: k\n"); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void DigitalOut_next_a_once(DigitalOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = unit->mDigitalPin; + float* in = IN(1); + + float newinput = 0; + // int lastOut = unit->mLastOut; + + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + newinput = in[n]; + if (newinput > 0.5) { + digitalWriteOnce(context, n, pinid, 1); + } else { + digitalWriteOnce(context, n, pinid, 0); + } + // else if ( lastOut == 1 ) { + // digitalWrite(context, n, pinid, 0 ); + // } + } + // unit->mLastOut = lastOut; +} + +void DigitalOut_next_a(DigitalOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = unit->mDigitalPin; + float* in = IN(1); + + float newinput = 0; + int lastOut = unit->mLastOut; + + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + newinput = in[n]; + if (newinput > 0.5) { + if (lastOut == 0) { + lastOut = 1; + digitalWrite(context, n, pinid, 1); + } + } else if (lastOut == 1) { + lastOut = 0; + digitalWrite(context, n, pinid, 0); + } + } + unit->mLastOut = lastOut; +} + +void DigitalOut_next_k(DigitalOut* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = unit->mDigitalPin; + float in = IN0(1); + + int lastOut = unit->mLastOut; + if (in > 0.5) { + if (lastOut == 0) { + lastOut = 1; + digitalWrite(context, 0, pinid, 1); + } + } else if (lastOut == 1) { + lastOut = 0; + digitalWrite(context, 0, pinid, 0); + } + unit->mLastOut = lastOut; +} + +void DigitalOut_next_dummy(DigitalOut* unit, int inNumSamples) {} + +void DigitalOut_Ctor(DigitalOut* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + float fDigital = ZIN0(0); // digital in pin -- cannot change after construction + int writeMode = + (int)ZIN0(2); // method of writing; 1 = writeOnce; 0 = write on change -- cannot change after construction + unit->mDigitalPin = (int)fDigital; + unit->mLastOut = 0; + + if ((unit->mDigitalPin < 0) || (unit->mDigitalPin >= context->digitalChannels)) { + rt_printf("DigitalOut warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + unit->mDigitalPin); + // initiate first sample + DigitalOut_next_dummy(unit, 1); + // set calculation method + SETCALC(DigitalOut_next_dummy); + } else { + pinMode(context, 0, unit->mDigitalPin, OUTPUT); + // initiate first sample + DigitalOut_next_k(unit, 1); + + if (unit->mCalcRate == calc_FullRate) { // ugen running at audio rate; + if (INRATE(1) == calc_FullRate) { // output changed at audio rate + if (writeMode) { + // rt_printf("DigitalOut: a once\n"); + SETCALC(DigitalOut_next_a_once); + } else { + // rt_printf("DigitalOut: a\n"); + SETCALC(DigitalOut_next_a); + } + } else { // not much reason to actually do audiorate output + rt_printf("DigitalOut warning: inputs are control rate, so DigitalOut will run at control rate\n"); + // rt_printf("DigitalOut: k\n"); + SETCALC(DigitalOut_next_k); + } + } else { // ugen at control rate + if (INRATE(1) == calc_FullRate) { + rt_printf("DigitalOut warning: UGen rate is control rate, so cannot change inputs at audio rate\n"); + } + // rt_printf("DigitalOut: k\n"); + SETCALC(DigitalOut_next_k); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void DigitalIO_next_aaaa_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* pinid = IN(0); + float* in = IN(1); // input value + float* iomode = IN(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin; + float newmode = 0; // input + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + int newDigOut = unit->mLastDigitalOut; + + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + newDigOut = (int)in[n]; + newmode = iomode[n]; + if (newmode < 0.5) { + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } else { + pinModeOnce(context, n, newpin, OUTPUT); + digitalWriteOnce(context, n, newpin, newDigOut); + } + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + + +void DigitalIO_next_aaak_once(DigitalIO* unit, int inNumSamples) { + // pinMode at control rate + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* pinid = IN(0); + float* in = IN(1); // input value + float iomode = IN0(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + + int newDigOut = unit->mLastDigitalOut; + // float newinput; + + int newpin; + if (iomode < 0.5) { + for (unsigned int n = 0; n < inNumSamples; n++) { + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + pinModeOnce(context, n, newpin, OUTPUT); + newDigOut = (int)in[n]; + digitalWriteOnce(context, n, newpin, newDigOut); + } + *++out = (float)newDigInInt; + } + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + +// output changing at control rate, rest audio +void DigitalIO_next_aaka_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* pinid = IN(0); + float in = IN0(1); // input value + float* iomode = IN(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin; + float newmode = 0; // input + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + // int newDigOut = unit->mLastDigitalOut; + int newDigOut = (int)in; + + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + newmode = iomode[n]; + if (newmode < 0.5) { + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } else { + pinModeOnce(context, n, newpin, OUTPUT); + digitalWriteOnce(context, n, newpin, newDigOut); + } + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + + +// output changing at control rate, and pin mode at control rate +void DigitalIO_next_aakk_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float* pinid = IN(0); + float in = IN0(1); // input value + float iomode = IN0(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin; + float newmode = 0; // input + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + // int newDigOut = unit->mLastDigitalOut; + int newDigOut = (int)in; + + if (iomode < 0.5) { + for (unsigned int n = 0; n < inNumSamples; n++) { + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + newpin = (int)pinid[n]; + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, + context->digitalChannels, newpin); + } else { + pinModeOnce(context, n, newpin, OUTPUT); + digitalWriteOnce(context, n, newpin, newDigOut); + } + *++out = (float)newDigInInt; + } + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + + +// pin changing at control rate, output control rate, rest audio rate +void DigitalIO_next_akaa_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float pinid = IN0(0); + float in = IN0(1); // input value + float* iomode = IN(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin = (int)pinid; + float newmode = 0; // input + // float newinput; + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + + int newDigOut = (int)in; + + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + newpin); + } else { + for (unsigned int n = 0; n < inNumSamples; n++) { + // newinput = in[n]; + newmode = iomode[n]; + if (newmode < 0.5) { + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } else { + pinModeOnce(context, n, newpin, OUTPUT); + digitalWriteOnce(context, n, newpin, newDigOut); + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + +// result audio rate, pin changing at control rate, output value audio rate, pin mode change control rate +void DigitalIO_next_akak_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float pinid = IN0(0); + float* in = IN(1); // input value + float iomode = IN0(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin = (int)pinid; + float newmode = 0; // input + // float newinput; + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + + int newDigOut = (int)in; + + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + newpin); + } else { + if (iomode < 0.5) { + pinMode(context, 0, newpin, INPUT); + for (unsigned int n = 0; n < inNumSamples; n++) { + newDigInInt = digitalRead(context, n, newpin); + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } else { + pinMode(context, 0, newpin, OUTPUT); + for (unsigned int n = 0; n < inNumSamples; n++) { + newDigOut = (int)in[n]; + digitalWriteOnce(context, n, newpin, newDigOut); + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } + } + + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + +// audio rate ugen output, pin changing at control rate, output at control rate, mode at audio rate +void DigitalIO_next_akka_once(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float pinid = IN0(0); + float in = IN0(1); // input value + float* iomode = IN(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin = (int)pinid; + float newinput = in; + + float newmode = 0; // input + + int newDigInInt = unit->mLastDigitalIn; + int newDigOut = unit->mLastDigitalOut; + + if ((newpin < 0) || (newpin >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + newpin); + } + + for (unsigned int n = 0; n < inNumSamples; n++) { + newmode = iomode[n]; + if (newmode < 0.5) { // digital read + pinModeOnce(context, n, newpin, INPUT); + newDigInInt = digitalRead(context, n, newpin); + } else { // digital write + pinModeOnce(context, n, newpin, OUTPUT); + if (newinput > 0.5) { + newDigOut = 1; + } else { + newDigOut = 0; + } + digitalWriteOnce(context, n, newpin, newDigOut); + } + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + + +// all inputs at control rate, output at audio rate +void DigitalIO_next_ak(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + float pinid = IN0(0); + float in = IN0(1); // input value + float iomode = IN0(2); // IO mode : < 0.5 = input, else output + float* out = ZOUT(0); // output value = last output value + + int newpin = (int)pinid; + + int newDigInInt = unit->mLastDigitalIn; + float newDigIn = (float)newDigInInt; + int newDigOut = (int)in; + + if ((pinid < 0) || (pinid >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + newpin); + } else { + if (iomode < 0.5) { + pinMode(context, 0, newpin, INPUT); + for (unsigned int n = 0; n < inNumSamples; n++) { + // read input + newDigInInt = digitalRead(context, n, newpin); + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } else { + pinMode(context, 0, newpin, OUTPUT); + for (unsigned int n = 0; n < inNumSamples; n++) { + if (in > 0.5) { + newDigOut = 1; + } else { + newDigOut = 0; + } + digitalWriteOnce(context, n, newpin, newDigOut); + // always write to the output of the UGen + *++out = (float)newDigInInt; + } + } + } + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + +// all at control rate, output at control rate +void DigitalIO_next_kk(DigitalIO* unit, int inNumSamples) { + World* world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext* context = world->mBelaContext; + + int pinid = (int)IN0(0); + float in = IN0(1); // input value + float iomode = IN0(2); // IO mode : < 0.5 = input, else output + // float *out = ZOUT(0); // output value = last output value + + int newDigInInt = unit->mLastDigitalIn; + int newDigOut = unit->mLastDigitalOut; + + if ((pinid < 0) || (pinid >= context->digitalChannels)) { + rt_printf("DigitalIO warning: digital pin must be between %i and %i, it is %i \n", 0, context->digitalChannels, + pinid); + } else { + if (iomode < 0.5) { + pinMode(context, 0, pinid, INPUT); + newDigInInt = digitalRead(context, 0, pinid); + } else { + pinMode(context, 0, pinid, OUTPUT); + if (in > 0.5) { + newDigOut = 1; + } else { + newDigOut = 0; + } + digitalWrite(context, 0, pinid, newDigOut); + } + } + ZOUT0(0) = (float)newDigInInt; + + unit->mLastDigitalIn = newDigInInt; + unit->mLastDigitalOut = newDigOut; +} + +/* +void DigitalIO_next(DigitalIO *unit, int inNumSamples) +{ + World *world = unit->mWorld; + int bufLength = world->mBufLength; + BelaContext *context = world->mBelaContext; + + float *pinid = IN(0); + float *in = IN(1); // input value + float *iomode = IN(2); // IO mode : < 0.5 = input, else output + float *out = ZOUT(0); // output value = last output value + + int newpin; + float newmode = 0; // input + float newinput = 0; + int newinputInt = 0; + int newoutput = unit->mLastIn; + + // context->audioFrames should be equal to inNumSamples +// for(unsigned int n = 0; n < context->digitalFrames; n++) { + for(unsigned int n = 0; n < inNumSamples; n++) { + // read input + newpin = (int) pinid[n]; + if ( (newpin < 0) || (newpin >= context->digitalChannels) ){ + rt_printf( "digital pin must be between %i and %i, it is %i", 0, context->digitalChannels, newpin ); + } else { + newinput = in[n]; + newmode = iomode[n]; + if ( newmode < 0.5 ){ + // pinModeOnce( context, n, newpin, INPUT ); + pinMode( context, n, newpin, INPUT ); + newoutput = digitalRead(context, n, newpin); + } else { + // pinModeOnce( context, n, newpin, OUTPUT ); + pinMode( context, n, newpin, OUTPUT ); + if ( newinput > 0.5 ){ + newinputInt = 1; + } else { + newinputInt = 0; + } + // digitalWriteOnce(context, n, newpin, newinputInt); + digitalWrite(context, n, newpin, newinputInt); + } + } + // always write to the output of the UGen + *++out = (float) newoutput; + } + unit->mLastDigitalIn = newoutput; + unit->mLastDigitalOut = newinput; +} +*/ + +void DigitalIO_Ctor(DigitalIO* unit) { + BelaContext* context = unit->mWorld->mBelaContext; + + unit->mLastDigitalIn = 0; + unit->mLastDigitalOut = 0; + + // int writeMode = (int) ZIN0(3); // method of writing; 1 = writeOnce; 0 = write on change + + // initiate first sample + DigitalIO_next_kk(unit, 1); + // set calculation method + // SETCALC(DigitalIO_next); + if (unit->mCalcRate == calc_FullRate) { // ugen running at audio rate; + if (INRATE(0) == calc_FullRate) { // pin changed at audio rate + if (INRATE(1) == calc_FullRate) { // output changed at audio rate + if (INRATE(2) == calc_FullRate) { // pinmode changed at audio rate + // if ( writeMode ){ + // rt_printf("DigitalIO: aaaa once\n"); + SETCALC(DigitalIO_next_aaaa_once); + // } else { + // SETCALC(DigitalIO_next_aaaa); + // } + } else { + // if ( writeMode ){ + // rt_printf("DigitalIO: aaak once\n"); + SETCALC(DigitalIO_next_aaak_once); + // } else { + // SETCALC(DigitalIO_next_aaak); + // } + } + } else { // output changed at control rate + if (INRATE(2) == calc_FullRate) { // pinmode changed at audio rate + // if ( writeMode ){ + // rt_printf("DigitalIO: aaka once\n"); + SETCALC(DigitalIO_next_aaka_once); + // } else { + // SETCALC(DigitalIO_next_aaka); + // } + } else { + // if ( writeMode ){ + // rt_printf("DigitalIO: aakk once\n"); + SETCALC(DigitalIO_next_aakk_once); + // } else { + // SETCALC(DigitalIO_next_aakk); + // } + } + } + } else { // pin changed at control rate + if (INRATE(1) == calc_FullRate) { // output changed at audio rate + if (INRATE(2) == calc_FullRate) { // pinmode changed at audio rate + // if ( writeMode ){ + // rt_printf("DigitalIO: akaa once\n"); + SETCALC(DigitalIO_next_akaa_once); + // } else { + // SETCALC(DigitalIO_next_akaa); + // } + } else { + // if ( writeMode ){ + // rt_printf("DigitalIO: akak once\n"); + SETCALC(DigitalIO_next_akak_once); + // } else { + // SETCALC(DigitalIO_next_akak); + // } + } + } else { // output changed at control rate + if (INRATE(2) == calc_FullRate) { // pinmode changed at audio rate + // if ( writeMode ){ + // rt_printf("DigitalIO: akka once\n"); + SETCALC(DigitalIO_next_akka_once); + // } else { + // SETCALC(DigitalIO_next_akka); + // } + } else { // pinmode at control rate + // rt_printf("DigitalIO: ak once\n"); + SETCALC(DigitalIO_next_ak); + } + } + } + } else { // ugen at control rate + if ((INRATE(0) == calc_FullRate) || (INRATE(1) == calc_FullRate) || (INRATE(2) == calc_FullRate)) { + rt_printf("DigitalIO warning: UGen rate is control rate, so cannot change inputs at audio rate\n"); + } + // rt_printf("DigitalIO: kk\n"); + SETCALC(DigitalIO_next_kk); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +/* +int noScopeChannels = 0; +Scope * belaScope; + + +void BelaScopeChannel_next( BelaScope *unit ) +{ + int scopeChannel = unit->mScopeChannel; + float *in = IN(1); + + for(unsigned int n = 0; n < inNumSamples; n++) { + belaScope->logChannel( scopeChannel, in[n] ); + } +} + + +void BelaScopeChannel_Ctor(BelaScope *unit) +{ + BelaContext *context = unit->mWorld->mBelaContext; + +// belaScope = Scope(); + // which channel is an input variable +// belaScope->setup(3, context->audioSampleRate); + float fChan = ZIN0(0); // number of channels + mScopeChannel = (int ) fChan; + // check whether channel is within number of channels of scope + if ( mScopeChannel > noScopeChannels ){ + // error + } + // initiate first sample + + BelaScopeChannel_next( unit, 1); + // set calculation method + SETCALC(BelaScopeChannel_next); +} + + + +void BelaScope_next(BelaScope *unit) +{ +} + +void BelaScope_Ctor(BelaScope *unit) +{ + BelaContext *context = unit->mWorld->mBelaContext; + + float fChan = ZIN0(0); // number of channels + noScopeChannels = (int) fChan; + + belaScope = Scope(); + // number of channels is a variable + belaScope->setup(noScopeChannels, context->audioSampleRate); + + // initiate first sample + BelaScope_next( unit, 1); + // set calculation method + SETCALC(BelaScope_next); +} + +void BelaScope_Dtor(BelaScope *unit) +{ + belaScope->stop(); + delete belaScope; + noScopeChannels = 0; +} +*/ + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// extern "C" +// { +// +// +// } + +// // the functions below are needed?? +// +// void render(BelaContext *belaContext, void *userData) +// { +// // SC_BelaDriver *driver = (SC_BelaDriver*)userData; +// // driver->BelaAudioCallback(belaContext); +// } +// // setup() is called once before the audio rendering starts. +// // Use it to perform any initialisation and allocation which is dependent +// // on the period size or sample rate. +// // +// // userData holds an opaque pointer to a data structure that was passed +// // in from the call to initAudio(). +// // +// // Return true on success; returning false halts the program. +// bool setup(BelaContext* belaContext, void* userData) +// { +// if(userData == 0){ +// printf("BelaPLUGINS: error, setup() got no user data\n"); +// return false; +// } +// +// return true; +// } +// +// // cleanup() is called once at the end, after the audio has stopped. +// // Release any resources that were allocated in setup(). +// void cleanup(BelaContext *belaContext, void *userData) +// { +// } + + +PluginLoad(BELA) { + ft = inTable; + + DefineSimpleUnit(MultiplexAnalogIn); + DefineSimpleUnit(AnalogIn); + DefineSimpleUnit(AnalogOut); + DefineSimpleUnit(DigitalIn); + DefineSimpleUnit(DigitalOut); + DefineSimpleUnit(DigitalIO); +} + + +// C_LINKAGE SC_API_EXPORT void unload(InterfaceTable *inTable) +// { +// +// } diff --git a/server/plugins/CMakeLists.txt b/server/plugins/CMakeLists.txt index b1838ceb084..98647935e38 100644 --- a/server/plugins/CMakeLists.txt +++ b/server/plugins/CMakeLists.txt @@ -69,6 +69,47 @@ foreach(plugin ${plugin_sources}) list(APPEND plugins ${plugin_name}) endforeach(plugin) +if(AUDIOAPI STREQUAL bela) + # The Bela lib will have its own set of libraries and includes + # However, the Bela audio backend for SC has some direct calls to Xenomai + # Therefore we need to get flags for those as well + find_package(Xenomai) + if(NOT XENOMAI_FOUND) + message(FATAL_ERROR "Bela selected as audio API, but Xenomai development files not found") + endif() + find_package(Bela) + if(NOT BELA_FOUND) + message(FATAL_ERROR "Bela selected as audio API, but Bela development files not found") + endif() + message(STATUS "bela libs: ${BELA_LIBRARIES}") + if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + # recommended compile flags for beaglebone etc; set here because bela api flag directly implies the architecture + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${BELA_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BELA_CXX_FLAGS}") + endif() +endif() + +if (BELA_FOUND) + add_library(BELAUGens MODULE + BELAUGens.cpp +# ${BELA_SOURCES} +# ${scplugin_common_sources} + ) + set(belaugens BELAUGens) + add_definitions("-DBELA" ${XENOMAI_DEFINITIONS} ${BELA_DEFINITIONS}) + include_directories(${XENOMAI_INCLUDE_DIRS}) + include_directories(${BELA_INCLUDE_DIRS}) + +# set(CMAKE_EXECUTABLE_RUNTIME_C_FLAG "-Wl,-wrap,clock_gettime,-rpath,") +# set(CMAKE_EXECUTABLE_RUNTIME_CXX_FLAG "-Wl,-wrap,clock_gettime,-rpath,") +# set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-wrap,clock_gettime,-rpath,") +# set(CMAKE_SHARED_LIBRARY_RUNTIME_CXX_FLAG "-Wl,-wrap,clock_gettime,-rpath,") +# target_link_libraries(BELAUGens ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES} libscsynth.a) + target_link_libraries(BELAUGens libscsynth) + + list(APPEND plugins BELAUGens) +endif() + if(NOT NO_X11) if (APPLE) add_library(UIUGens MODULE UIUGens.mm) @@ -215,6 +256,12 @@ if(NOT NO_X11) endforeach() endif() +if( BELA_FOUND ) + foreach(ugen ${belaugens}) + target_link_libraries(${ugen} ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES}) + endforeach() +endif() + foreach(plugin ${plugins}) if(WIN32) target_link_libraries(${plugin} wsock32 ws2_32) diff --git a/server/scsynth/CMakeLists.txt b/server/scsynth/CMakeLists.txt index 4bf5602d539..c69e742fec3 100644 --- a/server/scsynth/CMakeLists.txt +++ b/server/scsynth/CMakeLists.txt @@ -33,7 +33,7 @@ if(AUDIOAPI STREQUAL "default") endif(APPLE) endif() -if(NOT AUDIOAPI MATCHES "^(jack|coreaudio|portaudio)$") +if(NOT AUDIOAPI MATCHES "^(jack|coreaudio|portaudio|bela)$") message(FATAL_ERROR "Unrecognised audio API: ${AUDIOAPI}") endif() @@ -47,6 +47,24 @@ elseif(AUDIOAPI STREQUAL portaudio AND SYSTEM_PORTAUDIO) if(NOT PORTAUDIO_FOUND) message(FATAL_ERROR "Portaudio selected as audio API, but development files not found") endif() +elseif(AUDIOAPI STREQUAL bela) + # The Bela lib will have its own set of libraries and includes + # However, the Bela audio backend for SC has some direct calls to Xenomai + # Therefore we need to get flags for those as well + find_package(Xenomai) + if(NOT XENOMAI_FOUND) + message(FATAL_ERROR "Bela selected as audio API, but Xenomai development files not found") + endif() + find_package(Bela) + if(NOT BELA_FOUND) + message(FATAL_ERROR "Bela selected as audio API, but Bela development files not found") + endif() + message(STATUS "bela libs: ${BELA_LIBRARIES}") + if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + # recommended compile flags for beaglebone etc; set here because bela api flag directly implies the architecture + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${BELA_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BELA_CXX_FLAGS}") + endif() endif() message(STATUS "Audio API (scsynth): ${AUDIOAPI}") @@ -124,6 +142,11 @@ elseif (AUDIOAPI STREQUAL portaudio) list(APPEND scsynth_sources SC_PortAudio.cpp ${CMAKE_SOURCE_DIR}/common/SC_PaUtils.cpp) add_definitions("-DSC_AUDIO_API=SC_AUDIO_API_PORTAUDIO" ${PORTAUDIO_DEFINITIONS}) include_directories(${PORTAUDIO_INCLUDE_DIRS}) +elseif (AUDIOAPI STREQUAL bela) + list(APPEND scsynth_sources SC_Bela.cpp) + add_definitions("-DSC_AUDIO_API=SC_AUDIO_API_BELA" "-DBELA" ${XENOMAI_DEFINITIONS} ${BELA_DEFINITIONS}) + include_directories(${XENOMAI_INCLUDE_DIRS}) + include_directories(${BELA_INCLUDE_DIRS}) endif() set (FINAL_BUILD 0) # disable final build for scsynth @@ -191,6 +214,8 @@ if (AUDIOAPI STREQUAL jack) target_link_libraries(libscsynth ${JACK_LIBRARIES}) elseif(AUDIOAPI STREQUAL portaudio) target_link_libraries(libscsynth ${PORTAUDIO_LIBRARIES}) +elseif(AUDIOAPI STREQUAL bela) + target_link_libraries(libscsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES}) elseif(AUDIOAPI STREQUAL coreaudio) target_link_libraries(libscsynth "-framework CoreAudio") endif() @@ -235,6 +260,10 @@ add_executable(scsynth ) target_link_libraries(scsynth libscsynth) +if(AUDIOAPI STREQUAL bela) + target_link_libraries(scsynth ${XENOMAI_LIBRARIES} ${BELA_LIBRARIES}) +endif() + if (PTHREADS_FOUND) target_link_libraries(scsynth ${PTHREADS_LIBRARIES}) endif() diff --git a/server/scsynth/SC_Bela.cpp b/server/scsynth/SC_Bela.cpp new file mode 100644 index 00000000000..691eb47c4c5 --- /dev/null +++ b/server/scsynth/SC_Bela.cpp @@ -0,0 +1,502 @@ +/* + Bela audio driver for SuperCollider. + Copyright (c) 2015 Dan Stowell. All rights reserved. + Copyright (c) 2016 Marije Baalman. All rights reserved. + Copyright (c) 2016 Giulio Moro. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + This file contains elements from SC_PortAudio.cpp and SC_Jack.cpp, + copyright their authors, and published under the same licence. +*/ +#include "SC_CoreAudio.h" +#include +#include "SC_Prototypes.h" +#include "SC_HiddenWorld.h" +#include "SC_WorldOptions.h" +#include "SC_Time.hpp" +#include +#include + +#ifdef XENOMAI_SKIN_native +# include // needed for CLOCK_HOST_REALTIME +#endif +#ifdef XENOMAI_SKIN_posix +# include // needed for CLOCK_HOST_REALTIME +#endif + +extern "C" { +// This will be wrapped by Xenomai without requiring linker flags +int __wrap_clock_gettime(clockid_t clock_id, struct timespec* tp); +// this is provided by Xenomai +int rt_vprintf(const char* format, va_list ap); +} + +#include "Bela.h" +// Xenomai-specific includes +#include + +using namespace std; + +int32 server_timeseed() { return timeSeed(); } + +int64 gOSCoffset = 0; + +static inline int64 GetCurrentOSCTime() { return OSCTime(getTime()); } + +int64 oscTimeNow() { return GetCurrentOSCTime(); } + +void initializeScheduler() { gOSCoffset = GetCurrentOSCTime(); } + + +class SC_BelaDriver : public SC_AudioDriver { + int mInputChannelCount, mOutputChannelCount; + +protected: + // Driver interface methods + virtual bool DriverSetup(int* outNumSamplesPerCallback, double* outSampleRate); + virtual bool DriverStart(); + virtual bool DriverStop(); + +public: + SC_BelaDriver(struct World* inWorld); + virtual ~SC_BelaDriver(); + + void BelaAudioCallback(BelaContext* belaContext); + void SignalReceived(int); + static void staticMAudioSyncSignal(void*); + static AuxiliaryTask mAudioSyncSignalTask; + static int countInstances; + static SC_SyncCondition* staticMAudioSync; + +private: + uint32 mSCBufLength; +}; + +AuxiliaryTask SC_BelaDriver::mAudioSyncSignalTask; +int SC_BelaDriver::countInstances; +SC_SyncCondition* SC_BelaDriver::staticMAudioSync; +SC_BelaDriver* mBelaDriverInstance = 0; + +SC_AudioDriver* SC_NewAudioDriver(struct World* inWorld) { + if (mBelaDriverInstance != 0) { + scprintf("Warning: SC_NewAudioDriver called with an existing SC_BelaDriver instance.\n"); + } + + mBelaDriverInstance = new SC_BelaDriver(inWorld); + return mBelaDriverInstance; +} + +SC_BelaDriver::SC_BelaDriver(struct World* inWorld): SC_AudioDriver(inWorld) { + mStartHostSecs = 0; + mSCBufLength = inWorld->mBufLength; + + staticMAudioSync = &mAudioSync; + ++countInstances; + if (countInstances != 1) { + fprintf(stderr, "Error: there are %d instances of SC_BelaDriver running at the same time. Exiting\n", + countInstances); + exit(1); + } +} + +SC_BelaDriver::~SC_BelaDriver() { + // Clean up any resources allocated for audio + Bela_cleanupAudio(); + scprintf("SC_BelaDriver: >>Bela_cleanupAudio\n"); + --countInstances; + mBelaDriverInstance = 0; +} + +static float gBelaSampleRate; +// Return true on success; returning false halts the program. +bool sc_belaSetup(BelaContext* belaContext, void* userData) { + // cast void pointer + // SC_BelaDriver *belaDriver = (SC_BelaDriver*) userData; + gBelaSampleRate = belaContext->audioSampleRate; + return true; +} + +void sc_belaRender(BelaContext* belaContext, void* userData) { + SC_BelaDriver* driver = (SC_BelaDriver*)userData; + + driver->BelaAudioCallback(belaContext); +} + +void sc_belaAudioThreadDone(BelaContext*, void* userData) { + SC_BelaDriver* driver = (SC_BelaDriver*)userData; + if (driver) + driver->SignalReceived(0); +} + +void sc_belaSignal(int arg) { + if (mBelaDriverInstance != 0) + mBelaDriverInstance->SignalReceived(arg); +} + +void sc_SetDenormalFlags(); + +void SC_BelaDriver::BelaAudioCallback(BelaContext* belaContext) { + struct timespec tspec; + + sc_SetDenormalFlags(); + World* world = mWorld; + // add a pointer to belaWorld + // mWorld->mBelaContext = belaContext; + world->mBelaContext = belaContext; + + // NOTE: code here is adapted from the SC_Jack.cpp, the version not using the DLL + + // Use Xenomai-friendly clock_gettime() -- note that this requires a -wrap argument to build + __wrap_clock_gettime(CLOCK_HOST_REALTIME, &tspec); + + double hostSecs = (double)tspec.tv_sec + (double)tspec.tv_nsec * 1.0e-9; + double sampleTime = static_cast(belaContext->audioFramesElapsed); + + if (mStartHostSecs == 0) { + mStartHostSecs = hostSecs; + mStartSampleTime = sampleTime; + } else { + double instSampleRate = (sampleTime - mPrevSampleTime) / (hostSecs - mPrevHostSecs); + double smoothSampleRate = mSmoothSampleRate; + smoothSampleRate = smoothSampleRate + 0.002 * (instSampleRate - smoothSampleRate); + if (fabs(smoothSampleRate - mSampleRate) > 10.) { + smoothSampleRate = mSampleRate; + } + mOSCincrement = (int64)(mOSCincrementNumerator / smoothSampleRate); + mSmoothSampleRate = smoothSampleRate; + } + + mPrevHostSecs = hostSecs; + mPrevSampleTime = sampleTime; + + try { + mFromEngine.Free(); + mToEngine.Perform(); + mOscPacketsToEngine.Perform(); + + const uint32_t numInputs = belaContext->audioInChannels; + const uint32_t numOutputs = belaContext->audioOutChannels; + + int numSamples = NumSamplesPerCallback(); + int bufFrames = mWorld->mBufLength; + int numBufs = numSamples / bufFrames; + + float* inBuses = mWorld->mAudioBus + mWorld->mNumOutputs * bufFrames; + float* outBuses = mWorld->mAudioBus; + int32* inTouched = mWorld->mAudioBusTouched + mWorld->mNumOutputs; + int32* outTouched = mWorld->mAudioBusTouched; + + int minInputs = sc_min(numInputs, mWorld->mNumInputs); + int minOutputs = sc_min(numOutputs, mWorld->mNumOutputs); + + int anaInputs = 0; + if (numInputs < (int)mWorld->mNumInputs) { + anaInputs = sc_min(belaContext->analogInChannels, (int)(mWorld->mNumInputs - numInputs)); + } + int anaOutputs = 0; + if (numOutputs < (int)mWorld->mNumOutputs) { + anaOutputs = sc_min(belaContext->analogOutChannels, (int)(mWorld->mNumOutputs - numOutputs)); + } + + int bufFramePos = 0; + + // THIS IS TO DO LATER -- LOOK AT CACHEING AND CONSTING TO IMPROVE EFFICIENCY + // cache I/O buffers + // for (int i = 0; i < minInputs; ++i) { + // inBuffers[i] = (sc_jack_sample_t*)jack_port_get_buffer(inPorts[i], numSamples); + //} + // + // for (int i = 0; i < minOutputs; ++i) { + // outBuffers[i] = (sc_jack_sample_t*)jack_port_get_buffer(outPorts[i], numSamples); + //} + + // main loop + int64 oscTime = mOSCbuftime = + ((int64)(tspec.tv_sec + kSECONDS_FROM_1900_to_1970) << 32) + (int64)(tspec.tv_nsec * kNanosToOSCunits); + + int64 oscInc = mOSCincrement; + double oscToSamples = mOSCtoSamples; + + // clear out anything left over in audioOut buffer + for (int i = 0; i < belaContext->audioFrames * belaContext->audioOutChannels; i++) { + belaContext->audioOut[i] = 0; + } + + for (int i = 0; i < numBufs; ++i, mWorld->mBufCounter++, bufFramePos += bufFrames) { + int32 bufCounter = mWorld->mBufCounter; + int32* tch; + + // copy+touch inputs + tch = inTouched; + memcpy(inBuses, belaContext->audioIn, sizeof(belaContext->audioIn[0]) * bufFrames * minInputs); + for (int k = 0; k < minInputs; ++k) { + *tch++ = bufCounter; + } + + memcpy(inBuses + minInputs * bufFrames, belaContext->analogIn, + sizeof(belaContext->analogIn[0]) * bufFrames * anaInputs); + for (int k = minInputs; k < (minInputs + anaInputs); ++k) { + *tch++ = bufCounter; + } + + // run engine + int64 schedTime; + int64 nextTime = oscTime + oscInc; + + while ((schedTime = mScheduler.NextTime()) <= nextTime) { + float diffTime = (float)(schedTime - oscTime) * oscToSamples + 0.5; + float diffTimeFloor = floor(diffTime); + world->mSampleOffset = (int)diffTimeFloor; + world->mSubsampleOffset = diffTime - diffTimeFloor; + + if (world->mSampleOffset < 0) + world->mSampleOffset = 0; + else if (world->mSampleOffset >= world->mBufLength) + world->mSampleOffset = world->mBufLength - 1; + + SC_ScheduledEvent event = mScheduler.Remove(); + event.Perform(); + } + + world->mSampleOffset = 0; + world->mSubsampleOffset = 0.f; + World_Run(world); + + // copy touched outputs + tch = outTouched; + + for (int k = 0; k < minOutputs; ++k) { + if (*tch++ == bufCounter) { + memcpy(belaContext->audioOut + k * bufFrames, outBuses + k * bufFrames, + sizeof(belaContext->audioOut[0]) * bufFrames); + } + } + + for (int k = minOutputs; k < (minOutputs + anaOutputs); ++k) { + if (*tch++ == bufCounter) { + unsigned int analogChannel = k - minOutputs; // starting at 0 + memcpy(belaContext->analogOut + analogChannel * bufFrames, outBuses + k * bufFrames, + sizeof(belaContext->analogOut[0]) * bufFrames); + } + } + + // advance OSC time + mOSCbuftime = oscTime = nextTime; + } + } catch (std::exception& exc) { + scprintf("SC_BelaDriver: exception in real time: %s\n", exc.what()); + } catch (...) { + scprintf("SC_BelaDriver: unknown exception in real time\n"); + } + + // this avoids Xenomai mode switches in the audio thread ... + Bela_scheduleAuxiliaryTask(mAudioSyncSignalTask); +} + +void SC_BelaDriver::staticMAudioSyncSignal(void*) { + // ... but mode switches are still happening here, in a lower priority thread. + // FIXME: this triggers a mode switch in Xenomai. + staticMAudioSync->Signal(); +} +// ==================================================================== + +bool SC_BelaDriver::DriverSetup(int* outNumSamples, double* outSampleRate) { + BelaInitSettings* settings = Bela_InitSettings_alloc(); + Bela_defaultSettings(settings); + settings->setup = sc_belaSetup; + settings->render = sc_belaRender; +#if (BELA_MAJOR_VERSION == 1 && BELA_MINOR_VERSION >= 8) || (BELA_MAJOR_VERSION > 1) + // if the feature is supported on Bela, add a callback to be called when + // the audio thread stops. This is useful e.g.: to gracefully exit from + // scsynth when pressing the Bela button + settings->audioThreadDone = sc_belaAudioThreadDone; +#endif // BELA >= 1.8 + settings->interleave = 0; + settings->uniformSampleRate = 1; + settings->analogOutputsPersist = 0; + + if (mPreferredHardwareBufferFrameSize) { + settings->periodSize = mPreferredHardwareBufferFrameSize; + } + if (settings->periodSize != mSCBufLength) { + scprintf("Warning in SC_BelaDriver::DriverSetup(): hardware buffer size (%i) different from SC audio buffer " + "size (%i). Changed the hardware buffer size to be equal to the SC audio buffer size .\n", + settings->periodSize, mSCBufLength); + settings->periodSize = mSCBufLength; + } + // note that Bela doesn't give us an option to choose samplerate, since it's baked-in. + // This can be retrieved in sc_belaSetup() + + // configure the number of analog channels - this will determine their internal samplerate + settings->useAnalog = 0; + + // explicitly requested number of analog channels + int numAnalogIn = mWorld->mBelaAnalogInputChannels; + int numAnalogOut = mWorld->mBelaAnalogOutputChannels; + + int extraAudioIn = mWorld->mNumInputs - settings->numAudioInChannels; + int extraAudioOut = mWorld->mNumOutputs - settings->numAudioOutChannels; + // if we need more audio channels than there actually are audio + // channels, make sure we have some extra analogs + if (extraAudioIn > 0) { + numAnalogIn = sc_max(numAnalogIn, extraAudioIn); + } + if (extraAudioOut > 0) { + numAnalogOut = sc_max(numAnalogOut, extraAudioOut); + } + + // snap the number of requested analog channels to the 0, 4, 8. + // 4 will give same actual sample rate as audio, 8 will give half of it. + if (numAnalogIn > 0) { + if (numAnalogIn < 5) { + numAnalogIn = 4; + } else { + numAnalogIn = 8; + } + } + + if (numAnalogOut > 0) { + if (numAnalogOut < 5) { + numAnalogOut = 4; + } else { + numAnalogOut = 8; + } + } + + // final check: right now the number of analog output channels on bela needs to be the same as analog input + // channels. this is likely to change in the future, that is why we factored it out + if (numAnalogOut != numAnalogIn) { + // Chosing the maximum of the two + numAnalogOut = sc_max(numAnalogOut, numAnalogIn); + numAnalogIn = numAnalogOut; + printf("Number of analog input channels must match number of analog outputs. Using %u for both\n", numAnalogIn); + } + settings->numAnalogInChannels = numAnalogOut; + settings->numAnalogOutChannels = numAnalogIn; + + if (settings->numAnalogInChannels > 0 || settings->numAnalogOutChannels > 0) { + settings->useAnalog = 1; + } + + // enable the audio expander capelet for the first few "analog as audio" channels + // inputs and ... + for (int n = 0; n < extraAudioIn; ++n) { + printf("Using analog in %d as audio in %d\n", n, n + settings->numAudioInChannels); + settings->audioExpanderInputs |= (1 << n); + } + + // ... outputs + for (int n = 0; n < extraAudioOut; ++n) { + printf("Using analog out %d as audio out %d\n", n, n + settings->numAudioOutChannels); + settings->audioExpanderOutputs |= (1 << n); + } + + // configure the number of digital channels + settings->useDigital = 0; + + if (mWorld->mBelaDigitalChannels > 0) { + settings->numDigitalChannels = mWorld->mBelaDigitalChannels; + settings->useDigital = 1; + } + if ((mWorld->mBelaHeadphoneLevel >= -63.5) + && (mWorld->mBelaHeadphoneLevel <= 0.)) { // headphone output level (0dB max; -63.5dB min) + settings->headphoneLevel = mWorld->mBelaHeadphoneLevel; + } + if ((mWorld->mBelaPGAGainLeft >= 0) && (mWorld->mBelaPGAGainLeft <= 59.5)) { // (0db min; 59.5db max) + settings->pgaGain[0] = mWorld->mBelaPGAGainLeft; + } + if ((mWorld->mBelaPGAGainRight >= 0) && (mWorld->mBelaPGAGainRight <= 59.5)) { // (0db min; 59.5db max) + settings->pgaGain[1] = mWorld->mBelaPGAGainRight; + } + + if (mWorld->mBelaSpeakerMuted) { + settings->beginMuted = 1; + } else { + settings->beginMuted = 0; + } + if ((mWorld->mBelaDACLevel >= -63.5) && (mWorld->mBelaDACLevel <= 0.)) { // (0dB max; -63.5dB min) + settings->dacLevel = mWorld->mBelaDACLevel; + } + if ((mWorld->mBelaADCLevel >= -12) && (mWorld->mBelaADCLevel <= 0.)) { // (0dB max; -12dB min) + settings->adcLevel = mWorld->mBelaADCLevel; + } + + settings->numMuxChannels = mWorld->mBelaNumMuxChannels; + + if ((mWorld->mBelaPRU == 0) || (mWorld->mBelaPRU == 1)) { + settings->pruNumber = mWorld->mBelaPRU; + } + + scprintf("SC_BelaDriver: >>DriverSetup - Running on PRU (%i)\nConfigured with \n (%i) analog input and (%i) analog " + "output channels, (%i) digital channels, and (%i) multiplexer channels.\n HeadphoneLevel (%f dB), " + "pga_gain_left (%f dB) and pga_gain_right (%f dB)\n DAC Level (%f dB), ADC Level (%f dB)\n", + settings->pruNumber, settings->numAnalogInChannels, settings->numAnalogOutChannels, + settings->numDigitalChannels, settings->numMuxChannels, settings->headphoneLevel, settings->pgaGain[0], + settings->pgaGain[1], settings->dacLevel, settings->adcLevel); + if (settings->beginMuted == 1) { + scprintf("Speakers are muted.\n"); + } else { + scprintf("Speakers are not muted.\n"); + } + + settings->verbose = mWorld->mVerbosity; + // Initialise the PRU audio device. This function prepares audio rendering in Bela. It should be called from main() + // sometime after command line option parsing has finished. It will initialise the rendering system, which in the + // process will result in a call to the user-defined setup() function. + if (Bela_initAudio(settings, this) != 0) { + scprintf("Error in SC_BelaDriver::DriverSetup(): unable to initialise audio\n"); + return false; + } + mAudioSyncSignalTask = Bela_createAuxiliaryTask( + staticMAudioSyncSignal, 90, "mAudioSyncSignalTask"); // needs to be created after the call to Bela_initAudio() + if (!mAudioSyncSignalTask) { + fprintf(stderr, "Error: unable to create Bela auxiliary task\n"); + exit(1); + } + + *outNumSamples = settings->periodSize; + *outSampleRate = gBelaSampleRate; + Bela_InitSettings_free(settings); + + // Set up interrupt handler to catch Control-C and SIGTERM + signal(SIGINT, sc_belaSignal); + signal(SIGTERM, sc_belaSignal); + + return true; +} + +bool SC_BelaDriver::DriverStart() { + SetPrintFunc((PrintFunc)rt_vprintf); // Use Xenomai's realtime-friendly printing function +#ifdef XENOMAI_SKIN_native + rt_print_auto_init(1); // Make sure the buffers for rt_vprintf are actually initialized. +#endif + if (Bela_startAudio()) { + scprintf("Error in SC_BelaDriver::DriverStart(): unable to start real-time audio\n"); + return false; + } + return true; +} + +bool SC_BelaDriver::DriverStop() { + Bela_stopAudio(); + return true; +} + +void SC_BelaDriver::SignalReceived(int signal) { + scprintf("SC_BelaDriver: signal received: %d; terminating\n", signal); + mWorld->hw->mTerminating = true; + mWorld->hw->mQuitProgram->post(); +} diff --git a/server/scsynth/SC_CoreAudio.cpp b/server/scsynth/SC_CoreAudio.cpp index 81371514648..45abd7f50a3 100644 --- a/server/scsynth/SC_CoreAudio.cpp +++ b/server/scsynth/SC_CoreAudio.cpp @@ -359,6 +359,9 @@ void SC_AudioDriver::RunThread() { while (mRunThreadFlag) { // wait for sync mAudioSync.WaitNext(); +#ifdef BELA + rt_print_flush_buffers(); +#endif /* BELA */ reinterpret_cast(mWorld->mNRTLock)->lock(); @@ -661,7 +664,7 @@ bool SC_CoreAudioDriver::DriverSetup(int* outNumSamplesPerCallback, double* outS count = sizeof(mOutputDevice); // get the output device: // err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &count, (void *) & - // mOutputDevice); + //mOutputDevice); propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; @@ -678,7 +681,7 @@ bool SC_CoreAudioDriver::DriverSetup(int* outNumSamplesPerCallback, double* outS if (mInputDevice == kAudioDeviceUnknown) { count = sizeof(mInputDevice); // err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &count, (void *) & - // mInputDevice); + //mInputDevice); propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; @@ -1624,16 +1627,16 @@ bool SC_CoreAudioDriver::DriverStart() { try { if (UseSeparateIO()) { - // err = AudioDeviceAddIOProc(mOutputDevice, appIOProc, (void *) this); // setup Out device with an - // IO proc + // err = AudioDeviceAddIOProc(mOutputDevice, appIOProc, (void *) this); // setup Out device with an IO + //proc err = AudioDeviceCreateIOProcID(mOutputDevice, appIOProcFunc, (void*)this, &mOutputID); if (err != kAudioHardwareNoError) { scprintf("AudioDeviceAddIOProc failed %s %d\n", &err, (int)err); return false; } - // err = AudioDeviceAddIOProc(mInputDevice, appIOProcSeparateIn, (void *) this); // setup In - // device with an IO proc + // err = AudioDeviceAddIOProc(mInputDevice, appIOProcSeparateIn, (void *) this); // setup In device + //with an IO proc err = AudioDeviceCreateIOProcID(mInputDevice, appIOProcSeparateIn, (void*)this, &mInputID); if (err != kAudioHardwareNoError) { @@ -1772,8 +1775,8 @@ bool SC_CoreAudioDriver::DriverStart() { AudioObjectSetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); // for now no spotting of hardware changes, assumption is that ServerOptions inviolate. However, if a device was - // unplugged, could react to loss of that device by switching to system default? note that the number of listeners - // is stripped down to only one for now, to react to headphone swaps in the case of Built-in Output + // unplugged, could react to loss of that device by switching to system default? note that the number of listeners is + // stripped down to only one for now, to react to headphone swaps in the case of Built-in Output AddDeviceListeners(mOutputDevice, this); return true; diff --git a/server/scsynth/SC_CoreAudio.h b/server/scsynth/SC_CoreAudio.h index bd012073158..a95e9150e62 100644 --- a/server/scsynth/SC_CoreAudio.h +++ b/server/scsynth/SC_CoreAudio.h @@ -34,6 +34,7 @@ #define SC_AUDIO_API_PORTAUDIO 3 #define SC_AUDIO_API_AUDIOUNITS 4 #define SC_AUDIO_API_COREAUDIOIPHONE 5 +#define SC_AUDIO_API_BELA 6 #ifdef SC_IPHONE # define SC_AUDIO_API SC_AUDIO_API_COREAUDIOIPHONE diff --git a/server/scsynth/SC_World.cpp b/server/scsynth/SC_World.cpp index 1f9689cbe98..4d5915459ae 100644 --- a/server/scsynth/SC_World.cpp +++ b/server/scsynth/SC_World.cpp @@ -159,6 +159,25 @@ void sc_SetDenormalFlags() { #if BOOST_HW_SIMD_X86 >= BOOST_HW_SIMD_X86_SSE_VERSION _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); _mm_setcsr(_mm_getcsr() | 0x40); // DAZ +#elif defined(__VFP_FP__) + // the Cortex A8, along with the SIMD Neon unit. + // This function turns on "fast mode" by enabling + // flushing denormals to zero on the VFP. + // The NEON already flushe to zero, so it requires + // no specific settings. + /* This code is from math-neon/math_runfast.c + Copyright (c) 2015 Lachlan Tychsen-Smith (lachlan.ts@gmail.com) + MIT License + */ + static const unsigned int x = 0x04086060; + static const unsigned int y = 0x03000000; + int r; + asm volatile("fmrx %0, fpscr \n\t" // r0 = FPSCR + "and %0, %0, %1 \n\t" // r0 = r0 & 0x04086060 + "orr %0, %0, %2 \n\t" // r0 = r0 | 0x03000000 + "fmxr fpscr, %0 \n\t" // FPSCR = r0 + : "=r"(r) + : "r"(x), "r"(y)); #endif } @@ -390,6 +409,21 @@ World* World_New(WorldOptions* inOptions) { } else { world->hw->mPassword[0] = 0; } +#ifdef BELA + // world->mBelaAnalogChannels = inOptions->mBelaAnalogChannels; + // scprintf("INFO: WORLD: number of analog channels %i.\n", world->mBelaAnalogChannels ); + world->mBelaAnalogInputChannels = inOptions->mBelaAnalogInputChannels; + world->mBelaAnalogOutputChannels = inOptions->mBelaAnalogOutputChannels; + world->mBelaDigitalChannels = inOptions->mBelaDigitalChannels; + world->mBelaHeadphoneLevel = inOptions->mBelaHeadphoneLevel; + world->mBelaPGAGainLeft = inOptions->mBelaPGAGainLeft; + world->mBelaPGAGainRight = inOptions->mBelaPGAGainRight; + world->mBelaSpeakerMuted = inOptions->mBelaSpeakerMuted; + world->mBelaDACLevel = inOptions->mBelaDACLevel; + world->mBelaADCLevel = inOptions->mBelaADCLevel; + world->mBelaNumMuxChannels = inOptions->mBelaNumMuxChannels; + world->mBelaPRU = inOptions->mBelaPRU; +#endif #ifdef __APPLE__ world->hw->mInputStreamsEnabled = inOptions->mInputStreamsEnabled; diff --git a/server/scsynth/scsynth_main.cpp b/server/scsynth/scsynth_main.cpp index f9489d188cf..6111f3cedc4 100644 --- a/server/scsynth/scsynth_main.cpp +++ b/server/scsynth/scsynth_main.cpp @@ -87,6 +87,20 @@ void Usage() { " -I \n" " -O \n" #endif +#ifdef BELA + // " -J \n" + " -J \n" + " -K \n" + " -G \n" + " -Q (0dB max, -63.5dB min)\n" + " -X \n" + " -Y \n" + " -s \n" + " -x \n" + " -y \n" + " -g \n" + " -T \n" +#endif #if (_POSIX_MEMLOCK - 0) >= 200112L " -L enable memory locking\n" #endif @@ -147,9 +161,27 @@ int scsynth_main(int argc, char** argv) { WorldOptions options; +#ifdef BELA + // defaults + options.mBelaAnalogInputChannels = 0; + options.mBelaAnalogOutputChannels = 0; + options.mBelaDigitalChannels = 0; + options.mBelaHeadphoneLevel = -6.; + options.mBelaPGAGainLeft = 20; + options.mBelaPGAGainRight = 20; + options.mBelaSpeakerMuted = 0; + options.mBelaADCLevel = 0; + options.mBelaDACLevel = 0; + options.mBelaNumMuxChannels = 0; + options.mBelaPRU = 1; +#endif + for (int i = 1; i < argc;) { +#ifdef BELA +#define EXTRA_OPTIONS "JKGQXYxygTO" +#endif // BELA if (argv[i][0] != '-' || argv[i][1] == 0 - || strchr("utBaioczblndpmwZrCNSDIOsMHvVRUhPL", argv[i][1]) == nullptr) { + || strchr("utBaioczblndpmwZrCNSDIOsMHvVRUhPL" EXTRA_OPTIONS, argv[i][1]) == nullptr) { scprintf("ERROR: Invalid option %s\n", argv[i]); Usage(); } @@ -280,6 +312,48 @@ int scsynth_main(int argc, char** argv) { options.mMemoryLocking = false; #endif break; +#ifdef BELA + case 'J': + checkNumArgs(2); + options.mBelaAnalogInputChannels = atoi(argv[j + 1]); + break; + case 'K': + checkNumArgs(2); + options.mBelaAnalogOutputChannels = atoi(argv[j + 1]); + break; + case 'G': + checkNumArgs(2); + options.mBelaDigitalChannels = atoi(argv[j + 1]); + break; + case 'Q': + checkNumArgs(2); + options.mBelaHeadphoneLevel = atof(argv[j + 1]); + break; + case 'X': + checkNumArgs(2); + options.mBelaPGAGainLeft = atof(argv[j + 1]); + break; + case 'Y': + checkNumArgs(2); + options.mBelaPGAGainRight = atof(argv[j + 1]); + break; + case 'x': + checkNumArgs(2); + options.mBelaDACLevel = atof(argv[j + 1]); + break; + case 'y': + checkNumArgs(2); + options.mBelaADCLevel = atof(argv[j + 1]); + break; + case 'g': + checkNumArgs(2); + options.mBelaNumMuxChannels = atoi(argv[j + 1]); + break; + case 'T': + checkNumArgs(2); + options.mBelaPRU = atoi(argv[j + 1]); + break; +#endif case 'V': checkNumArgs(2); options.mVerbosity = atoi(argv[j + 1]);