27.10.2014, 10:48 PM
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.
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.
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!