This commit is contained in:
alban 2020-09-27 21:02:19 +02:00
parent 266a31f0bb
commit 678aae0c94
3 changed files with 112 additions and 58 deletions

View File

@ -1,10 +1,42 @@
# Redilysis = Redis + Audio Analysis # Redilysis = Redis + Audio Analysis
Redilysis sends audio analysis to a redis install. What's the use? Using that information for multiple visualizations, of course! Redilysis sends audio analysis to a redis server.
The idea is to share a single audio analysis to many Visual Jockey filters, in our case for lasers.
Two modes exist for now, you need to run two processes to get the complete experience!
### Spectrum Mode
This is the default mode.
It performs some frequency analysis (Fast Fourier Transform) to detect "energy" in the human audition bandwidths.
It will record if there is sound and at which frequencies.
It can run at sub-second frequency (100ms) with no problem.
It reports realistic data: spectrum analysis is the easy part.
### BPM Mode
This mode is more experimental.
It attempts to detect beats based on the
## Keys and contents in Redis
bpm_time : (milliseconds integer timestamp) last update time
onset
bpm
beats
spectrum_time
## Installation ## Installation
```python ```python
sudo apt install python-pyaudio python3
git clone https://git.interhacker.space/tmplab/redilysis.git git clone https://git.interhacker.space/tmplab/redilysis.git
cd redilysis cd redilysis
pip install -r requirements.txt pip install -r requirements.txt

131
redilysis.py Normal file → Executable file
View File

@ -7,6 +7,7 @@ For more examples using PyAudio:
https://github.com/mwickert/scikit-dsp-comm/blob/master/sk_dsp_comm/pyaudio_helper.py https://github.com/mwickert/scikit-dsp-comm/blob/master/sk_dsp_comm/pyaudio_helper.py
""" """
from __future__ import print_function
import argparse import argparse
import json import json
import librosa import librosa
@ -14,43 +15,40 @@ import numpy
import os import os
import pyaudio import pyaudio
import redis import redis
import sys
import time import time
def debug(*args, **kwargs):
if( verbose == False ):
return
print(*args, file=sys.stderr, **kwargs)
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
# Define default variables. # Define default variables.
_BAND_RANGE = 96 _BAND_RANGE = 7
_CHANNELS = 1 _CHANNELS = 1
_ENERGY_THRESHOLD = 0.4 _ENERGY_THRESHOLD = 0.1
_FRAMES_PER_BUFFER = 4410 _FRAMES_PER_BUFFER = 4410
_N_FFT = 4096 _N_FFT = 4096
_RATE = 44100 _RATE = 44100
_SAMPLING_FREQUENCY = 0.1 _SAMPLING_FREQUENCY = 0.1
# Argument parsing # Argument parsing
# Audio Args
parser = argparse.ArgumentParser(prog='realtime_redis') 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('--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('--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('--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('--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('--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('--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)) parser.add_argument('--rate','-r', required=False, default=44100, type=int, help='Which rate. Default={} '.format(_RATE))
parser.add_argument('--energy-threshold','-e', required=False, default=0.4, type=float, help='Which energy triggers spectrum detection flag. Default={} '.format(_ENERGY_THRESHOLD)) parser.add_argument('--energy-threshold','-e', required=False, default=0.4, type=float, help='Which energy triggers spectrum detection flag. Default={} '.format(_ENERGY_THRESHOLD))
# 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() args = parser.parse_args()
# Set real variables # Set real variables
@ -58,61 +56,85 @@ BAND_RANGE = _BAND_RANGE
CHANNELS = args.channels CHANNELS = args.channels
DEVICE = args.device DEVICE = args.device
ENERGY_THRESHOLD = args.energy_threshold ENERGY_THRESHOLD = args.energy_threshold
FRAMES_PER_BUFFER = args.frames FRAMES_PER_BUFFER = int(args.rate * args.sampling_frequency )
LIST_DEVICES = args.list_devices LIST_DEVICES = args.list_devices
MODE = args.mode MODE = args.mode
N_FFT = _N_FFT N_FFT = _N_FFT
RATE = args.rate RATE = args.rate
SAMPLING_FREQUENCY = args.sampling_frequency SAMPLING_FREQUENCY = args.sampling_frequency
ip = args.ip
port = args.port
verbose = args.verbose
debug( "frames", FRAMES_PER_BUFFER)
if( MODE == "bpm" and RATE < 0.5 ):
debug( "You should use a --rate superior to 0.5 in BPM mode...")
# Define the frequency range of the log-spectrogram. # Define the frequency range of the log-spectrogram.
F_LO = librosa.note_to_hz('C2') F_LO = librosa.note_to_hz('C2')
F_HI = librosa.note_to_hz('C9') F_HI = librosa.note_to_hz('C9')
M = librosa.filters.mel(RATE, N_FFT, BAND_RANGE, fmin=F_LO, fmax=F_HI) melFilter = librosa.filters.mel(RATE, N_FFT, BAND_RANGE, fmin=F_LO, fmax=F_HI)
r = redis.Redis( r = redis.Redis(
host='localhost', host=ip,
port=6379) port=port)
# Early exit to list devices # 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()
debug("\nFound {} devices\n".format(n))
debug (" {} {}".format('ID', 'Device name'))
while i < n:
dev = p.get_device_info_by_index(i)
if dev['maxInputChannels'] > 0:
debug (" {} {}".format(i, dev['name']))
i += 1
if( LIST_DEVICES ): if( LIST_DEVICES ):
list_devices() list_devices()
os._exit(1) os._exit(1)
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
# global # global
bpm = 120.0 bpm = 120.0
def m_bpm(audio_data): def m_bpm(audio_data):
""" """
This function saves slow analysis to redis This function saves slow analysis to redis
* onset
* bpm
* beat * beat
""" """
global bpm global bpm
# Get RMS if( bpm <= 10):
rms = librosa.feature.rmse( audio_data ) 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"
)
onset = librosa.onset.onset_detect( # Save to Redis
y=audio_data,
sr=RATE)
new_bpm, beats = librosa.beat.beat_track(
y=audio_data,
sr=RATE,
trim=False,
start_bpm=bpm,
units="time"
)
print ( bpm, new_bpm)
# Save spectrum
r.set( 'onset', json.dumps( onset.tolist() ) ) r.set( 'onset', json.dumps( onset.tolist() ) )
r.set( 'bpm', json.dumps( new_bpm ) ) r.set( 'bpm', json.dumps( new_bpm ) )
r.set( 'beats', json.dumps( beats.tolist() ) ) r.set( 'beats', json.dumps( beats.tolist() ) )
bpm = new_bpm bpm = new_bpm
debug( "bpm:{} onset:{} beats:{}".format(bpm,onset,beats) )
return True return True
def m_spectrum(audio_data): def m_spectrum(audio_data):
@ -120,27 +142,25 @@ def m_spectrum(audio_data):
This function saves fast analysis to redis This function saves fast analysis to redis
* spectrum * spectrum
* RMS * RMS
* tuning
""" """
# Compute real FFT. # Compute real FFT.
x_fft = numpy.fft.rfft(audio_data, n=N_FFT) fft = numpy.fft.rfft(audio_data, n=N_FFT)
# Compute mel spectrum. # Compute mel spectrum.
melspectrum = M.dot(abs(x_fft)) melspectrum = melFilter.dot(abs(fft))
# Get RMS # Get RMS
rms = librosa.feature.rmse( S=melspectrum, frame_length=FRAMES_PER_BUFFER ) rms = librosa.feature.rmse( S=melspectrum, frame_length=FRAMES_PER_BUFFER )
# Initialize output characters to display. # Initialize output characters to display.
bit_list = [0]*BAND_RANGE bit_list = [0]*BAND_RANGE
count = 0 count = 0
highest_index = -1
highest_index = -1 highest_value = 0
highest_value = 0
for i in range(BAND_RANGE): for i in range(BAND_RANGE):
val = melspectrum[i] val = melspectrum[i]
# If this is the highest tune, record it # If this is the highest tune, record it
if( val > highest_value ) : if( val > highest_value ) :
highest_index = i highest_index = i
@ -148,10 +168,11 @@ def m_spectrum(audio_data):
# If there is energy in this frequency, mark it # If there is energy in this frequency, mark it
if val > ENERGY_THRESHOLD: if val > ENERGY_THRESHOLD:
count += 1 count += 1
bit_list[i] = 1 bit_list[i] = val
# Save to redis # Save to redis
debug( 'rms:{} bit_list:{} highest_index:{}'.format(rms , bit_list, highest_index ))
r.set( 'rms', "{}".format(rms.tolist()) ) r.set( 'rms', "{}".format(rms.tolist()) )
r.set( 'spectrum', json.dumps( bit_list ) ) r.set( 'spectrum', json.dumps( bit_list ) )
r.set( 'tuning', highest_index ) r.set( 'tuning', highest_index )
@ -167,14 +188,18 @@ def callback(in_data, frame_count, time_info, status):
elif MODE == 'bpm': elif MODE == 'bpm':
m_bpm( audio_data) m_bpm( audio_data)
else: else:
print( "Unknown mode. Exiting") debug( "Unknown mode. Exiting")
os._exit(2) os._exit(2)
end = time.time() end = time.time()
print ("\rLoop took {:.2}s on {}s ".format(end - start, SAMPLING_FREQUENCY), end="") # debug ("\rLoop took {:.2}s on {}s ".format(end - start, SAMPLING_FREQUENCY))
return (in_data, pyaudio.paContinue) return (in_data, pyaudio.paContinue)
print( "\n\nRunning! Using mode {}.\n\n".format(MODE)) 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, stream = p.open(format=pyaudio.paFloat32,
channels=CHANNELS, channels=CHANNELS,

View File

@ -1,5 +1,2 @@
Redilysis librosa==0.6.1
librosa=0.6.1
numpy=1.14.2
pyaudio
redis redis