<!DOCTYPE html>
<!--
Author: Twily 2015 - 2025
Website: http://twily.info/
AI Generated music album, (no lora), using MusicGen from
hugginface run locally on home computer for experimenting.
Delivered in a very nostalgic inspired Winamp, (re)designed
by myself. Help from grok to setup visualizer and equalizer.
-->
<html>
<head>
<title>Twily AI Music</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name='viewport' content='width=device-width,initial-scale=1'>
<!--<link id="favicon" rel="shortcut icon" href="http://twily.info/favicon.ico" />-->
<!--<link rel="stylesheet" type="text/css" href="./css/style.css?v=1" />-->
<style type="text/css">
html,body {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: auto;
font-family: "Droid Sans", "Liberation Sans", "DejaVu Sans", "Segoe UI", Sans;
font-size: 12pt; font-weight: bold;
background: #02161c; color: #AAABAD;
user-select: none;
}
* { box-sizing: border-box; }
*:focus { outline: none !important; }
a:link, a:visited { color: #606163; text-decoration: none; }
a:hover, a:active { color: #0f0; }
.tbl { display: table; width: 100%; }
.tr { display: table-row; }
.td { display: table-cell; vertical-align: middle; /*border: 1px solid transparent; box-shadow: inset 0 0 2px 2px #000;*/ }
.sidebar { width: 28px; min-width: 28px; }
/*
#wrap { width: 100%; height: 100%; display: flex; position: absolute; z-index: 9; }
#wrap #box { padding: 128px; background: #111113; margin: auto; display: inline-block; position: relative; }*/
.brd { border-top: 1px solid #620; }
.frm {
width: 100%; height: 100%;
position: relative;
margin: 0; padding: 0;
}
#backdrop {
position: fixed; top: 0; left: 0; z-index: 1;
width: 100%; height: 100%;
}
#above {
position: absolute; top: 0; left: 0; z-index: 10000;
width: 100%; height: 100%;
}
#backlay {
position: fixed; top: 0; left: 0; z-index: 2;
width: 100%; height: 100%;
background: transparent;
background: radial-gradient(ellipse at 50%, rgba(26,72,68,.25), rgba(44,53,70,.25) 50%, transparent 100%);
/*background: radial-gradient(ellipse at 50%, transparent, rgba(35,52,84,.25) 50%, rgba(52,112,114,.25) 100%);*/
}
.td label:nth-child(1) {
margin-right: 10px;
}
.playing.selected::before {
content: "►";
/*content: "♬"▶;*/
}
.track.selected {
color: #0f8;
}
#triangle.on::before {
font-size: 22pt; color: #0f0;
content: "►";
position: absolute; z-index: 100;
}
.pad18 {
padding: 4px;
}
#player {
border: 1px solid #620;
border-radius: 2px;
width: 100%;
max-width: 600px;
text-align: left;
}
.btn {
border-right: 1px solid #2f303c;
border-bottom: 1px solid #2f303c;
border-left: 1px solid #747cd3;
border-top: 1px solid #747cd3;
background: #525793;
color: #0f0;
-webkit-appearance: none; border-radius: 2px; cursor: pointer;
}
.btn:hover {
background: #5f5ea9;
}
.btn:active, .btn.tog {
border-top: 1px solid #2f303c;
border-left: 1px solid #2f303c;
border-right: 1px solid #747cd3;
border-bottom: 1px solid #747cd3;
background: #3e4270;
}
#bar {
/*position: fixed; bottom: 0; left: 0;*/
display: block; height: 7px; width: 100%;
background: #525793;
background: linear-gradient(0deg, #2d3a7e, #4d2f95);
border-top: 1px solid #2f303c;
border-left: 1px solid #2f303c;
border-right: 1px solid #747cd3;
border-bottom: 1px solid #747cd3;
}
#progress {
display: block; height: 100%; width: 0%;
background: #0f0;
background: linear-gradient(0deg, #01af01, #00ff2c);
}
#lbloop { display: none; }
#lbshuf { display: none; }
.center { text-align: center; }
.grey {
padding-left: 6px;
color: #666;
font-size: 8pt;
font-family: monospace;
}
#vol {
/*position: fixed; bottom: 0; left: 0;*/
display: inline-block; height: 7px; width: 100px;
background: #525793;
background: linear-gradient(0deg, #2d3a7e, #4d2f95);
border-top: 1px solid #2f303c;
border-left: 1px solid #2f303c;
border-right: 1px solid #747cd3;
border-bottom: 1px solid #747cd3;
}
#level {
display: block; height: 100%; width: 100%;
background: #0f0;
background: linear-gradient(0deg, #01af01, #00ff2c);
}
.eqbar {
/*position: fixed; bottom: 0; left: 0;*/
position: relative;
display: inline-block; height: 60px; width: 7px;
background: #525793;
background: linear-gradient(270deg, #2d3a7e, #4d2f95);
border-top: 1px solid #2f303c;
border-left: 1px solid #2f303c;
border-right: 1px solid #747cd3;
border-bottom: 1px solid #747cd3;
}
.eqlevel {
position: absolute; bottom: 0;
display: block; height: 100%; width: 100%;
background: #0f0;
background: linear-gradient(270deg, #01af01, #00ff2c);
}
#eq0 { height: 50%; }
#eq1 { height: 50%; }
#eq2 { height: 50%; }
#eq3 { height: 50%; }
#eq4 { height: 50%; }
#eq5 { height: 50%; }
#eq6 { height: 50%; }
#eq7 { height: 50%; }
#eq8 { height: 50%; }
#eq9 { height: 50%; }
#display {
display: block;
background: #1f1f1f;
border: 1px solid #363; border-radius: 2px;
width:100%; height: 100%;
position: relative;
}
#time {
font-size: 22pt;
font-family: monospace;
color: #0f0; /*background: #333;*/
float: right;
padding: 4px;
position: relative; z-index: 100;
}
#title {
background: #06070a;
color: #0f0;
border: 1px solid #026; border-radius: 2px;
/*position: relative;*/
}
#inner {
width: 100%;
}
#visualizer {
background: transparent;
width: 100%; height: 69%;
position: absolute; bottom: 0;
z-index: 1;
}
#spaceship {
position: fixed; top: 0; left: 0;
width: 100%; height: 100%;
/*background: url('./spaceship.png') no-repeat center center transparent;
background-size: cover;*/
background: transparent;
visibility: hidden;
opacity: 0; /* start hidden - preload async */
z-index: 5;
}
.lnkbtn {
padding: 8px;
border: 1px solid #aaa;
border-radius: 10px;
}
#btn_ship {
position: fixed;
bottom: 20px; right: 20px;
z-index: 10025;
}
#btn_dir {
position: fixed;
bottom: 20px; left: 20px;
z-index: 10025;
}
</style>
<!--<script type="text/javascript" src="./js/main.js?v=1"></script>-->
<script type="text/javascript">
var $=function(id) { return document.getElementById(id); };
var lerp=function(a,b,t) { return a + t * ( b - a ) };
var clamp=function(val,min,max,) { return Math.min(Math.max(val,min), max) };
var playing=false;
var idxPlaying=1;
var idxText="1. ";
var volume=100;
var idxCurrentTime=0;
var idxDuration=0;
function playpause() {
if(!playing) {
play(idxPlaying);
} else {
clearplay();
}
}
function prev() {
var elms=$('player').getElementsByClassName('playing');
if(loopon) {
play(idxPlaying);
return;
} else if(shufon) {
var n=-1;
while(n==-1 || n==idxPlaying) {
n=rndMinMax(0,elms.length-1);
}
play(n);
return;
}
if(idxCurrentTime>1) {
// rewind song
play(idxPlaying);
} else {
if(idxPlaying-1 <= 0) {
// rewind list
play(elms.length);
} else {
// prev
play(idxPlaying-1);
}
}
}
function rndMinMax(min,max) { // min and max included
return Math.floor(Math.random()*(max-min+1)+min);
}
function next() {
var elms=$('player').getElementsByClassName('playing');
if(loopon) {
play(idxPlaying);
return;
} else if(shufon) {
var n=-1;
while(n==-1 || n==idxPlaying) {
n=rndMinMax(0,elms.length-1);
}
play(n);
return;
}
if(idxPlaying-1 >= elms.length-1) {
// rewind
play(1);
} else {
// next
play(idxPlaying+1);
}
}
var shufon=false;
function shuftog() {
shufon=!shufon;
if(shufon) {
if(loopon) {
looptog();
}
$('lbshuf').style.display="inline-block";
$('btnshuftog').className="btn tog";
} else {
$('lbshuf').style.display="none";
$('btnshuftog').className="btn";
}
}
var loopon=false;
function looptog() {
loopon=!loopon;
if(loopon) {
if(shufon) {
shuftog();
}
$('lbloop').style.display="inline-block";
$('btnlooptog').className="btn tog";
} else {
$('lbloop').style.display="none";
$('btnlooptog').className="btn";
}
}
var eqon=false;
function eqtog() {
eqon=!eqon;
if(eqon) {
$('btneqtog').className="btn tog";
$('eqfrm').style.display="block";
$('eqbrd').style.border="";
} else {
$('btneqtog').className="btn";
$('eqfrm').style.display="none";
$('eqbrd').style.border="none";
}
}
var liston=false;
function listtog() {
liston=!liston;
if(liston) {
$('btnlisttog').className="btn tog";
$('listfrm').style.display="block";
$('listbrd').style.border="";
} else {
$('btnlisttog').className="btn";
$('listfrm').style.display="none";
$('listbrd').style.border="none";
}
}
// set colors here
var mColor=[ // [color1],[color2] = [inner],[outer]
//[255,0,0],[0,255,0], // red,green
//[255,255,0],[255,0,255], // yellow,pink
//[0,255,255],[0,0,255] // cyan,blue
[165,49,83],[44,53,70], // stage0 red,blue
[56,164,83],[75,75,25], // stage1 green,yellow
[26,72,68],[143,49,165] // stage2 cyan,violet
];
var mStage=0;
var mLapse=0; // 0-100, once 100 mStage++ or reset
var color1=[0,0,0];
var color2=[0,0,0];
var pos0=0;
var pos1=1;
var alpha=0.0;
var wW,wH,wH2,bH,bH2;
var bgAspect=15/10; // 16/9
//var fps=12; // tick
var speed=1; // percentage per tick
//var alT;
function animloop() {
//return;
//clearTimeout(alT);
mLapse+=speed;
if(mLapse>=100) {
mLapse=0;
mStage++;
if(mStage>2) mStage=0;
//console.log("new stage = "+mStage);
}
pos0=mStage*2; // skip every other for multi array
pos1=pos0+2;
alpha=mLapse/100;
if(pos1>2*2) pos1=0;
//console.log("pos0:"+pos0+", pos1:"+pos1);
color1[0]=lerp(mColor[pos0][0],mColor[pos1][0],alpha);
color1[1]=lerp(mColor[pos0][1],mColor[pos1][1],alpha);
color1[2]=lerp(mColor[pos0][2],mColor[pos1][2],alpha);
color2[0]=lerp(mColor[pos0+1][0],mColor[pos1+1][0],alpha);
color2[1]=lerp(mColor[pos0+1][1],mColor[pos1+1][1],alpha);
color2[2]=lerp(mColor[pos0+1][2],mColor[pos1+1][2],alpha);
$('backlay').style.background="radial-gradient(ellipse at 50%, rgba("+color1.toString()+",0.25), rgba("+color2.toString()+",.25) 50%, transparent 100%)";
$('backlay').style.height=bH+"px"; // width is 100% always
$('backlay').style.top=(wH2 - bH2)+"px";
//alT=setTimeout(function() { animloop(); }, 1000/fps);
}
var kAT;
var fps=12;
//var keepAliveRunning=false;
var switchNext=-1; // automatic for end of song unless loop on
var updateTime=0;
function keepAlive() {
clearTimeout(kAT);
//keepAliveRunning=true;
if(playing) {
idxCurrentTime=$('myAudio').currentTime;
//idxDuration//
//console.log(idxCurrentTime);
// seekpos = percentage * duration / 100 = time
var iPercent = idxCurrentTime*100/idxDuration;
iPercent=clamp(iPercent,0,100);
$('progress').style.width=iPercent+"%";
if(switchNext>-1) {
switchNext--;
//console.log("ready switch "+switchNext);
if(switchNext==0) {
// switch now
switchNext=-1;
//console.log("switching next done")
next();
}
} else if((idxDuration - idxCurrentTime <= 1) && !loopon) {
switchNext=fps;
}
if(updateTime<=0) {
var cmin=0;
var csec=parseInt(idxCurrentTime);
while(csec>=60) {
csec-=60;
cmin++;
}
var strcsec=(csec<10)?"0"+csec:csec;
var strcmin=(cmin<10)?"0"+cmin:cmin;
$('time').innerHTML=strcmin+":"+strcsec;
updateTime=fps; // 1 per fps
} else {
updateTime--;
}
//$('visualizer').width = $('visualizer').clientWidth;
//$('visualizer').height = $('visualizer').clientHeight;
}
animloop(); // always update bg colors
//if(playing) {
kAT=setTimeout(function() { keepAlive(); },1000/fps);
//}
//keepAliveRunning=false;
if(shipOn && shipOpa<100) {
shipOpa+=3;
if(shipOpa>=100) {
shipOpa=100;
}
if(lastShipOpa==0) {
$('spaceship').style.visibility="visible";
}
} else if(!shipOn && shipOpa>0) {
shipOpa-=3;
if(shipOpa<=0) {
shipOpa=0;
$('spaceship').style.visibility="hidden";
}
}
if(lastShipOpa!=shipOpa) {
$('spaceship').style.opacity=(shipOpa/100);
lastShipOpa=shipOpa;
}
}
var cache=2;
var t="";
function play(idx) {
//alert(idx);
var reloading=true;
if(idx==idxPlaying && !playing) {
reloading=false;
} else {
switchNext=-1;
}
idxPlaying=idx;
var elms=$('player').getElementsByClassName('playing');
for(var i=0;i<elms.length;i++) {
var c=i+1;
if(c==idx) {
elms[i].className="playing selected";
$('aud'+c).className="track selected";
idxText=c+". "+$('aud'+c).getElementsByClassName('td')[2].innerHTML; // idx|name
t=$('aud'+c).getElementsByClassName('mp3name')[0].value;
} else {
elms[i].className="playing";
$('aud'+c).className="track";
}
}
playing=true;
$('btnplaypause').value="∎";
$('triangle').className="triangle on";
if(reloading) {
//$('myAudio').src=idx+".mp3"; // need t, not c
$('myAudio').src=t+".mp3?c="+cache;
}
$('inner').innerHTML=idxText;
$('myAudio').play();
if(!audioContextInitiated) {
createAudioContext();
}
if(eqloaded==1 || eqtrig==1) {
updateeq();
eqloaded=2;
}
//if(!keepAliveRunning) keepAlive();
}
var initClear=0;
function clearplay() {
var elms=$('player').getElementsByClassName('playing');
for(var i=0;i<elms.length;i++) {
var c=i+1;
if(c==idxPlaying) {
elms[i].className="playing"; // not playing
$('aud'+c).className="track selected";
idxText=c+". "+$('aud'+c).getElementsByClassName('td')[2].innerHTML; // idx|name
t=$('aud'+c).getElementsByClassName('mp3name')[0].value;
} else {
elms[i].className="playing";
$('aud'+c).className="track";
}
}
playing=false;
$('btnplaypause').value="►";
$('triangle').className="triangle";
if(initClear==0) {
$('myAudio').src=t+".mp3?c="+cache;
initClear=1;
}
$('inner').innerHTML=idxText;
$('myAudio').pause();
}
var plX=0;
var plY=0;
function mousehandle(e) {
e=event || window.event;
var lX=e.clientX || e.targetTouches[0].pageX;
var lY=e.clientY || e.targetTouches[0].pageY;
//var sY=window.scrollY;
//var sX=window.scrollX;
plX=lX;
plY=lY;
// update on move (and hold)
if(seeklock) {
seekpos();
} else if(vollock) {
setvol();
} else if(eqlock!=-1) {
seteq(eqlock);
}
}
var seeklock=false;
var vollock=false;
var eqlock=-1;
function unlockHold() {
seeklock=false;
vollock=false;
eqlock=-1;
}
function lockseek() {
seeklock=true;
seekpos(); // initial
}
function lockvol() {
vollock=true;
setvol(); // initial
}
function lockeq(x) {
eqlock=x;
seteq(eqlock); // initial
}
function seekpos() {
var elm=$('bar').getBoundingClientRect();
var ilX=plX - elm.left;
var ilY=plY - elm.top;
var iPercent = ilX * 100 / elm.width; // 0 - 100
//alert("mouseX = "+ilX+" mouseY = "+ilY+" percent = "+iPercent);
iPercent=clamp(iPercent,0,100);
$('progress').style.width = iPercent+"%";
var seekpos = iPercent * idxDuration / 100;
$('myAudio').currentTime = seekpos;
}
function setvol() {
var elm=$('vol').getBoundingClientRect();
var ilX=plX - elm.left;
var ilY=plY - elm.top;
var iPercent = ilX * 100 / elm.width; // 0 - 100
//alert("mouseX = "+ilX+" mouseY = "+ilY+" percent = "+iPercent);
iPercent=clamp(iPercent,0,100);
$('level').style.width = iPercent+"%";
volume = parseInt(iPercent);
$('myAudio').volume = volume/100;
localStorage.setItem("volume",iPercent);
}
function loadvol() {
if(localStorage.getItem("volume")) {
var iPercent=localStorage.getItem("volume")
$('level').style.width = iPercent+"%";
volume = parseInt(iPercent);
$('myAudio').volume = volume/100;
}
}
var ryg=[
[1,175,1],[0,255,44], // green
[175,175,0],[255,255,0], // yellow
[175,50,0],[255,50,0] // red
];
var rcolor1=[0,0,0];
var rcolor2=[0,0,0];
var arpha=0.0;
function coloreq(x,iPercent) {
// lerp [0][0-2] to [2][0-2] and [1][0-2] to [3][0-2] 0-50
// lerp [2][0-2] to [4][0-2] and [3][0-2] to [5][0-2] 50-100
var ridx=0;
arpha=iPercent/50;
if(iPercent>50) {
ridx=2;
arpha-=1.0;
}
rcolor1[0]=lerp(ryg[ridx][0],ryg[ridx+2][0],arpha);
rcolor1[1]=lerp(ryg[ridx][1],ryg[ridx+2][1],arpha);
rcolor1[2]=lerp(ryg[ridx][2],ryg[ridx+2][2],arpha);
rcolor2[0]=lerp(ryg[ridx+1][0],ryg[ridx+3][0],arpha);
rcolor2[1]=lerp(ryg[ridx+1][1],ryg[ridx+3][1],arpha);
rcolor2[2]=lerp(ryg[ridx+1][2],ryg[ridx+3][2],arpha);
//
$('eq'+x).style.background="linear-gradient(270deg, rgb("+rcolor1+"), rgb("+rcolor2+"))";
//console.log("arpha = "+arpha+", ");
//console.log("rcolor1="+rcolor1.toString()+" rcolor2="+rcolor2.toString());
}
function seteq(x) {
var elm=$('eq'+x).parentNode.getBoundingClientRect();
var ilX=plX - elm.left;
var ilY=plY - elm.top;
var iPercent = 100 - (ilY * 100 / elm.height); // 0 - 100
//alert("mouseX = "+ilX+" mouseY = "+ilY+" percent = "+iPercent);
iPercent=clamp(iPercent,0,100);
$('eq'+x).style.height = iPercent+"%";
coloreq(x,iPercent);
// convert 0 - 100 iPercent to
// -20 to 0 to 20 where 0 = 50
audioFilters[x][1] = parseFloat((iPercent * .4) - 20);
//volume = parseInt(iPercent);
//$('myAudio').volume = volume/100;
updateeq();
saveeq();
}
var eqpreset=[
[50,50,50,50,50,50,50,50,50,50], // flat
[42,52,58,38,50,40,30,50,46,40], // preset1
[45,60,83,45,35,30,30,70,80,50], // preset2
[36,45,65,80,70,70,85,80,50,44], // preset3
[35,48,65,75,86,86,75,65,48,35], // preset4
];
function loadpreset(x) {
loadeq(x);
}
var eqloaded=0;
var eqtrig=0;
function updateeq() {
eqtrig=1;
if(eqloaded==0) {
eqloaded=1;
} else if(eqloaded==1) {
if(!audioContextInitiated) {
createAudioContext();
}
eqloaded=2;
} else if(eqloaded==2) {
eqtrig=0;
for(var i=0;i<audioFilters.length;i++) {
audioFilters[i][2].gain.value=audioFilters[i][1];
//console.log("update gain eq "+i+" to "+audioFilters[i][1]);
}
}
}
function saveeq() {
var arr=[];
var iPercent=0;
for(var i=0;i<audioFilters.length;i++) {
iPercent=(audioFilters[i][1]+20)*2.5; // realign and scale
iPercent=Math.floor(clamp(iPercent,0,100));
arr.push(iPercent);
//console.log("convert "+audioFilters[i][1]+" to "+iPercent);
}
//console.log(JSON.stringify(arr));
localStorage.setItem("eqdata",JSON.stringify(arr));
}
function loadeq(f=-1) {
var arr;
var iPercent=0;
if(localStorage.getItem("eqdata") && f==-1) {
var data=localStorage.getItem("eqdata")
arr=JSON.parse(data);
} else {
if(f==-1) f=1; // default load
arr=eqpreset[f];
}
for(var i=0;i<arr.length;i++) {
iPercent=arr[i];
$('eq'+i).style.height = iPercent+"%";
coloreq(i,iPercent);
audioFilters[i][1] = parseFloat((iPercent * .4) - 20);
//console.log("loading eq"+i+" = "+iPercent);
}
if(eqloaded==1 && f==-1) {
eqloaded=1;
} else {
updateeq();
saveeq();
}
}
function dl() {
//alert(idxPlaying+".mp3");
downloadResource('//twily.info/s/firefox/mp3/'+t+".mp3","twily-album1-track"+idxPlaying+".mp3");
}
function forceDownload(blob, filename) {
var a = document.createElement('a');
a.download = filename;
a.href = blob;
// For Firefox https://stackoverflow.com/a/32226068
document.body.appendChild(a);
a.click();
a.remove();
}
// Current blob size limit is around 500MB for browsers
function downloadResource(url, filename) {
if (!filename) filename = url.split('\\').pop().split('/').pop();
fetch(url, {
headers: new Headers({
'Origin': location.origin
}),
mode: 'cors'
})
.then(response => response.blob())
.then(blob => {
let blobUrl = window.URL.createObjectURL(blob);
forceDownload(blobUrl, filename);
})
.catch(e => console.error(e));
}
var audioFilters=[ // hz,db,obj
[60,0.0,null],
[170,0.0,null],
[310,0.0,null],
[600,0.0,null],
[1000,0.0,null],
[3000,0.0,null],
[6000,0.0,null],
[12000,0.0,null],
[14000,0.0,null],
[16000,0.0,null]
];
var audioContextInitiated=false;
var audioContext, source, analyser, bufferLength, dataArray;
var canvas,canvasCtx;
function createAudioContext() {
audioContextInitiated=true;
audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Create a source node from the audio element
source = audioContext.createMediaElementSource($('myAudio'));
// Create an AnalyserNode for audio analysis
analyser = audioContext.createAnalyser();
analyser.fftSize = 128; // Number of samples for FFT (higher = more detail)
bufferLength = analyser.frequencyBinCount; // Half of fftSize
dataArray = new Uint8Array(bufferLength); // Array to store frequency data
for(var i=0;i<audioFilters.length;i++) {
audioFilters[i][2] = audioContext.createBiquadFilter();
var type="lowshelf";
if(i>6) {
type="highshelf";
} else if(i>3) {
type="peaking";
}
audioFilters[i][2].type=type;
audioFilters[i][2].frequency.value=audioFilters[i][0];
}
updateeq();
// Connect the audio source to the analyser and then to the output (speakers)
source.connect(analyser);
analyser.connect(audioFilters[0][2]);
for(var i=0;i<audioFilters.length-1;i++) {
audioFilters[i][2].connect(audioFilters[(i+1)][2]);
}
//analyser.connect(audioContext.destination);
audioFilters[9][2].connect(audioContext.destination);
canvas = $('visualizer');
canvasCtx = canvas.getContext("2d");
// Start the visualization loop
draw();
}
// Visualization function
var barWidth,barHeight;
var grad,red,green;
function draw() {
// Schedule the next frame
requestAnimationFrame(draw);
// Get the current frequency data
analyser.getByteFrequencyData(dataArray);
// Clear the canvas
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
//console.log(canvas.width+"x"+canvas.height);
// Calculate bar width for the spectrum
barWidth = ((canvas.width - 3) / bufferLength) * 2.5; // -3 adjust right edge last bar
let x = 0;
// Draw bars for each frequency bin
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]; // Amplitude value (0-255)
//grad = canvasCtx.createLinearGradient(0, 0, 0, barHeight);
grad = canvasCtx.createLinearGradient(0, canvas.height / 4, 0, canvas.height);
red=barHeight-50;
green=255-red;
grad.addColorStop(0, "rgb(255,50,0)");
grad.addColorStop(0.5, "rgb(255,255,0)");
grad.addColorStop(1, "rgb(0,127,0)");
//canvasCtx.fillStyle = `rgb(${barHeight + 100}, 50, 50)`; // Dynamic color
canvasCtx.fillStyle = grad;
canvasCtx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight / 2);
x += barWidth + 1; // Move to the next bar with a small gap
}
}
function newSize() {
wW=window.innerWidth;
wH=window.innerHeight;
wH2=wH/2;
//bW=wW;
bH=wW * bgAspect;
bH2 = bH/2;
}
function init() {
$('eqbrd').style.border="none"; // default off
listtog(); // default on
$('myAudio').addEventListener('loadedmetadata',function() {
idxDuration = $('myAudio').duration;
//console.log(idxDuration);
});
idxPlaying=1;
clearplay();
preloadSpaceship();
newSize();
keepAlive();
loadvol();
loadeq();
}
var shipOpa=0;
var lastShipOpa=0;
var shipOn=false;
var shipLoaded=false;
function toggle_direction() {
$('star_frm').src=$('star_frm').src;
}
function toggle_ship() {
if(!shipLoaded) return;
shipOn=!shipOn;
if(shipOn) {
$('btn_ship').innerHTML="[ Float through space ]";
} else {
$('btn_ship').innerHTML="[ Ride spaceship ]";
}
}
async function preloadSpaceship() {
var img=new Image();
img.onload=function() {
shipLoaded=true;
$('spaceship').style.background="url("+this.src+") no-repeat center center transparent";
$('spaceship').style.backgroundSize="cover";
toggle_ship();
}
img.src="./spaceship.png";
}
</script>
</head>
<body onload="init();" onmousemove="mousehandle(event);" onmouseup="unlockHold();" ontouchmove="mousehandle(event);" ontouchend="unlockHold();">
<div id="backdrop">
<iframe src="stars-transparent.html" frameborder="0" class="frm" id="star_frm"></iframe>
</div>
<div id="spaceship"></div>
<div id="backlay"></div>
<!-- Audio element with controls and a sample audio file -->
<audio loop id="myAudio">
<source src="1.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<div id="above">
<div class="tbl" style="height: 100%;">
<div class="tr" style="height: 50px;">
<div class="td" style="text-align: center; vertical-align: middle;">
Twily AI Album #1
</div>
</div>
<div class="tr">
<div class="td">
<div class="tbl">
<div class="tr">
<div class="td sidebar">
</div>
<div class="td">
<center>
<div id="player">
<div class="tbl">
<div class="tr">
<div class="td" style="background: rgba(25,32,70,1.0); background: linear-gradient(0deg, #192046, #1e275c); /*#46192e, #172158);*/ border-radius: 2px;">
<!-- player -->
<div class="tbl" style="height: 100%;">
<div class="tr">
<div class="td" style="width: 150px; height: 100%; /* ff fix */">
<!-- player left -->
<div class="pad18" style="height: 100%;">
<div id="display">
<span id="triangle"></span>
<span id="time">00:00</span>
<!-- Canvas for visualization -->
<canvas id="visualizer"></canvas>
</div>
</div>
</div>
<div class="td">
<!-- player right -->
<div class="pad18">
<!--<div id="title"><marquee><div id="inner">1. </div></marquee></div>-->
<div id="title"><div id="inner">1. </div></div>
<br />
<div id="bar" onmousedown="lockseek();" ontouchstart="lockseek();">
<div id="progress"></div>
</div>
<br />
<div id="controls">
<input type="button" class="btn" value="⊲" onclick="prev();" />
<input type="button" class="btn" value="►" id="btnplaypause" onclick="playpause();" />
<input type="button" class="btn" value="⊳" onclick="next();" />
<input type="button" class="btn" value="≠" id="btnshuftog" onclick="shuftog();" /><label class="grey" id="lbshuf">Random</label>
<input type="button" class="btn" value="∞" id="btnlooptog" onclick="looptog();" /><label class="grey" id="lbloop">Loop one</label>
<input type="button" class="btn" value="⏏" onclick="dl();" title="Download" alt="Download" />
<div style="float: right;">
<input type="button" class="btn" value="≣" id="btnlisttog" onclick="listtog();" />
<input type="button" class="btn" value="~" id="btneqtog" onclick="eqtog();" />
<label id="lbvol">Vol:</label><div id="vol" onmousedown="lockvol();" ontouchstart="lockvol();">
<div id="level"></div>
</div>
</div>
<div style="clear:both;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tr">
<div class="td brd" id="eqbrd" style="background: rgba(25,32,70,1.0); background: linear-gradient(0deg, #1e275c, #192046); /*#46192e, #172158);*/ border-radius: 2px;">
<div class="pad18" id="eqfrm" style="display: none;">
<!--Equalizer
<br />-->
<center>
<div class="tbl">
<div class="tr">
<div class="td" style="vertical-align: top; text-align: left;">
<!-- left -->
<input type="button" class="btn" value="flat" onclick="loadpreset(0);" />
<br />
</div>
<div class="td">
<div class="tbl" style="width: 75%;"> <!-- db from +20 to 0 to -20 -->
<div class="tr">
<div class="td" style="width: 9%;">
<div class="tbl" style="height: 100%;text-align: right;">
<div class="tr">
<div class="td" style="vertical-align: top; padding-bottom: 6px;">
<label class="grey">+20</label>
</div>
</div>
<div class="tr">
<div class="td">
<label class="grey">-</label>
</div>
</div>
<div class="tr">
<div class="td" style="vertical-align: bottom; padding-top: 6px;">
<label class="grey">-20</label>
</div>
</div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(0);" ontouchstart="lockeq(0);">
<div class="eqlevel" id="eq0"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(1);" ontouchstart="lockeq(1);">
<div class="eqlevel" id="eq1"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(2);" ontouchstart="lockeq(2);">
<div class="eqlevel" id="eq2"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(3);" ontouchstart="lockeq(3);">
<div class="eqlevel" id="eq3"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(4);" ontouchstart="lockeq(4);">
<div class="eqlevel" id="eq4"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(5);" ontouchstart="lockeq(5);">
<div class="eqlevel" id="eq5"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(6);" ontouchstart="lockeq(6);">
<div class="eqlevel" id="eq6"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(7);" ontouchstart="lockeq(7);">
<div class="eqlevel" id="eq7"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(8);" ontouchstart="lockeq(8);">
<div class="eqlevel" id="eq8"></div>
</div>
</div>
<div class="td" style="width: 9%;">
<div class="eqbar" onmousedown="lockeq(9);" ontouchstart="lockeq(9);">
<div class="eqlevel" id="eq9"></div>
</div>
</div>
</div>
<div class="tr">
<div class="td center"><label class="grey"> </label></div>
<div class="td center"><label class="grey">60</label></div>
<div class="td center"><label class="grey">170</label></div>
<div class="td center"><label class="grey">310</label></div>
<div class="td center"><label class="grey">600</label></div>
<div class="td center"><label class="grey">1K</label></div>
<div class="td center"><label class="grey">3K</label></div>
<div class="td center"><label class="grey">6K</label></div>
<div class="td center"><label class="grey">12K</label></div>
<div class="td center"><label class="grey">14K</label></div>
<div class="td center"><label class="grey">16K</label></div>
</div>
</div>
</div>
<div class="td" style="vertical-align: top; text-align: right;">
<!-- right -->
<input type="button" class="btn" value="preset1" onclick="loadpreset(1);" />
<br />
<input type="button" class="btn" value="preset2" onclick="loadpreset(2);" />
<br />
<input type="button" class="btn" value="preset3" onclick="loadpreset(3);" />
<br />
<input type="button" class="btn" value="preset4" onclick="loadpreset(4);" />
<br />
</div>
</div>
</div>
</center>
</div><!--pad18-->
</div>
</div>
<div class="tr">
<div class="td brd" id="listbrd" style="background: rgba(16,20,38,.5);">
<!-- list -->
<div class="pad18" id="listfrm">
</div>
</div>
</div>
</div>
</div> <!-- end player -->
</center>
</div>
<div class="td sidebar">
</div>
</div>
</div>
</div>
</div>
<div class="tr" style="height: 50px;">
<div class="td" style="text-align: center; vertical-align: middle;">
<div id="jswarning">This website requires JavaScript to function</div>
<br />
Runtime: <span id="runtime"></span>
<br />
<br />
(produced with MusicGen~)
<br />
<br />
(ɔ) Twily 2025
<br />
<br />
</div>
</div>
</div>
</div>
<a href="javascript:toggle_ship();" target="_self" id="btn_ship" class="lnkbtn">[ Loading spaceship ]</a>
<a href="javascript:toggle_direction();" target="_self" id="btn_dir" class="lnkbtn">[ Change direction ]</a>
<script type="text/javascript">
$('jswarning').style.display="none";
var tracks=[
"1",90,
"2",90,
"3",90,
"4",90,
"5",90,
"6",90,
"7",90,
"8",90,
"9",90,
"10",90,
"11",80,
"12",80,
"13",80,
"14",80,
"15",80,
"16",90,
"17",90,
"18",90,
"19",90,
"20",90,
"21",90,
"22",90,
"23",90,
"24",90,
"25",90,
"26",90,
"27",90,
"28",90,
"29",90,
"30",90,
"31",90,
"32",90,
"33",90,
"34",90
];
var c=0;
var playmins=0;
var playsecs=0;
var maxtry=0;
for(var i=0;i<tracks.length;i+=2) {
c++;
let t=tracks[i];
let secs=tracks[i+1];
playsecs+=secs;
playing="";
//if(c==1) {
// playing=" selected";
//}
maxtry=1000;
var mins=0;
while(secs>=60 && maxtry>0) {
secs-=60;
mins++;
maxtry--;
//console.log('maxtry1: '+maxtry);
}
let html="<a href=\"javascript:play("+c+");\" class=\"track\" id=\"aud"+c+"\">";
html+="<div class=\"tbl\">";
html+="<div class=\"tr\">";
html+="<div class=\"td\" style=\"width: 24px;\">";
html+="<span class=\"playing"+playing+"\">";
html+="</div>";
html+="<div class=\"td\" style=\"width: 48px;\">";
html+="#"+c+"";
html+="</div>";
html+="<div class=\"td\">";
html+="Track"+t+"";
html+="</div>";
html+="<div class=\"td\" style=\"width: 50px;\">";
html+=""+mins+":"+secs+"";
html+="</div>\n";
html+="</div>";
html+="</div>";
html+="<input type=\"hidden\" class=\"mp3name\" value=\""+t+"\" />";
html+="</a>\n\n";
$('listfrm').insertAdjacentHTML('beforeend',html);
}
maxtry=1000;
while(playsecs>60 && maxtry>0) {
playmins++;
playsecs-=60;
maxtry--;
//console.log('maxtry2: '+maxtry);
}
$('runtime').insertAdjacentHTML('beforeend',playmins+" min "+playsecs+" sec");
var rzT;
window.addEventListener("resize",function() {
clearTimeout(rzT);
rzT=setTimeout(function() { newSize(); }, 100);
});
// grok visualizer implement
//Adapting for Volume
//
//To visualize volume instead, you can use time-domain data:
//
//Replace getByteFrequencyData(dataArray) with getFloatTimeDomainData(timeDataArray) (where timeDataArray is a Float32Array of length analyser.fftSize).
//
//
//
//Calculate the root mean square (RMS) to estimate volume:
//
//javascript
//
//
//const timeDataArray = new Float32Array(analyser.fftSize);
//analyser.getFloatTimeDomainData(timeDataArray);
//let sum = 0;
//for (let i = 0; i < timeDataArray.length; i++) {
// sum += timeDataArray[i] * timeDataArray[i];
//}
//const rms = Math.sqrt(sum / timeDataArray.length); // Volume level
//
//
//Use rms to adjust the height of a single bar or scale a waveform.
//
//Notes on Pitch
//
//Extracting pitch directly is more complex and requires pitch
// detection algorithms (e.g., autocorrelation or finding the
// dominant frequency from the frequency data). The frequency
// spectrum provided by getByteFrequencyData gives raw frequency
// amplitudes, which can hint at pitch but don’t directly isolate
// it for complex sounds. For a simple visualization, the spectrum i
// s often sufficient, as pitch relates to frequency content.
</script>
</body>
</html>
Top