Compare commits
No commits in common. "73f0765519f9bd26f9c064620cfd40a9a2ad3ebc" and "9887f622024ab566d50ac1e8a247b0cd3cab473a" have entirely different histories.
73f0765519
...
9887f62202
159
README.md
159
README.md
@ -19,7 +19,7 @@ $ redis-cli get spectrum_120
|
||||
**rms**
|
||||
* **Mode** spectrum
|
||||
* **Type** float number
|
||||
* **Length** scalar(1)
|
||||
* **Length** scalar
|
||||
* **Meaning** Represents the root-mean-square -a mean value- for all frequencies between ```C0``` and ```C9```, e.g. between 12Hz and 8,372Hz.
|
||||
* **Use** A fairly basic information about the scene audio volume.
|
||||
* **Example**
|
||||
@ -50,92 +50,36 @@ $ redis-cli get spectrum_120
|
||||
* The audio volume for the `C2` note is `spectrum_10[23]` (12x2 - 1).
|
||||
* That value being `0.81` is average meaning there is some audio volume for that octave.
|
||||
|
||||
**bpm_sample_interval**
|
||||
* **Mode** bpm
|
||||
* **Type** float
|
||||
* **Length** scalar(1)
|
||||
* **Meaning** Represents the duration in milliseconds of the interval at which beat detection sample are done, in milliseconds. Beat detection require longer sampling duration than spectrum. The former requires intervals superior to 1s, while a 0.1s interval is sufficent for the latter.
|
||||
* **Use** This is useful only if you try to guess future beats.
|
||||
* **Example**:
|
||||
* `"3000.0"`
|
||||
* Each audio sample used to detect the beats is 3s long.
|
||||
|
||||
**bpm_delay**
|
||||
|
||||
bpm
|
||||
* **Mode** bpm
|
||||
* **Type** float
|
||||
* **Length** scalar(1)
|
||||
* **Meaning** Represents the duration in milliseconds of the time taken to make the sample and analyze it, excluding the time spent saving it to redis. In other words, it is (bpm_sample_interval + bpm treatment time).
|
||||
* **Use** This is useful only if you try to guess future beats.
|
||||
* **Type**
|
||||
* **Length**
|
||||
* **Meaning** Represents
|
||||
* **Use**
|
||||
* **Example**
|
||||
* `"3197.49093056"`
|
||||
* The capture + detection time for this tempo detection was 3.19s
|
||||
* If the `bpm_sample_interval` is 3.0s, it took 0.19s to analyze and detect beats in the sample.
|
||||
|
||||
**bpm**
|
||||
bpm_sample_interval
|
||||
* **Mode** bpm
|
||||
* **Type** float
|
||||
* **Length** scalar(1)
|
||||
* **Meaning** Represents the tempo of the audio landscape, in Beats Per Minute (BPM).
|
||||
* **Use** A simple way to know how fast the music goes, in musical environments
|
||||
* **Expiration** This is the only key with an expire value in milliseconds set using's Redis ```PEXPIREAT``` command.
|
||||
* Each time this key is saved to redis, it is set to expire in 2 * bpm_sample_interval.
|
||||
* For example the key will be set expire in 6 000ms if the audio sample lasts for 3s.
|
||||
* This is useful to know for how long the key has been in the redis by using the Redis ```PTTL``` command.
|
||||
* For example, if you run the following command ```redis-cli PTTL bpm``` which results in ```"3848"``` provided bpm_sample_interval is equal to ```3000.0```
|
||||
* It means you can compute when the bpm key was saved to redis i.e. ```2 * 3000 - 3848 = 2352 milliseconds``` ago, plus the TCP transaction time of the redis query.
|
||||
* **Type**
|
||||
* **Length**
|
||||
* **Meaning** Represents
|
||||
* **Example**
|
||||
* `"126.05"`
|
||||
* There are ~126 beats per minutes.
|
||||
|
||||
|
||||
**beats**
|
||||
bpm_delay
|
||||
* **Mode** bpm
|
||||
* **Type** list
|
||||
* **Length** variable
|
||||
* **Meaning** Represents the beats positions in the same
|
||||
* **Use** This is useful only if you try to guess future beats. And in this case, this is the key information.
|
||||
* **Type**
|
||||
* **Length**
|
||||
* **Meaning** Represents
|
||||
* **Example**
|
||||
* `"[0.34829932 0.81269841 1.20743764 1.60217687 2.00852608 2.48453515]"`
|
||||
* After the audio captured, the first beat was detected at 0.35 seconds, the second beat ath 0.81, and so on until the last beat at 2.48
|
||||
* Based on the BPM and this information, we can project future beats around 2.90, 3.30, 3.70, etc.
|
||||
* See below for beat projection
|
||||
|
||||
|
||||
### Calculating the next beats at user times
|
||||
|
||||
This computation requires the following values
|
||||
* bpm_sample_interval
|
||||
* bpm_delay
|
||||
* bpm
|
||||
* pttl_delta : Double the bpm_sample_interval minus (-) the PTTL value of the bpm key in redis minus
|
||||
* last_beat : last beat time in sample
|
||||
|
||||
Redis_latency times are not considered here, but could be
|
||||
|
||||
Examples are given based on previous values for redis keys.
|
||||
|
||||
The calculation goes like
|
||||
0. How many second per beats?
|
||||
* seconds_per_beat = 60 / bpm
|
||||
* ```60 / 126.05 =~ 0.4760```
|
||||
1. When did the capture start?
|
||||
* total_delay = bpm_delay + pttl_delta
|
||||
* ```3197.49093056 + 2352 =~ 5549.5 ``` i.e. the capture started 5.5 seconds ago
|
||||
2. When was the last beat (in milliseconds)?
|
||||
* last_beat_delay = bpm_delay - last_beat*1000 + pttl_delta
|
||||
* ```3197.49093056 - (2.48453515*1000) + 2352 =~ 3064.96 ``` i.e. the last beat was 3.1 seconds ago
|
||||
3. How many beats were there between the last beat and the redis `get bpm` query?
|
||||
* count_past_beats = floor( (last_beat_delay / 1000) / seconds_per_beat)
|
||||
* ```(3064.96/1000) / 0.4760 = 6.439``` e.g. there were at least 6 beats
|
||||
4. When are the next beats relative to the redis bpm key retrieval time (in milliseconds)?
|
||||
* next_beats = f(i){ i * seconds_per_beat * 1000 - last_beat_delay } where i >= count_past_beats
|
||||
* ```f(i){ i * 0.4760 * 1000 - 3064.96 } where i >= 4
|
||||
* f(6) = 6 * 0.4760 * 1000 - 3064.96 = -208.9600
|
||||
* f(7) = 7 * 0.4760 * 1000 - 3064.96 = 267.0400
|
||||
* The next beat is in 267 milliseconds after the time we got the redis key
|
||||
* f(8) = 8 * 0.4760 * 1000 - 3064.96 = 743.0400
|
||||
* etc. until you have f(i) > bpm_sample_interval
|
||||
|
||||
beats
|
||||
* **Mode** bpm
|
||||
* **Type**
|
||||
* **Length**
|
||||
* **Meaning** Represents
|
||||
* **Example**
|
||||
|
||||
### Requirements and installation
|
||||
|
||||
@ -143,7 +87,7 @@ The calculation goes like
|
||||
* audio card
|
||||
* redis server
|
||||
|
||||
To be honest, in my experience installation on Debian 9,10,11 is a mess, due to mandatory LLVM's version when compiling the numba library for librosa.
|
||||
#### Installation
|
||||
|
||||
```python
|
||||
sudo apt install python-pyaudio python
|
||||
@ -153,50 +97,10 @@ pip install -r requirements.txt
|
||||
python redilysis.py --help
|
||||
```
|
||||
|
||||
### Running in Spectrum Mode
|
||||
|
||||
### Running redilysis: Common parameters
|
||||
**Get the help**
|
||||
```
|
||||
python redilysis.py -h
|
||||
```
|
||||
|
||||
**Run with debug info**
|
||||
```
|
||||
python redilysis.py -v
|
||||
```
|
||||
|
||||
**Get a list of audio devices**
|
||||
```
|
||||
python redilysis.py -L
|
||||
```
|
||||
|
||||
**Run with a given audio device**
|
||||
```
|
||||
python redilysis.py -v -d 5
|
||||
```
|
||||
|
||||
**Run with a sampling frequency of 0.5s**
|
||||
```
|
||||
python redilysis.py -v -s 0.5
|
||||
```
|
||||
|
||||
**Connect to redis on address 192.168.2.20 and port 6379**
|
||||
```
|
||||
python redilysis.py -v -i 192.168.2.20 -p 6379
|
||||
```
|
||||
|
||||
**Change the internals of capture: run at 22000Hz with 2200 frames per buffer and 2 channels **
|
||||
```
|
||||
python redilysis.py -v -r 22000 -f 2200 -c 2
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Running redilysis in Spectrum Mode
|
||||
|
||||
**Choosing the spectrum mode**
|
||||
```
|
||||
python redilysis.py -v -m spectrum -s 0.1
|
||||
python redilysis.py -m spectrum
|
||||
```
|
||||
This is the default mode.
|
||||
|
||||
@ -208,22 +112,15 @@ It can run at sub-second frequency (100ms) with no problem.
|
||||
|
||||
It reports realistic data: spectrum analysis is the easy part.
|
||||
|
||||
### Running redilysis in BPM Mode
|
||||
### Running in BPM Mode
|
||||
|
||||
**Choosing the BPM mode**
|
||||
```
|
||||
python redilysis.py -v -m bpm -s 3
|
||||
```
|
||||
**Choosing a minimum and maximum BPM**
|
||||
```
|
||||
python redilysis.py -v -m bpm -s 3 --bpm-min 100 --bpm-max 200
|
||||
python redilysis.py -m bpm -s 0.5
|
||||
```
|
||||
|
||||
This mode is less sure that the spectrum mode.
|
||||
This mode is experimental.
|
||||
|
||||
It attempts to detect beats based on audio "jumps" in intensity and energy.
|
||||
|
||||
To correct a well-known error called the "octave error" where the detected tempo is twice/half or thrice/third of the real tempo, you can use the Min/Max BPM. When the calculated tempo is outside of the range, it will attempt to find more legitimate values.
|
||||
It attempts to detect beats based on complex parameters.
|
||||
|
||||
|
||||
|
||||
|
47
redilysis.py
47
redilysis.py
@ -38,12 +38,8 @@ _BPM_MIN=10
|
||||
_BPM_MAX=400
|
||||
|
||||
# Argument parsing
|
||||
# Audio Args
|
||||
parser = argparse.ArgumentParser(prog='realtime_redis')
|
||||
# Standard Args
|
||||
parser.add_argument("-v","--verbose",action="store_true",help="Verbose")
|
||||
# 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)
|
||||
# Audio Capture Args
|
||||
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')
|
||||
@ -51,11 +47,16 @@ parser.add_argument('--device','-d', required=False, type=int, help='Which pyaud
|
||||
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='The audio capture rate in Hz. Default={} '.format(_RATE))
|
||||
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))
|
||||
# BPM Mode Args
|
||||
parser.add_argument('--bpm-min', required=False, default=_BPM_MIN, type=int, help='BPM mode only. The low BPM threshold. Default={} '.format(_BPM_MIN))
|
||||
parser.add_argument('--bpm-max', required=False, default=_BPM_MAX, type=int, help='BPM mode only. The high BPM threshold. Default={} '.format(_BPM_MAX))
|
||||
|
||||
# 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)
|
||||
# Standard Args
|
||||
parser.add_argument("-v","--verbose",action="store_true",help="Verbose")
|
||||
args = parser.parse_args()
|
||||
|
||||
# global
|
||||
@ -109,6 +110,7 @@ if( LIST_DEVICES ):
|
||||
list_devices()
|
||||
os._exit(1)
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
|
||||
def m_bpm(audio_data):
|
||||
@ -120,6 +122,7 @@ def m_bpm(audio_data):
|
||||
global bpm
|
||||
global start
|
||||
|
||||
bpm_delay = SAMPLING_FREQUENCY + start - time.time()
|
||||
|
||||
# Detect tempo / bpm
|
||||
new_bpm, beats = librosa.beat.beat_track(
|
||||
@ -135,25 +138,19 @@ def m_bpm(audio_data):
|
||||
'''
|
||||
# Correct the eventual octave error
|
||||
if new_bpm < bpm_min or new_bpm > bpm_max:
|
||||
found = False
|
||||
octaveErrorList = [ 0.5, 2, 0.3333, 3 ]
|
||||
for key,factor in enumerate(octaveErrorList):
|
||||
correction = new_bpm * factor
|
||||
if correction > bpm_min and correction < bpm_max:
|
||||
debug( "Corrected high/low bpm:{} to:{}".format(new_bpm, correction))
|
||||
debug( "Corrected bpm to:{}".format(correction))
|
||||
new_bpm = correction
|
||||
found = True
|
||||
break
|
||||
if found == False:
|
||||
if new_bpm < bpm_min :
|
||||
new_bpm = bpm_min
|
||||
else :
|
||||
new_bpm = bpm_max
|
||||
|
||||
debug("new_bpm:{}".format(new_bpm))
|
||||
'''
|
||||
How to guess the next beats based on the data sent to redis
|
||||
~~ A Dirty Graph ~~
|
||||
|
||||
|start end|
|
||||
Capture |........................|
|
||||
@ -170,29 +167,22 @@ def m_bpm(audio_data):
|
||||
. passed (...b....b....b.)
|
||||
. guessed (..b....b....b....b...
|
||||
Next Beat Calculation b....b....b....b.|..b
|
||||
Beats |last beat
|
||||
0 1 2 3 4
|
||||
|
||||
=> (Delay - last beat) + x*BPM/60 (with x >= read_delay/BPM/60)
|
||||
Redis:
|
||||
|
||||
key bpm_sample_interval
|
||||
visual |........................|
|
||||
|
||||
key bpm_delay
|
||||
visual |.........................|
|
||||
bpm_sample_interval
|
||||
|........................|
|
||||
bpm_delay
|
||||
|.........................|
|
||||
|
||||
'''
|
||||
bpm = new_bpm
|
||||
bpm_sample_interval = SAMPLING_FREQUENCY * 1000
|
||||
bpm_delay = (SAMPLING_FREQUENCY + time.time() - start ) * 1000
|
||||
pexpireat = int( 2 * bpm_sample_interval);
|
||||
# Save to Redis
|
||||
r.set( 'bpm', round(bpm,2), px = pexpireat )
|
||||
r.set( 'bpm_sample_interval', bpm_sample_interval )
|
||||
r.set( 'bpm', new_bpm, px=( 2* int(SAMPLING_FREQUENCY * 1000)))
|
||||
r.set( 'bpm_sample_interval', SAMPLING_FREQUENCY )
|
||||
r.set( 'bpm_delay', bpm_delay )
|
||||
r.set( 'beats', json.dumps( beats.tolist() ) )
|
||||
#debug( "pexpireat:{}".format(pexpireat))
|
||||
debug( "bpm:{} bpm_delay:{} bpm_sample_interval:{} beats:{}".format(bpm,bpm_delay,bpm_sample_interval,beats) )
|
||||
debug( "bpm:{} bpm_delay:{} beats:{}".format(bpm,bpm_delay,beats) )
|
||||
return True
|
||||
|
||||
def m_spectrum(audio_data):
|
||||
@ -260,7 +250,6 @@ if MODE == 'spectrum':
|
||||
elif MODE == 'bpm':
|
||||
debug("In this mode, we will set keys: onset, bpm, beats")
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
stream = p.open(format=pyaudio.paFloat32,
|
||||
channels=CHANNELS,
|
||||
rate=RATE,
|
||||
|
Loading…
Reference in New Issue
Block a user