Tuesday, April 14, 2015

Using a Chromebox as a low-cost VSTi player

The Google Chromebox devices are great little computers: they're small, quiet, inexpensive, and run Linux as the basis of Chrome OS. I wondered if it were possible to use it as a portable Virtual Instrument player.

Turns out with a little know-how and patience the device works surprisingly well. Here are the high-level steps to to make one of your very own!

  1. Purchase an Intel-based Chromebox. I'm using the popular Asus model.
  2. Install Ubuntu on Chrome OS (Chrubuntu).
  3. Boot Ubuntu by default and bypass the developer mode 'CTRL-D' screen.
  4. Configure Ubuntu: wireless networking, ssh, and the power button.
  5. Setup priorities for audio and confgure the audio interface. I used the built-in 3.5mm headphone port.
  6. Setup Wine.
  7. Setup jackd and fsthost.
  8. Setup a suitable VSTi. I used u-he's Zebra2 synthesizer.
  9. Configure your VSTi to start on boot.
Let's go through each of these steps in a bit more detail.

Selecting your Chromebox

We're going to be running Wine and Windows-based VSTi plugins. Both of these have to run on an x86 CPU so make sure you don't purchase an ARM-based device.

I decided on the Asus CHROMEBOX-M004U mostly because it is the most popular one and most of the information on the web seems to be about this device. There's also a more expensive model that uses a Core i3 processor which may be more useful if the plugin you want to run requires more CPU resources.

I'd also like to say that you don't necessarily need a Chromebox. If you already have an x86 based PC running Ubuntu then this software and configuration should work equally as well. Just skip the steps that are tied to Chrome OS, its firmware, or booting.

Install Ubuntu

I followed Roger Stringer's guide and he did a great job of getting me to an Ubuntu login prompt. I didn't upgrade my hardware though. Here are the choices I made when configuring the software:
chrome $ sudo chromeos-firmwareupdate --mode=todev
chrome $ curl -L -O http://goo.gl/s9ryd > /tmp/s9ryd
chrome $ sudo bash /tmp/s9ryd ubuntu-standard
I selected the dual-boot option and gave Ubuntu 6GB of space.

Boot Ubuntu by Default

Next I ran the command in ChromeOS to always boot Ubuntu:
chrome $ sudo cgpt add -i 6 -P 5 -S 1 /dev/sda && sudo reboot
When you reboot now you should see a warning about using the device in developmental mode and are required to hit "CTRL-D" to continue. DO NOT HIT THE SPACE BAR, IT WILL WIPE OUT UBUNTU!

This was a problem for the way I intended to use the device: without a computer keyboard or monitor. I needed a way to bypass the "CTRL-D" screen.

Once you have verified Ubuntu is working you need to boot back into Chrome OS to modify the firmware so that you don't need to hit "CTRL-D" every time you boot. At a Linux prompt enter the following:
ubuntu $ sudo cgpt add -i 6 -P 0 -S 0 /dev/sda && sudo reboot
You have to physically open the device and remove the firmware write-protect screw. Every model is different. I used Don Slesnick's guide for my Asus. You'll have to use Google to find out how to do it on your Chromebox if it's a different vendor. THIS WILL VOID YOUR WARRANTY! ONLY DO SO AT YOUR OWN RISK.

Next,  an article by John Lewis helped me get the Chrome OS commands to modify the firmware settings. John's guide is a bit dated and intended for ARM devices though so the commands I used are slightly different.
Backup the firmware:
chrome $ sudo mkdir /usr/local/fw && sudo flashrom -r /usr/local/fw/bios.bin
Change the firmware to a developer-friendly mode (fast boot with no CTRL-D delay and support booting from USB devices):
chrome $ sudo /usr/bin/set_gbb_flags.sh 0x11 && sudo reboot
The device should reboot into Chrome OS without needing "CTRL-D". All that's left now is to switch back to Ubuntu as your boot partition:
chrome $ sudo cgpt add -i 6 -P 5 -S 1 /dev/sda && sudo reboot
If all goes well the Chromebox should boot into Ubuntu in just under 7 seconds.

Configure Ubuntu

The "ubuntu-standard" setup is really pretty minimal. You'll need to install a few packages to make your life easier and to prepare for setting up Wine and fsthost. By default the only editor installed is nano. If you want something different add it at this time. I chose emacs, but it's not required for the build. The apt-file package also isn't strictly required but it made my life easier when searching for dependencies and packages that own particular files.

Don't forget the default username is "user" and the password is also "user". The account has sudo access to run all your requests.

Here are the packages I installed first:
ubuntu $ sudo apt-get update && sudo apt-get install gcc make apt-file unzip emacs23-nox openssh-server wpasupplicant wireless-tools acpid

Setup wireless networking

To keep the device mostly portable I opted for wireless networking in my home. I did not use the Ubuntu standard networking because I did not want the device to wait for an IP address on boot. I'd rather it connect after the plugin is loaded.  Because I run WPA2 at home there's a bit more work to setup a WiFi connection.

I created /etc/wpa_supplicant.conf with the following contents. You need to generate your own file with wpa_supplicant:
network={
  ssid="my_home_ssid"
  #psk="password"
  psk=a133925147b607bdda00872e400000000039d489a9d7ca8a0e7e1e8c64500000
}
Change the WiFi module parameters to enhance performance over power savings. Create the file /etc/modprobe.d/ath9k.conf with the following:
options ath9k nohwcrypt=1 blink=1 btcoex_enable=1 enable_diversity=1
And now we make sure networking starts at boot by adding the following lines to /etc/rc.local:
# starts wifi in the background
/sbin/wpa_supplicant -B -i wlan0 -Dwext -c /etc/wpa_supplicant.conf
/sbin/iwconfig wlan0 power off
/sbin/dhclient -nw wlan0
Change the last line to assign a static IP address if you want to be able to reliably ssh into your device. Since you've already installed openssh-server you are all set to accept incoming connections.

Power button

I wanted the power button to immediately and safely power down the device when pressed. This minimizes the potential for the Linux filesystem to become corrupted and require manual maintenance. The acpid package is already installed and all it takes is a minor edit to the acpi daemon settings.  Edit the file /etc/acpi/events/powerbtn and make the following modification:
# action=/etc/acpi/powerbtn.sh
action=/sbin/poweroff

Now a quick tap of the power button will immediately power off your Chromebox. 

Audio Priorities

Ted Felix has a great introduction to Audio and MIDI on Linux. From this guide I decided to use Jack version 2.x. First we need to install it:
ubuntu $ sudo apt-get update && sudo apt-get install jackd2 alsa-utils alsa-base
When it installs be sure to answer <YES> to real-time audio priority. It will setup the audio group and configure process limits in /etc/security/limits.d/audio.conf.

Now we just need to add the default user 'user' to the audio group. (If you decide to use a different username for audio you'll need to adjust the command accordingly).
ubuntu $ sudo gpasswd -a user audio

Next we need to check for the audio interface. You should see the following output when running this command:
$ cat /proc/asound/cards
 0 [MID            ]: HDA-Intel - HDA Intel MID
                      HDA Intel MID at 0xe0710000 irq 62
 1 [PCH            ]: HDA-Intel - HDA Intel PCH
                      HDA Intel PCH at 0xe0714000 irq 64
The important line in my case is number 1 that contains "PCH". The number 0 interface is the HDMI digital audio path. A quick test of the audio will tell us it's working:
ubuntu $ amixer -c 1 set Master 100 unmute  # unmutes and maxes volume for card #1 above
ubuntu $ wget http://www.kozco.com/tech/piano2.wav  # fetch piano testfile
ubuntu $ aplay -D hw:1 piano2.wav  # play piano testfile on card #1 above
If you hear audio, great! If not you'll probably have to spend some time debugging why and is certainly beyond the scope of this tutorial. Google is your friend in this case.

Wine Setup

The installer for chrubuntu is a bit non-standard and doesn't do everything a normal Unbuntu installer does. You need to do a bit more to get Wine installed.
ubuntu $ sudo add-apt-repository ppa:ubuntu-wine/ppa
ubuntu $ sudo dpkg --add-architecture i386 
ubuntu $ sudo apt-get update
ubuntu $ sudo apt-get install wine1.7
I'm using the PPA version to get the latest optimizations to Wine. We have to add the i386 arch because Wine depends on several i386 libraries.

Compiling fsthost

We're almost done. Next we need to compile the latest trunk version of fsthost.  Use the "Download Snapshot" button on the top right of this page. This is necessary to prevent a segfault while executing.

You'll need to install a few more packages to meet the requirements to build:
ubuntu $ sudo apt-get install libglib2.0-dev libxml2-dev libxml-perl wine1.7 libjack-jackd2-dev libc6-dev-i386 wine1.7-dev libx11-dev dbus-x11
Edit the Makefile and disable building 32-bit, LASH, and GTK. I had trouble with all three of these options and found this to be the simplest way to a compiled fsthost64 binary.  Here's the diff generated to the Makefile:
ubuntu $ diff -u fsthost/Makefile.orig fsthost/Makefile
--- fsthost/Makefile.orig       2015-04-14 14:01:19.623081021 +0000
+++ fsthost/Makefile    2015-04-14 13:56:24.983086298 +0000
@@ -1,11 +1,11 @@
 ### Generated by Winemaker ... and improved by Xj ;-)
 SRCDIR             := .
 SUBDIRS            :=
-PLAT               := 32
-GTK                := 3
+PLAT               := 64
+GTK                := 0
 VUMETER            := 0
 LBITS              := $(shell getconf LONG_BIT)
-LASH_EXISTS        := $(shell if pkg-config --exists lash-1.0; then echo yes; else echo no; fi)
+LASH_EXISTS        := no # $(shell if pkg-config --exists lash-1.0; then echo yes; else echo no; fi)
 #LAST_EXISTS := 'no'

 # Modules
@@ -97,9 +97,9 @@
 endif

 # On 64 bit platform build also fsthost64
-ifeq ($(LBITS), 64)
-PLAT               += 64
-endif
+#ifeq ($(LBITS), 64)
+#PLAT               += 64
+#endif

 EXES               := $(PLAT:%=fsthost%)
Now all you should need to do to compile is run make wherever you unziped the source:
ubuntu $ make

Setup your VSTi

I chose Zebra2 because I love it and because u-he have very unobtrusive plugins that only use a simple serial file to register and do not add keys to the Windows Registry.

All I had to do was Zip up the installed Windows VST directory and unzip it to ~/Zebra2 on my Linux machine. It's already registered to me and remembers my MIDI mappings for XY controllers and master volume. It looks like this:
ubuntu $ ls -F $HOME/Zebra2
Zebra2.data/    Zebra2(x64).dll

Running it all

It only takes a few commands to get everything working:
ubuntu $ eval $(dbus-launch --auto-syntax)
ubuntu $ amixer -c 1 set Master 100 unmute
ubuntu $ jack_control exit
ubuntu $ jackd -R -d alsa --device hw:1 --rate 48000 --period 256 --midi raw &
ubuntu $ $HOME/fsthost/fsthost64 -V "$HOME/Zebra2/Zebra2(x64).dll" &
At this point you should be able to plug in your favorite USB MIDI Keyboard and play notes!

The settings above give about a 5.5ms latency to the MIDI channel. If you want even faster response change --period to 128. Be warned though, this increases CPU usage.

Starting it at boot

I made a slightly more refined script to get the whole thing started at boot:
ubuntu $ $ cat ~/run-vst.sh
#!/bin/bash

if [ -n "$1" -o -n "$(pgrep jackd)" ]; then
    kill $(pgrep fsthost64.so)
    kill $(pgrep jackd)
    [ -n "$1" ] && exit 0
fi

export DISPLAY=:0
eval $(dbus-launch --auto-syntax)

card_num=$(grep ' - HDA Intel PCH$' /proc/asound/cards |
    awk '{print $1}')
amixer -c $card_num set Master 100 unmute

jack_control exit
jackd -R -d alsa --device hw:$card_num --rate 48000 \
    --period 256 --midi raw &>/dev/null &

while [ -z "$(pgrep jackd)" ] ; do
    sleep 0.2
done

$HOME/fsthost/fsthost64 -V "$HOME/Zebra2/Zebra2(x64).dll" \
    &>/dev/null &

And I launch it on boot with the following addition to /etc/rc.local:
# Start the plugin
runuser -u user /home/user/run-vst.sh
When all is said and done I can boot to Zebra in just under 10 seconds. Not bad at all. :)

I change patches with MIDI program change message and morph the patch with the pre-mapped XYs from my Windows setup.

Overall Performance

Zebra2 is a very CPU-efficient synththesizer. The default sawtooth sound plays without any issues on the Chromebox easily playing multiple notes and chords. However, more complex patches, heavy use of FX, arpeggiator, or CPU-intensive filter modes like the XMF can quickly cause the little Intel Celeron to hit max CPU.

Some patches will need tweaking to bring their CPU down. As long as presets are created with a mindset of reducing CPU usage I see this as a very functional setup.

If you are concerned about CPU there is an Intel i3 Chromebox made by Dell and the Intel NUC has a Core i5 option: something I may consider in a future build.

Items for further exploration

Ubuntu has a realtime-kernel option. I didn't use it because the Chromebox is doing tricky things to boot and is most certainly not doing the usual grub2 PC setup.

Completely replace the firmware with SeaBIOS. I chose not to do this for fear of bricking the device and I am uncertain to its level of support.

fsthost -S telnet option. The -S option allows you to telnet to a specified port and view/alter VST settings. I haven't tried this at all but it looks like an interesting way to interact with the plugin. Definitely worth examination.

Multicore usage. I haven't explored this at all and have no idea if plugins that attempt to execute in a multi-threaded manor will actually consume other cores. I might try using u-he's Diva to see what happens. I'm not sure how I will enable multicore though since I don't have a gui and I don't think that button can be MIDI learned.

Using the device with other plugins. I am considering an install for Camel Audio's Alchemy. Now that the company was acquired by Apple and Alchemy is discontinued it would provide for a stable way to use the plugin for years to come.