RPi Zero v1.3 (no wifi) Audio Visualizer

Bonjour, j’ai un RPi Zero v1.3, celui sans le Wifi, et depuis pas mal de temps je cherche à lui donner un projet.

Après pas mal de réflexion, et vu le prix des visualisateur audio (VU Meter), j’en suis venu à voir si c’est possible de le faire avec le RPi Zero, j’avais déjà sous la main un microphone « adafruit » et un petit OLED 0.91".

Requirement

SI vous désirez utiliser un écran « standard », comme un 3.5" sur le GPIO ou en HDMI, seul « cava » serait requis et donne un résultat intéressant « out-of-the-box », mais ici ont utilise un écran OLED, qui ne passe pas par le canal régulier d’affichage.

Alors avec l’aide de ChatGPT, j’ai réussi a créer de quoi d’intéressant. L’idée est d’utiliser Cava pour fournir un résultat lisible par le Python pour l’envoyer sur l’affichage. Mais vu les faibles performances de RPi Zero, faut trouver un ajustement entre fluidité, précision et rapidité. Ce n’est pas une chose simple.

Mise en route

Je vous laisse le plaisir de préparer le Cava et d’installer votre écran OLED, ont saute directement à l’installation et ajustement pour le projet.

  1. Alors préparons le système , dans le /boot/config.txt, il faut faire quelques ajustement;
#Active l'I2C et SPI
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on

#Désactive l'audio embarqué
dtparam=audio=off

#Booster le port I2C pour un rendu plus rapide
dtparam=i2c_arm_baudrate=400000

#Le microphone
dtoverlay=googlevoicehat-soundcard
  1. Ajuster les valeurs de Cava pour l’utilisation par défaut, dans home/pi/.config/cava/config;
## Configuration file for CAVA.
# Remove the ; to change parameters.
[general]
# Accepts only non-negative values.
framerate =  25
autosens = 0
sensitivity = 5000
bars = 64
lower_cutoff_freq = 60
higher_cutoff_freq = 20000

[input]
method = alsa
source = hw:0,0
framerate = 25
buffer = 256
sample_bits = 8

[output]
method = raw
raw_target = /tmp/cava.fifo
data_format = ascii
channels = mono

[smoothing]
;integral = 0
;gravity = 100
;noise_reduction = 50

25FPS, pas d’ajustement de la sensibilité, capture de 100Hz à 12KHz, capture de Alsa, appareil 0, prise 0, avec un buffer limité à 256 (au lieu de 4096). Sort le résultat dans un fichier FIFO en ASCII. Le Smoothing est désactivé, avec une gravité forcé de 100 (peut ^tre ajuster sinon via noise_reduction)

  1. Ajouter le script dans /home/pi/oledlive.py;
import os
import select
from PIL import Image, ImageDraw
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306

WIDTH, HEIGHT = 128, 32
EXPECTED_BARS = 64
FIFO_PATH = "/tmp/cava.fifo"

serial = i2c(port=1, address=0x3C)
device = ssd1306(serial, width=WIDTH, height=HEIGHT)

try:
    fifo_fd = os.open(FIFO_PATH, os.O_RDONLY | os.O_NONBLOCK)
except FileNotFoundError:
    exit(1)

buffer = ""
bar_width = WIDTH // EXPECTED_BARS
img = Image.new('1', (WIDTH, HEIGHT))
draw = ImageDraw.Draw(img)

while True:
    rlist, _, _ = select.select([fifo_fd], [], [])
    if fifo_fd in rlist:
        try:
            data = os.read(fifo_fd, 2048)
            if not data:
                continue
            buffer += data.decode(errors='ignore')
            lines = buffer.split('\n')
            buffer = lines[-1] if buffer[-1] != '\n' else ""

            for line in lines[:-1]:
                vals = line.strip().split(';')
                if len(vals) < EXPECTED_BARS:
                    continue

                # Parsing optimisé
                values = []
                append = values.append
                for v in vals[:EXPECTED_BARS]:
                    try:
                        append(int(v))
                    except:
                        append(0)

                # Dessin rapide
                draw.rectangle((0, 0, WIDTH, HEIGHT), fill=0)
                for i, val in enumerate(values):
                    bar_height = max(1, val * HEIGHT // 200)
                    x0 = i * bar_width
                    x1 = x0 + bar_width - 1
                    draw.rectangle((x0, HEIGHT - bar_height, x1, HEIGHT), fill=255)

                device.display(img)

        except OSError:
            continue

Exécution
Avant d’implanter en démarrage auto, il faut tester, vous pouvez ajuster selon vos résultats pour tenter d’améliorer le rendu.

  1. Lancer Cava : cava &
  2. Lancer le script : python3 oledlive.py &

Observez votre écran OLED, si tout marche correctement, en son « nul », une ligne sera présent en bas d’écran, à la présence de son, le visualisateur à bande s’activera. Le rendu est quand même un peu lent (latence de quelques ms, avec un effet de fondu des fréquences), à date, c’est le mieux que j’arrive a tirer du projet.

Sans le boost du I2C, le FPS max varie de 10 à 12 sur le OLED, ont peut toucher le 25 FPS avec le boost.

Alléger le RPi

sudo systemctl disable --now \
    avahi-daemon \
    bluetooth \
    triggerhappy \
    rsyslog \
    systemd-timesyncd \
    fake-hwclock

sudo systemctl disable getty@tty2.service getty@tty3.service \
    getty@tty4.service getty@tty5.service getty@tty6.service

sudo dphys-swapfile swapoff
sudo systemctl disable dphys-swapfile

sudo apt purge --auto-remove \
    triggerhappy \
    logrotate \
    dphys-swapfile \
    rsyslog \
    fake-hwclock \
    manpages \
    man-db \
    avahi-daemon \
    bluez \

sudo journalctl --vacuum-size=10M
sudo sed -i 's/#Storage=auto/Storage=volatile/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald

Dans /etc/rc.local, pour désactiver le HDMI;

#!/bin/bash
/opt/vc/bin/tvservice -o
exit 0

Alors l’idée est là, merci d’apporter vos commentaires, et suggestion pour le rendre meilleur.

1 « J'aime »