Warni
Warni

Reputation: 43

Real time continuous sounds with Pydub

I'm trying to make a program that generates sounds from a wifi probe log, so that the number of devices (within a certain distance) generate a tone, and that the rssi is to be the frequence.

I'm trying to make it as real time as possible, but can't figure out how to make the tones continuous and change the frequence based on value change.

'''
This program takes a log file from a
wifi probe and translates it into sound
'''

import time
import math        #import needed modules
import pyaudio     #sudo apt-get install python-pyaudio
import threading
from threading import Thread
from pydub import AudioSegment
from pydub.generators import Sine
from pydub.playback import play
import signal


def logData():
    '''
    Takes log file data and puts it into database
    updates every 1 sec
    '''
    global dic
    global tone
    tone = []
    dic = {}
    while True:
        with open("/Users/CWT/Documents/VÆRKER/probemon.log") as f:
            for line in f:
                (key, val) = line.split()
                if val <= str(-50):
                    dic[(key)] = val
        print (dic)
        time.sleep(1)



def sound():

    '''
    Generate sounds
    '''

    # Play final tone
    while (True):
        with open("/Users/CWT/Documents/VÆRKER/probemon.log") as i:
            try:
                tone1 = Sine(abs(int(list(dic.values())[0]))).to_audio_segment(3000)            
                tone2 = Sine(abs(int(list(dic.values())[1]))).to_audio_segment(3000)        
                tone3 = Sine(abs(int(list(dic.values())[2]))).to_audio_segment(3000)
            except:
                print('Index error')
            try:
                multitone1 = tone1
                multitone2 = tone1.overlay(tone2)
                multitone3 = tone3.overlay(multitone2)
            except:
                print('Multitone error')


            try:
                if len(dic) <= 1:
                    play(multitone1.fade_in(250).fade_out(250))
                elif len(dic) == 2:
                    play(multitone2.fade_in(250).fade_out(250))
                elif len(dic) >= 3:
                    play(multitone3.fade_in(250).fade_out(250))
            except:
                print('Playback error')

if __name__ == '__main__':
    try:
        Thread(target = logData).start()
        time.sleep(1)
        Thread(target = sound).start()
    except KeyboardInterrupt:
        print('Interrupted')

Upvotes: 2

Views: 2614

Answers (1)

Anil_M
Anil_M

Reputation: 11443

I was able to craft a boiler-plate solution that you can tailor per your needs.

Here's central idea

1) Read last line of log file using os.popen in a continuous loop that repeats every second

2) the RSSI value is very small and the difference between these values is also small. We multiply that by constant 100 here to create noticeable difference. You can try different values.

3) Using pydub we create sine tones and play them

Code

from pydub.generators import Sine
from pydub import AudioSegment
from pydub.playback import play
import os
import time

sr = 44100  # sample rate
bd = 16     # bit depth
l  = 50.0     # duration in millisec

last_line = ""  #to avoid same line played again
log_file = "probemon.log"


while True:

    line = os.popen('tail -n 1 {}'.format(log_file)).read()
    if last_line  == line:
        pass

    else:
        key, val = line.split()
        f = abs(int(val)) * 100

        #create sine wave of given freq
        sine_wave = Sine(f, sample_rate=sr, bit_depth=bd)

        #Convert waveform to audio_segment for playback and export
        sine_segment = sine_wave.to_audio_segment(duration=l)

        print "mac:{} , rssi:{}".format(key,val)
        #Play audio segment
        play(sine_segment)

        last_line = line
        time.sleep(1)  #sleep 1 sec, synch this with log file fill

I tested by filling up probemon.log file line by line from a different terminal with a delay of 1 second. The loop will wait if there is no new data.

EDIT1

Audio "tone" have "frequency" , when you change Frequency, tone changes. Based on our discussion, since we need tone to vary in real-time, we can't use pydub which is mostly good for offline manipulation.

pyaudio has a non-blocking method using callback, which allows manipulating stream data while its being played in realtime.

This solution plays audio based on last line of log continuously until log data changes.

This solution also eliminated popping / cracking sound that occurs when merging two tones.

Inspiration from here.

import pyaudio
import numpy as np
from time import time,sleep
import os

CHANNELS = 2
RATE = 44100

TT = time()
freq = 100
newfreq = 100
phase = 0
log_file = "probemon.log"

def callback(in_data, frame_count, time_info, status):
    global TT,phase,freq,newfreq
    if newfreq != freq:
        phase = 2*np.pi*TT*(freq-newfreq)+phase
        freq=newfreq
    left = (np.sin(phase+2*np.pi*freq*(TT+np.arange(frame_count)/float(RATE))))
    data = np.zeros((left.shape[0]*2,),np.float32)
    data[0::2] = left  #left data
    data[1::2] = left  #right data
    TT+=frame_count/float(RATE)
    return (data, pyaudio.paContinue)

p = pyaudio.PyAudio()

stream = p.open(format=pyaudio.paFloat32,
                channels=CHANNELS,
                rate=RATE,
                output=True,
                stream_callback=callback)

stream.start_stream()
tmphold = ""
try:
    while True:
        line = os.popen('tail -n 1 {}'.format(log_file)).read()
        try:
            key, val = line.split()
        except:
            key, val = "default", 0.0

        f = abs(int(val))  
        newfreq = f * 10  #update freq per log
        if newfreq != tmphold:
            tmphold = newfreq
            print "mac:{} , rssi:{} , freq:{} 
finally:
    stream.stop_stream()
    stream.close()
    p.terminate()

Result

mac:default , rssi:0.0 , freq:0 Hz
mac:d8:8f:76:1a:cb:65 , rssi:-43 , freq:430 Hz
mac:04:4f:4c:77:72:8f , rssi:-51 , freq:510 Hz
mac:20:39:56:af:51:49 , rssi:-39 , freq:390 Hz
mac:20:39:56:af:51:49 , rssi:-45 , freq:450 Hz
mac:5e:e2:1d:a3:d2:da , rssi:-47 , freq:470 Hz
mac:5e:e2:1d:a3:d2:da , rssi:-49 , freq:490 Hz
mac:12:84:16:9c:75:ee , rssi:-43 , freq:430 Hz
mac:da:a1:19:71:4d:0c , rssi:-55 , freq:550 Hz
mac:d8:8f:76:1a:cb:65 , rssi:-49 , freq:490 Hz

Upvotes: 3

Related Questions