redilysis/redilysis.py

219 lines
6.5 KiB
Python
Executable File

"""
Sends live audio analysis to the terminal.
Based on musicinformationretrieval.com/realtime_spectrogram.py
For more examples using PyAudio:
https://github.com/mwickert/scikit-dsp-comm/blob/master/sk_dsp_comm/pyaudio_helper.py
"""
from __future__ import print_function
import argparse
import json
import librosa
import math
import numpy
import os
import pyaudio
import redis
import statistics
import sys
import time
def debug(*args, **kwargs):
if( verbose == False ):
return
print(*args, file=sys.stderr, **kwargs)
# Define default variables.
BAND_OCTAVES = 10 # 12 * 9 octaves
_BAND_TONES = BAND_OCTAVES * 12 # octaves * notes per octave
_CHANNELS = 1
_FRAMES_PER_BUFFER = 4410
_N_FFT = 4096
_RATE = 44100
_SAMPLING_FREQUENCY = 0.1
# Argument parsing
# Audio Args
parser = argparse.ArgumentParser(prog='realtime_redis')
parser.add_argument('--list-devices','-L', action='store_true', help='Which devices are detected by pyaudio')
parser.add_argument('--mode','-m', required=False, default='spectrum', choices=['spectrum', 'bpm'], type=str, help='Which mode to use. Default=spectrum')
parser.add_argument('--device','-d', required=False, type=int, help='Which pyaudio device to use')
#parser.add_argument('--frames','-f', required=False, default=4410, type=int, help='How many frames per buffer. Default={}'.format(_FRAMES_PER_BUFFER))
parser.add_argument('--sampling-frequency','-s', required=False, default=0.1, type=float, help='Which frequency, in seconds. Default={}f '.format(_SAMPLING_FREQUENCY))
parser.add_argument('--channels','-c', required=False, default=_CHANNELS, type=int, help='How many channels. Default={} '.format(_CHANNELS))
parser.add_argument('--rate','-r', required=False, default=44100, type=int, help='Which rate. Default={} '.format(_RATE))
# Redis Args
parser.add_argument("-i","--ip",help="IP address of the Redis server ",default="127.0.0.1",type=str)
parser.add_argument("-p","--port",help="Port of the Redis server ",default="6379",type=str)
# Stardard Args
parser.add_argument("-v","--verbose",action="store_true",help="Verbose")
args = parser.parse_args()
# global
bpm = 120.0
# Set real variables
F_LO = librosa.note_to_hz('C0')
F_HI = librosa.note_to_hz('C10')
BAND_TONES = _BAND_TONES
CHANNELS = args.channels
DEVICE = args.device
FRAMES_PER_BUFFER = int(args.rate * args.sampling_frequency )
LIST_DEVICES = args.list_devices
MODE = args.mode
N_FFT = _N_FFT
RATE = args.rate
SAMPLING_FREQUENCY = args.sampling_frequency
ip = args.ip
port = args.port
verbose = args.verbose
if( MODE == "bpm" and SAMPLING_FREQUENCY < 0.5 ):
debug( "You should use a --sampling_frequency superior to 0.5 in BPM mode...")
melFilter = librosa.filters.mel(RATE, N_FFT, BAND_TONES, fmin=F_LO, fmax=F_HI)
r = redis.Redis(
host=ip,
port=port)
# Early exit to list devices
# As it may crash later if not properly configured
#
def list_devices():
# List all audio input devices
p = pyaudio.PyAudio()
i = 0
n = p.get_device_count()
print("\nFound {} devices\n".format(n))
print(" {} {}".format('ID', 'Device name'))
while i < n:
dev = p.get_device_info_by_index(i)
if dev['maxInputChannels'] > 0:
print(" {} {}".format(i, dev['name']))
i += 1
if( LIST_DEVICES ):
list_devices()
os._exit(1)
p = pyaudio.PyAudio()
def m_bpm(audio_data):
"""
This function saves slow analysis to redis
* onset
* bpm
* beat
"""
global bpm
if( bpm <= 10):
bpm = 10
onset = librosa.onset.onset_detect(
y = audio_data,
sr = RATE
)
new_bpm, beats = librosa.beat.beat_track(
y = audio_data,
sr = RATE,
trim = False,
start_bpm = bpm,
units = "time"
)
# Save to Redis
r.set( 'onset', json.dumps( onset.tolist() ) )
r.set( 'bpm', json.dumps( new_bpm ) )
r.set( 'beats', json.dumps( beats.tolist() ) )
bpm = new_bpm
debug( "bpm:{} onset:{} beats:{}".format(bpm,onset,beats) )
return True
def m_spectrum(audio_data):
"""
This function saves fast analysis to redis
"""
# Compute real FFT.
fft = numpy.fft.rfft(audio_data, n=N_FFT)
# Compute mel spectrum.
melspectrum = melFilter.dot(abs(fft))
# Initialize output characters to display.
spectrum_120 = [0]*BAND_TONES
spectrum_10 = [0]*BAND_OCTAVES
spectrum_oct = [[] for i in range(10)]
# Assign values
for i in range(BAND_TONES):
val = round(melspectrum[i],2)
spectrum_120[i] = val
key = int(math.floor( i / 12 ))
spectrum_oct[key].append(val)
for i in range(BAND_OCTAVES):
spectrum_10[i] = round(sum( spectrum_oct[i] ) / len( spectrum_oct[i]),2)
# Get RMS
#rms = librosa.feature.rms( S=melspectrum )
rms = librosa.feature.rms( y=audio_data ).tolist()[0]
rms_avg = round(sum(rms) / len(rms),2)
# Save to redis
#debug( 'spectrum_120:{} '.format(spectrum_120))
#debug( 'spectrum_10:{}'.format(spectrum_10))
#debug( 'rms:{}'.format(rms_avg))
r.set( 'spectrum_120', json.dumps( spectrum_120 ) )
r.set( 'spectrum_10', json.dumps( spectrum_10 ) )
r.set( 'rms', "{}".format(rms_avg) )
return True
def callback(in_data, frame_count, time_info, status):
audio_data = numpy.fromstring(in_data, dtype=numpy.float32)
start = time.time()
if MODE == 'spectrum':
m_spectrum(audio_data)
elif MODE == 'bpm':
m_bpm( audio_data)
else:
debug( "Unknown mode. Exiting")
os._exit(2)
end = time.time()
debug ("\rLoop took {:.2}s on {}s ".format(end - start, SAMPLING_FREQUENCY))
return (in_data, pyaudio.paContinue)
debug( "\n\nRunning! Using mode {}.\n\n".format(MODE))
if MODE == 'spectrum':
debug("In this mode, we will set keys: rms, spectrum, tuning")
elif MODE == 'bpm':
debug("In this mode, we will set keys: onset, bpm, beats")
stream = p.open(format=pyaudio.paFloat32,
channels=CHANNELS,
rate=RATE,
input=True, # Do record input.
output=False, # Do not play back output.
frames_per_buffer=FRAMES_PER_BUFFER,
input_device_index = DEVICE,
stream_callback=callback)
stream.start_stream()
while stream.is_active():
time.sleep(SAMPLING_FREQUENCY)
stream.stop_stream()
stream.close()
p.terminate()