DesignSpark Electrical Logolinkedin
Menu Suche
Ask a Question

15 Jul 2019, 11:47

Erweiterte Audioeffekte mit einem BeagleBone Black und Bela, Teil 1: Inbetriebnahme

Ein Gerät mit erweiterten Audioeffekten für das Red Tin bauen und dabei den BeagleBone Black SBC und die Bela-Software mit einem Audioprozessor-Cape verwenden.

Nachdem ich ein einfaches auf Samples basierendes Soundeffekt-Modul und ein Fuzz Box-Gitarrenpedal gebaut habe, dachte ich, es wäre lustig, etwas Fortschrittlicheres zu entwickeln, das in das Red Tin integriert werden könnte.

Ich hatte von der Bela-Plattform, der Software mit einer Audioprozessorplatine, dem sogenannten „Cape“, gehört – ähnlich wie der Raspberry Pi HAT oder das Arduino Shield, aber für die BeagleBone Black-Entwicklungsplatine – und entschied mich dafür, es mit einem BeagleBone Black Wireless (125-2412) , das wir da hatten, auszuprobieren.

Bela wurde an der Queen Mary University of London als Projekt des Augmented Instruments Laboratory entwickelt. Das Team benötigte eine vielseitige Audio-Plattform mit geringer Latenz für ein Projekt, an dem es arbeitete. Da es diese nicht im Handel finden konnte, baute es seine eigene Plattform. Die komplette Story finden Sie hier.

Einführung

Bela bietet sehr nützliche und umfassende Seiten für die ersten Schritte auf Github, also habe ich dort angefangen.

Die erste Aufgabe bestand darin, das Bela-Cape an den BeagleBone Black zu montieren. Anschließend konnte ich die Bela-Software einrichten. Ich verwende Ubuntu für meinen Arbeitscomputer und Windows im Red Tin, daher werde ich hier das Verfahren für beide Betriebssysteme erläutern.

Ich habe das Bela-Image hier heruntergeladen und entpackt. Meine Kopie von Ubuntu verfügte bereits über eine Software, die das von Bela verwendete .xz-Format entpackte. Wenn Sie jedoch Windows verwenden, müssen Sie möglicherweise eine Software wie 7Zip verwenden, um die .img-Datei zu entpacken.

In Ubuntu können Sie mit der rechten Maustaste auf die .img-Datei klicken und „Open with Disk Image Writer“ (Mit Disk Image Writer öffnen) auswählen.

Stellen Sie anschließend sicher, dass Sie die SD-Karte im Menü „Destination“ (Ziel) ausgewählt haben, klicken Sie auf „Start Restoring...“ (Wiederherstellung starten ...), bestätigen Sie, dass Sie den Vorgang wirklich ausführen möchten, geben Sie Ihr Kennwort ein und warten Sie dann einfach, bis das Image auf die Karte übertragen wird. Dadurch werden alle auf Ihrer SD-Karte vorhandenen Daten gelöscht!

Wenn Sie dies lieber über die Befehlszeile tun möchten, finden Sie die Anweisungen im Abschnitt „Flashing the Bela Image“ (Bela-Image flashen) auf den Bela-Seiten auf Github. Wenn Sie Windows verwenden, wird empfohlen, den Win32 Disk Imager zu verwenden.

Nachdem ich die oben genannten Schritte ausgeführt und die Micro-SD-Karte mit dem zuvor geflashten Bela-Image in den Steckplatz des BeagleBone Black eingesetzt hatte, schloss ich den Beaglebone mit dem Bela-Cape an meinen Computer an und wartete, bis er verbunden war. Es dauert eine Weile, doch die blau blinkenden LEDs wiesen darauf hin, dass sich etwas tat. Dann musste ich nur noch einen Webbrowser öffnen und die IP-Adresse des BeagleBone Black unter http://192.168.7.2 aufrufen.

Daraufhin sollte die Bela-IDE in Ihrem Browserfenster angezeigt werden. Das Einzige, was ich sah, war die Beaglebone-Seite ohne jegliche Spur von Bela. Nach einigen Untersuchungen stellte ich fest, dass der BeagleBone Black über seinen integrierten eMMC-Speicher startet, der ein standardmäßiges Betriebssystem-Image enthält. Sie können den Startvorgang über die Micro-SD-Karte erzwingen, indem Sie ihn ausschalten, die Benutzertaste gedrückt halten und ihn dann wieder verbinden. Halten Sie die Taste gedrückt, bis die Gruppe der 4 LEDs am Micro-USB-Anschluss eine Aktivität anzeigt.

Die kleine Taste ist bei angebrachtem Bela-Cape ziemlich schwer zu erreichen, daher ist es einfacher, es zunächst zu entfernen.

Sobald ich Zugriff auf die Bela-IDE hatte, konnte ich den folgenden Befehl ausführen, indem ich ihn in den Konsolenabschnitt unten in der IDE eingab:

/opt/Bela/bela_flash_emmc.sh

Nachdem das Bela-Image auf dem eMMC gespeichert war, konnte er automatisch darüber gestartet werden. Deshalb schaltete ich ihn aus, entfernte die Micro-SD und montierte das Bela-Cape, bevor ich ihn neu startete. Wie erwartet, wurde er dieses Mal über das Bela-Image gestartet.

Die Bela-Hardware

Das Bela-Cape hat eine ganze Menge Steckverbinder, darunter: 

  • Audioeingang: 16-Bit-Stereo-Audioeingang bei 44,1 kHz
  • Audioausgang: 16-Bit-Stereo-Audioausgang bei 44,1 kHz
  • Audio-Leistungsausgang: 2x 1-W-8-Ohm-Lautsprecherverstärker (verfügbar bei Stromversorgung über DC-Buchse)
  • Analogeingang: 8 x 16-Bit-Analogeingänge bei 22,05 kHz
  • Analogausgang: 8 x 16-Bit-Analogausgänge bei 22,05 kHz
  • Digitale Kanäle: 16 x digitale GPIO bei 44,1 kHz oder 88,2 kHz

Analogeingänge und -ausgänge sind auch per Software konfigurierbar, um 4 Kanäle bei 44,1 kHz oder 2 Kanäle bei 88,1 kHz bereitzustellen.

Das Cape wird mit Kabeln geliefert, über die 3,5-mm-Stereo-Klinkenbuchsen mit den Steckverbindern des Analogeingangs und -ausgangs verbunden werden, was das Ganze einfacher macht.

Einige Beispiele ausprobieren

Ich beschloss, dass es jetzt das Beste wäre, einige Beispiele durchzugehen, die auf der Seite Examples and Projects (Beispiele und Projekte) zu finden sind.

Dies wäre eine gute Möglichkeit, mich mit der Bela-IDE vertraut zu machen, und wie ich feststellte, gab es Links zum Code für alle Beispiele im IDE-Menü.

So bringen Sie das Ganze zum Piepen!

#include <Bela.h>
#include <cmath>

float gFrequency = 440.0;
float gPhase;
float gInverseSampleRate;

bool setup(BelaContext *context, void *userData)
{
	gInverseSampleRate = 1.0 / context->audioSampleRate;
	gPhase = 0.0;

	return true;
}

void render(BelaContext *context, void *userData)
{
	for(unsigned int n = 0; n < context->audioFrames; n++) {
		float out = 0.8f * sinf(gPhase);
		gPhase += 2.0f * (float)M_PI * gFrequency * gInverseSampleRate;
		if(gPhase > M_PI)
			gPhase -= 2.0f * (float)M_PI;

		for(unsigned int channel = 0; channel < context->audioOutChannels; channel++) {
			audioWrite(context, n, channel, out);
		}
	}
}

void cleanup(BelaContext *context, void *userData)
{

}


/**
\example sinetone/render.cpp
Producing your first bleep!
---------------------------
This sketch is the hello world of embedded interactive audio. Better known as bleep, it 
produces a sine tone.
The frequency of the sine tone is determined by a global variable, `gFrequency`. 
The sine tone is produced by incrementing the phase of a sin function 
on every audio frame.
In render() you'll see a nested for loop structure. You'll see this in all Bela projects. 
The first for loop cycles through 'audioFrames', the second through 'audioChannels' (in this case left 0 and right 1). 
It is good to familiarise yourself with this structure as it's fundamental to producing sound with the system.
*/

Ich begann mit dem Sinuswellengenerator. Es war ziemlich einfach, den Code in die Bela-IDE zu laden und dann einfach auf die große grüne „Run“-Schaltfläche zu klicken. Ich schloss meinen Kopfhörer über das mitgelieferte Adapterkabel an und konnte tatsächlich eine Sinuswelle hören.

Wie Sie im obigen Code sehen können, enthält jedes Beispiel eine umfassende Erklärung in Form von Kommentaren unten, was der Code tut. Wie beschrieben, wird die Frequenz des Sinustons durch die globale Variable ‚gFrequency‘ bestimmt. Nachdem ich diese Variable im Code gefunden hatte, konnte ich den Wert einfach ändern, den Code erneut ausführen und den Unterschied hören.

LED-Pegelmesser

Dessen Zusammenbau nahm etwas Zeit in Anspruch. Wir benötigen: 

Sobald ich die Komponenten hatte, war es ziemlich einfach, sie zusammenzubauen. Und schon hatte ich sie mit dem Cape verbunden, den Code geladen und in der IDE ausgeführt. Zu diesem Zeitpunkt stellte ich fest, dass die LEDs im Beispiel von hinten nach vorne zeigen. Ich hätte die gelben und roten LEDs einfach durch die drei grünen unten austauschen und das Ganze auf die andere Seite drehen können. Das hätte jedoch einen Kabelsalat ergeben. Daher war es genauso einfach, die Reihenfolge der Anschlusskabel umzukehren und schon funktionierte es wie erwartet.

Verzögerung

// Simple Delay on Audio Input with Low Pass Filter

#include <Bela.h>

#define DELAY_BUFFER_SIZE 44100

// Buffer holding previous samples per channel
float gDelayBuffer_l[DELAY_BUFFER_SIZE] = {0};
float gDelayBuffer_r[DELAY_BUFFER_SIZE] = {0};
// Write pointer
int gDelayBufWritePtr = 0;
// Amount of delay
float gDelayAmount = 1.0;
// Amount of feedback
float gDelayFeedbackAmount = 0.999;
// Level of pre-delay input
float gDelayAmountPre = 0.75;
// Amount of delay in samples (needs to be smaller than or equal to the buffer size defined above)
int gDelayInSamples = 22050;

// Butterworth coefficients for low-pass filter @ 8000Hz
float gDel_a0 = 0.1772443606634904;
float gDel_a1 = 0.3544887213269808;
float gDel_a2 = 0.1772443606634904;
float gDel_a3 = -0.5087156198145868;
float gDel_a4 = 0.2176930624685485;

// Previous two input and output values for each channel (required for applying the filter)
float gDel_x1_l = 0;
float gDel_x2_l = 0;
float gDel_y1_l = 0;
float gDel_y2_l = 0;
float gDel_x1_r = 0;
float gDel_x2_r = 0;
float gDel_y1_r = 0;
float gDel_y2_r = 0;

bool setup(BelaContext *context, void *userData)
{
    
    return true;
}

void render(BelaContext *context, void *userData)
{

    for(unsigned int n = 0; n < context->audioFrames; n++) {
        
        float out_l = 0;
        float out_r = 0;
        
        // Read audio inputs
        out_l = audioRead(context,n,0);
        out_r = audioRead(context,n,1);
        
        // Increment delay buffer write pointer
        if(++gDelayBufWritePtr>DELAY_BUFFER_SIZE)
            gDelayBufWritePtr = 0;
        
        // Calculate the sample that will be written into the delay buffer...
        // 1. Multiply the current (dry) sample by the pre-delay gain level (set above)
        // 2. Get the previously delayed sample from the buffer, multiply it by the feedback gain and add it to the current sample
        float del_input_l = (gDelayAmountPre * out_l + gDelayBuffer_l[(gDelayBufWritePtr-gDelayInSamples+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE] * gDelayFeedbackAmount);
        float del_input_r = (gDelayAmountPre * out_r + gDelayBuffer_r[(gDelayBufWritePtr-gDelayInSamples+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE] * gDelayFeedbackAmount);
        
        // ...but let's not write it into the buffer yet! First we need to apply the low-pass filter!
        
        // Remember these values so that we can update the filter later, as we're about to overwrite it
        float temp_x_l = del_input_l;
        float temp_x_r = del_input_r;
        
        // Apply the butterworth filter (y = a0*x0 + a1*x1 + a2*x2 + a3*y1 + a4*y2)
        del_input_l = gDel_a0*del_input_l
                    + gDel_a1*gDel_x1_l
                    + gDel_a2*gDel_x2_l
                    + gDel_a3*gDelayBuffer_l[(gDelayBufWritePtr-1+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE]
                    + gDel_a4*gDelayBuffer_l[(gDelayBufWritePtr-2+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE];
        
        // Update previous values for next iteration of filter processing
        gDel_x2_l = gDel_x1_l;
        gDel_x1_l = temp_x_l;
        gDel_y2_l = gDel_y1_l;
        gDel_y1_l = del_input_l;
        
        // Repeat process for the right channel
        del_input_r = gDel_a0*del_input_r
                    + gDel_a1*gDel_x1_r
                    + gDel_a2*gDel_x2_r
                    + gDel_a3*gDelayBuffer_r[(gDelayBufWritePtr-1+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE]
                    + gDel_a4*gDelayBuffer_r[(gDelayBufWritePtr-2+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE];
    
        gDel_x2_r = gDel_x1_r;
        gDel_x1_r = temp_x_r;
        gDel_y2_r = gDel_y1_r;
        gDel_y1_r = del_input_r;
        
        //  Now we can write it into the delay buffer
        gDelayBuffer_l[gDelayBufWritePtr] = del_input_l;
        gDelayBuffer_r[gDelayBufWritePtr] = del_input_r;
        
        // Get the delayed sample (by reading `gDelayInSamples` many samples behind our current write pointer) and add it to our output sample
        out_l += gDelayBuffer_l[(gDelayBufWritePtr-gDelayInSamples+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE] * gDelayAmount;
        out_r += gDelayBuffer_r[(gDelayBufWritePtr-gDelayInSamples+DELAY_BUFFER_SIZE)%DELAY_BUFFER_SIZE] * gDelayAmount;
        
        // Write the sample into the output buffer -- done!
        audioWrite(context, n, 0, out_l);
        audioWrite(context, n, 1, out_r);
    }
    
}

void cleanup(BelaContext *context, void *userData)
{

}

/**
\example delay/render.cpp

Jetzt war ich gespannt, den einen oder anderen Effekt auszuprobieren, da mein endgültiges Ziel darin bestand, diese für ein Soundeffekt-Modul zu verwenden.

Wie bei den anderen Beispielen fand ich den Code im rechten Abschnitt der IDE, der im Abschnitt „Code Examples“ (Codebeispiele) aufgeführt und durch Klicken auf das Glühbirnensymbol abrufbar war.

Ich dachte, jetzt wäre ein guter Zeitpunkt, um zu lernen, wie man einen Sketch beim Hochfahren ohne Verbindung mit einem Computer lädt, da dies für einen eigenständigen Effektgenerator von entscheidender Bedeutung wäre.

Zunächst speicherte ich den Verzögerungscode als Projekt, indem ich auf das Ordnersymbol in der IDE, dann auf die Schaltfläche „Save project as“ (Projekt speichern unter) klickte, es „Delay“ (Verzögerung) nannte und auf „Save“ (Speichern) klickte. Dann konnte ich die „Project Settings“ (Projekteinstellungen) (das Zahnradsymbol) aufrufen, die Dropdown-Liste neben „Run project on boot“ (Projekt beim Start ausführen) anklicken und mein neu gespeichertes Verzögerungsprojekt auswählen. Ich schaltete den BeagleBone aus und trennte ihn vom Computer. Dann schaltete ich ihn mit einem separaten Netzteil ein, und sobald er hochgefahren war, konnte ich die Verzögerung in vollem Umfang hören.

Tremelo

Die Geschwindigkeit des Tremolos wird über ein 10-kΩ-Potenziometer (249-9200) gesteuert, das an 3,3 V und die Masse an Pin 1 und 3 des Cape angeschlossen ist, wobei der 2. mittlere Pin mit analogIn 0 verbunden ist.

Ich führte eine Sinuswelle in einer Schleife über meine DJ-Software aus, um zu verdeutlichen, wie der Effekt klingt.

Weitere Schritte

Mein Plan besteht nun darin, den BeagleBone Black und das Bela-Cape in ein Modul einzubauen, das ähnlich wie das von mir gebaute Soundeffekt-Modul in das Red Tin passt. Außerdem muss ich herausfinden, wie ich mehrere Effekte laden und mit auf dem Gehäuse angebrachten Tasten zwischen ihnen wechseln kann. Ich weiß, dass dies möglich ist, da es Beispiele für Gitarreneffektgeräte gibt, die mit Bela gebaut wurden und genau das tun.

I currently look after production at AB Open. I have a background in the arts, environmental conservation and IT support. In my spare time I do a bit of DJing and I like making things.

15 Jul 2019, 11:47

Kommentare