diff --git a/app.py b/app.py index f03b852..b522688 100644 --- a/app.py +++ b/app.py @@ -1,451 +1,164 @@ -import pyray as pr -import math -from ctypes import c_float -from gapless_player import GaplessPlayer, Song, song_data_to_Song -from scrolling_text import ScrollingText -import numpy as np -import threading +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +# Copyright (c) 2017 Adafruit Industries +# Author: James DeVito +# Ported to RGB Display by Melissa LeBlanc-Williams +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This example is for use on (Linux) computers that are using CPython with +# Adafruit Blinka to support CircuitPython libraries. CircuitPython does +# not support PIL/pillow (python imaging library)! +""" +This example is for use on (Linux) computers that are using CPython with +Adafruit Blinka to support CircuitPython libraries. CircuitPython does +not support PIL/pillow (python imaging library)! +""" + +import random import time -import os -from jelly import server, client +from colorsys import hsv_to_rgb -# # --- Configuration Constants --- -# INITIAL_SCREEN_WIDTH = 240 -# INITIAL_SCREEN_HEIGHT = 240 -# TARGET_FPS =60 +import board +from digitalio import DigitalInOut, Direction +from PIL import Image, ImageDraw, ImageFont -# # --- State Variables --- -# state = { -# "screen_width": INITIAL_SCREEN_WIDTH, -# "screen_height": INITIAL_SCREEN_HEIGHT, -# } +from adafruit_rgb_display import st7789 +import old_app -# # --- Utility Functions --- +# Create the display +cs_pin = DigitalInOut(board.CE0) +dc_pin = DigitalInOut(board.D25) +reset_pin = DigitalInOut(board.D24) +BAUDRATE = 24000000 - -# def format_time_mm_ss(seconds): -# """Converts a time in seconds to an 'MM:SS' string format.""" -# seconds = int(seconds) -# minutes = seconds // 60 -# seconds_remainder = seconds % 60 -# return f"{minutes:02d}:{seconds_remainder:02d}" - - -# def get_progress_bar_rect(screen_width, screen_height): -# width = screen_width -# height = screen_height*0.021 -# x = (screen_width - width) / 2 -# y = screen_height - height -# return pr.Rectangle(x, y, width, height) - - -# def draw_progress_bar(rect, current_time, total_time): -# if total_time > 0: -# progress_ratio = current_time / total_time -# else: -# progress_ratio = 0.0 - -# pr.draw_rectangle_rec(rect, pr.Color(100, 100, 100, 255)) -# progress_width = rect.width * progress_ratio -# pr.draw_rectangle( -# int(rect.x), -# int(rect.y)+1, -# int(progress_width), -# int(rect.height), -# pr.Color(200, 50, 50, 255), -# ) -# # pr.draw_rectangle_lines_ex(rect, 2, pr.Color(50, 50, 50, 255)) - -# time_text = f"{format_time_mm_ss(current_time)} / {format_time_mm_ss(total_time)}" -# text_width = pr.measure_text(time_text, int(rect.height * 0.7)) -# # pr.draw_text( -# # time_text, -# # int(rect.x + rect.width / 2 - text_width / 2), -# # int(rect.y + rect.height * 0.15), -# # int(rect.height * 0.7), -# # pr.WHITE, -# # ) - -# pr.set_config_flags(pr.ConfigFlags.FLAG_WINDOW_RESIZABLE) -# # pr.set_config_flags(pr.FLAG_MSAA_4X_HINT) -# #pr.set_config_flags(pr.FLAG_FULLSCREEN_MODE) -# pr.init_window(state["screen_width"], state["screen_height"], "UgPod") -# pr.set_target_fps(TARGET_FPS) - -player = GaplessPlayer() - -print("add queue") - -player.add_to_queue( - Song( - "bruhh", - "music/pink floyd/dark side of the moon/06 Money.flac", - "Money", - 1, - "The Dark Side Of The Moon", - "", - "Pink Floyd", - ) +spi = board.SPI() +disp = st7789.ST7789( + spi, + height=240, + y_offset=80, + rotation=180, + cs=cs_pin, + dc=dc_pin, + rst=reset_pin, + baudrate=BAUDRATE, ) -player.add_to_queue( - Song( - "bruhh", - "music/pink floyd/dark side of the moon/07 Us and Them.flac", - "Us and Them", - 1, - "The Dark Side Of The Moon", - "", - "Pink Floyd", - ) -) +# Input pins: +button_A = DigitalInOut(board.D5) +button_A.direction = Direction.INPUT -# albums = client.jellyfin.user_items( -# params={ -# "IncludeItemTypes": "MusicAlbum", -# "SearchTerm": "Dawn FM", # album name -# "Recursive": True, -# }, -# ) +button_B = DigitalInOut(board.D6) +button_B.direction = Direction.INPUT -# album = albums["Items"][0] # pick the album you want -# album_id = album["Id"] +button_L = DigitalInOut(board.D27) +button_L.direction = Direction.INPUT +button_R = DigitalInOut(board.D23) +button_R.direction = Direction.INPUT -# tracks = client.jellyfin.user_items( -# params={ -# "ParentId": album_id, -# "IncludeItemTypes": "Audio", -# "SortBy": "IndexNumber", -# "SortOrder": "Ascending", -# }, -# ) +button_U = DigitalInOut(board.D17) +button_U.direction = Direction.INPUT -# for track in tracks["Items"]: -# player.add_to_queue( -# song_data_to_Song( -# track, server -# ) -# ) +button_D = DigitalInOut(board.D22) +button_D.direction = Direction.INPUT -# print("add queue done") -# player.load_state("data/player.json") -# close_event = threading.Event() -# def save_state_loop(): -# while not close_event.wait(10): -# player.save_state("data/player.lock.json") -# os.rename("data/player.lock.json", "data/player.json") -# # save_state_thread = threading.Thread(target=save_state_loop) -# # save_state_thread.start() +button_C = DigitalInOut(board.D4) +button_C.direction = Direction.INPUT -# current_path = None -# texture = None +# Turn on the Backlight +backlight = DigitalInOut(board.D26) +backlight.switch_to_output() +backlight.value = True +# Create blank image for drawing. +# Make sure to create image with mode 'RGB' for color. +width = disp.width +height = disp.height +image = Image.new("RGB", (width, height)) -# def load_texture(path): -# global texture, current_path +# Get drawing object to draw on image. +draw = ImageDraw.Draw(image) -# if not path: -# return +# Clear display. +draw.rectangle((0, 0, width, height), outline=0, fill=(255, 0, 0)) +disp.image(image) -# if path == current_path: -# return +# Get drawing object to draw on image. +draw = ImageDraw.Draw(image) -# if texture is not None: -# pr.unload_texture(texture) +# Draw a black filled box to clear the image. +draw.rectangle((0, 0, width, height), outline=0, fill=0) -# texture = pr.load_texture(path) -# current_path = path +udlr_fill = "#00FF00" +udlr_outline = "#00FFFF" +button_fill = "#FF00FF" +button_outline = "#FFFFFF" +fnt = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 30) -# def draw_play_pause_button(pos: pr.Vector2, size: pr.Vector2, is_playing: bool) -> bool: -# clicked = False - -# rect = pr.Rectangle(pos.x, pos.y, size.x, size.y) - -# # Optional hover background -# if pr.check_collision_point_rec(pr.get_mouse_position(), rect): -# pr.draw_rectangle_rec(rect, pr.fade(pr.BLACK, 0.4)) -# if pr.is_mouse_button_pressed(pr.MOUSE_LEFT_BUTTON): -# clicked = True -# cx = pos.x + size.x / 2 -# cy = pos.y + size.y / 2 - -# icon_padding = size.x * 0.25 -# icon_size = size.x - icon_padding * 2 - -# if is_playing: -# # PAUSE (two bars centered, same visual weight as play) -# bar_width = icon_size * 0.25 -# bar_height = icon_size - -# left_x = cx - bar_width - bar_width * 0.4 -# right_x = cx + bar_width * 0.4 -# top_y = 1+cy - bar_height / 2 - -# pr.draw_rectangle( -# int(left_x), -# int(top_y), -# int(bar_width), -# int(bar_height), -# pr.WHITE, -# ) -# pr.draw_rectangle( -# int(right_x), -# int(top_y), -# int(bar_width), -# int(bar_height), -# pr.WHITE, -# ) -# else: -# # PLAY (centered triangle) -# p1 = pr.Vector2(cx - icon_size / 2, cy - icon_size / 2) -# p2 = pr.Vector2(cx - icon_size / 2, cy + icon_size / 2) -# p3 = pr.Vector2(cx + icon_size / 2, cy) - -# pr.draw_triangle(p1, p2, p3, pr.WHITE) - -# return clicked - - -# title = ScrollingText( -# "", -# 15 -# ) - -# # --- Main Game Loop --- -# while not pr.window_should_close(): -# # 1. Update -# current_width = pr.get_screen_width() -# current_height = pr.get_screen_height() - -# if pr.is_key_pressed(pr.KEY_F11): -# pr.toggle_fullscreen() -# if pr.is_key_pressed(pr.KeyboardKey.KEY_SPACE): -# if player.playing: -# player.pause() -# else: -# player.play() -# if pr.is_key_pressed(pr.KeyboardKey.KEY_LEFT): -# player.seek(player.position - 5) -# if pr.is_key_pressed(pr.KeyboardKey.KEY_RIGHT): -# player.seek(player.position + 5) - -# pr.begin_drawing() -# pr.clear_background(pr.Color(40, 40, 40, 255)) -# dt = pr.get_frame_time() - -# progress_rect = get_progress_bar_rect(current_width, current_height) - -# # pr.draw_text( -# # "UgPod", -# # int(current_width * 0.05), -# # int(current_height * 0.05), -# # int(current_height * 0.05), -# # pr.SKYBLUE, -# # ) - -# current_song = player.get_current_song() - - -# draw_progress_bar( -# progress_rect, -# player.position, -# (current_song and current_song.duration) or 0.0, -# ) -# if current_song: -# load_texture(current_song.album_cover_path) -# title_font_size = int(current_height*0.05) -# album_cover_size = int(min(current_width, current_height*0.7)) -# title.speed = title_font_size*2.5 -# title_size = pr.Vector2(current_width-int(current_height * 0.01)*2, title_font_size) -# title.update(dt,title_size) -# title.set_text(f"{current_song.name} - {current_song.artist_name}", title_font_size) -# title.draw(pr.Vector2(int(current_height * 0.01),int(current_height * 0.8)),title_size) -# # pr.draw_text( -# # , -# # , -# # int(current_height * 0.03), -# # pr.WHITE, -# # ) -# points = player.oscilloscope_data_points -# if texture is not None: -# scale = min(album_cover_size / texture.width, album_cover_size / texture.height) - -# dest_rect = pr.Rectangle( -# current_width//2 - album_cover_size//2, -# (current_height*0.8)//2 - album_cover_size//2, -# texture.width * scale, -# texture.height * scale, -# ) - -# src_rect = pr.Rectangle(0, 0, texture.width, texture.height) - -# pr.draw_texture_pro( -# texture, src_rect, dest_rect, pr.Vector2(0, 0), 0.0, pr.WHITE -# ) -# else: -# clip = pr.Rectangle(int(current_width//2 - album_cover_size//2), -# int((current_height*0.8)//2 - album_cover_size//2), -# int(album_cover_size), -# int(album_cover_size)) -# pr.begin_scissor_mode( -# int(clip.x), -# int(clip.y), -# int(clip.width), -# int(clip.height), -# ) -# pr.draw_rectangle( -# int(clip.x), -# int(clip.y), -# int(clip.width), -# int(clip.height), pr.BLACK) - -# # cx = current_width * 0.5+1 -# # cy = current_height * 0.4+1 - -# # MAX_LEN = album_cover_size * 0.25 # tune this -# # MIN_ALPHA = 10 -# # MAX_ALPHA = 255 - -# # for i in range(len(points) - 1): -# # x1 = cx + points[i][0] * album_cover_size * 0.5 -# # y1 = cy + -points[i][1] * album_cover_size * 0.5 -# # x2 = cx + points[i+1][0] * album_cover_size * 0.5 -# # y2 = cy + -points[i+1][1] * album_cover_size * 0.5 - -# # dx = x2 - x1 -# # dy = y2 - y1 -# # length = (dx * dx + dy * dy) ** 0.5 - -# # # 1.0 = short line, 0.0 = long line -# # t = max(0.0, min(1.0, 1.0 - (length / MAX_LEN)))*math.pow(i/len(points), 2) - -# # alpha = int(MIN_ALPHA + t * (MAX_ALPHA - MIN_ALPHA)) - -# # color = pr.Color(255, 255, 255, alpha) - -# # pr.draw_line(int(x1), int(y1), int(x2), int(y2), color) -# # draw background square -# if len(points) >= 2: -# samples = np.fromiter( -# ((p[0] + p[1]) * 0.5 for p in points), -# dtype=np.float32 -# ) - -# # Guard: FFT must have meaningful size -# if samples.size > 128: - - -# rect_x = int(current_width // 2 - album_cover_size // 2) -# rect_y = int((current_height * 0.8) // 2 - album_cover_size // 2) - -# # ---- FFT ---- - -# FFT_SIZE = min(samples.size, 2048) -# window = np.hanning(FFT_SIZE) - -# fft = np.fft.rfft(samples[:FFT_SIZE] * window) -# magnitudes = np.abs(fft) - -# # remove DC component (important for visuals) -# magnitudes[0] = 0.0 - -# # ---- LOG BINNING ---- - -# num_bars = album_cover_size//10 -# num_bins = magnitudes.size - -# # logarithmic bin edges (low end stretched) -# log_min = 1 -# log_max = math.log10(num_bins) - -# log_edges = np.logspace( -# math.log10(log_min), -# log_max, -# num_bars + 1 -# ).astype(int) - -# bar_values = np.zeros(num_bars, dtype=np.float32) - -# for i in range(num_bars): -# start = log_edges[i] -# end = log_edges[i + 1] - -# if end <= start: -# continue - -# bar_values[i] = np.mean(magnitudes[start:end]) - -# # ---- STATIC SCALING ---- - -# # Instead of normalizing to the max of the frame, we scale by the FFT size. -# # For a Hanning windowed FFT, dividing by (FFT_SIZE / 4) maps -# # maximum possible volume roughly to 1.0. -# bar_values = bar_values / (FFT_SIZE / 4.0) - -# # ---- DRAW ---- - -# def map_to_screen(val): -# return rect_x + (math.log10(max(1, val)) / log_max) * album_cover_size - -# spacing = 0 - -# for i in range(num_bars): -# # 1. Calculate integer pixel boundaries first -# # This ensures the right edge of one bar is exactly the left edge of the next -# x_start_int = int(map_to_screen(log_edges[i])) -# x_end_int = int(map_to_screen(log_edges[i+1])) - -# # 2. Width is the difference between these fixed integer points -# w = (x_end_int - x_start_int) - spacing - -# value = bar_values[i] -# h = int(min(1.0, value) * album_cover_size) - -# # 3. Anchor to bottom -# y = (rect_y + album_cover_size) - h - -# alpha = min(1.0, ((value+1)**2)-1) -# r = 255 -# g = 0 -# b = 0 - -# # Keep alpha at 255 (fully opaque) -# color = pr.Color(r, g, b, int(255 * alpha)) - -# # 4. Draw the bar -# # Use max(1, w) to ensure high-frequency bars don't disappear on small screens -# pr.draw_rectangle( -# x_start_int, -# int(y), -# max(1, int(w)), -# h, -# color -# ) -# pr.end_scissor_mode() - -# pos = pr.Vector2(current_width * 0.5 - current_height * 0.05, current_height * 0.9-progress_rect.height) -# size = pr.Vector2(current_height * 0.1, current_height * 0.1) - -# if draw_play_pause_button(pos, size, player.playing): -# if player.playing: -# player.pause() -# else: -# player.play() -# pr.end_drawing() - -# # Cleanup -# if texture is not None: -# pr.unload_texture(texture) - -# pr.close_window() -# close_event.set() -# # save_state_thread.join() - - - - -player.play() while True: - time.sleep(1) \ No newline at end of file + up_fill = 0 + if not button_U.value: # up pressed + up_fill = udlr_fill + draw.polygon([(40, 40), (60, 4), (80, 40)], outline=udlr_outline, fill=up_fill) # Up + + down_fill = 0 + if not button_D.value: # down pressed + down_fill = udlr_fill + draw.polygon([(60, 120), (80, 84), (40, 84)], outline=udlr_outline, fill=down_fill) # down + + left_fill = 0 + if not button_L.value: # left pressed + left_fill = udlr_fill + draw.polygon([(0, 60), (36, 42), (36, 81)], outline=udlr_outline, fill=left_fill) # left + + right_fill = 0 + if not button_R.value: # right pressed + right_fill = udlr_fill + draw.polygon([(120, 60), (84, 42), (84, 82)], outline=udlr_outline, fill=right_fill) # right + + center_fill = 0 + if not button_C.value: # center pressed + center_fill = button_fill + draw.rectangle((40, 44, 80, 80), outline=button_outline, fill=center_fill) # center + + A_fill = 0 + if not button_A.value: # left pressed + A_fill = button_fill + draw.ellipse((140, 80, 180, 120), outline=button_outline, fill=A_fill) # A button + + B_fill = 0 + if not button_B.value: # left pressed + B_fill = button_fill + draw.ellipse((190, 40, 230, 80), outline=button_outline, fill=B_fill) # B button + + # make a random color and print text + rcolor = tuple(int(x * 255) for x in hsv_to_rgb(random.random(), 1, 1)) + draw.text((20, 150), "Hello World", font=fnt, fill=rcolor) + rcolor = tuple(int(x * 255) for x in hsv_to_rgb(random.random(), 1, 1)) + draw.text((20, 180), "Hello World", font=fnt, fill=rcolor) + rcolor = tuple(int(x * 255) for x in hsv_to_rgb(random.random(), 1, 1)) + draw.text((20, 210), "Hello World", font=fnt, fill=rcolor) + + # Display the Image + disp.image(image) + + time.sleep(0.1) \ No newline at end of file diff --git a/old_app.py b/old_app.py new file mode 100644 index 0000000..453b9df --- /dev/null +++ b/old_app.py @@ -0,0 +1,451 @@ +import pyray as pr +import math +from ctypes import c_float +from gapless_player import GaplessPlayer, Song, song_data_to_Song +from scrolling_text import ScrollingText +import numpy as np +import threading +import time +import os +from jelly import server, client + +# # --- Configuration Constants --- +# INITIAL_SCREEN_WIDTH = 240 +# INITIAL_SCREEN_HEIGHT = 240 +# TARGET_FPS =60 + +# # --- State Variables --- +# state = { +# "screen_width": INITIAL_SCREEN_WIDTH, +# "screen_height": INITIAL_SCREEN_HEIGHT, +# } + +# # --- Utility Functions --- + + +# def format_time_mm_ss(seconds): +# """Converts a time in seconds to an 'MM:SS' string format.""" +# seconds = int(seconds) +# minutes = seconds // 60 +# seconds_remainder = seconds % 60 +# return f"{minutes:02d}:{seconds_remainder:02d}" + + +# def get_progress_bar_rect(screen_width, screen_height): +# width = screen_width +# height = screen_height*0.021 +# x = (screen_width - width) / 2 +# y = screen_height - height +# return pr.Rectangle(x, y, width, height) + + +# def draw_progress_bar(rect, current_time, total_time): +# if total_time > 0: +# progress_ratio = current_time / total_time +# else: +# progress_ratio = 0.0 + +# pr.draw_rectangle_rec(rect, pr.Color(100, 100, 100, 255)) +# progress_width = rect.width * progress_ratio +# pr.draw_rectangle( +# int(rect.x), +# int(rect.y)+1, +# int(progress_width), +# int(rect.height), +# pr.Color(200, 50, 50, 255), +# ) +# # pr.draw_rectangle_lines_ex(rect, 2, pr.Color(50, 50, 50, 255)) + +# time_text = f"{format_time_mm_ss(current_time)} / {format_time_mm_ss(total_time)}" +# text_width = pr.measure_text(time_text, int(rect.height * 0.7)) +# # pr.draw_text( +# # time_text, +# # int(rect.x + rect.width / 2 - text_width / 2), +# # int(rect.y + rect.height * 0.15), +# # int(rect.height * 0.7), +# # pr.WHITE, +# # ) + +# pr.set_config_flags(pr.ConfigFlags.FLAG_WINDOW_RESIZABLE) +# # pr.set_config_flags(pr.FLAG_MSAA_4X_HINT) +# #pr.set_config_flags(pr.FLAG_FULLSCREEN_MODE) +# pr.init_window(state["screen_width"], state["screen_height"], "UgPod") +# pr.set_target_fps(TARGET_FPS) + +player = GaplessPlayer() + +print("add queue") + +player.add_to_queue( + Song( + "bruhh", + "music/pink floyd/dark side of the moon/06 Money.flac", + "Money", + 1, + "The Dark Side Of The Moon", + "", + "Pink Floyd", + ) +) + +player.add_to_queue( + Song( + "bruhh", + "music/pink floyd/dark side of the moon/07 Us and Them.flac", + "Us and Them", + 1, + "The Dark Side Of The Moon", + "", + "Pink Floyd", + ) +) + +# albums = client.jellyfin.user_items( +# params={ +# "IncludeItemTypes": "MusicAlbum", +# "SearchTerm": "Dawn FM", # album name +# "Recursive": True, +# }, +# ) + +# album = albums["Items"][0] # pick the album you want +# album_id = album["Id"] + + +# tracks = client.jellyfin.user_items( +# params={ +# "ParentId": album_id, +# "IncludeItemTypes": "Audio", +# "SortBy": "IndexNumber", +# "SortOrder": "Ascending", +# }, +# ) + +# for track in tracks["Items"]: +# player.add_to_queue( +# song_data_to_Song( +# track, server +# ) +# ) + +# print("add queue done") +# player.load_state("data/player.json") +# close_event = threading.Event() +# def save_state_loop(): +# while not close_event.wait(10): +# player.save_state("data/player.lock.json") +# os.rename("data/player.lock.json", "data/player.json") +# # save_state_thread = threading.Thread(target=save_state_loop) +# # save_state_thread.start() + +# current_path = None +# texture = None + + +# def load_texture(path): +# global texture, current_path + +# if not path: +# return + +# if path == current_path: +# return + +# if texture is not None: +# pr.unload_texture(texture) + +# texture = pr.load_texture(path) +# current_path = path + + +# def draw_play_pause_button(pos: pr.Vector2, size: pr.Vector2, is_playing: bool) -> bool: +# clicked = False + +# rect = pr.Rectangle(pos.x, pos.y, size.x, size.y) + +# # Optional hover background +# if pr.check_collision_point_rec(pr.get_mouse_position(), rect): +# pr.draw_rectangle_rec(rect, pr.fade(pr.BLACK, 0.4)) +# if pr.is_mouse_button_pressed(pr.MOUSE_LEFT_BUTTON): +# clicked = True +# cx = pos.x + size.x / 2 +# cy = pos.y + size.y / 2 + +# icon_padding = size.x * 0.25 +# icon_size = size.x - icon_padding * 2 + +# if is_playing: +# # PAUSE (two bars centered, same visual weight as play) +# bar_width = icon_size * 0.25 +# bar_height = icon_size + +# left_x = cx - bar_width - bar_width * 0.4 +# right_x = cx + bar_width * 0.4 +# top_y = 1+cy - bar_height / 2 + +# pr.draw_rectangle( +# int(left_x), +# int(top_y), +# int(bar_width), +# int(bar_height), +# pr.WHITE, +# ) +# pr.draw_rectangle( +# int(right_x), +# int(top_y), +# int(bar_width), +# int(bar_height), +# pr.WHITE, +# ) +# else: +# # PLAY (centered triangle) +# p1 = pr.Vector2(cx - icon_size / 2, cy - icon_size / 2) +# p2 = pr.Vector2(cx - icon_size / 2, cy + icon_size / 2) +# p3 = pr.Vector2(cx + icon_size / 2, cy) + +# pr.draw_triangle(p1, p2, p3, pr.WHITE) + +# return clicked + + +# title = ScrollingText( +# "", +# 15 +# ) + +# # --- Main Game Loop --- +# while not pr.window_should_close(): +# # 1. Update +# current_width = pr.get_screen_width() +# current_height = pr.get_screen_height() + +# if pr.is_key_pressed(pr.KEY_F11): +# pr.toggle_fullscreen() +# if pr.is_key_pressed(pr.KeyboardKey.KEY_SPACE): +# if player.playing: +# player.pause() +# else: +# player.play() +# if pr.is_key_pressed(pr.KeyboardKey.KEY_LEFT): +# player.seek(player.position - 5) +# if pr.is_key_pressed(pr.KeyboardKey.KEY_RIGHT): +# player.seek(player.position + 5) + +# pr.begin_drawing() +# pr.clear_background(pr.Color(40, 40, 40, 255)) +# dt = pr.get_frame_time() + +# progress_rect = get_progress_bar_rect(current_width, current_height) + +# # pr.draw_text( +# # "UgPod", +# # int(current_width * 0.05), +# # int(current_height * 0.05), +# # int(current_height * 0.05), +# # pr.SKYBLUE, +# # ) + +# current_song = player.get_current_song() + + +# draw_progress_bar( +# progress_rect, +# player.position, +# (current_song and current_song.duration) or 0.0, +# ) +# if current_song: +# load_texture(current_song.album_cover_path) +# title_font_size = int(current_height*0.05) +# album_cover_size = int(min(current_width, current_height*0.7)) +# title.speed = title_font_size*2.5 +# title_size = pr.Vector2(current_width-int(current_height * 0.01)*2, title_font_size) +# title.update(dt,title_size) +# title.set_text(f"{current_song.name} - {current_song.artist_name}", title_font_size) +# title.draw(pr.Vector2(int(current_height * 0.01),int(current_height * 0.8)),title_size) +# # pr.draw_text( +# # , +# # , +# # int(current_height * 0.03), +# # pr.WHITE, +# # ) +# points = player.oscilloscope_data_points +# if texture is not None: +# scale = min(album_cover_size / texture.width, album_cover_size / texture.height) + +# dest_rect = pr.Rectangle( +# current_width//2 - album_cover_size//2, +# (current_height*0.8)//2 - album_cover_size//2, +# texture.width * scale, +# texture.height * scale, +# ) + +# src_rect = pr.Rectangle(0, 0, texture.width, texture.height) + +# pr.draw_texture_pro( +# texture, src_rect, dest_rect, pr.Vector2(0, 0), 0.0, pr.WHITE +# ) +# else: +# clip = pr.Rectangle(int(current_width//2 - album_cover_size//2), +# int((current_height*0.8)//2 - album_cover_size//2), +# int(album_cover_size), +# int(album_cover_size)) +# pr.begin_scissor_mode( +# int(clip.x), +# int(clip.y), +# int(clip.width), +# int(clip.height), +# ) +# pr.draw_rectangle( +# int(clip.x), +# int(clip.y), +# int(clip.width), +# int(clip.height), pr.BLACK) + +# # cx = current_width * 0.5+1 +# # cy = current_height * 0.4+1 + +# # MAX_LEN = album_cover_size * 0.25 # tune this +# # MIN_ALPHA = 10 +# # MAX_ALPHA = 255 + +# # for i in range(len(points) - 1): +# # x1 = cx + points[i][0] * album_cover_size * 0.5 +# # y1 = cy + -points[i][1] * album_cover_size * 0.5 +# # x2 = cx + points[i+1][0] * album_cover_size * 0.5 +# # y2 = cy + -points[i+1][1] * album_cover_size * 0.5 + +# # dx = x2 - x1 +# # dy = y2 - y1 +# # length = (dx * dx + dy * dy) ** 0.5 + +# # # 1.0 = short line, 0.0 = long line +# # t = max(0.0, min(1.0, 1.0 - (length / MAX_LEN)))*math.pow(i/len(points), 2) + +# # alpha = int(MIN_ALPHA + t * (MAX_ALPHA - MIN_ALPHA)) + +# # color = pr.Color(255, 255, 255, alpha) + +# # pr.draw_line(int(x1), int(y1), int(x2), int(y2), color) +# # draw background square +# if len(points) >= 2: +# samples = np.fromiter( +# ((p[0] + p[1]) * 0.5 for p in points), +# dtype=np.float32 +# ) + +# # Guard: FFT must have meaningful size +# if samples.size > 128: + + +# rect_x = int(current_width // 2 - album_cover_size // 2) +# rect_y = int((current_height * 0.8) // 2 - album_cover_size // 2) + +# # ---- FFT ---- + +# FFT_SIZE = min(samples.size, 2048) +# window = np.hanning(FFT_SIZE) + +# fft = np.fft.rfft(samples[:FFT_SIZE] * window) +# magnitudes = np.abs(fft) + +# # remove DC component (important for visuals) +# magnitudes[0] = 0.0 + +# # ---- LOG BINNING ---- + +# num_bars = album_cover_size//10 +# num_bins = magnitudes.size + +# # logarithmic bin edges (low end stretched) +# log_min = 1 +# log_max = math.log10(num_bins) + +# log_edges = np.logspace( +# math.log10(log_min), +# log_max, +# num_bars + 1 +# ).astype(int) + +# bar_values = np.zeros(num_bars, dtype=np.float32) + +# for i in range(num_bars): +# start = log_edges[i] +# end = log_edges[i + 1] + +# if end <= start: +# continue + +# bar_values[i] = np.mean(magnitudes[start:end]) + +# # ---- STATIC SCALING ---- + +# # Instead of normalizing to the max of the frame, we scale by the FFT size. +# # For a Hanning windowed FFT, dividing by (FFT_SIZE / 4) maps +# # maximum possible volume roughly to 1.0. +# bar_values = bar_values / (FFT_SIZE / 4.0) + +# # ---- DRAW ---- + +# def map_to_screen(val): +# return rect_x + (math.log10(max(1, val)) / log_max) * album_cover_size + +# spacing = 0 + +# for i in range(num_bars): +# # 1. Calculate integer pixel boundaries first +# # This ensures the right edge of one bar is exactly the left edge of the next +# x_start_int = int(map_to_screen(log_edges[i])) +# x_end_int = int(map_to_screen(log_edges[i+1])) + +# # 2. Width is the difference between these fixed integer points +# w = (x_end_int - x_start_int) - spacing + +# value = bar_values[i] +# h = int(min(1.0, value) * album_cover_size) + +# # 3. Anchor to bottom +# y = (rect_y + album_cover_size) - h + +# alpha = min(1.0, ((value+1)**2)-1) +# r = 255 +# g = 0 +# b = 0 + +# # Keep alpha at 255 (fully opaque) +# color = pr.Color(r, g, b, int(255 * alpha)) + +# # 4. Draw the bar +# # Use max(1, w) to ensure high-frequency bars don't disappear on small screens +# pr.draw_rectangle( +# x_start_int, +# int(y), +# max(1, int(w)), +# h, +# color +# ) +# pr.end_scissor_mode() + +# pos = pr.Vector2(current_width * 0.5 - current_height * 0.05, current_height * 0.9-progress_rect.height) +# size = pr.Vector2(current_height * 0.1, current_height * 0.1) + +# if draw_play_pause_button(pos, size, player.playing): +# if player.playing: +# player.pause() +# else: +# player.play() +# pr.end_drawing() + +# # Cleanup +# if texture is not None: +# pr.unload_texture(texture) + +# pr.close_window() +# close_event.set() +# # save_state_thread.join() + + + + +player.play() +# while True: +# time.sleep(1) \ No newline at end of file