Skip to main content
shopping_basket Warenkorb 0
Einloggen

Tonbearbeitung mit Raspberry Pi und Pmods

In diesem Projekt nutzen wir einen Raspberry Pi und Digilent Pmods, um verschiedene Toneffekte auf ein eingehendes Tonsignal anzuwenden. Die Nutzerschnittstelle bestimmt das Ausmaß und die Art des Effekts.

Parts list

Qty Produkt Part number
1 Raspberry Pi 4 B 8GB 182-2098
1 DesignSpark Pmod HAT with 3 Digilent Pmod Sockets for Raspberry Pi 144-8419
1 Digilent Analog-to-Digital Converter Expansion Module 410-064 134-6443
1 Digilent Digital to Analog Converter Expansion Module 410-241 134-6456
1 Digilent LED Expansion Module 410-163 134-6450
1 Digilent Expansion Module 410-135 136-8061
1 Digilent Rotary Encoder Expansion Module 410-117 410-117
1 RS PRO 3.5 mm PCB Mount Stereo Jack Socket, 5Pole 913-1021
1 Digilent Analog Discovery 2 PC Based Oscilloscope, 30MHz, 2 Channels 134-6480
1 Digilent, 240-000 184-0451

Einleitung

Die Hauptaufgabe besteht darin, Toneffekte auf ein eingehendes Signal anzuwenden. Um die Dinge so einfach wie möglich zu halten, handelt es sich bei den beiden Toneffekten, die für dieses Projekt als Wirksamkeitsnachweis ausgewählt wurden, um den Echo-Effekt und eine Tonhöhenveränderung. Die Art dieser Effekte wird weiter unten in dieser Anleitung besprochen.

Block diagram showing circuit operation

Der Raspberry Pi besitzt die nötige Rechenleistung, um ein eingehendes Signal fast in Echtzeit zu verändern. Standardmäßig fehlen ihm die notwendigen Peripheriegeräte, um diese Signale zu erhalten und wiederzugeben. Aber mithilfe des Pmod-HAT-Adapters können Digilent Pmods und eine großen Auswahl an Plug'n'Play-Peripheriemodulen leicht mit dem Einplatinencomputer verbunden werden. Wir holen uns den Ton, ändern ihn und spielen ihn dann wieder ab. Zunächst wird das Tonsignal in einen Analog-Digital-Umsetzer (ADC) eingegeben. Die meisten ADCs können kein unbearbeitetes eingehendes Tonsignal direkt verarbeiten, deswegen kann eine Aufbereitung des Signals notwendig sein. Nach der Verarbeitung des digitalen Signals muss es mithilfe eines Digital-Analog-Umsetzers (DAC) wieder in den analogen Bereich zurückgewandelt werden. Bevor das erzeugte Signal an einen Verstärker oder Aktivlautsprecher weitergeleitet wird, kann eine erneute Bearbeitung notwendig werden.


Um den Toneffekt zu regeln, erstellen wir die Nutzerschnittstelle. Für diese Anwendung nutzen wir drei Reglerelemente: einen Drehgeber, um das Ausmaß des angewendeten Effekts einzustellen, einen Schalter, um die Art des Effekts zu ändern, und einen Knopf, um den Zustand des Geräts zurückzusetzen. Weil der Nutzer eine Rückmeldung zum Zustand der Regler braucht, wird ein LED-Balkendiagramm, das aus 8 LEDs besteht, als Anzeige genutzt. Ein LED zur Betriebszustandsanzeige ist ebenfalls nützlich. Damit wird angezeigt, ob die externe Schaltung Strom hat oder nicht.

Den Raspberry Pi einrichten

Wir nutzen für dieses Projekt Python 3, das auf dem Raspberry-Pi-OS (Version 3.7) vorinstalliert ist, aber das Standard-Python ist Python 2. Um die Dinge zu vereinfachen, sollte Python 3 als Standard-Interpretierprogramm eingestellt werden. Öffnen Sie ein Terminal auf dem Raspberry Pi und tippen Sie dann Folgendes:

sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1

 

Dann sollten die notwendigen Python-Pakete installiert oder auf die neueste Version aktualisiert werden, und zwar mit folgendem Befehl:

pip install numpy matplotlib gpiozero RPi.GPIO spidev --upgrade

 

Weil wir Peripheriegeräte nutzen , die über die SPI-Schnittstelle geregelt werden, sollte diese Schnittstelle aktiviert sein. Öffnen Sie die Konfigurationseinstellungen mit folgendem Befehl:

sudo raspi-config

Wählen Sie „Schnittstellenoptionen, dann „SPI“ und aktivieren Sie die Schnittstelle, indem Sie „Ja“ auswählen.

Eingangssignal

Pmod AD1

Image of a Pmod AD1 board

Der Raspberry Pi kann analoge Signale nicht direkt abtasten, deswegen nutzen wir den Digilent Pmod AD1, ein ADC mit 12 Bit und 1 MS/s. Der Pmod AD1 wird mit dem AD7476A von Analog Devices betrieben. Er kommuniziert mit dem Raspberry Pi über eine SPI-Schnittstelle.

Die Umwandlung erfolgt nach dieser Formel: n = 212 * Vin / Vref, dabei ist n die ausgegebene Zahl, Vin ist die Eingangsspannung und Vref ist die Referenzspannung, die gleich der Versorgungsspannung (3,3 V) ist. Der ADC kann aber keine Spannung verarbeiten, die geringer als 0 V oder höher als die Referenzspannung ist. Obwohl die Amplitude des Tonsignals bei den meisten Geräte ziemlich niedrig ist (ungefähr 1 V), hat sie einen Offset von 0 V. Der Spannungsbereich liegt zwischen -1 V und 1 V. Um dieses Problem zu lösen, muss eine Signalaufbereitung eingerichtet werden.

Aufbereitung des Eingangssignals

Weil die Amplitude des Tonsignals viel niedriger ist als die Referenzspannung (2 * A < Vref), reicht es aus, dem Signal einen positiven Offset zu verleihen, um es größer als 0 V zu machen. Dazu wird ein Summierverstärker eingesetzt, wie in dem Bild unten dargestellt.

Input Signal Conditioning Circuit

In dieser Konfiguration wird die gewünschte Offset-Spannung von den Widerständen R4 und R5 eingestellt: Voffset = VSS * R4 / (R4 + R5), wobei VSS die negative Versorgungsspannung ist. Die Ausgangsspannung wird gemäß der folgenden Formel erreicht: Vout = - (Vin * R2 / R1 + Voffset * R2 / R3) = - (Vin * R2 / R1 + VSS * R4 / (R4 + R5)). In diesem Fall stellen die Widerstände R4 und R5 die Offset-Spannung ein und die Widerstände R1 und R2 stellen die Verstärkung ein. Obwohl das Signal invertiert wird, hat das keine Auswirkung auf den Schaltkreis.

Stromversorgung

Der Raspberry Pi hat eine Stromversorgung von 5 V auf den Pins 2 und 4, aber die Signalaufbereitung benötigt eine negative Versorgung. Um eine negative Versorgungsspannung zu erzielen, können wir den isolierten DC/DC-Wandler LTM8067 verwenden. Zunächst verbinden wir den Eingang mit der 5-V-Versorgung und mit Masse. Dann erden wir den positiven Ausgabe-Pin des Wandlers. Weil Eingang und Ausgang isoliert sind, verursacht die Erdung des positiven Pins keinen Kurzschluss im Modul. Das Spannungspotenzial des negativen Pins liegt verglichen mit der Erdung des Raspberry Pis unter 0 V. Machen Sie das nicht mit einem nicht-isolierten Wandler! Nutzen Sie ein Voltmeter, um die negative Ausgangsspannung zu messen. Drehen Sie das Potenziometer mit einem Schraubendreher so weit, bis Sie –5 V erhalten.

Ausgangssignal

Pmod DA3

Image of a Pmod DA3 board

Der Raspberry Pi hat nur einen analogen Ausgang, die 3,5-mm-Audiobuchse, die für den Systemton verwendet wird. Um einen separaten Ausgang für das bearbeitete Tonsignal zu haben, nutzen wir den Digilent Pmod DA3. Der Pmod DA3 ist ein 16-Bit-DAC, der mit dem AD5541A von Analog Devices betrieben wird. Der Pmod DA3 kann mit dem Raspberry Pi über eine SPI-Schnittstelle kommunizieren.

Die Umwandlung erfolgt nach dieser Formel: Vout = n * Vre f / 216, dabei ist Vout die Ausgangsspannung, n ist die Eingangszahl und Vref ist die Referenzspannung, die 2,5 Volt entspricht (interne Referenz). Da der DAC nur vorzeichenlose 16-Bit-Zahlen verarbeiten kann, kann beim Ausgang keine Spannung erreicht werden, die geringer als 0 V oder höher als Vref  ist. Ein Verstärker oder Aktivlautsprecher „erwartet“ aber ein Signal mit einem Offset von 0 V und normalerweise einer Amplitude von maximal 1 V, deswegen muss das Ausgangssignal bearbeitet werden.

Die Bearbeitung des Ausgangssignals

Der Bereich von 0–2,5 V ermöglicht ein Ausgangssignal mit einer Amplitude von 1 V, wenn es mindestens einen Offset von 1 V besitzt. Der Offset kann mit einem Entkoppelkondensator entfernt werden, gefolgt von einem Spannungsfolger. Ein Tiefpassfilter kann für die Ausgabe ebenfalls nötig sein. Die negative Versorgung des Spannungsfolgers wird von dem zuvor erwähnten DC/DC-Wandler genommen, der ein Schaltregler (Sperrwandler) ist, deswegen erzeugt er ein hochfrequentes Schaltgeräusch. Wegen der begrenzten Geschwindigkeit des Raspberry Pis ist die Abtastrate ebenfalls begrenzt. Mit einer reduzierten Abtastrate kann die Ausgabe scharfkantig klingen, deswegen sollten die Oberschwingungen der Ausgangsfrequenzen ebenfalls ausgefiltert werden.

Die Vokale der menschlichen Sprache können Frequenzen bis zu 2 kHz erreichen, wohingegen Konsonanten Frequenzen bis zu 6 kHz erreichen. Wenn ein einfacher Tiefpassfilter verwendet wird, erscheint es angebracht, die Grenzfrequenz zwischen 3 kHz und 4 kHz einzustellen, weil der größte Teil der Töne unter 3500 Hz liegt (Quelle).

Output Signal Conditioning Circuit

Wenn Standardwerte für Widerstände und Kondensatoren verwendet werden, wird die Grenzfrequenz des Filters zu fc = 1 / (2 * π * R8 * C2) = 3,4 kHz.

Nutzerschnittstelle

Pmod ENC

Image of a Pmod ENC board

Mit dem Pmod ENC können wir einen Schalter nutzen, um die Tonbearbeitung einzuschalten, einen Drehgeber zur Einstellung des Effektgrads und einen Reset-Knopf.

Pmod 8LD

Image of a Pmod 8LD board

Der Pmod 8LD enthält 8 LEDs mit großer Helligkeit, die über Logikpegel mit geringer Leistung betrieben werden. Damit kann der Nutzer eine Rückmeldung erhalten.

Stromanzeige

Der Raspberry Pi verfügt zwar über eine LED zur Betriebsanzeige, aber eine zweite Anzeige ist nützlich, um zu signalisieren, ob die Signalaufbereitung Strom hat oder nicht. Um die Stromanzeige einzurichten, verbinden Sie einfach eine LED mit der 5 V-Versorgung in Reihe mit einem Strombegrenzungswiderstand.

Power Indicator Circuit

Der Wert des Strombegrenzungswiderstands kann mit der folgenden Formel berechnet werden: R9 = (VCC – VLED) / ILED, wobei VLED die Durchlassspannung der LED (normalerweise etwa 1,8 V für rote LEDs) ist und ILED die gewünschte Spannung durch die LED ist. Der Widerstand muss so gewählt werden, dass diese Spannung unter dem Maximum liegt. Die Helligkeit einer LED ist proportional zur durchgehenden Spannung. Wenn eine weniger helle Anzeige gewünscht ist, muss ein höherer Widerstand gewählt werden.

Schnittstellen-Pmods für den Raspberry Pi

Pmod HAT Adapter

Image of Pmod HAT Adapter

Wir können Digilent Pmods über den Pmod-HAT-Adapter mit dem Raspberry Pi verbinden. Der Pmod-HAT-Adapter bricht den 40-poligen GPIO-Stecker des Raspberry Pis herunter auf drei 2x6-Digilent Pmod-Stecker (JA, JB und JC) und alle können auch als zwei separate 1x6-Pmod-Stecker verwendet werden (zum Beispiel kann JA in JAA und JAB unterteilt werden). Alle Pmod-Ports enthalten eine Erdung und einen 3,3-V-Pin für die Stromversorgung des eingesteckten Pmods. Alle Ports können als GPIO (General Purpose Input/Output) verwendet werden, allerdings haben einige Ports zusätzliche Funktionen: JAA und JBA können verwendet werden, um Pmods mit der SPI-Schnittstelle zu verbinden, die I2C-Schnittstelle kann auf dem Port JBB und UART auf JCA verwendet werden. Der Adapter kann direkt mit der Stromversorgung vom Raspberry Pi oder mit einer externen 5-V-Stromversorgung über den DC-Hohlstecker genutzt werden (verwenden Sie nicht beide gleichzeitig!).

Die folgenden Verbindungen werden empfohlen:

Pmod-HAT-Adapter-Port Verbundenes Pmod Protocol Used
JAA Pmod AD1 SPI
JAB Pmod ENC GPIO
JBA Pmod DA3 SPI
JC Pmod 8LD GPIO

Um sowohl Pmod AD1 und Pmod ENC mit dem JA-Port des Pmod-HAT-Adapters zu verbinden, kann man den 12-poligen Pmod-TPH2-Prüfkopf nutzen.

Image of Pmod TPH2 board

Die ganze Schaltung

Nachdem die Signalaufbereitungen, die negative Stromversorgung und die Stromanzeige auf dem Lochbrett montiert sind, verbinden Sie die 5-V-Schiene mit Pin 2 auf dem 40-poligen GPIO-Stecker des Raspberry Pis und die GND-Schiene mit Pin 39. Auf diese Weise erhalten die Schaltungen auf dem Lochbrett Strom. Verbinden Sie den Ausgang der ersten Signalbearbeitung mit dem Kanal A1 auf dem Pmod AD1 und den Eingang der zweiten Signalbearbeitung mit dem SMA-Stecker des Pmod DA3 (statt eines SMA-Steckers kann auch ein MTE-Kabel in den Stecker eingesetzt werden).

The complete circuit

Software

Wie zuvor besprochen wird die Software, die die Tonbearbeitung regelt, in Python3 geschrieben. Das Projekt besteht aus sechs Modulen, die mit einem Top-Down-Ansatz vorgestellt werden.

main.py

Das Hauptmodul besteht aus den wichtigsten Einstellungen des Projekts und initialisiert die anderen Module. Jede wichtige Menge sollte an einem leicht zugänglichen Ort wie dem Beginn des Hauptmoduls erscheinen, um die Feinabstimmung zu erleichtern.

# global variables
spi_clock_speed = int(4e06)   # spi clock frequency in Hz
sample_time = 5e-05  # seconds between samples
buffer_size = 5000  # data points in the buffer
DEBUG = "None"  # "ADC", "DAC", "PROC", "ALL" or "None"
adc_res = 4095  # resolution of the ADC
dac_res = 65535  # resolution of the DAC

Der Raspberry Pi soll 4 wichtige Aufgaben erledigen: Toneingang erhalten, Tonsignale bearbeiten und senden und mit dem Nutzer kommunizieren. Wenn diese Aufgaben nacheinander erledigt werden, gibt es zwei entscheidende Mängel:

  1. Eine große Verzögerung zwischen dem Eingangston und dem Ausgangston (die Zeit, in der das Signal aufgezeichnet, verarbeitet und wieder abgespielt wird)
  1. Unterbrechungen im Ausgangston.

Um das zu vermeiden, müssen die Aufgaben parallel erledigt werden.

Die Nutzerschnittstelle kann mit dem Python-Modul gpiozero realisiert werden, das asynchrone Ereignisse nutzt (wie Unterbrechungen auf einem Mikrokontroller), um mit dem Nutzer zu kommunizieren. Das Hauptmodul weist diesen Ereignissen einfach Aktionen zu.

 

# set user interface actions
# increment/decrement a value, when the rotary encoder is rotated
UI.enc.when_rotated = UI.set_value
# reset the value, when the button is pressed
UI.btn.when_pressed = UI.reset_value
# set a flag according to the state of the switch
UI.swt.when_pressed = UI.change_mode
UI.swt.when_released = UI.change_mode

Das Modell B des Raspberry Pi 4 besitzt einen Quad-Core-Cortex-A72-Prozessor, der es uns ermöglicht, verschiedene Aufgaben auf verschiedenen Prozessorkernen über das simultan verarbeitende Python-Modul auszuführen. Der erste Hauptprozess initialisiert lediglich die Kindprozesse. Ein Kindprozess zeichnet die eingehenden Daten auf, der nächste verarbeitet die Daten und der letzte spielt sie wieder ab.

Um Unterbrechungen der Ausgabe zu vermeiden, werden drei gemeinsam genutzte Puffer verwendet: Der Aufzeichnungsprozess füllt die drei Puffer nacheinander. Wenn der erste Puffer vom Abspielprozess geleert wurde, beginnt der ganze Prozess von vorn. Die Datenverarbeitung wartet auf die Aufzeichnung und modifiziert den Inhalt der Puffer.

Diagram showing the three shared buffers

Gemeinsame Flags werden genutzt, um den Zustand jedes Puffers anzuzeigen.

 

# create shared lists
manager = multiprocessing.Manager()
# 3 buffers to use them in rotation
buffer = manager.list([[], [], []])
# flags to signal aquisition state
get_flag = manager.list([False, False, False])
# flags to signal processing state
set_flag = manager.list([False, False, False])
# flags to signal write-out state
ready_flag = manager.list([True, True, True])

 

Der Wrapper startet die Kindprozesse und wartet dann darauf, dass sie fertig werden (das Programm wird mit Ctrl + C beendet).

# main part
if __name__ == "__main__":
    UI.reset_value()   # reset counter

    # initialize processes
    acquisition = multiprocessing.Process(target=DI.acquire_data)
    processing = multiprocessing.Process(target=DP.process_data)
    playing = multiprocessing.Process(target=DO.output_data)

    # start threads
    acquisition.start()
    processing.start()
    playing.start()

    # wait for exit condition
    acquisition.join()
    processing.join()
    playing.join()

    UI.reset_value()   # reset counters

    # terminate processes
    acquisition.terminate()
    processing.terminate()
    playing.terminate()

user_interface.py

Das Modul der Nutzerschnittstelle enthält alle Funktionen der Nutzerinteraktion. Diese Funktionen

  1. setzen eine Variable gemäß dem Zustand des Drehgebers
  2. lassen die LEDs entsprechend dieser Variable aufleuchten
  3. ändern den Zustand der Flag bei verschiedenen Schalterpositionen (der Schalter muss hoch oder herunter gezogen werden, weil sonst die Grenzpunkte nicht erkannt werden)
  4. setzen alle Werte und Flags zurück, wenn der Reset-Knopf gedrückt wird.
def set_value():
    # map the counter between 0 and 1 using the rotary encoder
    global param
    param[0] = enc.steps / (2 * enc.max_steps) + 0.5
    set_leds()  # set LED states
    return
def set_leds():
    global param
    # set the leds on/off according to the counter
    if param[1]:
        led.value = param[0]
    else:
        led.value = -param[0]
    return
def change_mode():
    # switch the flag
    global param
    param[1] = bool(swt.value)
    # force software pull-up/-down
    if param[1]:
        GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    else:
        GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    set_leds()  # set LED states
    return
def reset_value():
    # reset the counter
    global param
    param[0] = 0
    enc.steps = -enc.max_steps  # reset rotary encoder state
    param[1] = bool(swt.value)  # reset switch state
    set_leds()  # reset LED states
    return

 

Diese Module nutzen Teile des Python-Pakets gpiozero, um den Umgang mit den Ein- und Ausgabegeräten zu vereinfachen.

# initialize devices
# Rotary Encoder
enc = RotaryEncoder(19, 21)
btn = Button(20)
swt = Button(18)

# pull down the switch
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# LEDs
led = LEDBarGraph(16, 14, 15, 17, 4, 12, 5, 6)

 

Die erhaltenen Werte und Flags werden in einer gemeinsamen Liste gespeichert, damit sie anderen Prozessen zur Verfügung stehen.

# shared user-interface parameters
manager = multiprocessing.Manager()
param = manager.list([0, False])

data_input.py

Das Dateneingabe-Modul ist verantwortlich für die Initialisierung der SPI-Kommunikation mit dem Pmod AD1 und nutzt das Python-Paket spidev. Dieses Modul füllt einen Puffer mit den erhaltenen 12-Bit-Datenwörtern und wartet nach jedem Erhalt für eine vorab festgelegte Zeit (Abwarten zwischen den Samples ist notwendig, damit sichergestellt wird, dass die Zeit zwischen zwei Samples immer gleich ist – sonst kann es Tonhöhenveränderungen geben). Außerdem setzt es Flags, wenn der Puffer gefüllt ist, um diesen Zustand den anderen Prozessen anzuzeigen.

 

# initialize ADC
adc = spidev.SpiDev()
adc.open(SPI_port, CS_pin)
adc.max_speed_hz = main.spi_clock_speed
for _ in range(main.buffer_size):
  # measure start time
  start_time = time.perf_counter()

  # read data bytes
  adc_raw = adc.readbytes(2)
  # recreate the number from the bytes
  adc_number = adc_raw[1] | (adc_raw[0] << 8)
  # insert the number in the buffer
  buff.append(adc_number)

  # check the duration of the operation
  duration = time.perf_counter() - start_time
  # wait if necessary
  if main.sample_time > duration:
    time.sleep(main.sample_time - duration)
# assign buffer and set flags
if main.ready_flag[0]:
  main.buffer[0] = buff
  main.get_flag[0] = True
  main.ready_flag[0] = False
  continue_flag = True
elif main.ready_flag[1]:
  main.buffer[1] = buff
  main.get_flag[1] = True
  main.ready_flag[1] = False
  continue_flag = True
elif main.ready_flag[2]:
  main.buffer[2] = buff
  main.get_flag[2] = True
  main.ready_flag[2] = False
  continue_flag = True

data_output.py

Das Datenausgabemodul ist dem Dateneingabemodul sehr ähnlich. Es regelt den DAC per SPI und verwendet das Python-Paket spidev. Allerdings überprüft das Ausgabemodul globale Flags, die die Zustände der drei Puffer beschreiben, bevor der Puffer verarbeitet wird. Nachdem die Samples vom Puffer an den DAC gesendet wurden, kann die Wartezeit ungleich der Wartezeit des ADCs sein. Das liegt daran, dass das erste Element jedes Puffers Informationen über die erforderliche Höhenänderung (zur Anwendung dieses Effekts) enthält.

 

# output buffer
if case != None and len(buff) != 0:
  # calculate the duration of a sample
  # (this is needed because of the pitchbend effect)
  sample_duration = main.sample_time - buff[0]
  # discard the first sample
  # (this contains information about the pitch)
  buff.pop(0)

  # output every sample
  for point in buff:
    # measure start time
    start_time = time.perf_counter()

    # get high byte
    highbyte = point >> 8
    # get low byte
    lowbyte = point & 0xFF
    # send both bytes
    dac.writebytes([highbyte, lowbyte])

    # check the duration of the operation
    duration = time.perf_counter() - start_time
    # wait if necessary
    if sample_duration > duration:
      time.sleep(sample_duration - duration)

data_processing.py

Das Datenverarbeitungsmodul überprüft die globalen Flags, bevor es den Puffer bearbeitet. Dieses Modul bildet den Eingangspuffer zwischen -1 und 1 ab (normalisierte Werte), wendet einen der Effekte auf den normalisierten Puffer entsprechend dem Zustand des Reglerschalter und des Drehgebers an, interpoliert den normalisierten Puffer entsprechend der Auflösung des DACs und fügt die erforderliche Zeitverschiebung an der ersten Stelle ein. Die Toneffekte „Echo“ und „Pitchbend“ werden in separaten Modulen erzeugt.

 

# normalize values
buff = [interp(element, [0, main.adc_res], [-1, 1]) for element in buff]
# apply audio effect
bend = 0    # store the timeshift if needed
if UI.param[1]:
  bend = AE.pitchbend(UI.param[0], main.sample_time)
else:
  buff = AE.echo(buff, UI.param[0], main.sample_time)
# scale buffer
buff = [round(interp(element, [-1, 1], [0, main.dac_res])) for element in buff]

# insert timeshift
buff.insert(0, bend)

audio_effects.py

Dieses Modul enthält einige Konstanten, die die Eigenschaften der Toneffekte einstellen:

  1. echo_mag stellt die Lautstärke des Echoeffekts ein,
  2. echo_del stellt die maximale Verzögerung des Echos in Millisekunden ein (falls eine größere Verzögerung verwendet wird, muss die Puffergröße ebenfalls erhöht werden, was zu einer längeren Latenz führt. Dagegen erhalten wir möglicherweise mit einer kürzeren Verzögerung einen Halleffekt statt eines Echos),
  3. pitch_bend stellt die maximale Anzahl der Höhenveränderung im Vergleich zur Sampling-Frequenz ein (wenn der Ton alle 50 Mikrosekunden gesampelt wird, ergibt eine maximale Veränderung von 0,25 eine Verzögerung von 37,5 Mikrosekunden zwischen den ausgegebenen Samples, deswegen ist das Ausgangssignal 1,33 mal so hoch).
echo_mag = 0.8  # echo magnitude between 0 and 1
echo_del = 100  # maximum delay for echo (in ms)
pitch_bend = 0.25   # maximum delay for pitchbend
                    # in % compared to the sample time

 

Der erste Effekt, der pitch_bend, berechnet den Verzögerungsunterschied zwischen den Samples, indem er die ursprüngliche Samplingzeit mit der Position des Drehgebers und der maximalen Menge der Höhenänderung multipliziert. Dieser Wert wird später am Anfang des Puffers eingefügt.

def pitchbend(counter, sample_time):
    # calculate sample delay/advance for pitch bending
    bend = sample_time * counter * pitch_bend
    return bend

 

Der Echoeffekt nimmt den ursprünglichen Puffer und erzeugt eine verzögerte Version davon, indem die Sample-Anzahl für jede Verzögerungszeit berechnet wird und dann entsprechend viele 0–s eingefügt werden, um den Puffer zu starten. Der verzögerte Puffer wird entsprechend der Konstante echo_mag gedämpft und dann dem ursprünglichen Puffer hinzugefügt.

def echo(buffer, counter, sample_time):
    # count delay for samples
    counter = round(echo_del * counter / (sample_time * 1000))
    # create dummy buffer
    delay = [0 for _ in range(counter)]
    # shift samples to get the echo
    delayed_buff = delay + buffer
    # add the echo to the original buffer
    result = [buffer[index] + echo_mag * delayed_buff[index]
              for index in range(len(buffer))]
    return result

Debugging

Die Hardware debuggen

Analog Discovery 2 kann zusammen mit der WaveForms-Software verwendet werden, um die Hardware zu debuggen. Verbinden Sie das negative Kabel (das orange-weiße Kabel) des analogen Eingangskanals des AD2 mit der Masse des Raspberry Pis und verwenden Sie dann das positive Kabel (das orange Kabel), um die Spannungen zu messen und analoge Signale an verschiedenen Punkten des Schaltkreises anzuzeigen. Zeigen Sie die Ergebnisse mit dem Oszilloskopinstrument in WaveForms an. Nutzen Sie ein Eingangssignal mit fester Frequenz und Amplitude, um zu wissen, welche Ausgabe Sie erwarten können.

Einige Spannungen und analoge Signale, die Sie darstellen sollten, sind die negative Schiene der Stromversorgung (die sollte bei etwa -5 V liegen), die Ausgabe des Spannungsteilers in der eingehenden Tonbearbeitung (die sollte bei etwa -1,5 V liegen), die Ausgabe der eingehenden Tonbearbeitung (der Eingang im Bild ist ein Sinussignal von 1 kHz mit einer Lautstärke von 50 %),

Waveforms screen showing a signwave

die Ausgabe des DAC (die schlechte Qualität liegt an der niedrigen Abtastrate),

Wavforms - output of the DAC

die Ausgabe der ausgehenden Tonbearbeitung

Wavforms - the output of the output conditioning circuit,

und die Ausgabe des gesamten Geräts nach einem Tiefpassfilter.

Waveforms - output of the whole device, after the low-pass filter

Wenn ein oder mehrere Signale nicht im erwarteten Bereich liegen, sollte die Umwandlungsrate des DC/DC-Wandlers mithilfe des Potenziometers angepasst werden. Um die Amplitude eines Signals zu ändern, sollten die entsprechenden Widerstände modifiziert werden.

Nutzen Sie den Pmod TPH2 zwischen dem Pmod-HAT-Adapter und dem DAC oder dem ADC, um Testpunkte für die SPI-Signale zu erhalten. Verbinden Sie die digitalen I/O-Pins des AD2 mit den Testpunkten und nutzen Sie dann das logische Analyseinstrument in WaveForms, um die eingehenden/ausgehenden Daten darzustellen.

Waveforms - showing the select, clock and data lines

Die Software debuggen

Während die Ein- und Ausgangssignale leicht mit dem Oszilloskop oder der logischen Analyse dargestellt werden können, gibt es interne „Signale“, Zustände der verschiedenen Puffer, die nur virtuell existieren. Um diese Datenpunkte darzustellen, kann das Python-Modul matplotlib.pyplot genutzt werden. Um den Namen des Moduls abzukürzen und seine Funktion anzuzeigen, kann es in das Projekt als „debug“ importiert werden.

 

# display the buffer if needed
if main.DEBUG == "ADC" or main.DEBUG == "ALL":
  debug.plot(buff)
  debug.show()

Feinabstimmung

Die Leistung der Anwendung hängt von einigen Schlüsselparametern ab. Zwei der wichtigsten Werte im Projekt sind die Samplingzeit und die Puffergröße. Wenn die Samplingzeit reduziert wird, erhöht sich die Qualität der Ausgabe und die Bandbreite (vor dem Tiefpassfilter), aber die Zeit, die zur Füllung jedes Puffers benötigt wird, erhöht sich ebenfalls. Wenn der Puffer zu langsam gefüllt wird, gibt es Unterbrechungen bei der Ausgabe. Das kann korrigiert werden, wenn die Puffergröße reduziert wird, aber bei einer reduzierten Puffergröße kann der Echoeffekt nicht angewendet werden und es kann Probleme mit dem Timing des Pitchbends geben. Bei einer sehr kurzen Samplingzeit kann eine zufällige Tonhöhenveränderung bei der Tonausgabe auftreten. Die Lösung liegt darin, eine Balance zwischen guter Tonqualität und einem unterbrechungsfreien Betrieb zu finden.

Ergebnisse

Einige Ergebnisse mit einer Samplingzeit von 50 Mikrosekunden und einem Puffer von 5000 Samples:

Eingangston – weibliche Stimme

Ausgangston – weibliche Stimme

Weibliche Stimme mit 50 % Echo

Weibliche Stimme mit 100 % Echo

Weibliche Stimme mit 50 % Tonhöhenänderung

Weibliche Stimme mit 100 % Tonhöhenänderung

Eingangston – männliche Stimme

Ausgangston – männliche Stimme

Männliche Stimme mit 50 % Echo

Männliche Stimme mit 100 % Echo

Männliche Stimme mit 50 % Tonhöhenänderung

Männliche Stimme mit 100 % Tonhöhenänderung

Love learn engineering in hands-on approach. Interested in new technology. Work in Digilent as International Sales and Distribution Manager.
DesignSpark Electrical Logolinkedin