mirror of
https://github.com/correl/turntable.git
synced 2024-12-27 11:09:53 +00:00
Replace pyglet with pygame
This commit is contained in:
parent
08834afe21
commit
a4a42bd632
5 changed files with 178 additions and 126 deletions
39
poetry.lock
generated
39
poetry.lock
generated
|
@ -280,7 +280,6 @@ scipy = "1.3.1"
|
|||
reference = "e56a4a221ad204654a191d217f92aebf3f058b62"
|
||||
type = "git"
|
||||
url = "https://github.com/worldveil/dejavu.git"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Manipulate audio with an simple and easy high level interface"
|
||||
|
@ -291,11 +290,11 @@ version = "0.23.1"
|
|||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Cross-platform windowing and multimedia library"
|
||||
name = "pyglet"
|
||||
description = "Python Game Development"
|
||||
name = "pygame"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.5.7"
|
||||
version = "1.9.6"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
|
@ -422,7 +421,7 @@ secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0
|
|||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "3af542607f4ccaa3afe08d275174f664cdacedac31ce8278e059d4aca2b9edeb"
|
||||
content-hash = "9fa7d79cd4464df797b8d866bfa3a426ab1399854faf48b02f00d65d46a507be"
|
||||
lock-version = "1.0"
|
||||
python-versions = "^3.8"
|
||||
|
||||
|
@ -634,9 +633,33 @@ pydub = [
|
|||
{file = "pydub-0.23.1-py2.py3-none-any.whl", hash = "sha256:d29901a486fb421c5d7b0f3d5d3a60527179204d8ffb20e74e1ae81c17e81b46"},
|
||||
{file = "pydub-0.23.1.tar.gz", hash = "sha256:c362fa02da1eebd1d08bd47aa9b0102582dff7ca2269dbe9e043d228a0c1ea93"},
|
||||
]
|
||||
pyglet = [
|
||||
{file = "pyglet-1.5.7-py3-none-any.whl", hash = "sha256:9832442d59ee06acbeff12e128cf6d5aee271e94c09386040db8f0feae277013"},
|
||||
{file = "pyglet-1.5.7.zip", hash = "sha256:3faac2dad34946aecbce79a8658f89155436fe5c07332229160c6eba302ff40d"},
|
||||
pygame = [
|
||||
{file = "pygame-1.9.6-cp27-cp27m-macosx_10_11_intel.whl", hash = "sha256:4aaff572a273a32e70ec3593d213e59ab11c183a9916616562247930f17a5447"},
|
||||
{file = "pygame-1.9.6-cp27-cp27m-win32.whl", hash = "sha256:73cd9df328c7e72638dbcc1d18e7155225faed880a53db6bad90d1d7c0a71dfd"},
|
||||
{file = "pygame-1.9.6-cp27-cp27m-win_amd64.whl", hash = "sha256:9ce22fb72298ea33dbb3a1c6c60a4a4e19d9698df6f3f5782eba4dada7b7736d"},
|
||||
{file = "pygame-1.9.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:5f052dc2975a399aa1830c1f04c5f72856aa416bf3cd4b31375a058015a5c620"},
|
||||
{file = "pygame-1.9.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0480fe82cd41a43e3eea497fa2c059c72ac54cb5d003d5aa2ed06a04541c384e"},
|
||||
{file = "pygame-1.9.6-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:a6e8d2f99dbe1dfe72d0c019693c14d93c410f702d0b04ec9a81b36dacd55a23"},
|
||||
{file = "pygame-1.9.6-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:698433a9fcefca0527244dc44dff9503eb26157494730b1cc80e6e4dbb246e92"},
|
||||
{file = "pygame-1.9.6-cp34-cp34m-win32.whl", hash = "sha256:68ea43e51150316b9fb08e251209d4e2b4e76a340b5b6fc8cdf1a898c78f7e5b"},
|
||||
{file = "pygame-1.9.6-cp34-cp34m-win_amd64.whl", hash = "sha256:4e1065577f1b29111113be5deb2ea88553551a5e1cf33e0c08fa32768f285809"},
|
||||
{file = "pygame-1.9.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6f714986f7987f10cb94f1be0753318e341a7ea6b12d66f37a4d5d6dd4695023"},
|
||||
{file = "pygame-1.9.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ae1bc3e78ed28f20878e7ca2c98663a6634e9c00d7746d39413fc18e907dc162"},
|
||||
{file = "pygame-1.9.6-cp35-cp35m-win32.whl", hash = "sha256:854e87b8b2b76e3ed11d64985fcfdd7af919659503de99fc2b0a717b314c3cf0"},
|
||||
{file = "pygame-1.9.6-cp35-cp35m-win_amd64.whl", hash = "sha256:2622b9dd95f445c887a36a57eade42c672598589f69a8052ccdb8eeeffa4dbb1"},
|
||||
{file = "pygame-1.9.6-cp36-cp36m-macosx_10_11_intel.whl", hash = "sha256:398c42b605ecc514e62f68f1944a2d21e247938309f598de6cb0ad3c207324a8"},
|
||||
{file = "pygame-1.9.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fa788f775680fc5d268ab00a2da29c9a22830032cfab732730298a2952cd87f3"},
|
||||
{file = "pygame-1.9.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c895cf9c1b6d1cbba8cb8cc3f5427febcf8aa41a9333697741abeea1c537a350"},
|
||||
{file = "pygame-1.9.6-cp36-cp36m-win32.whl", hash = "sha256:a37b6c59e7b8feadc51db5197052b86ceb6443f9fb2a6f7d6527620e707c558c"},
|
||||
{file = "pygame-1.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:be7e70f91bd4eb35ae081062f16bf434619b3292358d9b061f8159ddc570c7f0"},
|
||||
{file = "pygame-1.9.6-cp37-cp37m-macosx_10_11_intel.whl", hash = "sha256:7876d1f29f66d3d7cac46479503891ee1ef409b0fbce54b0d74f3a6b33a46dba"},
|
||||
{file = "pygame-1.9.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1f5714d2c23f6a64ef2ac4fcd36a2dd2689da85978d951a99a6ae5dfdf9bdbc"},
|
||||
{file = "pygame-1.9.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:136a3b5711d9ec369a0407e4e08ffced3ba61aa41059e9280ffffa79c8614f65"},
|
||||
{file = "pygame-1.9.6-cp37-cp37m-win32.whl", hash = "sha256:a9ac862dd7159861f2c6443b0029089e1c0c4ec762a8074022914ec52fe4dfac"},
|
||||
{file = "pygame-1.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8da13704ad45b7d5de8a8cca135a7f44c7fc6aa9f691abe7b0392468a34a8013"},
|
||||
{file = "pygame-1.9.6-cp38-cp38-win32.whl", hash = "sha256:396320aa29a925feed0b64639f77ce1418722ea7f536b4e4936083dd8d4c4535"},
|
||||
{file = "pygame-1.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:e3e7e4a09dfd8b03663222d6bcadec9fef021404f4d9eecf56825342e039dfc1"},
|
||||
{file = "pygame-1.9.6.tar.gz", hash = "sha256:301c6428c0880ecd4a9e3951b80e539c33863b6ff356a443db1758de4f297957"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
|
|
|
@ -10,7 +10,7 @@ python = "^3.8"
|
|||
pyalsaaudio = "^0.9.0"
|
||||
pydejavu = {git = "https://github.com/worldveil/dejavu.git"}
|
||||
requests = "^2.24.0"
|
||||
pyglet = "^1.5.7"
|
||||
pygame = "^1.9.6"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^20.8b1"
|
||||
|
|
|
@ -23,63 +23,69 @@ from turntable.turntable import (
|
|||
VERSION = importlib.metadata.version("turntable")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@contextmanager
|
||||
def run() -> "Iterator[Queue[Event]]":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--config", default=os.path.expanduser("~/.config/turntable.json")
|
||||
)
|
||||
args = parser.parse_args()
|
||||
with open(args.config, "r") as config_file:
|
||||
config: Dict[str, Any] = json.load(config_file)
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG if config.get("debug") else logging.INFO)
|
||||
logger.info("Turntable version %s", VERSION)
|
||||
class Application:
|
||||
def __init__(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--config", default=os.path.expanduser("~/.config/turntable.json")
|
||||
)
|
||||
args = parser.parse_args()
|
||||
with open(args.config, "r") as config_file:
|
||||
self.config: Dict[str, Any] = json.load(config_file)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG if self.config.get("debug") else logging.INFO
|
||||
)
|
||||
logger.info("Turntable version %s", VERSION)
|
||||
|
||||
pcm_in: "Queue[PCM]" = Queue()
|
||||
pcm_out: "Queue[PCM]" = Queue()
|
||||
events: "Queue[Event]" = Queue()
|
||||
pcm_in: "Queue[PCM]" = Queue()
|
||||
pcm_out: "Queue[PCM]" = Queue()
|
||||
self.pcm_display: "Queue[PCM]" = Queue()
|
||||
self.events: "Queue[Event]" = Queue()
|
||||
|
||||
audio_config = config.get("audio", dict())
|
||||
listener = Listener(
|
||||
[pcm_in, pcm_out],
|
||||
events,
|
||||
audio_config.get("device", "default"),
|
||||
framerate=audio_config.get("framerate", 44100),
|
||||
channels=audio_config.get("channels", 2),
|
||||
period_size=audio_config.get("period_size", 4096),
|
||||
)
|
||||
audio_config = self.config.get("audio", dict())
|
||||
listener = Listener(
|
||||
[pcm_in, pcm_out, self.pcm_display],
|
||||
self.events,
|
||||
audio_config.get("device", "default"),
|
||||
framerate=audio_config.get("framerate", 44100),
|
||||
channels=audio_config.get("channels", 2),
|
||||
period_size=audio_config.get("period_size", 4096),
|
||||
)
|
||||
|
||||
player = Player(
|
||||
pcm_out,
|
||||
audio_config.get("output_device", "null"),
|
||||
framerate=audio_config.get("framerate", 44100),
|
||||
channels=audio_config.get("channels", 2),
|
||||
period_size=audio_config.get("period_size", 4096),
|
||||
)
|
||||
player = Player(
|
||||
pcm_out,
|
||||
audio_config.get("output_device", "null"),
|
||||
framerate=audio_config.get("framerate", 44100),
|
||||
channels=audio_config.get("channels", 2),
|
||||
period_size=audio_config.get("period_size", 4096),
|
||||
)
|
||||
|
||||
dejavu = Dejavu(config.get("dejavu", dict()))
|
||||
dejavu = Dejavu(self.config.get("dejavu", dict()))
|
||||
|
||||
turntable = Turntable(listener.framerate, listener.channels, dejavu, pcm_in, events)
|
||||
turntable = Turntable(
|
||||
listener.framerate, listener.channels, dejavu, pcm_in, self.events
|
||||
)
|
||||
|
||||
icecast_config = config.get("icecast", dict())
|
||||
icecast = Icecast(
|
||||
host=icecast_config.get("host", "localhost"),
|
||||
port=icecast_config.get("port", 8000),
|
||||
mountpoint=icecast_config.get("mountpoint", "stream.mp3"),
|
||||
user=icecast_config.get("admin_user", "admin"),
|
||||
password=icecast_config.get("admin_password", "hackme"),
|
||||
)
|
||||
icecast_config = self.config.get("icecast", dict())
|
||||
icecast = Icecast(
|
||||
host=icecast_config.get("host", "localhost"),
|
||||
port=icecast_config.get("port", 8000),
|
||||
mountpoint=icecast_config.get("mountpoint", "stream.mp3"),
|
||||
user=icecast_config.get("admin_user", "admin"),
|
||||
password=icecast_config.get("admin_password", "hackme"),
|
||||
)
|
||||
|
||||
processes = [listener, player, turntable]
|
||||
for process in processes:
|
||||
process.daemon = True
|
||||
process.start()
|
||||
try:
|
||||
yield events
|
||||
except:
|
||||
logging.exception("Terminating")
|
||||
for process in processes:
|
||||
self.processes = [listener, player, turntable]
|
||||
|
||||
def run(self) -> "Iterator[Queue[Event]]":
|
||||
for process in self.processes:
|
||||
process.daemon = True
|
||||
process.start()
|
||||
|
||||
def shutdown(self) -> None:
|
||||
logging.info("Terminating")
|
||||
for process in self.processes:
|
||||
if process.is_alive():
|
||||
process.terminate()
|
||||
process.kill()
|
||||
|
|
|
@ -4,7 +4,8 @@ from turntable import application, turntable
|
|||
|
||||
|
||||
def main() -> None:
|
||||
with application.run() as events:
|
||||
app = application.Application()
|
||||
with app.run() as events:
|
||||
while event := events.get():
|
||||
if not isinstance(event, turntable.Audio):
|
||||
logging.info("Event: %s", event)
|
||||
|
|
154
turntable/gui.py
154
turntable/gui.py
|
@ -1,19 +1,23 @@
|
|||
import logging
|
||||
import os
|
||||
import queue
|
||||
from statistics import fmean
|
||||
from typing import Iterable, List, Optional, Tuple, Union
|
||||
|
||||
import numpy as np # type: ignore
|
||||
import pyglet # type: ignore
|
||||
import pyglet.clock # type: ignore
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
import scipy.signal # type: ignore
|
||||
|
||||
from turntable import application, models, turntable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plot:
|
||||
def __init__(
|
||||
self,
|
||||
screen,
|
||||
x: int,
|
||||
y: int,
|
||||
width: int,
|
||||
|
@ -21,8 +25,8 @@ class Plot:
|
|||
bars: int = 20,
|
||||
bar_width: int = 40,
|
||||
color: Tuple[int, int, int] = (255, 255, 255),
|
||||
batch: Optional[pyglet.graphics.Batch] = None,
|
||||
) -> None:
|
||||
self.screen = screen
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
|
@ -30,80 +34,98 @@ class Plot:
|
|||
self.bars = bars
|
||||
self.bar_width = bar_width
|
||||
self.color = color
|
||||
self.batch = batch or pyglet.graphics.Batch()
|
||||
self.lines: List[pyglet.shapes.Line] = []
|
||||
self.audio = b""
|
||||
|
||||
def update(self):
|
||||
def draw(self) -> None:
|
||||
data = np.fromstring(self.audio, dtype=np.int16)
|
||||
if len(data) == 0:
|
||||
return
|
||||
fft = abs(np.fft.fft(data).real)
|
||||
fft = fft[: len(fft) // 2]
|
||||
heights = scipy.signal.resample(fft, self.bars) * self.height / 2 ** 16
|
||||
self.lines = [
|
||||
pyglet.shapes.Line(
|
||||
self.x + x / self.bars * self.width,
|
||||
for i, height in enumerate(heights):
|
||||
pygame.draw.rect(
|
||||
self.screen,
|
||||
self.color,
|
||||
(
|
||||
self.x + i / self.bars * self.width,
|
||||
self.height,
|
||||
self.bar_width,
|
||||
-height,
|
||||
),
|
||||
0,
|
||||
self.x + x / self.bars * self.width,
|
||||
y,
|
||||
width=self.bar_width,
|
||||
color=self.color,
|
||||
batch=self.batch,
|
||||
)
|
||||
for x, y in enumerate(heights)
|
||||
]
|
||||
|
||||
def draw(self) -> None:
|
||||
self.batch.draw()
|
||||
|
||||
|
||||
def main():
|
||||
window = pyglet.window.Window(fullscreen=True)
|
||||
with application.run() as events:
|
||||
audio = b""
|
||||
label = pyglet.text.Label(
|
||||
"<Idle>",
|
||||
font_name="Noto Sans",
|
||||
font_size=36,
|
||||
x=window.width // 2,
|
||||
y=window.height // 2,
|
||||
anchor_x="center",
|
||||
anchor_y="center",
|
||||
)
|
||||
batch = pyglet.graphics.Batch()
|
||||
plot = Plot(
|
||||
x=0,
|
||||
y=0,
|
||||
width=window.width,
|
||||
height=window.height,
|
||||
bars=40,
|
||||
bar_width=window.width // 45,
|
||||
color=(139, 0, 139),
|
||||
batch=batch,
|
||||
)
|
||||
app = application.Application()
|
||||
config = app.config.get("gui", dict())
|
||||
FPS = int(config.get("fps", 30))
|
||||
WIDTH = int(config.get("width", 800))
|
||||
HEIGHT = int(config.get("height", 600))
|
||||
disp_no = os.getenv("DISPLAY")
|
||||
if disp_no:
|
||||
logger.info("I'm running under X display = {0}".format(disp_no))
|
||||
|
||||
@window.event
|
||||
def on_draw():
|
||||
window.clear()
|
||||
batch.draw()
|
||||
label.draw()
|
||||
# Check which frame buffer drivers are available
|
||||
# Start with fbcon since directfb hangs with composite output
|
||||
drivers = ["x11", "fbcon", "directfb", "svgalib"]
|
||||
found = False
|
||||
for driver in drivers:
|
||||
# Make sure that SDL_VIDEODRIVER is set
|
||||
if not os.getenv("SDL_VIDEODRIVER"):
|
||||
os.putenv("SDL_VIDEODRIVER", driver)
|
||||
try:
|
||||
pygame.display.init()
|
||||
except pygame.error:
|
||||
logger.warn("Driver: {0} failed.".format(driver))
|
||||
continue
|
||||
found = True
|
||||
break
|
||||
|
||||
def check_events(dt):
|
||||
try:
|
||||
event = events.get(False)
|
||||
if isinstance(event, turntable.StartedPlaying):
|
||||
label.text = "<Record starting...>"
|
||||
elif isinstance(event, turntable.StoppedPlaying):
|
||||
label.text = "<Idle>"
|
||||
elif isinstance(event, turntable.NewMetadata):
|
||||
label.text = event.title
|
||||
elif isinstance(event, turntable.Audio):
|
||||
plot.audio = event.pcm.raw
|
||||
except queue.Empty:
|
||||
if not found:
|
||||
raise Exception("No suitable video driver found!")
|
||||
|
||||
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
|
||||
logger.info("Window size: %d x %d" % (size[0], size[1]))
|
||||
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
# Clear the screen to start
|
||||
screen.fill((0, 0, 0))
|
||||
# Initialise font support
|
||||
pygame.font.init()
|
||||
# Render the screen
|
||||
pygame.display.update()
|
||||
|
||||
plot = Plot(
|
||||
screen=screen,
|
||||
x=0,
|
||||
y=0,
|
||||
width=screen.get_width(),
|
||||
height=screen.get_height(),
|
||||
bars=40,
|
||||
bar_width=screen.get_width() // 45,
|
||||
color=(139, 0, 139),
|
||||
)
|
||||
|
||||
app.run()
|
||||
clock = pygame.time.Clock()
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
|
||||
app.shutdown()
|
||||
pygame.quit()
|
||||
return
|
||||
try:
|
||||
while event := app.events.get(False):
|
||||
...
|
||||
|
||||
def update_vis(dt):
|
||||
plot.update()
|
||||
|
||||
pyglet.clock.schedule(check_events)
|
||||
pyglet.clock.schedule_interval(update_vis, 0.03)
|
||||
pyglet.app.run()
|
||||
except queue.Empty:
|
||||
...
|
||||
try:
|
||||
while pcm := app.pcm_display.get(False):
|
||||
plot.audio = pcm.raw
|
||||
except queue.Empty:
|
||||
...
|
||||
screen.fill((0, 0, 0))
|
||||
plot.draw()
|
||||
pygame.display.update()
|
||||
clock.tick(30)
|
||||
|
|
Loading…
Reference in a new issue