• .
  • Willkommen im Forum!
  • Alles beim Alten...
  • Du hast kaum etwas verpasst ;-)
  • Jetzt noch sicherer mit HTTPS
Hallo, Gast! Anmelden Registrieren


PyAudio
#1
audio-daten mit Python [D]zer[/D] verarbeiten...

btw die Vorlesung >Bildverarbeitung und Mustererkennung< nannten wir Bildzerarbeitung und Musterverkennung Tongue
    Don't worry about getting older.  You're still gonna do dump stuff...only slower
 
Reply
#2
Danke, Alfsch, dann kanns ja losgehen Confused
...mit der Lizenz zum Löten!
 
Reply
#3
Anlass für dieses Projekt ist der Wunsch AudioSignale zu erfassen,
ggfs zu filtern und das Ergebnis zu speichern. So eine Art "AudioLogger"
Da bietet sich als "Bordmittel" natürlich der PC/Laptop + Soundkarte an.
Anfangs hatte ich "Audacity" im Visier, denn dieser ist durchaus ein geeigneter Audiologger.
Allerdings kann Audacity nicht in Echtzeit filtern, da ist er eben doch nur ein "Audio-Editor".
Also ist selbst Programmieren angesagt.

Als Programmiersprache wurde Python gewählt, eine Open Source
Interpretersprache die auf allen gängigen PC-Plattformen verfügbar ist.
Der Code sollte also portierbar sein (soweit die Theorie).

Python ist eine Sprache, wo auf lesbaren Code Wert gelegt wird.
Dazu kommen die vorhandenen Mathe- und Plot-Bibliothen,
also ein starker Unterbau mit dem man recht kompakte und lesbare
Programme schreiben kann.

Die Programmentwicklung erfolgte eher mühselig mit wenig Grundkenntnissen
von Python und PC-Soundkarten. Es wurden verschiedene Programmschnipsel
aus dem Inet ausgewertet und so das Ganze häppchenweise aufgebaut.

Im Laufe der Zeit werde ich nun den einen oder anderen Happen in Form von
Quelltexten reinreichen und hoffe dass auch andere an dieser Programmierung
Spass finden könne und sich auch mal an PC-Audio rantrauen.

Ich werde bis auf weiteres auf 3 verschiedenen Linux-Laptops arbeiten.
Vielleicht findet sich ja jemand bereit, die Sache bei Win einzupflegen.


...mit der Lizenz zum Löten!
 
Reply
#4
Vorab sind ein paar Programmpakete zu installieren. Das folgende Script basiert auf apt,
sollte also auf sämtlichen Debin/Ubuntu-basierten Distributionen laufen
Code:
#! /bin/bash
sudo apt-get update
sudo apt-get install python
sudo apt-get install python-pyaudio
sudo apt-get install python-numpy
sudo apt-get install python-pyplot
sudo apt-get install python-matplotlib
sudo apt-get install python-scipy
sudo apt-get autoremove
exit
...mit der Lizenz zum Löten!
 
Reply
#5
Hier kommt der erste Python Code-Schnipsel.
Diese Routine zeigt alle aufgefundenen Soundkarten an.

Code:
#! /usr/bin/python2.7
import pyaudio
def DevsInfo():
    p=pyaudio.PyAudio()
    print "\nSoundCards available"
    for i in range(p.get_default_host_api_info()["deviceCount"]):
        if p.get_device_info_by_index(i)["maxInputChannels"]>0:
                DevId = p.get_device_info_by_index(i)["index"]
                DevName = p.get_device_info_by_index(i)["name"]
                DevInCh = p.get_device_info_by_index(i)["maxInputChannels"]
                DevOutCh = p.get_device_info_by_index(i)["maxOutputChannels"]
                DevSRate = p.get_device_info_by_index(i)["defaultSampleRate"]
                print "Id=%d\tName=%s\tMaxInChannels=%d\tMaxOutChannels=%d\tDefaultSampleRate=%d"%(DevId,DevName,DevInCh,DevOutCh,DevSRate)
DevsInfo()
...mit der Lizenz zum Löten!
 
Reply
#6
Die hier beschriebenen Python-scripte werden
in der Linux Konsole ("xterm") ausgeführt.
Dazu gehört in jedem script die Anfangszeile mit #! /usr/bin/python2.7
Alternativ kann Python auch interaktiv ausgeführt werden.
Das ist sehr gut um Dinge einfach auszuprobieren,
geht aber nicht bei diesen zeitkritischen Anwendungen.

Nachdem man die Dateien vom Typ *.py in ein Verzeichnis gepackt hat,
und man sich in diesem Verzeichnis befindet,
müssen einmal die Ausführungsrechte für alle Python-Scripte gesetzt werden:

$chmod a+x *py

Der Aufruf eines Python-Scripts aus diesem Verzeichnis heraus erfolgt über

$./Dateiname

Nun spult das Python-Programm ab (oder bricht gleich mit einer Fehlermeldung ab)

Mit

CTL+S läßt es sich anhalten
Enter geht es weiter
CTL+C Programmabbruch
...mit der Lizenz zum Löten!
 
Reply
#7
Der SoundKartenLister zeigt folgendes Resultat in der Konsole:
Code:
$ ./sho_soundcards.py
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_dmix.c:961:(snd_pcm_dmix_open) The dmix plugin supports only playback stream

SoundCards available
Id=0    Name=HDA Intel: CX20561 Analog (hw:0,0)    MaxInChannels=2    MaxOutChannels=4    DefaultSampleRate=44100
Id=2    Name=USB Audio CODEC: USB Audio (hw:1,0)    MaxInChannels=2    MaxOutChannels=2    DefaultSampleRate=44100
Id=3    Name=sysdefault    MaxInChannels=128    MaxOutChannels=128    DefaultSampleRate=44100
Id=10    Name=pulse    MaxInChannels=32    MaxOutChannels=32    DefaultSampleRate=44100
Id=12    Name=default    MaxInChannels=32    MaxOutChannels=32    DefaultSampleRate=44100
$

Die anfänglichen ALSA-Fehlermeldungen kann man getrost ignorieren,
irgendwo gibt es einen fix dazu, aber das war es mir nicht wert.

Man erkennt unter id=0 die interne soundcard (hw:0,0)
unter id=2 die externe USB-Karte (hw:1,0)

...mit der Lizenz zum Löten!
 
Reply
#8
Im Moment baue ich an einer Aufnahmeprozedur, die die Audiodaten durch ein digitales Bandpassfilter schickt. Das sieht soweit schon recht gut aus, allerdings sind die herauskommenden Daten noch nicht überprüft.

Vorsicht Aufnahme: Unterbrechungsfrei AudioDaten sammeln

Der fortlaufende Datenstrom soll unterbrechungsfrei durch ein Bandpassfilter geleitet werden. Die Soundkarte liefert Daten immer Blockweise ab in sog. "chunks", üblicherweise 1024 (Stereo-) Samples. „Ruckelfreies“ Einsammeln dieser Datenblöcke erfordert den Einsatz der callback-funktion, einer Interruptroutine die von sich aus die AudioDaten in Einheiten von chunks übergibt. 1024 AudioSamples dauern bei 44,1kHz Abtastrate nur 23ms, zuwenig Zeit, um jedesmal ein rechenintensives Hauptprogramm dazwischen zu quetschen. Um SignalAussetzer zu vermeiden, muß dieser Puffer also vergrößert werden. Alternativ könnte natürlich auch die Abtastrate herabgesetzt werden, auf z.B 8kHz.

Die callback-routine sammelt immer mehrere chunks ein, hängt sie aneinander in einem lokalen Puffer und übergibt sie am Schluss an die Aussenwelt im globalen chunkbuffer. Durch diese doppelte Pufferung wird es möglich, unmittelbar nach Ablage des letzten Datenblockes in den chunkbuffer mit dem Einsammeln neuer Daten fort zu fahren.

Wenn der chunk-buffer gerade mal wieder voll ist, setzt die callback-Funktion das globale flag "DataReady". Dies wird vom Hauptprogramm gepollt. Je nach Größe des chunkbuffer hat das Hauptprogramm nun mehr oder weniger Zeit dies abzuarbeiten und das "DataReady" zu löschen.

Wenn zu Beginn eines neuen chunkbuffers das "DataReady" immer noch nicht gelöscht worden ist, erkennt die callback-routine auf ein nicht abgeholtes DatenPaket und gibt eine Bildschirmwarnung aus.

Um unnötige alsa-underrun-errors zu vermeiden, sollte der Datenstrom ein reiner input-stream geöffnet werden.

...und durch ein mitlaufendes Bandpassfilter schicken

Digitale Filter, berechnet von der CPU, dauern an dieser Stelle zu lange, um innerhalb der callback-Funktion abgearbeitet werden zu können. Wobei die Durchlaufzeit mit der FilterOrdnung deutlich zunimmt. Somit werden sämtlich array-Operationen und der Filter ins Hauptprogramm verlegt, wobei jeweils ein Block von chunks auf einmal abgearbeitet wird. Das zugehörige Demo-Programm „demo_butterworth_bandpass_filter.py“ gibt bei jedem callback-Aufruf den aktuellen Chunk-Schleifenzähler aus, dazwischen meldet sich das Hauptprogramm mit der Rechenzeit des Filters.

Ein "screenshot" aufgenommen während eines Programmlaufes, sieht z.B. so aus
Code:
callback buffer count =5
callback buffer count =4
callback buffer count =3
callback buffer count =2
callback buffer count =1
callback buffer count =0
callback buffer count =9
callback buffer count =8
callback buffer count =7
0.0205011367798
callback buffer count =6
callback buffer count =5
callback buffer count =4
callback buffer count =3
callback buffer count =2
callback buffer count =1
callback buffer count =0
callback buffer count =9
0.0198979377747
callback buffer count =8
callback buffer count =7
callback buffer count =6
callback buffer count =5
callback buffer count =4
callback buffer count =3
callback buffer count =2
callback buffer count =1
callback buffer count =0
callback buffer count =9
0.0199301242828
callback buffer count =8
callback buffer count =7
callback buffer count =6
callback buffer count =5
callback buffer count =4

Man erkennt die Rechenzeitausgaben des Hauptprogramms bei 20ms, eingesprenkelt in die callback Ausgaben des Puffer- Schleifenzählers. Übrigens erkennbar asynchron bezogen auf den callback-Schleifenzähler: Das Haupprogramm hängt teilweise mehrere callback-Durchläufe hinterher, bleibt dabei aber völlig im grünen Bereich.


Hi-Speed Mitlauffilter

Es hat sich herausgestellt, dass die Berechnung der Filterkoeffizienten um ein Vielfaches länger dauert als die eigentliche Filterung des Signales. Da diese Koeffizienten sich zur Laufzeit aber nicht ändern, spricht nichts dagegen, diese ein einziges mal am Programmanfang zu ermitteln. Der screenshot des modifizierten Programmes sieht nun folgendermaßen aus:
Code:
.
.
callback buffer count =1
callback buffer count =0
callback buffer count =9
0.000388860702515
callback buffer count =8
callback buffer count =7
callback buffer count =6
callback buffer count =5
callback buffer count =4
callback buffer count =3
callback buffer count =2
callback buffer count =1
callback buffer count =0
callback buffer count =9
0.000384092330933
callback buffer count =8
callback buffer count =7
callback buffer count =6
.
.
Die Filterberechnung dauert nur noch <0,4ms! Angesichts dieser Tatsache scheint Echtzeitfilterung mit direkter Monitor-Wiedergabe ja doch nicht so ganz ausgeschlossen.


Anhang Quelltext
Referenz : „python - How to implement band-pass Butterworth filter with Scipy.signal.butter - Stack Overflow-Dateien“

Code:
#! /usr/bin/python2.7
import pyaudio
import time
import sys
import numpy as np
from scipy.signal import butter, lfilter, freqz

# audio signal parameters
STREAM_CHANNELS = 2
STREAM_SAMPLE_RATE = 44100 #8000 #44100               #!!! sets time available for main program
STREAM_FORMAT = pyaudio.paFloat32
CHUNK_BUFFERS = 10                     #!!! sets time available for main program

# bandpass filter parameters
BPLOWCUT = 900.0
BPHIGHCUT = 1100.0
BPORDER = 3

def GetFilterCoefficients():
    global a, b
    nyq = 0.5 * STREAM_SAMPLE_RATE
    low = BPLOWCUT / nyq
    high = BPHIGHCUT / nyq
    b, a = butter(BPORDER, [low, high], btype='band')
    return b, a

def BwBpFilter(data):
    y = lfilter(b, a, data)
    #    y = lfilter(b, a, data, axis = (-1))
    return y

def GetDefaultInputDevice():
    CardId = p.get_default_input_device_info()["index"]
    return CardId

def callback_2buffered (in_data, frame_count, time_info, status):
    global RawData, DataReady, BufferCount, ChunkBuffer, FrameCount
    # restart next data buffer
    # data buffer = full
    if BufferCount==0:
        if DataReady==True:
            print "!warning: 1 buffer may or may not have been lost!"
        DataReady = True
        # get first chunk
        RawData = in_data
        # save data = double buffered:
        ChunkBuffer = RawData
        BufferCount = CHUNK_BUFFERS
    else:
        # append one chunk
        RawData = RawData + in_data
    BufferCount = BufferCount - 1
    FrameCount = frame_count
    print "callback buffer count =%d"%BufferCount
    return (in_data, pyaudio.paContinue)

#   main program

print ""
print "full-duplex record/play through default soundcard"
print ""
p=pyaudio.PyAudio()
CardId=GetDefaultInputDevice()
print "Card id = %d"%CardId
#start with a full cycle of buffering & initialize RawData
BufferCount = 0
DataReady = False
InputStream = p.open(
    format=STREAM_FORMAT,
    channels=STREAM_CHANNELS,
    rate = STREAM_SAMPLE_RATE,
    input_device_index=CardId,
    input=True,
    output=False,
    stream_callback=callback_2buffered)
InputStream.start_stream()
# wait for valid data to initialize arrays
GetFilterCoefficients()
time.sleep(2)

while InputStream.is_active():
    if DataReady==True:
        start = time.time()
        NumArray = np.fromstring(ChunkBuffer, dtype=np.float32)
        NumArrayRows = NumArray.size / STREAM_CHANNELS
        #print "main loop, num array =%d"%NumArrayRows
        NumArray = np.reshape(NumArray, (NumArrayRows, STREAM_CHANNELS))
        NumArray = BwBpFilter(NumArray)
        elapsed = time.time() - start
        print elapsed
        # send data done to callback
        DataReady = False
    #time.sleep(0.1)
InputStream.stop_stream()
InputStream.close()
p.terminate()


...mit der Lizenz zum Löten!
 
Reply
#9
Das AudioLoggerProgramm macht Fortschritte.
[Bild: 34_levelmeter.png]

Ahh - ich sehe auch hier hat Kahlo aufgeräumt Daumen hoch! Heart

Das Bild zeigt das laufende PegelMess-Programm.
Im Plotfenster dB-Pegelverlauf für links (rot) und rechts (grün).
Getestet mit USB-Soundkarte UCA202, linker Kanal gespeist mit Attiny-PWM-Sinus.
rechter Kanal=offen.

Es werden ca 3frames/sec aufgebaut, völlig ausreichend zur Pegelüberwachung.

Die CPU-Last dieses Prozesses ist dank trickreicher polling-Vermeidung eher gering.

Die Konsole zeigt die aktuellen Pegel numerisch an.

Das ist schon mal alles höchst erfreulich! Confused

Ups, und schon wieder so einen kleinen Programmfehler entdeckt! ;fight
Seht Ihr ihn auch?
...mit der Lizenz zum Löten!
 
Reply
#10
Beim Reparieren der Forensuche ist anscheinend ein Posting von Hoppi (die Frage nach der Aussprache von "Python") verloren gegangen. Das war nicht gewollt. Ich hoffe, dass Hoppi mir verzeiht...

Sad
 
Reply
#11
... nee, hab ich gelöscht. lachend

Zitat:Original geschrieben von voltwide

Lieber Hoppi

bitte benutze diesen Parallelthread, damit der Original-Thread möglichst lesbar bleibt.

Wie man Python ausspricht?
Ganz einfach: Genauso wie Monty Python, nur ohne Monty! lachend

An die hatte der Sprachschöpfer nämlich damals gedacht!
Bei Dicky Hoppenstedt konnte das Geschlecht auch nicht so einfach bestimmt werden.



 
Reply
#12
So, die letzten 2 Wochen war ich Grippemäßig außer Gefecht, jetzt wirds wieder so langsam. In der Zwischenzeit habe ich meine funktionsfähigen Prozeduren ausgelagert in eine eigene Bibliothek "mylib.py". Das schafft Übersicht und vermeidet cut-paste Fehler z.B. beim Einsetzen veralteter Code-Blöcke.
Ausserdem zwingt es einen dazu, globale variablen nur noch im HauptprogrammModul zu verwenden. (1Modul = 1Quelltextdatei/Bibliotheksdatei)

Das Nahziel ist soweit erreicht: 2 Audio-Kanäle werden als vertikale farbige Balken im logarithmischen Masstab dargestellt. Der Anzeigeumfang wurde auf -100..0dB skaliert. 0dB entsprechen hierbei FS-digital, werden also in der Praxis nie ganz erreicht. Die BehringerSoundkarte kommt bis auf 2dB an dieses Limit.

Sehr schön auf einen Blick zu sehen ist nun der verfügbare Dynamikumfang -
abhängig von der benutzten Soundkarte
...mit der Lizenz zum Löten!
 
Reply
#13
Erster screenshot: Interne soundkarte. Das interne Mikrofon ist aktiv, aber relative leise gestellt.
[Bild: 38_plotint.png]
Jetzt die Behringer UCA202, beide line-in-Eingänge offen
[Bild: 76_plotext0.png]
Nun wird der linke Kanal voll übersteuert
[Bild: 84_plotext1.png]
Ein gewisses Übersprechen auf den rechten Kanal ist durchaus erkennbar Wink
...mit der Lizenz zum Löten!
 
Reply
#14
Die Pegelwerte links und rechts werden nun in zwei gesonderte csv-Dateien weg geschrieben. Im Minutenabstand wird ein Zeitstempel eingefügt. Derzeit öffne und schließe ich die Datei mit jedem neuen Datum, so dass bei einem Programmabbruch nichts verloren geht. Vlt fällt mir da noch was Besseres ein.
...mit der Lizenz zum Löten!
 
Reply
#15
Wozu brauchst du die Pegelwerte in einer Datei?
 
Reply
#16
Der erste Anwendungsbereich sind EMV-Messungen zur Störfestigkeit. Hier werden mit 1kHz amplitudenmodulierte HF-Störungen eingekoppelt und der auftretende demodulierte NF-Pegel muß unterhalb normgerechter Grenzwerte verbleiben.
Diese Aufgabe übernahmen bisher zwei uralte selektive Messplätze von Wandel und Goltermann, wobei für eine 40Min Messung immer jemand auf die grünen Bildschirme starren muss ob denn der Pegel im Bereich bleibt.
Intern 8 bit Wandlung, also sehr limitierte Dynamik.
Die Messplätze überhaupt nicht EMV-fest, hab schon eigene EMV-Filter davor bauen müssen.
Und natürlich keinerlei logging-Ausgang.
Daher die Idee, das ganze Geraffel mit einem Laptop zu ersetzen.
Das ist dann nicht nur wesentlich smarter und komfortabler zu hantieren,
sondern bietet auch noch eine Anzeige-Dynamik von 80dB und mehr.

Und ein session-log macht sich da natürlich gut für die Dokumentation.

Das nächste Einsatzgebiet werden wohl (automatisierte) Audiomessungen sein mit programmgesteuerter Testtonausgabe und Messung.

Wenn ich meinen code von gestern so betrachte, möchte ich den heute schon gar keinem mehr zeigen. Solange sich dieser Zustand weiter fortsetzt, werde ich mich mit code-Veröffentlichungen noch zurück halten.

Habt also etwas Geduld mit mir!
...mit der Lizenz zum Löten!
 
Reply
#17
lachend lachend lachend
Das mit dem Code am nächsten Tag kenne ich...
---
Ok, also was richtig Nützliches überrascht .
 
Reply
#18
Jetzt kommt ein kleines Programm, das Sinusschwingungen ausgeben kann. Auf dem Netz habe ich nur solche gefunden, wo vorab eine Datei entsprechender Länge erzeugt wird, die dann abgespielt wird. Das ist aber nicht brauchbar als Tongenerator, wenn man erst etliche Minuten warten muss, bis die Datei für 30min Ton denn endlich erstellt ist.
Und ja, das geht auch anders, allerdings nicht so einfach. Das nun folgende Programm liefert Dauerton, sauber und ohne Unterbrechungen, nachdem ich in den letzten Tagen die unverzichtbare Phasensprungkorrektur mühsam debbugged habe.

Es wuerde mich freuen, wenn mal jemand das bei sich ausprobieren würde. Wink

Code:
#! /usr/bin/python2.7
"""
Tongenerator mit PC, Soundkarte und Python

Erzeugt Dauertoene aus einer Tabelle von der Groesse eines chunks (typ 1024 samples).
Die Soundkarte uebernimmt zyklisch diese chunks und haengt sie aneinander.

Nur in seltenen Ausnahmefaellen passt ein ganzzahliges Vielfaches einer Schwingungsperiode
genau in den chunk-Puffer, so dass an den Stossstellen zweier chunks immer ein Phasensprung statt findet.
Derartige Phasenspruenge sind stoerend, sie werden gehoermaessig sofort wahr genommen.
Die dynamische Phasenkorrektur verschiebt den Startpunkt im chunkbuffer immer passend,
so dass nahtlose Uebergaenge zustande kommen.

Der Kode ist ausgelegt fuer geringstmoegliche CPU-Last.
Massgeblich dafuer ist die Tonausgabe per callback-Funktion,
die Phasenkorrektur durch dynamische Indizierung des chunk-Puffers
und das mittels sleep-Anweisung ausgeduennte polling.
Am Lenovo X200 wurde 1% Zusatzlast bei 44,1kHz Abstastrate gemessen.

Waehrend die Soundkarte auf dem linken Ausgang Toene ausgibt,
kann audicity diese simultan ueber den Eingang aufzeichen
(kabelverbindung Ausgang-Eingang links).
Auf diese Weise wurden Tonschnipsel zur Dokumentation erstellt.

Zur Laufzeit koennen Frequenz und Amplitude modifiziert werden
durch Ueberschreiben des chunk-Puffers.

Bevor es losgeht
Div Python-Pakete installieren
Default Soundkarte einstellen ueber die PulsAudio-GUI.
Oder PulseAudio killen und direkt ueber Alsa gehen.
Dazu muss im home-Verzeichnis eine passende Datei ".asoundrc" abgelegt werden.

Getestet mit Lenovo X200, Behringer UCA 202 unter ubuntu 14.04, ohne PulseAudio
Stand 2014-10-27
"""
import pyaudio
import time
import numpy as np

#   Konfiguration

#   Audiosignal-Parameter
STREAM_CHANNELS = 1
STREAM_SAMPLE_RATE = 44100
STREAM_FORMAT = pyaudio.paInt16
#   Tongenerator
TONE_FQ = 1000.0
TONE_LEVEL = 0.5
CHUNK_LEN = 1024

#    callback Funktion

def callback_tonegen (out_data, frame_count, time_info, status):
    global NextChunk
    if NextChunk == True:
        print "!Fehler:output-chunk verloren!"
    # Handshake-Uebergabe an Hauptprogramm
    NextChunk = True
    # PhasenSprungKorrektur durch ArrayIndexOffset ("slicing")
    Data = DoubleChunkBuf[PhaseIdx::]
    return (Data, pyaudio.paContinue)

#   Befuelle Chunk-Puffer mit Sinus-Schwingungen
def DoubleChunkInit(fq, level, samplerate, chunklen):
    maxphase = 2 * np.pi * fq * chunklen / samplerate
    phaseofs = np.fmod(maxphase,(2 * np.pi))
    x = np.arange (2*chunklen)  
    doublechunkbuf = np.array(x)
    xcoeff = 2 * np.pi * fq / samplerate
    doublechunkbuf = level*2**15*np.sin(x * xcoeff)
    doublechunkbuf = doublechunkbuf.astype(np.int16)
    return(doublechunkbuf,phaseofs,maxphase)

#   Hauptprogramm Initialisierungen

Phase = 0.0
PhaseIdx = 0
NextChunk = False
DoubleChunkBuf, PhaseOfs, MaxPhase = DoubleChunkInit(TONE_FQ, TONE_LEVEL, STREAM_SAMPLE_RATE, CHUNK_LEN)

#   initialisiere die Soundkarte
p = pyaudio.PyAudio()

#   starte Audio-Stream
OutputStream = p.open(
    format=STREAM_FORMAT,
    channels=STREAM_CHANNELS,
    rate = STREAM_SAMPLE_RATE,
    input=False,
    output=True,
    stream_callback=callback_tonegen)
OutputStream.start_stream()

#   Hauptprogrammschleife

while OutputStream.is_active():
    if NextChunk == True:
        NextChunk = False
        # Versatz fuer PhasenSprungKorrektur
        Phase += PhaseOfs
        Phase = Phase % (2 * np.pi) #!!!
        PhaseIdx = int(Phase * CHUNK_LEN / (MaxPhase))
    time.sleep(0.001)

#   Programmende

OutputStream.stop_stream()
OutputStream.close()
p.terminate()
...mit der Lizenz zum Löten!
 
Reply
#19
hier eine 5sec Aufzeichnung des 1kHz-Tons ohne PhasenSprungKorrektur.
Die Störung ist deutlich zu hören, und z.B. in Audacity kann man nach hineinzoomen auch die Knackser finden.
...mit der Lizenz zum Löten!
 
Reply
#20
Das Gleiche nun mit PhasenSprungKorrektur
Klingt doch schon deutlich besser Confused
...mit der Lizenz zum Löten!
 
Reply