210 lines
6.4 KiB
Python
210 lines
6.4 KiB
Python
import time
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, ImageMath
|
|
|
|
from .constants import (
|
|
ACTIVE_FX_RECT_BOUNDS,
|
|
ACTIVE_SRC_RECT_BOUNDS,
|
|
BACKGROUND_IMG_PATH,
|
|
ESTIMATED_BOOT_TIME_SECONDS,
|
|
ESTIMATED_LONG_BOOT_TIME_SECONDS,
|
|
FONT_1_PATH,
|
|
FONT_2_PATH,
|
|
FORGE_SAVE_STATES_COUNT,
|
|
FX_GROUP,
|
|
FXA_FRAG,
|
|
FXB_FRAG,
|
|
LOADING_IMG_PATH,
|
|
MFX_FRAG,
|
|
SELECTED_RECT_BOUNDS,
|
|
SRC_GROUP,
|
|
SRCA_FRAG,
|
|
SRCB_FRAG,
|
|
)
|
|
from .forge_data import ForgeData
|
|
from .glsl_funcs import mod
|
|
|
|
FONT_1 = ImageFont.truetype(FONT_1_PATH, 5)
|
|
FONT_2 = ImageFont.truetype(FONT_2_PATH, 8)
|
|
LOADING_IMG = Image.open(LOADING_IMG_PATH)
|
|
BACKGROUND_IMG = Image.open(BACKGROUND_IMG_PATH)
|
|
BLANK_IMG = Image.new("1", (128, 64), "black")
|
|
|
|
|
|
def __xor(img1: Image.Image, img2: Image.Image) -> Image.Image:
|
|
return ImageMath.lambda_eval(
|
|
lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=img1, b=img2
|
|
)
|
|
|
|
|
|
def __get_loading_display(data: ForgeData, t0: float) -> Image.Image:
|
|
im = LOADING_IMG.copy()
|
|
draw = ImageDraw.Draw(im)
|
|
|
|
now = time.time()
|
|
elapsed = now - t0
|
|
|
|
if elapsed < ESTIMATED_BOOT_TIME_SECONDS:
|
|
draw.rectangle(
|
|
(24, 35, 24 + int(80 * elapsed / ESTIMATED_BOOT_TIME_SECONDS), 40),
|
|
fill="white",
|
|
)
|
|
elif elapsed < ESTIMATED_LONG_BOOT_TIME_SECONDS * 2.0:
|
|
draw.rectangle(
|
|
(
|
|
24,
|
|
35,
|
|
24 + int(80 * min(1.0, elapsed / ESTIMATED_LONG_BOOT_TIME_SECONDS)),
|
|
40,
|
|
),
|
|
fill="white",
|
|
)
|
|
draw.text((36, 43), "Compiling...", fill="white", font=FONT_1)
|
|
else:
|
|
draw.rectangle((24, 35, 24 + 80, 40), fill="white")
|
|
draw.text((28, 43), "Please reboot", fill="white", font=FONT_1)
|
|
|
|
return im
|
|
|
|
|
|
def __get_running_display(data: ForgeData) -> Image.Image:
|
|
im = BACKGROUND_IMG.copy()
|
|
draw = ImageDraw.Draw(im)
|
|
|
|
im_overlay = BLANK_IMG.copy()
|
|
draw2 = ImageDraw.Draw(im_overlay)
|
|
|
|
# tempo
|
|
draw.text((42, -2), f"{int(data.tempo)}", fill="white", font=FONT_1)
|
|
draw.rectangle((6, 7, 6 + int(53 * data.beat_progress), 10), fill="white", width=0)
|
|
|
|
# time
|
|
if data.demo:
|
|
draw.text((68, -2), "DEMO", fill="white", font=FONT_1)
|
|
else:
|
|
draw.text((68, -2), f"TIME: {int(data.time)}", fill="white", font=FONT_1)
|
|
|
|
if data.auto_random:
|
|
draw.rectangle(
|
|
(
|
|
67,
|
|
7,
|
|
67
|
|
+ int(
|
|
53
|
|
* mod(data.beats, data.auto_random_cycle)
|
|
/ data.auto_random_cycle
|
|
),
|
|
10,
|
|
),
|
|
fill="white",
|
|
width=0,
|
|
)
|
|
else:
|
|
draw.rectangle(
|
|
(67, 7, 67 + int(53 * data.time_progress), 10), fill="white", width=0
|
|
)
|
|
|
|
# page
|
|
draw.circle((55 + 8 * data.page, 61), 2, fill="white")
|
|
|
|
# selected
|
|
draw.rectangle(SELECTED_RECT_BOUNDS[data.frag_selected], outline="white", fill=None)
|
|
|
|
# active
|
|
draw.rectangle(ACTIVE_SRC_RECT_BOUNDS[data.group_active(SRC_GROUP)], fill="white")
|
|
draw.rectangle(ACTIVE_FX_RECT_BOUNDS[data.group_active(FX_GROUP)], fill="white")
|
|
|
|
# fx/mix values
|
|
draw.rectangle((41, 16 + round(14 * (1 - data.fxa_value)), 55, 30), fill="white")
|
|
draw.rectangle((41, 40 + round(14 * (1 - data.fxb_value)), 55, 54), fill="white")
|
|
draw.rectangle((99, 28 + round(14 * (1 - data.mfx_value)), 113, 42), fill="white")
|
|
draw.rectangle((70, 28 + round(14 * (1 - data.mix_value)), 84, 42), fill="white")
|
|
|
|
# active shaders
|
|
draw2.text(
|
|
(16, 20), hex(data.frag_state(SRCA_FRAG))[2].upper(), fill="white", font=FONT_2
|
|
)
|
|
draw2.text(
|
|
(16, 44), hex(data.frag_state(SRCB_FRAG))[2].upper(), fill="white", font=FONT_2
|
|
)
|
|
draw2.text(
|
|
(45, 20), hex(data.frag_state(FXA_FRAG))[2].upper(), fill="white", font=FONT_2
|
|
)
|
|
draw2.text(
|
|
(45, 44), hex(data.frag_state(FXB_FRAG))[2].upper(), fill="white", font=FONT_2
|
|
)
|
|
draw2.text(
|
|
(103, 32), hex(data.frag_state(MFX_FRAG))[2].upper(), fill="white", font=FONT_2
|
|
)
|
|
|
|
# mix
|
|
draw2.text((74, 32), "K" if data.mix_mode_key else "M", fill="white", font=FONT_2)
|
|
|
|
# random states indicator
|
|
if data.frag_state_rand(SRCA_FRAG):
|
|
draw2.rectangle((23, 18, 24, 19), fill="white")
|
|
if data.frag_state_rand(SRCB_FRAG):
|
|
draw2.rectangle((23, 42, 24, 43), fill="white")
|
|
if data.frag_state_rand(FXA_FRAG):
|
|
draw2.rectangle((52, 18, 53, 19), fill="white")
|
|
if data.frag_state_rand(FXB_FRAG):
|
|
draw2.rectangle((52, 42, 53, 43), fill="white")
|
|
if data.frag_state_rand(MFX_FRAG):
|
|
draw2.rectangle((110, 30, 111, 31), fill="white")
|
|
|
|
# inverted fx
|
|
if data.fxa_inverted:
|
|
draw2.rectangle((43, 18, 53, 18), fill="white")
|
|
if data.fxb_inverted:
|
|
draw2.rectangle((43, 42, 53, 42), fill="white")
|
|
if data.mfx_inverted:
|
|
draw2.rectangle((101, 30, 111, 30), fill="white")
|
|
|
|
# feedback / output
|
|
draw_base = False
|
|
|
|
if data.frag_state(SRCA_FRAG) == 1:
|
|
draw_base = True
|
|
draw.rectangle((119, 22, 121, 36), fill="white")
|
|
draw.rectangle((119, 22, 127, 24), fill="white")
|
|
draw.rectangle((123, 21, 126, 25), fill=None, outline="black")
|
|
draw.rectangle((0, 22, 11, 24), fill="white")
|
|
draw.rectangle((1, 21, 4, 25), fill=None, outline="black")
|
|
|
|
if data.frag_state(SRCB_FRAG) == 1:
|
|
draw_base = True
|
|
draw.rectangle((119, 34, 121, 48), fill="white")
|
|
draw.rectangle((119, 46, 127, 48), fill="white")
|
|
draw.rectangle((123, 45, 126, 49), fill=None, outline="black")
|
|
draw.rectangle((0, 46, 11, 48), fill="white")
|
|
draw.rectangle((1, 45, 4, 49), fill=None, outline="black")
|
|
|
|
if not data.output_blank:
|
|
draw_base = True
|
|
draw.rectangle((119, 34, 128, 36), fill="white")
|
|
|
|
if draw_base:
|
|
draw.rectangle((114, 34, 121, 36), fill="white")
|
|
|
|
# states
|
|
for i in range(FORGE_SAVE_STATES_COUNT):
|
|
if data.is_state(i):
|
|
draw.rectangle(
|
|
(
|
|
91 + (i % 3) * 5,
|
|
53 + (i // 3) * 5,
|
|
96 + (i % 3) * 5,
|
|
58 + (i // 3) * 5,
|
|
),
|
|
fill="white",
|
|
)
|
|
|
|
return __xor(im, im_overlay)
|
|
|
|
|
|
def get_display(data: ForgeData, t0: float) -> Image.Image:
|
|
if data.running:
|
|
return __get_running_display(data)
|
|
return __get_loading_display(data, t0)
|