Compare commits
2 Commits
harrowmyke
...
DMZTdhruv/
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba9dca7844 | ||
![]() |
7c6f10b3c1 |
73
fal_app.py
73
fal_app.py
@@ -1,73 +0,0 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
import threading
|
||||
from audiocraft.data.audio import audio_write
|
||||
import fal
|
||||
from fastapi import Response, status
|
||||
import torch
|
||||
|
||||
DATA_DIR = Path("/data/audio")
|
||||
|
||||
PROMPTS = [
|
||||
"Create a futuristic lo-fi beat that blends modern electronic elements with synthwave influences. Incorporate smooth, atmospheric synths and gentle, relaxing rhythms to evoke a sense of a serene, neon-lit future. Ensure the track is continuous with no background noise or interruptions, maintaining a calm and tranquil atmosphere throughout while adding a touch of retro-futuristic vibes.",
|
||||
"gentle lo-fi beat with a smooth, mellow piano melody in the background. Ensure there are no background noises or interruptions, maintaining a continuous and seamless flow throughout the track. The beat should be relaxing and tranquil, perfect for a calm and reflective atmosphere.",
|
||||
"Create an earthy lo-fi beat that evokes a natural, grounded atmosphere. Incorporate organic sounds like soft percussion, rustling leaves, and gentle acoustic instruments. The track should have a warm, soothing rhythm with a continuous flow and no background noise or interruptions, maintaining a calm and reflective ambiance throughout.",
|
||||
"Create a soothing lo-fi beat featuring gentle, melodic guitar riffs. The guitar should be the focal point, supported by subtle, ambient electronic elements and a smooth, relaxed rhythm. Ensure the track is continuous with no background noise or interruptions, maintaining a warm and mellow atmosphere throughout.",
|
||||
"Create an ambient lo-fi beat with a tranquil and ethereal atmosphere. Use soft, atmospheric pads, gentle melodies, and minimalistic percussion to evoke a sense of calm and serenity. Ensure the track is continuous with no background noise or interruptions, maintaining a soothing and immersive ambiance throughout.",
|
||||
]
|
||||
|
||||
|
||||
class InfinifiFalApp(fal.App, keep_alive=300):
|
||||
machine_type = "GPU-A6000"
|
||||
requirements = [
|
||||
"torch==2.1.0",
|
||||
"audiocraft==1.3.0",
|
||||
"torchaudio==2.1.0",
|
||||
"websockets==11.0.3",
|
||||
"numpy==1.26.4",
|
||||
]
|
||||
|
||||
__is_generating = False
|
||||
|
||||
def setup(self):
|
||||
import torchaudio
|
||||
from audiocraft.models.musicgen import MusicGen
|
||||
|
||||
self.model = MusicGen.get_pretrained("facebook/musicgen-large")
|
||||
self.model.set_generation_params(duration=60)
|
||||
|
||||
@fal.endpoint("/generate")
|
||||
def run(self):
|
||||
if self.__is_generating:
|
||||
return Response(status_code=status.HTTP_409_CONFLICT)
|
||||
threading.Thread(target=self.__generate_audio).start()
|
||||
|
||||
@fal.endpoint("/clips/{index}")
|
||||
def get_clips(self, index):
|
||||
if self.__is_generating:
|
||||
return Response(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
path = DATA_DIR.joinpath(f"{index}")
|
||||
with open(path.with_suffix(".mp3"), "rb") as f:
|
||||
data = f.read()
|
||||
return Response(content=data)
|
||||
|
||||
def __generate_audio(self):
|
||||
self.__is_generating = True
|
||||
|
||||
print(f"[INFO] {datetime.datetime.now()}: generating audio...")
|
||||
|
||||
wav = self.model.generate(PROMPTS)
|
||||
for i, one_wav in enumerate(wav):
|
||||
path = DATA_DIR.joinpath(f"{i}")
|
||||
audio_write(
|
||||
path,
|
||||
one_wav.cpu(),
|
||||
self.model.sample_rate,
|
||||
format="mp3",
|
||||
strategy="loudness",
|
||||
loudness_compressor=True,
|
||||
make_parent_dir=True,
|
||||
)
|
||||
|
||||
self.__is_generating = False
|
11
generate.py
11
generate.py
@@ -2,17 +2,22 @@ import torchaudio
|
||||
from audiocraft.models.musicgen import MusicGen
|
||||
from audiocraft.data.audio import audio_write
|
||||
|
||||
from prompts import PROMPTS
|
||||
|
||||
MODEL_NAME = "facebook/musicgen-large"
|
||||
MUSIC_DURATION_SECONDS = 60
|
||||
|
||||
model = MusicGen.get_pretrained(MODEL_NAME)
|
||||
model.set_generation_params(duration=MUSIC_DURATION_SECONDS)
|
||||
descriptions = [
|
||||
"Create a futuristic lo-fi beat that blends modern electronic elements with synthwave influences. Incorporate smooth, atmospheric synths and gentle, relaxing rhythms to evoke a sense of a serene, neon-lit future. Ensure the track is continuous with no background noise or interruptions, maintaining a calm and tranquil atmosphere throughout while adding a touch of retro-futuristic vibes.",
|
||||
"gentle lo-fi beat with a smooth, mellow piano melody in the background. Ensure there are no background noises or interruptions, maintaining a continuous and seamless flow throughout the track. The beat should be relaxing and tranquil, perfect for a calm and reflective atmosphere.",
|
||||
"Create an earthy lo-fi beat that evokes a natural, grounded atmosphere. Incorporate organic sounds like soft percussion, rustling leaves, and gentle acoustic instruments. The track should have a warm, soothing rhythm with a continuous flow and no background noise or interruptions, maintaining a calm and reflective ambiance throughout.",
|
||||
"Create a soothing lo-fi beat featuring gentle, melodic guitar riffs. The guitar should be the focal point, supported by subtle, ambient electronic elements and a smooth, relaxed rhythm. Ensure the track is continuous with no background noise or interruptions, maintaining a warm and mellow atmosphere throughout.",
|
||||
"Create an ambient lo-fi beat with a tranquil and ethereal atmosphere. Use soft, atmospheric pads, gentle melodies, and minimalistic percussion to evoke a sense of calm and serenity. Ensure the track is continuous with no background noise or interruptions, maintaining a soothing and immersive ambiance throughout.",
|
||||
]
|
||||
|
||||
|
||||
def generate(offset=0):
|
||||
wav = model.generate(PROMPTS)
|
||||
wav = model.generate(descriptions)
|
||||
|
||||
for idx, one_wav in enumerate(wav):
|
||||
# Will save under {idx}.wav, with loudness normalization at -14 db LUFS.
|
||||
|
@@ -3,8 +3,6 @@ import time
|
||||
from audiocraft.models.musicgen import MusicGen
|
||||
from audiocraft.data.audio import audio_write
|
||||
|
||||
from prompts import PROMPTS
|
||||
|
||||
MODEL_NAME = "facebook/musicgen-large"
|
||||
MUSIC_DURATION_SECONDS = 60
|
||||
|
||||
@@ -12,11 +10,18 @@ print("obtaining model...")
|
||||
|
||||
model = MusicGen.get_pretrained(MODEL_NAME)
|
||||
model.set_generation_params(duration=MUSIC_DURATION_SECONDS)
|
||||
descriptions = [
|
||||
"Create a futuristic lo-fi beat that blends modern electronic elements with synthwave influences. Incorporate smooth, atmospheric synths and gentle, relaxing rhythms to evoke a sense of a serene, neon-lit future. Ensure the track is continuous with no background noise or interruptions, maintaining a calm and tranquil atmosphere throughout while adding a touch of retro-futuristic vibes.",
|
||||
"gentle lo-fi beat with a smooth, mellow piano melody in the background. Ensure there are no background noises or interruptions, maintaining a continuous and seamless flow throughout the track. The beat should be relaxing and tranquil, perfect for a calm and reflective atmosphere.",
|
||||
"Create an earthy lo-fi beat that evokes a natural, grounded atmosphere. Incorporate organic sounds like soft percussion, rustling leaves, and gentle acoustic instruments. The track should have a warm, soothing rhythm with a continuous flow and no background noise or interruptions, maintaining a calm and reflective ambiance throughout.",
|
||||
"Create a soothing lo-fi beat featuring gentle, melodic guitar riffs. The guitar should be the focal point, supported by subtle, ambient electronic elements and a smooth, relaxed rhythm. Ensure the track is continuous with no background noise or interruptions, maintaining a warm and mellow atmosphere throughout.",
|
||||
"Create an ambient lo-fi beat with a tranquil and ethereal atmosphere. Use soft, atmospheric pads, gentle melodies, and minimalistic percussion to evoke a sense of calm and serenity. Ensure the track is continuous with no background noise or interruptions, maintaining a soothing and immersive ambiance throughout.",
|
||||
]
|
||||
|
||||
print("model obtained. generating audio...")
|
||||
|
||||
a = time.time()
|
||||
wav = model.generate(PROMPTS)
|
||||
wav = model.generate(descriptions)
|
||||
b = time.time()
|
||||
|
||||
print(f"audio generated. took {b - a} seconds.")
|
||||
|
@@ -1,7 +0,0 @@
|
||||
PROMPTS = [
|
||||
"Create a futuristic lo-fi beat that blends modern electronic elements with synthwave influences. Incorporate smooth, atmospheric synths and gentle, relaxing rhythms to evoke a sense of a serene, neon-lit future. Ensure the track is continuous with no background noise or interruptions, maintaining a calm and tranquil atmosphere throughout while adding a touch of retro-futuristic vibes.",
|
||||
"gentle lo-fi beat with a smooth, mellow piano melody in the background. Ensure there are no background noises or interruptions, maintaining a continuous and seamless flow throughout the track. The beat should be relaxing and tranquil, perfect for a calm and reflective atmosphere.",
|
||||
"Create an earthy lo-fi beat that evokes a natural, grounded atmosphere. Incorporate organic sounds like soft percussion, rustling leaves, and gentle acoustic instruments. The track should have a warm, soothing rhythm with a continuous flow and no background noise or interruptions, maintaining a calm and reflective ambiance throughout.",
|
||||
"Create a soothing lo-fi beat featuring gentle, melodic guitar riffs. The guitar should be the focal point, supported by subtle, ambient electronic elements and a smooth, relaxed rhythm. Ensure the track is continuous with no background noise or interruptions, maintaining a warm and mellow atmosphere throughout.",
|
||||
"Create an ambient lo-fi beat with a tranquil and ethereal atmosphere. Use soft, atmospheric pads, gentle melodies, and minimalistic percussion to evoke a sense of calm and serenity. Ensure the track is continuous with no background noise or interruptions, maintaining a soothing and immersive ambiance throughout.",
|
||||
]
|
92
server.py
92
server.py
@@ -1,10 +1,9 @@
|
||||
import threading
|
||||
import os
|
||||
from time import sleep
|
||||
import requests
|
||||
|
||||
import websocket
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, status
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from logger import log_info, log_warn
|
||||
@@ -14,32 +13,33 @@ from websocket_connection_manager import WebSocketConnectionManager
|
||||
current_index = -1
|
||||
# the timer that periodically advances the current audio track
|
||||
t = None
|
||||
inference_url = ""
|
||||
api_key = ""
|
||||
# websocket connection to the inference server
|
||||
ws = None
|
||||
ws_url = ""
|
||||
ws_connection_manager = WebSocketConnectionManager()
|
||||
active_listeners = set()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global ws, inference_url, api_key
|
||||
global ws, ws_url
|
||||
|
||||
inference_url = os.environ.get("INFERENCE_SERVER_URL")
|
||||
api_key = os.environ.get("API_KEY")
|
||||
|
||||
if not inference_url:
|
||||
inference_url = "http://localhost:8001"
|
||||
ws_url = os.environ.get("INFERENCE_SERVER_WS_URL")
|
||||
if not ws_url:
|
||||
ws_url = "ws://localhost:8001"
|
||||
|
||||
advance()
|
||||
|
||||
yield
|
||||
|
||||
if ws:
|
||||
ws.close()
|
||||
if t:
|
||||
t.cancel()
|
||||
|
||||
|
||||
def generate_new_audio():
|
||||
if not inference_url:
|
||||
if not ws_url:
|
||||
return
|
||||
|
||||
global current_index
|
||||
@@ -52,61 +52,31 @@ def generate_new_audio():
|
||||
else:
|
||||
return
|
||||
|
||||
log_info("requesting new audio...")
|
||||
log_info("generating new audio...")
|
||||
|
||||
try:
|
||||
requests.post(
|
||||
f"{inference_url}/generate",
|
||||
headers={"Authorization": f"key {api_key}"},
|
||||
)
|
||||
ws = websocket.create_connection(ws_url)
|
||||
|
||||
ws.send("generate")
|
||||
|
||||
wavs = []
|
||||
for i in range(5):
|
||||
raw = ws.recv()
|
||||
if isinstance(raw, str):
|
||||
continue
|
||||
wavs.append(raw)
|
||||
|
||||
for i, wav in enumerate(wavs):
|
||||
with open(f"{i + offset}.mp3", "wb") as f:
|
||||
f.write(wav)
|
||||
|
||||
log_info("audio generated.")
|
||||
|
||||
ws.close()
|
||||
except:
|
||||
log_warn(
|
||||
"inference server potentially unreachable. recycling cached audio for now."
|
||||
)
|
||||
return
|
||||
|
||||
is_available = False
|
||||
while not is_available:
|
||||
try:
|
||||
res = requests.post(
|
||||
f"{inference_url}/clips/0",
|
||||
stream=True,
|
||||
headers={"Authorization": f"key {api_key}"},
|
||||
)
|
||||
except:
|
||||
log_warn(
|
||||
"inference server potentially unreachable. recycling cached audio for now."
|
||||
)
|
||||
return
|
||||
|
||||
if res.status_code != status.HTTP_200_OK:
|
||||
print(res.status_code)
|
||||
print("still generating...")
|
||||
sleep(30)
|
||||
continue
|
||||
|
||||
print("inference complete! downloading new clips")
|
||||
|
||||
is_available = True
|
||||
with open(f"{offset}.mp3", "wb") as f:
|
||||
for chunk in res.iter_content(chunk_size=128):
|
||||
f.write(chunk)
|
||||
|
||||
for i in range(4):
|
||||
res = requests.post(
|
||||
f"{inference_url}/clips/{i + 1}",
|
||||
stream=True,
|
||||
headers={"Authorization": f"key {api_key}"},
|
||||
)
|
||||
|
||||
if res.status_code != status.HTTP_200_OK:
|
||||
continue
|
||||
|
||||
with open(f"{i + 1 + offset}.mp3", "wb") as f:
|
||||
for chunk in res.iter_content(chunk_size=128):
|
||||
f.write(chunk)
|
||||
|
||||
log_info("audio generated.")
|
||||
|
||||
|
||||
def advance():
|
||||
|
109
web/bg.js
109
web/bg.js
@@ -1,7 +1,61 @@
|
||||
const DOT_COLOR_LIGHT_MODE = "#dce0e8";
|
||||
const DOT_COLOR_DARK_MODE = "#45475a";
|
||||
let DOT_COLOR_DARK_MODE = "#45475a";
|
||||
const DOT_RADIUS = 1 * devicePixelRatio;
|
||||
|
||||
// theme variables
|
||||
const rootStyles = document.documentElement.style;
|
||||
console.log(rootStyles);
|
||||
|
||||
const changeThemeBtn = document.getElementById("theme-btn");
|
||||
const themeModal = document.querySelector(".theme-modal");
|
||||
const themes = {
|
||||
darkMode: [
|
||||
{
|
||||
name: "Gotham Theme",
|
||||
background: "#0C1014",
|
||||
textColor: "#98D1CE",
|
||||
highlight: "#1B2B34",
|
||||
baseColor: "#343D46",
|
||||
},
|
||||
{
|
||||
name: "Solarized Dark",
|
||||
background: "#002B36",
|
||||
textColor: "#839496",
|
||||
highlight: "#073642",
|
||||
baseColor: "#586e75",
|
||||
},
|
||||
{
|
||||
name: "Dracula",
|
||||
background: "#282A36",
|
||||
textColor: "#F8F8F2",
|
||||
highlight: "#44475A",
|
||||
baseColor: "#6272A4",
|
||||
},
|
||||
{
|
||||
name: "Material Dark",
|
||||
background: "#263238",
|
||||
textColor: "#FFFFFF",
|
||||
highlight: "#37474F",
|
||||
baseColor: "#80CBC4",
|
||||
},
|
||||
{
|
||||
name: "Monokai",
|
||||
background: "#272822",
|
||||
textColor: "#F8F8F2",
|
||||
highlight: "#49483E",
|
||||
baseColor: "#A6E22E",
|
||||
},
|
||||
{
|
||||
name: "Gruvbox Dark",
|
||||
background: "#282828",
|
||||
textColor: "#ebdbb2",
|
||||
highlight: "#3c3836",
|
||||
baseColor: "#b16286",
|
||||
},
|
||||
],
|
||||
lightThemes: [],
|
||||
};
|
||||
|
||||
const canvas = document.getElementById("bg");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
@@ -67,3 +121,56 @@ if (window.matchMedia) {
|
||||
|
||||
resizeCanvas(window.innerWidth, window.innerHeight);
|
||||
drawPattern();
|
||||
|
||||
changeThemeBtn.onmousedown = () => {
|
||||
themeModal.classList.add("active");
|
||||
displayThemes();
|
||||
};
|
||||
|
||||
function displayThemes() {
|
||||
themeModal.innerHTML = "";
|
||||
// biome-ignore lint/complexity/noForEach: <biome wanted me to use for..of loop but foreach is better for readablity ig.>
|
||||
themes.darkMode.forEach((theme) => {
|
||||
const themeItem = document.createElement("div");
|
||||
themeItem.className = "theme-item";
|
||||
|
||||
const themeName = document.createElement("div");
|
||||
themeName.className = "theme-name";
|
||||
themeName.innerText = theme.name;
|
||||
|
||||
const themePreview = document.createElement("div");
|
||||
themePreview.className = "theme-preview";
|
||||
themePreview.style.background = theme.name;
|
||||
|
||||
const themeColors = document.createElement("div");
|
||||
themeColors.className = "theme-colors";
|
||||
themeColors.style.background = theme.background;
|
||||
|
||||
const colors = ["textColor", "highlight", "baseColor"];
|
||||
// biome-ignore lint/complexity/noForEach: <biome wanted me to use for..of loop but foreach is better for readablity ig.>
|
||||
colors.forEach((color) => {
|
||||
const colorDiv = document.createElement("div");
|
||||
colorDiv.style.background = theme[color];
|
||||
colorDiv.style.width = "20px";
|
||||
colorDiv.style.height = "20px";
|
||||
colorDiv.style.borderRadius = "50%";
|
||||
colorDiv.title = color;
|
||||
themeColors.appendChild(colorDiv);
|
||||
});
|
||||
|
||||
themeItem.appendChild(themeName);
|
||||
themeItem.appendChild(themePreview);
|
||||
themeItem.appendChild(themeColors);
|
||||
themeModal.appendChild(themeItem);
|
||||
|
||||
themeItem.onmouseover = () => {
|
||||
rootStyles.setProperty("--text", theme.textColor);
|
||||
rootStyles.setProperty("--surface0", theme.highlight);
|
||||
rootStyles.setProperty("--base", theme.background);
|
||||
DOT_COLOR_DARK_MODE = theme.highlight;
|
||||
};
|
||||
themeItem.onmousedown = () => {
|
||||
themeModal.classList.remove("active");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
<svg width="100%" height="100%" viewBox="0 0 89 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M52.308 3.07812H57.8465V4.92428H56.0003V6.77043H54.1541V10.4627H57.8465V12.3089H54.1541V25.232H52.308V27.0781H46.7695V25.232H48.6157V12.3089H46.7695V10.4627H48.6157V6.77043H50.4618V4.92428H52.308V3.07812Z" fill="#cdd6f4"></path><path d="M79.3849 23.3858H81.2311V25.232H83.0772V27.0781H88.6157V25.232H86.7695V23.3858H84.9234V4.92428H79.3849V23.3858Z" fill="#cdd6f4"></path><path d="M57.8465 14.155H59.6926V12.3089H61.5388V10.4627H70.7695V12.3089H74.4618V23.3858H76.308V25.232H78.1541V27.0781H72.6157V25.232H70.7695V23.3858H68.9234V14.155H67.0772V12.3089H65.2311V14.155H63.3849V23.3858H65.2311V25.232H67.0772V27.0781H61.5388V25.232H59.6926V23.3858H57.8465V14.155Z" fill="#cdd6f4"></path><path d="M67.0772 25.232V23.3858H68.9234V25.232H67.0772Z" fill="#cdd6f4"></path><rect opacity="0.22" x="7.38477" y="29.5391" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="2.46094" y="19.6914" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="4.92383" y="17.2305" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="7.38477" y="27.0781" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" y="22.1562" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="7.38477" y="24.6133" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="7.38477" y="12.3086" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="2.46094" y="2.46094" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="4.92383" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="7.38477" y="9.84375" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" y="4.92188" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="7.38477" y="7.38281" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="24.6152" y="29.5391" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="19.6914" y="19.6914" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="22.1543" y="17.2305" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="24.6152" y="27.0781" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" x="17.2305" y="22.1562" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="24.6152" y="24.6133" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="24.6152" y="12.3086" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="19.6914" y="2.46094" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="22.1543" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="24.6152" y="9.84375" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" x="17.2305" y="4.92188" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="24.6152" y="7.38281" width="7.38462" height="2.46154" fill="#5F4CD9"></rect></svg>
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1 +0,0 @@
|
||||
<svg width="100%" height="100%" viewBox="0 0 89 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M52.308 3.07812H57.8465V4.92428H56.0003V6.77043H54.1541V10.4627H57.8465V12.3089H54.1541V25.232H52.308V27.0781H46.7695V25.232H48.6157V12.3089H46.7695V10.4627H48.6157V6.77043H50.4618V4.92428H52.308V3.07812Z" fill="currentColor"></path><path d="M79.3849 23.3858H81.2311V25.232H83.0772V27.0781H88.6157V25.232H86.7695V23.3858H84.9234V4.92428H79.3849V23.3858Z" fill="currentColor"></path><path d="M57.8465 14.155H59.6926V12.3089H61.5388V10.4627H70.7695V12.3089H74.4618V23.3858H76.308V25.232H78.1541V27.0781H72.6157V25.232H70.7695V23.3858H68.9234V14.155H67.0772V12.3089H65.2311V14.155H63.3849V23.3858H65.2311V25.232H67.0772V27.0781H61.5388V25.232H59.6926V23.3858H57.8465V14.155Z" fill="currentColor"></path><path d="M67.0772 25.232V23.3858H68.9234V25.232H67.0772Z" fill="currentColor"></path><rect opacity="0.22" x="7.38477" y="29.5391" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="2.46094" y="19.6914" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="4.92383" y="17.2305" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="7.38477" y="27.0781" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" y="22.1562" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="7.38477" y="24.6133" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="7.38477" y="12.3086" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="2.46094" y="2.46094" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="4.92383" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="7.38477" y="9.84375" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" y="4.92188" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="7.38477" y="7.38281" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="24.6152" y="29.5391" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="19.6914" y="19.6914" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="22.1543" y="17.2305" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="24.6152" y="27.0781" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" x="17.2305" y="22.1562" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="24.6152" y="24.6133" width="7.38462" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.22" x="24.6152" y="12.3086" width="2.46154" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.85" x="19.6914" y="2.46094" width="12.3077" height="2.46154" fill="#5F4CD9"></rect><rect x="22.1543" width="9.84615" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.4" x="24.6152" y="9.84375" width="4.92308" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.7" x="17.2305" y="4.92188" width="14.7692" height="2.46154" fill="#5F4CD9"></rect><rect opacity="0.5" x="24.6152" y="7.38281" width="7.38462" height="2.46154" fill="#5F4CD9"></rect></svg>
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -28,20 +28,26 @@
|
||||
<h2>infinite lo-fi music in the background</h2>
|
||||
<div class="button-container">
|
||||
<button id="play-btn" class="button">play</button>
|
||||
<button id="theme-btn" class="theme-btn">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" class="theme-icon" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99998 13.6667C6.1245 13.6667 5.25759 13.4942 4.44876 13.1592C3.63992 12.8242 2.90499 12.3331 2.28593 11.7141C1.03569 10.4638 0.333313 8.76812 0.333313 7.00001C0.333313 5.2319 1.03569 3.53621 2.28593 2.28596C3.53618 1.03572 5.23187 0.333344 6.99998 0.333344C10.6666 0.333344 13.6666 3.00001 13.6666 6.33334C13.6666 7.39421 13.2452 8.41162 12.4951 9.16177C11.7449 9.91192 10.7275 10.3333 9.66665 10.3333H8.46665C8.26665 10.3333 8.13331 10.4667 8.13331 10.6667C8.13331 10.7333 8.19998 10.8 8.19998 10.8667C8.46665 11.2 8.59998 11.6 8.59998 12C8.66665 12.9333 7.93331 13.6667 6.99998 13.6667ZM6.99998 1.66668C5.58549 1.66668 4.22894 2.22858 3.22874 3.22877C2.22855 4.22897 1.66665 5.58552 1.66665 7.00001C1.66665 8.4145 2.22855 9.77105 3.22874 10.7712C4.22894 11.7714 5.58549 12.3333 6.99998 12.3333C7.19998 12.3333 7.33331 12.2 7.33331 12C7.33331 11.8667 7.26665 11.8 7.26665 11.7333C6.99998 11.4 6.86665 11.0667 6.86665 10.6667C6.86665 9.73334 7.59998 9.00001 8.53331 9.00001H9.66665C10.3739 9.00001 11.0522 8.71906 11.5523 8.21896C12.0524 7.71886 12.3333 7.04059 12.3333 6.33334C12.3333 3.73334 9.93331 1.66668 6.99998 1.66668ZM3.33331 5.66668C3.86665 5.66668 4.33331 6.13334 4.33331 6.66668C4.33331 7.20001 3.86665 7.66668 3.33331 7.66668C2.79998 7.66668 2.33331 7.20001 2.33331 6.66668C2.33331 6.13334 2.79998 5.66668 3.33331 5.66668ZM5.33331 3.00001C5.86665 3.00001 6.33331 3.46668 6.33331 4.00001C6.33331 4.53334 5.86665 5.00001 5.33331 5.00001C4.79998 5.00001 4.33331 4.53334 4.33331 4.00001C4.33331 3.46668 4.79998 3.00001 5.33331 3.00001ZM8.66665 3.00001C9.19998 3.00001 9.66665 3.46668 9.66665 4.00001C9.66665 4.53334 9.19998 5.00001 8.66665 5.00001C8.13331 5.00001 7.66665 4.53334 7.66665 4.00001C7.66665 3.46668 8.13331 3.00001 8.66665 3.00001ZM10.6666 5.66668C11.2 5.66668 11.6666 6.13334 11.6666 6.66668C11.6666 7.20001 11.2 7.66668 10.6666 7.66668C10.1333 7.66668 9.66665 7.20001 9.66665 6.66668C9.66665 6.13334 10.1333 5.66668 10.6666 5.66668Z" fill="#4C4F69"/>
|
||||
</svg>
|
||||
|
||||
Change theme
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="theme-modal">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
<div id="listener-stats-container">
|
||||
<p id="timer-display-label">joined HH:MM</p>
|
||||
<p class="status-bar-listener-separator">·</p>
|
||||
<p id="listener-count">0 person tuned in</p>
|
||||
</div>
|
||||
<p id="listener-count">0 person tuned in</p>
|
||||
<div id="volume-slider-container">
|
||||
<output id="current-volume-label" for="volume-slider">100%</output>
|
||||
<input id="volume-slider" type="range" min="0" max="100" step="1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<aside role="alert" id="notification">
|
||||
@@ -55,15 +61,7 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
made by <a href="https://kennethnym.com">kennethnym</a> <3 · powered by
|
||||
<a class="fal-link" href="https://fal.ai">
|
||||
<picture>
|
||||
<source srcset="./images/fal-logo-light.svg" media="(prefers-color-scheme: light)" />
|
||||
<source srcset="./images/fal-logo-dark.svg" media="(prefers-color-scheme: dark)" />
|
||||
<img class="fal-logo" fill="none" src="./images/fal-logo-dark.svg">
|
||||
</picture>
|
||||
</a>
|
||||
·
|
||||
<span>made by kennethnym <3 · </span>
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/kennethnym/infinifi">github</a>
|
||||
</footer>
|
||||
|
||||
|
@@ -11,7 +11,6 @@ const heartImg = document.getElementById("heart");
|
||||
const volumeSlider = document.getElementById("volume-slider");
|
||||
const currentVolumeLabel = document.getElementById("current-volume-label");
|
||||
const listenerCountLabel = document.getElementById("listener-count");
|
||||
const timerDisplayLabel = document.getElementById("timer-display-label");
|
||||
const notificationContainer = document.getElementById("notification");
|
||||
const notificationTitle = document.getElementById("notification-title");
|
||||
const notificationBody = document.getElementById("notification-body");
|
||||
@@ -32,8 +31,6 @@ let saveVolumeTimeout = null;
|
||||
let meowCount = 0;
|
||||
let ws = connectToWebSocket();
|
||||
|
||||
const joinedTime = new Date();
|
||||
|
||||
function playAudio() {
|
||||
// add a random query parameter at the end to prevent browser caching
|
||||
currentAudio = new Audio(`./current.mp3?t=${Date.now()}`);
|
||||
@@ -232,33 +229,6 @@ function showNotification(title, content, duration) {
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function dateToHumanTime(dateVal, includeSeconds = false) {
|
||||
// Extract hours, minutes
|
||||
let hours = dateVal.getHours();
|
||||
let minutes = dateVal.getMinutes();
|
||||
let seconds = dateVal.getSeconds();
|
||||
|
||||
// Add leading zeros if the values are less than 10
|
||||
hours = hours < 10 ? '0' + hours : hours;
|
||||
minutes = minutes < 10 ? '0' + minutes : minutes;
|
||||
seconds = seconds < 10 ? '0' + seconds : seconds;
|
||||
|
||||
// Combine into HH:MM format
|
||||
if (includeSeconds) {
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function loadTimerDisplay() {
|
||||
const now = new Date();
|
||||
timerDisplayLabel.innerText = `${dateToHumanTime(now)} | joined ${dateToHumanTime(joinedTime)}`;
|
||||
|
||||
// seconds until next full minutes
|
||||
// + 1 seconds to ensure non null value
|
||||
setTimeout(loadTimerDisplay, (61 - now.getSeconds()) * 1000);
|
||||
}
|
||||
|
||||
playBtn.onmousedown = () => {
|
||||
clickAudio.play();
|
||||
document.addEventListener(
|
||||
@@ -315,7 +285,7 @@ meowAudio.onplay = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// don't wanna jumpscare ppl
|
||||
// don't wanna jumpscare ppl meow
|
||||
achievementUnlockedAudio.volume = 0.05;
|
||||
|
||||
window.addEventListener("offline", () => {
|
||||
@@ -330,4 +300,3 @@ loadMeowCount();
|
||||
loadInitialVolume();
|
||||
animateCat();
|
||||
enableSpaceBarControl();
|
||||
loadTimerDisplay();
|
118
web/style.css
118
web/style.css
@@ -36,6 +36,7 @@ body {
|
||||
padding: 2rem;
|
||||
margin: 0;
|
||||
background-color: var(--base);
|
||||
transition: 0.2s background-color ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
@@ -72,12 +73,9 @@ main {
|
||||
|
||||
footer {
|
||||
padding: 1rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
text-wrap: nowrap;
|
||||
overflow: auto;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
footer {
|
||||
@@ -85,6 +83,11 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
footer > span {
|
||||
color: var(--text);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-accent);
|
||||
}
|
||||
@@ -115,39 +118,18 @@ a {
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: -10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 2rem;
|
||||
z-index: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@media (min-width: 460px){
|
||||
.status-bar{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar #listener-stats-container {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-bar #listener-stats-container > * {
|
||||
display: inline-block;
|
||||
.status-bar > #listener-count {
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.status-bar #listener-stats-container > * {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.status-bar #listener-stats-container .status-bar-listener-separator {
|
||||
display: none;
|
||||
}
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -236,8 +218,8 @@ input[type="range"]::-ms-fill-upper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--surface1);
|
||||
background-size: 10px 10px;
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
@@ -250,19 +232,38 @@ input[type="range"]::-ms-fill-upper {
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.button:hover {
|
||||
background-color: #1e1e2e;
|
||||
opacity: 1;
|
||||
background-size: 10px 10px;
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
#585b70 0,
|
||||
#585b70 1px,
|
||||
#1e1e2e 0,
|
||||
#1e1e2e 50%
|
||||
var(--surface1) 0,
|
||||
var(--surface1) 1px,
|
||||
var(--base) 0,
|
||||
var(--base) 50%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
font-family: monospace;
|
||||
color: var(--text);
|
||||
background-color: transparent;
|
||||
margin-left: 20px ;
|
||||
border: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.theme-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.theme-icon path {
|
||||
fill: var(--text);
|
||||
}
|
||||
|
||||
.button.button-active,
|
||||
.button:active {
|
||||
background: var(--text) !important;
|
||||
@@ -398,11 +399,46 @@ aside#notification > #notification-title {
|
||||
}
|
||||
}
|
||||
|
||||
.fal-logo {
|
||||
vertical-align: bottom;
|
||||
height: 1rem;
|
||||
.theme-modal {
|
||||
display: none;
|
||||
width: 60vw;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--base);
|
||||
top: 50%;
|
||||
gap: 5px;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
border: 2px solid var(--text);
|
||||
}
|
||||
|
||||
.fal-link {
|
||||
text-decoration: none;
|
||||
.theme-modal.active{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.theme-modal:hover .theme-item {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.theme-item {
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.theme-modal .theme-item:hover{
|
||||
opacity: 1;
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.theme-colors{
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
border-radius: 20px;
|
||||
}
|
Reference in New Issue
Block a user