[wip}
This commit is contained in:
parent
266a31f0bb
commit
678aae0c94
34
README.md
34
README.md
@ -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
|
||||||
|
113
redilysis.py
Normal file → Executable file
113
redilysis.py
Normal file → Executable 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(
|
onset = librosa.onset.onset_detect(
|
||||||
y=audio_data,
|
y = audio_data,
|
||||||
sr=RATE)
|
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)
|
new_bpm, beats = librosa.beat.beat_track(
|
||||||
# Save spectrum
|
y = audio_data,
|
||||||
|
sr = RATE,
|
||||||
|
trim = False,
|
||||||
|
start_bpm = bpm,
|
||||||
|
units = "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save to Redis
|
||||||
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,13 +142,14 @@ 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 )
|
||||||
@ -134,13 +157,10 @@ def m_spectrum(audio_data):
|
|||||||
# 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
|
||||||
@ -149,9 +169,10 @@ 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,
|
||||||
|
@ -1,5 +1,2 @@
|
|||||||
Redilysis
|
librosa==0.6.1
|
||||||
librosa=0.6.1
|
|
||||||
numpy=1.14.2
|
|
||||||
pyaudio
|
|
||||||
redis
|
redis
|
||||||
|
Loading…
Reference in New Issue
Block a user