mirror of
https://github.com/correl/turntable.git
synced 2024-11-27 11:09:56 +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"
|
reference = "e56a4a221ad204654a191d217f92aebf3f058b62"
|
||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/worldveil/dejavu.git"
|
url = "https://github.com/worldveil/dejavu.git"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Manipulate audio with an simple and easy high level interface"
|
description = "Manipulate audio with an simple and easy high level interface"
|
||||||
|
@ -291,11 +290,11 @@ version = "0.23.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Cross-platform windowing and multimedia library"
|
description = "Python Game Development"
|
||||||
name = "pyglet"
|
name = "pygame"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "1.5.7"
|
version = "1.9.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
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)"]
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "3af542607f4ccaa3afe08d275174f664cdacedac31ce8278e059d4aca2b9edeb"
|
content-hash = "9fa7d79cd4464df797b8d866bfa3a426ab1399854faf48b02f00d65d46a507be"
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
python-versions = "^3.8"
|
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-py2.py3-none-any.whl", hash = "sha256:d29901a486fb421c5d7b0f3d5d3a60527179204d8ffb20e74e1ae81c17e81b46"},
|
||||||
{file = "pydub-0.23.1.tar.gz", hash = "sha256:c362fa02da1eebd1d08bd47aa9b0102582dff7ca2269dbe9e043d228a0c1ea93"},
|
{file = "pydub-0.23.1.tar.gz", hash = "sha256:c362fa02da1eebd1d08bd47aa9b0102582dff7ca2269dbe9e043d228a0c1ea93"},
|
||||||
]
|
]
|
||||||
pyglet = [
|
pygame = [
|
||||||
{file = "pyglet-1.5.7-py3-none-any.whl", hash = "sha256:9832442d59ee06acbeff12e128cf6d5aee271e94c09386040db8f0feae277013"},
|
{file = "pygame-1.9.6-cp27-cp27m-macosx_10_11_intel.whl", hash = "sha256:4aaff572a273a32e70ec3593d213e59ab11c183a9916616562247930f17a5447"},
|
||||||
{file = "pyglet-1.5.7.zip", hash = "sha256:3faac2dad34946aecbce79a8658f89155436fe5c07332229160c6eba302ff40d"},
|
{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 = [
|
pyparsing = [
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
|
|
|
@ -10,7 +10,7 @@ python = "^3.8"
|
||||||
pyalsaaudio = "^0.9.0"
|
pyalsaaudio = "^0.9.0"
|
||||||
pydejavu = {git = "https://github.com/worldveil/dejavu.git"}
|
pydejavu = {git = "https://github.com/worldveil/dejavu.git"}
|
||||||
requests = "^2.24.0"
|
requests = "^2.24.0"
|
||||||
pyglet = "^1.5.7"
|
pygame = "^1.9.6"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^20.8b1"
|
black = "^20.8b1"
|
||||||
|
|
|
@ -23,28 +23,31 @@ from turntable.turntable import (
|
||||||
VERSION = importlib.metadata.version("turntable")
|
VERSION = importlib.metadata.version("turntable")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def run() -> "Iterator[Queue[Event]]":
|
class Application:
|
||||||
|
def __init__(self):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config", default=os.path.expanduser("~/.config/turntable.json")
|
"--config", default=os.path.expanduser("~/.config/turntable.json")
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
with open(args.config, "r") as config_file:
|
with open(args.config, "r") as config_file:
|
||||||
config: Dict[str, Any] = json.load(config_file)
|
self.config: Dict[str, Any] = json.load(config_file)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG if config.get("debug") else logging.INFO)
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG if self.config.get("debug") else logging.INFO
|
||||||
|
)
|
||||||
logger.info("Turntable version %s", VERSION)
|
logger.info("Turntable version %s", VERSION)
|
||||||
|
|
||||||
|
|
||||||
pcm_in: "Queue[PCM]" = Queue()
|
pcm_in: "Queue[PCM]" = Queue()
|
||||||
pcm_out: "Queue[PCM]" = Queue()
|
pcm_out: "Queue[PCM]" = Queue()
|
||||||
events: "Queue[Event]" = Queue()
|
self.pcm_display: "Queue[PCM]" = Queue()
|
||||||
|
self.events: "Queue[Event]" = Queue()
|
||||||
|
|
||||||
audio_config = config.get("audio", dict())
|
audio_config = self.config.get("audio", dict())
|
||||||
listener = Listener(
|
listener = Listener(
|
||||||
[pcm_in, pcm_out],
|
[pcm_in, pcm_out, self.pcm_display],
|
||||||
events,
|
self.events,
|
||||||
audio_config.get("device", "default"),
|
audio_config.get("device", "default"),
|
||||||
framerate=audio_config.get("framerate", 44100),
|
framerate=audio_config.get("framerate", 44100),
|
||||||
channels=audio_config.get("channels", 2),
|
channels=audio_config.get("channels", 2),
|
||||||
|
@ -59,11 +62,13 @@ def run() -> "Iterator[Queue[Event]]":
|
||||||
period_size=audio_config.get("period_size", 4096),
|
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_config = self.config.get("icecast", dict())
|
||||||
icecast = Icecast(
|
icecast = Icecast(
|
||||||
host=icecast_config.get("host", "localhost"),
|
host=icecast_config.get("host", "localhost"),
|
||||||
port=icecast_config.get("port", 8000),
|
port=icecast_config.get("port", 8000),
|
||||||
|
@ -72,14 +77,15 @@ def run() -> "Iterator[Queue[Event]]":
|
||||||
password=icecast_config.get("admin_password", "hackme"),
|
password=icecast_config.get("admin_password", "hackme"),
|
||||||
)
|
)
|
||||||
|
|
||||||
processes = [listener, player, turntable]
|
self.processes = [listener, player, turntable]
|
||||||
for process in processes:
|
|
||||||
|
def run(self) -> "Iterator[Queue[Event]]":
|
||||||
|
for process in self.processes:
|
||||||
process.daemon = True
|
process.daemon = True
|
||||||
process.start()
|
process.start()
|
||||||
try:
|
|
||||||
yield events
|
def shutdown(self) -> None:
|
||||||
except:
|
logging.info("Terminating")
|
||||||
logging.exception("Terminating")
|
for process in self.processes:
|
||||||
for process in processes:
|
|
||||||
if process.is_alive():
|
if process.is_alive():
|
||||||
process.terminate()
|
process.kill()
|
||||||
|
|
|
@ -4,7 +4,8 @@ from turntable import application, turntable
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
with application.run() as events:
|
app = application.Application()
|
||||||
|
with app.run() as events:
|
||||||
while event := events.get():
|
while event := events.get():
|
||||||
if not isinstance(event, turntable.Audio):
|
if not isinstance(event, turntable.Audio):
|
||||||
logging.info("Event: %s", event)
|
logging.info("Event: %s", event)
|
||||||
|
|
140
turntable/gui.py
140
turntable/gui.py
|
@ -1,19 +1,23 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import queue
|
import queue
|
||||||
from statistics import fmean
|
from statistics import fmean
|
||||||
from typing import Iterable, List, Optional, Tuple, Union
|
from typing import Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import numpy as np # type: ignore
|
import numpy as np # type: ignore
|
||||||
import pyglet # type: ignore
|
import pygame
|
||||||
import pyglet.clock # type: ignore
|
from pygame.locals import *
|
||||||
import scipy.signal # type: ignore
|
import scipy.signal # type: ignore
|
||||||
|
|
||||||
from turntable import application, models, turntable
|
from turntable import application, models, turntable
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Plot:
|
class Plot:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
screen,
|
||||||
x: int,
|
x: int,
|
||||||
y: int,
|
y: int,
|
||||||
width: int,
|
width: int,
|
||||||
|
@ -21,8 +25,8 @@ class Plot:
|
||||||
bars: int = 20,
|
bars: int = 20,
|
||||||
bar_width: int = 40,
|
bar_width: int = 40,
|
||||||
color: Tuple[int, int, int] = (255, 255, 255),
|
color: Tuple[int, int, int] = (255, 255, 255),
|
||||||
batch: Optional[pyglet.graphics.Batch] = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
self.screen = screen
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.width = width
|
self.width = width
|
||||||
|
@ -30,80 +34,98 @@ class Plot:
|
||||||
self.bars = bars
|
self.bars = bars
|
||||||
self.bar_width = bar_width
|
self.bar_width = bar_width
|
||||||
self.color = color
|
self.color = color
|
||||||
self.batch = batch or pyglet.graphics.Batch()
|
|
||||||
self.lines: List[pyglet.shapes.Line] = []
|
|
||||||
self.audio = b""
|
self.audio = b""
|
||||||
|
|
||||||
def update(self):
|
def draw(self) -> None:
|
||||||
data = np.fromstring(self.audio, dtype=np.int16)
|
data = np.fromstring(self.audio, dtype=np.int16)
|
||||||
|
if len(data) == 0:
|
||||||
|
return
|
||||||
fft = abs(np.fft.fft(data).real)
|
fft = abs(np.fft.fft(data).real)
|
||||||
fft = fft[: len(fft) // 2]
|
fft = fft[: len(fft) // 2]
|
||||||
heights = scipy.signal.resample(fft, self.bars) * self.height / 2 ** 16
|
heights = scipy.signal.resample(fft, self.bars) * self.height / 2 ** 16
|
||||||
self.lines = [
|
for i, height in enumerate(heights):
|
||||||
pyglet.shapes.Line(
|
pygame.draw.rect(
|
||||||
self.x + x / self.bars * self.width,
|
self.screen,
|
||||||
|
self.color,
|
||||||
|
(
|
||||||
|
self.x + i / self.bars * self.width,
|
||||||
|
self.height,
|
||||||
|
self.bar_width,
|
||||||
|
-height,
|
||||||
|
),
|
||||||
0,
|
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():
|
def main():
|
||||||
window = pyglet.window.Window(fullscreen=True)
|
app = application.Application()
|
||||||
with application.run() as events:
|
config = app.config.get("gui", dict())
|
||||||
audio = b""
|
FPS = int(config.get("fps", 30))
|
||||||
label = pyglet.text.Label(
|
WIDTH = int(config.get("width", 800))
|
||||||
"<Idle>",
|
HEIGHT = int(config.get("height", 600))
|
||||||
font_name="Noto Sans",
|
disp_no = os.getenv("DISPLAY")
|
||||||
font_size=36,
|
if disp_no:
|
||||||
x=window.width // 2,
|
logger.info("I'm running under X display = {0}".format(disp_no))
|
||||||
y=window.height // 2,
|
|
||||||
anchor_x="center",
|
# Check which frame buffer drivers are available
|
||||||
anchor_y="center",
|
# Start with fbcon since directfb hangs with composite output
|
||||||
)
|
drivers = ["x11", "fbcon", "directfb", "svgalib"]
|
||||||
batch = pyglet.graphics.Batch()
|
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
|
||||||
|
|
||||||
|
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(
|
plot = Plot(
|
||||||
|
screen=screen,
|
||||||
x=0,
|
x=0,
|
||||||
y=0,
|
y=0,
|
||||||
width=window.width,
|
width=screen.get_width(),
|
||||||
height=window.height,
|
height=screen.get_height(),
|
||||||
bars=40,
|
bars=40,
|
||||||
bar_width=window.width // 45,
|
bar_width=screen.get_width() // 45,
|
||||||
color=(139, 0, 139),
|
color=(139, 0, 139),
|
||||||
batch=batch,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@window.event
|
app.run()
|
||||||
def on_draw():
|
clock = pygame.time.Clock()
|
||||||
window.clear()
|
while True:
|
||||||
batch.draw()
|
for event in pygame.event.get():
|
||||||
label.draw()
|
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
|
||||||
|
app.shutdown()
|
||||||
def check_events(dt):
|
pygame.quit()
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
event = events.get(False)
|
while event := app.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:
|
except queue.Empty:
|
||||||
...
|
...
|
||||||
|
try:
|
||||||
def update_vis(dt):
|
while pcm := app.pcm_display.get(False):
|
||||||
plot.update()
|
plot.audio = pcm.raw
|
||||||
|
except queue.Empty:
|
||||||
pyglet.clock.schedule(check_events)
|
...
|
||||||
pyglet.clock.schedule_interval(update_vis, 0.03)
|
screen.fill((0, 0, 0))
|
||||||
pyglet.app.run()
|
plot.draw()
|
||||||
|
pygame.display.update()
|
||||||
|
clock.tick(30)
|
||||||
|
|
Loading…
Reference in a new issue