Using Raspberry Pi as an automatic MIDI logger
Fri, Aug 4, 2017 in post General Raspberry Pi Tips and Tricks cron crontab kawai midi midi logger python udev
During my summer holidays I got an interesting idea: Pianoteq has a very nice feature of “always on MIDI logging” that saves everything you play on your keyboard while Pianoteq was on. I’ve previously made some MIDI projects and had a great idea:
How about building a small device that records everything I play on my piano, and save it as MIDI files?
This would enable me to later grab a good performance, and eliminate the “recording anxiety” I get if I know I’m recording and should definitely not do any mistakes during the next 1000+ notes. Furthermore, even with easy MIDI recording to USB stick, it’s still several manual steps plugging the memory stick in, starting recording, stopping it, lugging it to a computer, etc.
My first idea was to use some WLAN-enabled embedded device, but MIDI IN would require optoisolators and some custom electronics, and more modern digital pianos often come with only USB MIDI, so it could easily become an exercise in communication protocols. Fast forward a couple of minutes to my next revelation:
Raspberry Pi Model 0 W already has USB and WLAN, and it’s small. Why not use that?
Turns out using a RaspPi as fully automated MIDI logger is really easy. Read on for instructions!
Update: Also check out my follow-up post to split the recorded MIDI files automatically!
Recording MIDI with Raspbian
Turns out recording MIDI from a USB MIDI enabled device is really easy. When I plug in my Kawai CS-11 (sorry for the unsolicited link, I love my CS11 :) to the Pi (or just turn it on when it’s plugged in), dmesg
shows that the Pi automatically notices the new MIDI device:
[ 587.887059] usb 1-1.5: new full-speed USB device number 4 using dwc_otg [ 588.022788] usb 1-1.5: New USB device found, idVendor=0f54, idProduct=0101 [ 588.022800] usb 1-1.5: New USB device strings: Mfr=0, Product=2, SerialNumber=0 [ 588.022807] usb 1-1.5: Product: USB-MIDI [ 588.074579] usbcore: registered new interface driver snd-usb-audio
Once the USB MIDI device is found, you can use arecordmidi -l
to list available MIDI ports:
pi@raspberrypi:~ $ arecordmidi -l Port Client name Port name 14:0 Midi Through Midi Through Port-0 20:0 USB-MIDI USB-MIDI MIDI 1
My Kawai is in port 20:0, and I can now start recording a MIDI file:
arecordmidi -p 20:0 somename.mid
Easy! Only problem for automatic recording is, that you have to manually interrupt arecordmidi
to end recording. However, if we run it on background (add & to the end of the command), we can just kill it later:
pi@raspberrypi:~ $ arecordmidi -p 20:0 somename.mid & [1] 953 pi@raspberrypi:~ $ pgrep arecordmidi 953 pi@raspberrypi:~ $ kill -SIGINT 953 pi@raspberrypi:~ $ [1]+ Done arecordmidi -p 20:0 somename.mid
Automating MIDI logging with Python 3 and cron
Instead of running these commands myself, I made a simple Python 3 script to
1. List ALSA MIDI ports with “MIDI” mentioned (notice that “14:0 Midi Through” will not match as it is not all uppercase)
2. List all process ids of runnign arecordmidi
3. If there are ports available but no processes running, start recording
4. If there are processes running but no ports, kill the process
There are some additional tweaks in the code below to generate a MIDI file with current date and time in its name, and use my own timezone for the time instead of UTC. Here’s my full recmidi.py
:
#!/usr/bin/python3
import os, signal, time, pytz, sys
from datetime import datetime
from subprocess import check_output, Popen
recpath = '/home/pi/midi'
tz = pytz.timezone('Europe/Helsinki')
arec = '/usr/bin/arecordmidi'
def get_ports():
str = check_output([arec, '-l'], universal_newlines=True)
res = [line.split()[0] for line in str.split('\n') if 'MIDI' in line]
return res
def get_pids(proc):
try:
str = check_output(['pgrep', proc], universal_newlines=True)
return [int(pid) for pid in str.split()]
except: return []
pids = get_pids('arecordmidi')
ports = get_ports()
if ports and pids: print('Recording...')
elif ports:
timestr = datetime.now(tz).strftime('%Y-%m-%d_%H.%M.%S')
cmd = [arec, '-p', ports[0], '%s/rec%s.mid' % (recpath, timestr)]
pid = Popen(cmd).pid
print('Started [%d] %s' % (pid, ' '.join(cmd)))
elif pids:
os.kill(pids[0], signal.SIGINT)
print('Killed (SIGINT) [%d]' % pids[0])
You need to run sudo pip3 install pytz
to use the time zone support, or alternatively just delete the ‘tz = …’ line and remove tz from datetime.now(tz)...
. Oh, and of course you need to have sudo apt-get install python3
for the script to work.
I placed the recmidi.py
in the pi
user’s home directory, and used chmod a+x recmidi.py
to make it executable. At this point, you can try the command manually: plug a USB midi device in and run it, it should start arecordmidi
, and unplug it (or turn the keyboard off) and run the script again, it should kill the recording process. Oh, and remember to create the directory mentioned in recpath
with mkdir -p /home/pi/midi
.
Once you are satisfied the command does not throw any errors, you can add a cronjob to run it once a minute. Without additional changes it means, that the first minute of your piano playing might get missed, but we can correct that later. Just use crontab -e
to edit your cron jobs and add the line:
* * * * * /home/pi/recmidi.py >> /home/pi/recmidi.log
You’re all set! Just keep the Pi connected to your MIDI device and powered up, and you’ll automatically get files like rec2017-08-01_18.13.48.mid
in your recording folder! With the full set of Raspbian software at your fingertips, you could then do great stuff like:
- Serve the files over HTTP server so you can access them with your browser
- Use a software synthesizer to generate audio files of your playing sessions
- Make a script to split the resulting MIDI files based on longer pauses in playback (or special pedal or keypress combinations)
- Maybe even use audio fingerprinting to recognize what you are playing!
Starting recording right away
You can shorten the 1 minute delay in crontab by making the recmidi.py
run endlessly:
#!/usr/bin/python3
import os, signal, time, pytz, sys
from datetime import datetime
from subprocess import check_output, Popen
recpath = '/home/pi/midi'
tz = pytz.timezone('Europe/Helsinki')
arec = '/usr/bin/arecordmidi'
def get_ports():
str = check_output([arec, '-l'], universal_newlines=True)
res = [line.split()[0] for line in str.split('\n') if 'MIDI' in line]
return res
def get_pids(proc):
try:
str = check_output(['pgrep', proc], universal_newlines=True)
return [int(pid) for pid in str.split()]
except: return []
print('recmidi.sh started')
while True:
pids = get_pids('arecordmidi')
ports = get_ports()
timestr = datetime.now(tz).strftime('%Y-%m-%d_%H.%M.%S')
if ports and pids: print(timestr, 'Recording...')
elif ports:
cmd = [arec, '-p', ports[0], '%s/rec%s.mid' % (recpath, timestr)]
pid = Popen(cmd).pid
print(timestr, 'Started [%d] %s' % (pid, ' '.join(cmd)))
elif pids:
os.kill(pids[0], signal.SIGINT)
print(timestr, 'Killed (SIGINT) [%d]' % pids[0])
sys.stdout.flush()
time.sleep(10) # Sleep until retry
Notice the sys.stdout.flush() so Python won’t hog output in a buffer endlessly and we can see what’s happening in the log! Now change the crontab line to use a lock file with flock
:
* * * * * flock -n /home/pi/recmidi.lock /home/pi/recmidi.py >> /home/pi/recmidi.log 2>&1
That’s it! Now within 10 seconds of powering up your keyboard, the MIDI recording will start. Nice.
I even had a section about using udev to launch the recording session automatically, but I have yet to make this work (udev triggered the script 5+ times upon connection, midi files turned out empty, there were strange timeouts, you name it). So meanwhile, use the above solution or post me your solution.
Update: Also check out my follow-up post to split the recorded MIDI files automatically!
2 comments
Plebania:
I don’t have a midi device to test Your skript… long story, but i guess the line in cron should be with .py instead of .log. Correct me if i’m wrong pls
Plebania:
sorry… nevermind