audio beat detection temp
This commit is contained in:
parent
2aac7f7fac
commit
950e76e408
4 changed files with 166 additions and 53 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -11,7 +11,7 @@
|
||||||
"program": "beamshow.py",
|
"program": "beamshow.py",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"justMyCode": true,
|
"justMyCode": true,
|
||||||
"args": ["-w"]
|
"args": ["-w","-b"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
88
audio.py
Normal file
88
audio.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import time
|
||||||
|
import aubio
|
||||||
|
import pyaudiowpatch as pa
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
def find_audio_device(p: pa.PyAudio, api: int, name: str) -> dict:
|
||||||
|
api_info = p.get_host_api_info_by_type(api)
|
||||||
|
for i in range(api_info["deviceCount"]):
|
||||||
|
dev_info = p.get_device_info_by_host_api_device_index(api_info["index"], i)
|
||||||
|
if name in dev_info["name"] and dev_info["maxInputChannels"] > 0:
|
||||||
|
return dev_info
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class AudioProcess:
|
||||||
|
def __init__(self):
|
||||||
|
self.pa = pa.PyAudio()
|
||||||
|
|
||||||
|
self.win_s = 1024
|
||||||
|
self.hop_s = self.win_s // 2
|
||||||
|
|
||||||
|
i_dev = find_audio_device(self.pa, pa.paWASAPI, "Notepad")
|
||||||
|
self.rate = int(i_dev["defaultSampleRate"])
|
||||||
|
self.a_source = self.pa.open(
|
||||||
|
rate=self.rate,
|
||||||
|
channels=2,
|
||||||
|
format=pa.paFloat32,
|
||||||
|
input=True,
|
||||||
|
input_device_index=i_dev["index"],
|
||||||
|
frames_per_buffer=self.hop_s,
|
||||||
|
stream_callback=self.pyaudio_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.is_beat = False
|
||||||
|
|
||||||
|
self.tempo = aubio.tempo("default", self.win_s, self.hop_s)
|
||||||
|
self.thread = threading.Thread(name="AudioProcess", target=self.process)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
self.a_source.start_stream()
|
||||||
|
|
||||||
|
while self.a_source.is_active():
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.a_source.stop_stream()
|
||||||
|
self.a_source.close()
|
||||||
|
self.pa.terminate()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.a_source.is_active:
|
||||||
|
self.a_source.stop_stream()
|
||||||
|
if self.thread.is_alive:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
def pyaudio_callback(self, _in_data, _frame_count, _time_info, _status):
|
||||||
|
# samples, read = _in_data, _frame_count
|
||||||
|
# audio_data = np.fromstring(in_data, dtype=np.float32)
|
||||||
|
samples = np.fromstring(_in_data, dtype=np.float32, count=_frame_count)
|
||||||
|
read = _frame_count
|
||||||
|
is_beat = self.tempo(samples)
|
||||||
|
if is_beat:
|
||||||
|
with self.lock:
|
||||||
|
self.is_beat = True
|
||||||
|
# samples += click
|
||||||
|
# print("tick") # avoid print in audio callback
|
||||||
|
|
||||||
|
audiobuf = samples.tobytes()
|
||||||
|
if read < self.hop_s:
|
||||||
|
return (audiobuf, pa.paComplete)
|
||||||
|
return (audiobuf, pa.paContinue)
|
||||||
|
|
||||||
|
# self.stop = False
|
||||||
|
# self.thread = threading.Thread(name="AudioProcess", target=self.process)
|
||||||
|
# self.thread.start()
|
||||||
|
|
||||||
|
# def process(self):
|
||||||
|
# while not self.stop:
|
||||||
|
# samples = self.dev.record(self.hop_s, self.rate, 2)
|
||||||
|
# mono = samples.sum(axis=1)
|
||||||
|
# is_beat = self.tempo(samples)
|
||||||
|
|
||||||
|
# if is_beat:
|
126
beamshow.py
126
beamshow.py
|
@ -7,6 +7,7 @@ import sys
|
||||||
|
|
||||||
from effects.effect import Effect, Colors, color_darken, color_fadeout, color_wheel
|
from effects.effect import Effect, Colors, color_darken, color_fadeout, color_wheel
|
||||||
from effects.presets import Presets
|
from effects.presets import Presets
|
||||||
|
from audio import AudioProcess
|
||||||
|
|
||||||
|
|
||||||
def print_displays() -> None:
|
def print_displays() -> None:
|
||||||
|
@ -38,11 +39,15 @@ class Beamshow:
|
||||||
self.effects: List[Effect] = []
|
self.effects: List[Effect] = []
|
||||||
self.clock = pg.time.Clock()
|
self.clock = pg.time.Clock()
|
||||||
self.beat_reactive = beat_reactive
|
self.beat_reactive = beat_reactive
|
||||||
|
self.beat_factor = 1
|
||||||
|
self.beat_skip_counter = 0
|
||||||
|
|
||||||
pg.display.init()
|
pg.display.init()
|
||||||
self.window, self.background = self.initialize()
|
self.window, self.background = self.initialize()
|
||||||
|
self.audio = AudioProcess()
|
||||||
|
|
||||||
def initialize(self) -> Tuple[pg.Surface, pg.Surface]:
|
def initialize(self) -> Tuple[pg.Surface, pg.Surface]:
|
||||||
|
recreate_effect = not self.effects or pg.display.is_fullscreen == self.windowed
|
||||||
displays = pg.display.get_desktop_sizes()
|
displays = pg.display.get_desktop_sizes()
|
||||||
if not 0 <= self.display_id < len(displays):
|
if not 0 <= self.display_id < len(displays):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -63,20 +68,14 @@ class Beamshow:
|
||||||
background = pg.Surface(win.get_size())
|
background = pg.Surface(win.get_size())
|
||||||
background.fill(Colors.Black)
|
background.fill(Colors.Black)
|
||||||
|
|
||||||
self.presets = Presets(bounds=win.get_rect(), beat_reactive=self.beat_reactive)
|
if recreate_effect:
|
||||||
if self.randomize:
|
self.presets = Presets(
|
||||||
self.effects = self.presets.randomize()
|
bounds=win.get_rect(), beat_reactive=self.beat_reactive
|
||||||
else:
|
)
|
||||||
# self.effects = self.presets.default()
|
if self.randomize:
|
||||||
from effects.spiro import Spiro
|
self.effects = self.presets.randomize()
|
||||||
|
else:
|
||||||
self.effects = [
|
self.effects = self.presets.default()
|
||||||
Spiro(
|
|
||||||
bounds=win.get_rect(),
|
|
||||||
color=color_wheel(increase=10),
|
|
||||||
sizes=(win.get_rect().height * 0.8, win.get_rect().height * 0.8),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
return win, background
|
return win, background
|
||||||
|
|
||||||
|
@ -147,22 +146,34 @@ class Beamshow:
|
||||||
framecounter = 0
|
framecounter = 0
|
||||||
blackout = False
|
blackout = False
|
||||||
single_random = False
|
single_random = False
|
||||||
taps = []
|
# taps = []
|
||||||
tap_mean = 0
|
# tap_mean = 0
|
||||||
last_beat = 0
|
# last_beat = 0
|
||||||
fps_slidewindow = []
|
fps_slidewindow = []
|
||||||
frames_per_beat = 0
|
frames_per_beat = 0
|
||||||
while True:
|
while True:
|
||||||
is_beat = False
|
with self.audio.lock:
|
||||||
|
is_beat = self.audio.is_beat
|
||||||
|
self.audio.is_beat = False
|
||||||
|
if is_beat:
|
||||||
|
if self.beat_skip_counter > 0:
|
||||||
|
is_beat = False
|
||||||
|
print(' skip')
|
||||||
|
self.beat_skip_counter -= 1
|
||||||
|
else:
|
||||||
|
print('beat')
|
||||||
|
self.beat_skip_counter = self.beat_factor - 1
|
||||||
|
|
||||||
|
# is_beat = False
|
||||||
fps_mean = (
|
fps_mean = (
|
||||||
sum(fps_slidewindow) / len(fps_slidewindow) if fps_slidewindow else 0
|
sum(fps_slidewindow) / len(fps_slidewindow) if fps_slidewindow else 0
|
||||||
)
|
)
|
||||||
if tap_mean and last_beat:
|
# if tap_mean and last_beat:
|
||||||
this_beat = time.time()
|
# this_beat = time.time()
|
||||||
is_beat = this_beat >= last_beat + tap_mean
|
# is_beat = this_beat >= last_beat + tap_mean
|
||||||
frames_per_beat = fps_mean * tap_mean
|
# frames_per_beat = fps_mean * tap_mean
|
||||||
if is_beat:
|
# if is_beat:
|
||||||
last_beat = this_beat
|
# last_beat = this_beat
|
||||||
|
|
||||||
common_events = loop.send((is_beat, frames_per_beat))
|
common_events = loop.send((is_beat, frames_per_beat))
|
||||||
reinitialize = False
|
reinitialize = False
|
||||||
|
@ -170,12 +181,17 @@ class Beamshow:
|
||||||
if event.type == pg.QUIT or (
|
if event.type == pg.QUIT or (
|
||||||
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE
|
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE
|
||||||
):
|
):
|
||||||
|
self.audio.stop()
|
||||||
pg.quit()
|
pg.quit()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
elif event.type == pg.KEYDOWN:
|
elif event.type == pg.KEYDOWN:
|
||||||
if event.key == pg.K_F5:
|
if event.key == pg.K_F5:
|
||||||
single_random = True
|
single_random = True
|
||||||
print("Switching to new random preset")
|
print("Switching to new random preset")
|
||||||
|
elif event.key == pg.K_F6:
|
||||||
|
print("Reload")
|
||||||
|
self.effects.clear()
|
||||||
|
reinitialize = True
|
||||||
elif event.key == pg.K_F8:
|
elif event.key == pg.K_F8:
|
||||||
self.randomize = not self.randomize
|
self.randomize = not self.randomize
|
||||||
state_str = (
|
state_str = (
|
||||||
|
@ -201,35 +217,42 @@ class Beamshow:
|
||||||
print(f'BLACKOUT [{"ON" if blackout else "off"}]')
|
print(f'BLACKOUT [{"ON" if blackout else "off"}]')
|
||||||
elif event.key == pg.K_HOME:
|
elif event.key == pg.K_HOME:
|
||||||
print("resetting beat timing")
|
print("resetting beat timing")
|
||||||
this_tap = time.time()
|
self.beat_skip_counter = 0
|
||||||
last_beat = this_tap
|
# this_tap = time.time()
|
||||||
elif event.key == pg.K_END:
|
# last_beat = this_tap
|
||||||
print("starting new tap measurement")
|
# elif event.key == pg.K_END:
|
||||||
taps.clear()
|
# print("starting new tap measurement")
|
||||||
|
# taps.clear()
|
||||||
elif event.key == pg.K_PAGEUP:
|
elif event.key == pg.K_PAGEUP:
|
||||||
tap_mean /= 2
|
self.beat_factor = (
|
||||||
print(
|
self.beat_factor / 2 if self.beat_factor > 1 else 1
|
||||||
f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [x2]"
|
|
||||||
)
|
)
|
||||||
|
print(f"Trigger on every {self.beat_factor} beat")
|
||||||
|
# tap_mean /= 2
|
||||||
|
# print(
|
||||||
|
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [x2]"
|
||||||
|
# )
|
||||||
elif event.key == pg.K_PAGEDOWN:
|
elif event.key == pg.K_PAGEDOWN:
|
||||||
tap_mean *= 2
|
self.beat_factor = self.beat_factor * 2
|
||||||
print(
|
print(f"Trigger on every {self.beat_factor} beat")
|
||||||
f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [/2]"
|
# tap_mean *= 2
|
||||||
)
|
# print(
|
||||||
elif event.key == pg.K_KP_ENTER or event.key == pg.K_RETURN:
|
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [/2]"
|
||||||
this_tap = time.time()
|
# )
|
||||||
if taps and this_tap - taps[-1] > 10:
|
# elif event.key == pg.K_KP_ENTER or event.key == pg.K_RETURN:
|
||||||
print("starting new tap measurement")
|
# this_tap = time.time()
|
||||||
taps.clear()
|
# if taps and this_tap - taps[-1] > 10:
|
||||||
taps.append(this_tap)
|
# print("starting new tap measurement")
|
||||||
last_beat = this_tap
|
# taps.clear()
|
||||||
if len(taps) >= 2:
|
# taps.append(this_tap)
|
||||||
tap_mean = sum(
|
# last_beat = this_tap
|
||||||
map(lambda t1, t2: t2 - t1, taps, taps[1:])
|
# if len(taps) >= 2:
|
||||||
) / (len(taps) - 1)
|
# tap_mean = sum(
|
||||||
print(
|
# map(lambda t1, t2: t2 - t1, taps, taps[1:])
|
||||||
f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB"
|
# ) / (len(taps) - 1)
|
||||||
)
|
# print(
|
||||||
|
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB"
|
||||||
|
# )
|
||||||
|
|
||||||
if reinitialize:
|
if reinitialize:
|
||||||
self.window, self.background = self.initialize()
|
self.window, self.background = self.initialize()
|
||||||
|
@ -256,8 +279,9 @@ class Beamshow:
|
||||||
new_preset = self.presets.randomize()
|
new_preset = self.presets.randomize()
|
||||||
while new_preset == self.effects:
|
while new_preset == self.effects:
|
||||||
new_preset = self.presets.randomize()
|
new_preset = self.presets.randomize()
|
||||||
|
print(new_preset)
|
||||||
self.effects.clear()
|
self.effects.clear()
|
||||||
self.effects.extend(self.presets.randomize())
|
self.effects.extend(new_preset)
|
||||||
|
|
||||||
current_fps = self.clock.get_fps()
|
current_fps = self.clock.get_fps()
|
||||||
fps_slidewindow.append(current_fps)
|
fps_slidewindow.append(current_fps)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pygame==2.1.3
|
pygame==2.1.3
|
||||||
soundcard==0.4.2
|
soundcard==0.4.2
|
||||||
|
pyaudiowpatch==0.2.12.5
|
Loading…
Reference in a new issue