Skip to content

tylerlwsmith/pi-in-the-end

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

"In the End" player for Raspberry Pi

This project will run on a Raspberry Pi and will play the chorus from "In the End" by Linkin Park (mp3 not included) when the song's intro is played on a connected MIDI device. You can see me demoing an early version of the script on my Twitter. The demo prints to the console, but this completed project plays the chorus through connected speakers.

Installation

First, set up a virtual enviornment:

python3 -m venv .venv

Once the environment is created, activate it:

source .venv/bin/activate

Once activated, install the dependencies. If there are errors during this step, check the resolving dependency conflicts section below.

pip install -r requirements/development.txt -r requirements/production.txt

Next create a project .env file:

cp .example.env .env

Set the midi interface

The script may pick a different default midi interface than the one which was intended.

If this script does not fire when the correct notes are played, try the following steps to ensure that the the application is listening to the correct midi interface.

Activate the Python virtual environment if it is not already activated in your current terminal session:

source .venv/bin/activate

Once the virtual environment is activated, begin an interactive Python session:

python

In the interactive Python session, run the following commands:

import mido
mido.get_input_names()

These commands will return a list of audio interfaces such as the one below:

# Example output:
['Midi Through:Midi Through Port-0 14:0', 'Scarlett 18i8 USB:Scarlett 18i8 USB MIDI 1 20:0']

To manually select an audio interface, copy the full name of the audio interface and set the AUDIO_INTERFACE value to that string in .env:

# .env file
AUDIO_INTERFACE="Scarlett 18i8 USB:Scarlett 18i8 USB MIDI 1 20:0"

Set the audio file

The audio file is not provided as a part of this repository, so you'll need to provide your own. Save the file into the main project directory, then set its value in .env using the AUDIO_FILE variable.

Deploy to Pi

On the Pi, install the system-level dependencies for the PyGObject package:

sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-4.0

Then install the dependencies for GStreamer.

sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

Next, install Git:

sudo apt install git

After that, run the following commands to clone the repo into the home directory:

cd ~
git clone git@github.com:tylerlwsmith/pi-in-the-end.git

Once cloned, change into the directory and install the production dependencies. Because this is the only Python application the Pi will be running, we will opt to install the dependencies globally.

cd pi-in-the-end
pip install requirements/production.txt

If you are not using the default audio driver for output, follow Jeff Geerling's instructions to set a different audio device.

To adjust the ouptut volume, grab the name of the device you'd like to change the volume of with the following command:

amixer scontrols

It may return something like Simple mixer control 'PCM',0. You can target the device name in single quotes to change the volume of the output with the following command (more info on Stack Overflow):

amixer set PCM 100% # 100% means full volume.

Next create, a systemd unit file by running the following command:

sudo vi /etc/systemd/system/linkinpark.service

Paste the following into the new linkinpark.service file, replace <username> with the desired user, and save and quit with wq!:

[Unit]
Description=Linkin Park MIDI Playback Service
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/<username>/pi-in-the-end
User=<username>
Restart=always
ExecStart=/home/<username>/pi-in-the-end/.venv/bin/python /home/<username>/pi-in-the-end/in_the_end.py

[Install]
WantedBy=multi-user.target

Once created, run the following commands to activate the service:

sudo systemctl daemon-reload
sudo systemctl enable linkinpark.service
sudo systemctl start linkinpark.service
sudo systemctl status linkinpark.service

Limitations

This is a proof-of-concept implementation is not intented be put into production, so it has several potential issues that are not addressed:

  • There is no way to specify what audio interface plays the audio clip within the code: that happens at the operating system level.
  • Linux may change the ID of desired output audio interface.
  • If the midi device is disconnected while the app is running, the application has no way of reconnecting when the device is plugged back in.
  • The MIDI device name that the mido library sees may change between system boots.
  • If the midi device is disconnected when systemd starts the app, the service will go into a rapid boot loop.
  • Multiple midi channels on the same interface will all be collapsed into a single stream of notes.
  • The current implementation only supports listening on one device.
  • The Python playsound library requires an enormous number of dependencies on a headless Pi. It may be worth replacing with pygame as outlined in Jeff Geerling's article.
  • Even though the main thread is blocked while the audio is playing, the midi device is continuing to collect input that will execute immediately after the audio stops. If the melody is played during the audio playback, the audio playback will start again immediately after the playing audio stops.

All of these issues are fixable, but are not worth expanding this demo project's scope to mitigate.

Running tests

After installing the project requirements in a virtual environment, use the following command to run the project's tests:

pytest

Managing dependencies

This repo uses pip-tools to manage top-level dependencies and generate the appropriate requirements files. This approach was taken from pip-tools section of Python Application Dependency Management by Hynek Schlawack.

To add a dependency, add it to either development.in or production.in in the requirements/ directory, then run the following commands:

pip-compile -o requirements/production.txt requirements/production.in
pip-compile -o requirements/development.txt requirements/development.in

This will add the new dependency, but not upgrade any existing dependencies. To upgrade existing dependencies to their latest versions, run one of the following commands:

# Upgrade all packages.
pip-compile --upgrade -o requirements/production.txt requirements/production.in

# Upgrade single package.
pip-compile --upgrade-package mido -o requirements/production.txt requirements/production.in

Once you have made the desired changes to your requirements files, sync the virtual environment:

pip-sync requirements/production.txt requirements/development.txt

Resolving dependency conflicts

Sometimes, the development and production requirements files can't have the same transitive dependency, but require a different version. In this case, you must manually resolve the dependency conflict and ensure that both development and production use the sme version of the transitive dependency.

Formatting

This project uses Black for formatting.

About

When you play the intro to Linkin Park’s “In The End” on a digital piano that's connected to a Raspberry Pi, the Pi plays the song’s chorus.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages