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 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
```python
sudo apt install python-pyaudio python3
git clone https://git.interhacker.space/tmplab/redilysis.git
cd redilysis
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
"""
from __future__ import print_function
import argparse
import json
import librosa
@ -14,43 +15,40 @@ import numpy
import os
import pyaudio
import redis
import sys
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.
_BAND_RANGE = 96
_BAND_RANGE = 7
_CHANNELS = 1
_ENERGY_THRESHOLD = 0.4
_ENERGY_THRESHOLD = 0.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('--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))
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()
# Set real variables
@ -58,61 +56,85 @@ BAND_RANGE = _BAND_RANGE
CHANNELS = args.channels
DEVICE = args.device
ENERGY_THRESHOLD = args.energy_threshold
FRAMES_PER_BUFFER = args.frames
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
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.
F_LO = librosa.note_to_hz('C2')
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(
host='localhost',
port=6379)
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()
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 ):
list_devices()
os._exit(1)
p = pyaudio.PyAudio()
# global
bpm = 120.0
def m_bpm(audio_data):
"""
This function saves slow analysis to redis
* onset
* bpm
* beat
"""
global bpm
# Get RMS
rms = librosa.feature.rmse( audio_data )
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"
)
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"
)
print ( bpm, new_bpm)
# Save spectrum
# 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
bpm = new_bpm
debug( "bpm:{} onset:{} beats:{}".format(bpm,onset,beats) )
return True
def m_spectrum(audio_data):
@ -120,27 +142,25 @@ def m_spectrum(audio_data):
This function saves fast analysis to redis
* spectrum
* RMS
* tuning
"""
# 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.
melspectrum = M.dot(abs(x_fft))
melspectrum = melFilter.dot(abs(fft))
# 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.
bit_list = [0]*BAND_RANGE
count = 0
highest_index = -1
highest_value = 0
bit_list = [0]*BAND_RANGE
count = 0
highest_index = -1
highest_value = 0
for i in range(BAND_RANGE):
val = melspectrum[i]
val = melspectrum[i]
# If this is the highest tune, record it
if( val > highest_value ) :
highest_index = i
@ -148,10 +168,11 @@ def m_spectrum(audio_data):
# If there is energy in this frequency, mark it
if val > ENERGY_THRESHOLD:
count += 1
bit_list[i] = 1
count += 1
bit_list[i] = val
# Save to redis
debug( 'rms:{} bit_list:{} highest_index:{}'.format(rms , bit_list, highest_index ))
r.set( 'rms', "{}".format(rms.tolist()) )
r.set( 'spectrum', json.dumps( bit_list ) )
r.set( 'tuning', highest_index )
@ -167,14 +188,18 @@ def callback(in_data, frame_count, time_info, status):
elif MODE == 'bpm':
m_bpm( audio_data)
else:
print( "Unknown mode. Exiting")
debug( "Unknown mode. Exiting")
os._exit(2)
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)
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,
channels=CHANNELS,

View File

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