commit
4dd1391b9b
@ -0,0 +1,7 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="DiscordProjectSettings"> |
||||||
|
<option name="show" value="PROJECT_FILES" /> |
||||||
|
<option name="description" value="" /> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="JavaScriptLibraryMappings"> |
||||||
|
<includedPredefinedLibrary name="Node.js Core" /> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ProjectModuleManager"> |
||||||
|
<modules> |
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/visual.iml" filepath="$PROJECT_DIR$/.idea/visual.iml" /> |
||||||
|
</modules> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="VcsDirectoryMappings"> |
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="" /> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,12 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<module type="WEB_MODULE" version="4"> |
||||||
|
<component name="NewModuleRootManager"> |
||||||
|
<content url="file://$MODULE_DIR$"> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" /> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> |
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" /> |
||||||
|
</content> |
||||||
|
<orderEntry type="inheritedJdk" /> |
||||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||||
|
</component> |
||||||
|
</module> |
@ -0,0 +1,81 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="AutoImportSettings"> |
||||||
|
<option name="autoReloadType" value="SELECTIVE" /> |
||||||
|
</component> |
||||||
|
<component name="ChangeListManager"> |
||||||
|
<list default="true" id="c3d00201-7818-482a-ab38-ba8e6b43830e" name="Changes" comment=""> |
||||||
|
<change beforePath="$PROJECT_DIR$/../grammatical/grammatical.css" beforeDir="false" afterPath="$PROJECT_DIR$/../grammatical/grammatical.css" afterDir="false" /> |
||||||
|
<change beforePath="$PROJECT_DIR$/../grammatical/test.html" beforeDir="false" afterPath="$PROJECT_DIR$/../grammatical/test.html" afterDir="false" /> |
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> |
||||||
|
<change beforePath="$PROJECT_DIR$/script.js" beforeDir="false" afterPath="$PROJECT_DIR$/script.js" afterDir="false" /> |
||||||
|
</list> |
||||||
|
<option name="SHOW_DIALOG" value="false" /> |
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" /> |
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> |
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" /> |
||||||
|
</component> |
||||||
|
<component name="FileTemplateManagerImpl"> |
||||||
|
<option name="RECENT_TEMPLATES"> |
||||||
|
<list> |
||||||
|
<option value="HTML File" /> |
||||||
|
<option value="CSS File" /> |
||||||
|
<option value="JavaScript File" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="Git.Settings"> |
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." /> |
||||||
|
</component> |
||||||
|
<component name="MarkdownSettingsMigration"> |
||||||
|
<option name="stateVersion" value="1" /> |
||||||
|
</component> |
||||||
|
<component name="ProjectId" id="2LP95OKOUgfBsSNFDIsuu54TP0M" /> |
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" /> |
||||||
|
<component name="ProjectViewState"> |
||||||
|
<option name="hideEmptyMiddlePackages" value="true" /> |
||||||
|
<option name="showLibraryContents" value="true" /> |
||||||
|
</component> |
||||||
|
<component name="PropertiesComponent"><![CDATA[{ |
||||||
|
"keyToString": { |
||||||
|
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", |
||||||
|
"DefaultHtmlFileTemplate": "HTML File", |
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true", |
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true", |
||||||
|
"WebServerToolWindowFactoryState": "false", |
||||||
|
"javascript.nodejs.core.library.configured.version": "16.19.0", |
||||||
|
"javascript.nodejs.core.library.typings.version": "16.18.12", |
||||||
|
"last_opened_file_path": "C:/Users/wubzy/Desktop/bot/test/css/visual", |
||||||
|
"list.type.of.created.stylesheet": "CSS", |
||||||
|
"node.js.detected.package.eslint": "true", |
||||||
|
"node.js.detected.package.tslint": "true", |
||||||
|
"node.js.selected.package.eslint": "(autodetect)", |
||||||
|
"node.js.selected.package.tslint": "(autodetect)", |
||||||
|
"nodejs_package_manager_path": "npm", |
||||||
|
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings", |
||||||
|
"vue.rearranger.settings.migration": "true" |
||||||
|
} |
||||||
|
}]]></component> |
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" /> |
||||||
|
<component name="TaskManager"> |
||||||
|
<task active="true" id="Default" summary="Default task"> |
||||||
|
<changelist id="c3d00201-7818-482a-ab38-ba8e6b43830e" name="Changes" comment="" /> |
||||||
|
<created>1675761666673</created> |
||||||
|
<option name="number" value="Default" /> |
||||||
|
<option name="presentableId" value="Default" /> |
||||||
|
<updated>1675761666673</updated> |
||||||
|
<workItem from="1675761667860" duration="814000" /> |
||||||
|
<workItem from="1675762501502" duration="3223000" /> |
||||||
|
<workItem from="1675802395316" duration="11930000" /> |
||||||
|
</task> |
||||||
|
<servers /> |
||||||
|
</component> |
||||||
|
<component name="TypeScriptGeneratedFilesManager"> |
||||||
|
<option name="version" value="3" /> |
||||||
|
</component> |
||||||
|
<component name="VcsManagerConfiguration"> |
||||||
|
<ignored-roots> |
||||||
|
<path value="$PROJECT_DIR$/.." /> |
||||||
|
</ignored-roots> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,26 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" |
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"> |
||||||
|
<title>Customizable Visualizer</title> |
||||||
|
<script src="./script.js" defer async type="module"></script> |
||||||
|
<link rel="stylesheet" href="./styles.css"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="main"> |
||||||
|
<div id="controls"> |
||||||
|
<button id="begin" onclick="play()">Start Visualizer/Play</button> |
||||||
|
<input type="text" id="get-src" class="ctrl" value="https://media.wubzy.xyz/ipnU.mp3" placeholder='Audio URL...'><label for="get-src"></label> |
||||||
|
<button class="invis ctrl ctrl-btn" id="play" onclick="play()">Play</button> |
||||||
|
<button class="invis ctrl ctrl-btn" id="pause" onclick="pause()">Pause</button> |
||||||
|
<button id="reload" class="invis ctrl ctrl-btn" onclick="reload()">Reload Player</button> |
||||||
|
</div> |
||||||
|
<div id="visual-master"> |
||||||
|
<canvas id="visualizer"></canvas> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,131 @@ |
|||||||
|
let played = false; |
||||||
|
let asrc = ""; |
||||||
|
let audio; |
||||||
|
|
||||||
|
import tinygradient from 'https://cdn.jsdelivr.net/npm/tinygradient@1.1.5/+esm'; |
||||||
|
|
||||||
|
/*BASE CONTROLS*/ |
||||||
|
|
||||||
|
window.firstPlay = function() {return new Promise(async r => { |
||||||
|
played = true; |
||||||
|
document.getElementById('begin').classList.add('invis'); |
||||||
|
asrc = document.getElementById('get-src').value; |
||||||
|
audio = new Audio(asrc); |
||||||
|
audio.loop = true; |
||||||
|
audio.crossOrigin = "anonymous"; |
||||||
|
await audio.play(); |
||||||
|
document.getElementById('get-src').classList.add('invis'); |
||||||
|
createVisualizer(); |
||||||
|
r(0); |
||||||
|
});} |
||||||
|
window.play = async function() { |
||||||
|
if (!played) {await firstPlay();} |
||||||
|
document.getElementById('play').classList.add('invis'); |
||||||
|
document.getElementById('reload').classList.add('invis'); |
||||||
|
document.getElementById('pause').classList.remove('invis'); |
||||||
|
if (played) {await audio.play();} |
||||||
|
} |
||||||
|
window.pause = function() { |
||||||
|
document.getElementById('play').classList.remove('invis'); |
||||||
|
document.getElementById('reload').classList.remove('invis'); |
||||||
|
document.getElementById('pause').classList.add('invis'); |
||||||
|
audio.pause(); |
||||||
|
} |
||||||
|
window.reload = function() { |
||||||
|
audio.pause(); |
||||||
|
played = false; |
||||||
|
document.getElementById('begin').classList.remove('invis'); |
||||||
|
document.getElementById('get-src').classList.remove('invis'); |
||||||
|
document.getElementById('play').classList.add('invis'); |
||||||
|
document.getElementById('reload').classList.add('invis'); |
||||||
|
} |
||||||
|
|
||||||
|
/*ANIMATION*/ |
||||||
|
window.createVisualizer = function() { |
||||||
|
document.getElementById('visualizer').style.display = 'block'; // enable the visualizer canvas
|
||||||
|
|
||||||
|
let canvas = document.getElementById("visualizer"); // setup canvas
|
||||||
|
canvas.width = window.innerWidth; |
||||||
|
canvas.height = window.innerHeight; |
||||||
|
let ctx = canvas.getContext("2d"); |
||||||
|
|
||||||
|
let context = new AudioContext(); // connect to an audio analyzer
|
||||||
|
let src = context.createMediaElementSource(audio); |
||||||
|
let analyser = context.createAnalyser(); |
||||||
|
src.connect(analyser); |
||||||
|
analyser.connect(context.destination); |
||||||
|
|
||||||
|
analyser.fftSize = 2 ** 8; // CHANGE SECOND NUMBER ONLY || controls number of frequency channels. must be a power of 2
|
||||||
|
|
||||||
|
let bufferLength = analyser.frequencyBinCount; // the number of frequency channels being analyzed; max displayable bars
|
||||||
|
|
||||||
|
/*CONFIG VARIABLES*/ |
||||||
|
|
||||||
|
let colors = [ |
||||||
|
'rgb(161, 114, 166)', |
||||||
|
'rgb(157, 187, 177)' |
||||||
|
]; // Uses tinygradient. hex, rgb, etc. Basically write it like you would in CSS and chances are it'll work
|
||||||
|
|
||||||
|
let minChannel = 0; // cut off this many bars from the beginning; 0 = will render from first possible frequency channel till maxChannel
|
||||||
|
console.log(.65 * bufferLength); |
||||||
|
let maxChannel = (Math.floor(.65 * bufferLength)) || bufferLength; // cut off this many bars from the end; 0 = will render from minChannel to end
|
||||||
|
// tl;dr this is a range of possible frequency channels to actually render a bar for.
|
||||||
|
// if fftSize is 2^8 (256) you should have 128 channels; min 10 : max 50 : render from channel 10 to 50, 40 bars.
|
||||||
|
// my default is the first 65% of bars
|
||||||
|
let barGap = 1; // gap between each bar in px. can be 0.
|
||||||
|
let forceCanvasFill = true; // because decimals exist and they suck, turning off bar gap while this is false can leave tiny inconsistent gaps
|
||||||
|
// this should be 'true' if barGap is anything except for 0. this will leave unnoticeable inconsistencies in the bar gap but fill the entire width
|
||||||
|
// set this to 'false' to make sure bar gap is actually 0, but that will leave a little free space at the end.
|
||||||
|
let maxHeight = 25; // highest percentage of the canvas the bar can take up
|
||||||
|
let minHeight = 20; // height in px to pad under the bars. i will not be apologizing for this being px and the other being %
|
||||||
|
|
||||||
|
let brightFactor = 0; // brighten the entire gradient
|
||||||
|
let darkFactor = 0; // darken the entire gradient.
|
||||||
|
// KEEP ONE OR BOTH AT 0. max 100. See the tinycolor docs.
|
||||||
|
|
||||||
|
|
||||||
|
/*RENDERER SETUP*/ |
||||||
|
let WIDTH = canvas.width; let HEIGHT = canvas.height; |
||||||
|
|
||||||
|
let barCount = maxChannel - minChannel; |
||||||
|
let barWidth = forceCanvasFill ? WIDTH / barCount : Math.floor(WIDTH / barCount); // sets the base bar width on the basis that each frequency channel will be a member of the canvas
|
||||||
|
let barHeight; |
||||||
|
let gradient = tinygradient(colors); |
||||||
|
let barColors = [...Array(barCount).keys()].map(i => gradient.rgbAt((i + 1) / barCount)); // preset the gradient since it doesn't need to be evaluated each frame
|
||||||
|
let dataArray = new Uint8Array(maxChannel); // using maxChannel instead of bufferLength might help with performance in the event that you're not rendering some right channels
|
||||||
|
|
||||||
|
if (brightFactor) {barColors.map(bar => bar.brighten(brightFactor));} |
||||||
|
if (darkFactor) {barColors.map(bar => bar.darken(darkFactor));} |
||||||
|
|
||||||
|
let x = 0; |
||||||
|
|
||||||
|
function renderFrame() { |
||||||
|
requestAnimationFrame(renderFrame); // probably shouldn't touch these
|
||||||
|
x = 0; |
||||||
|
analyser.getByteFrequencyData(dataArray); |
||||||
|
|
||||||
|
ctx.clearRect(0, 0, WIDTH, HEIGHT); // clear the canvas
|
||||||
|
|
||||||
|
ctx.fillStyle = "#ffffff00"; // set transparent background for the frame
|
||||||
|
ctx.fillRect(0, 0, WIDTH, HEIGHT); |
||||||
|
|
||||||
|
for (let i = minChannel; i < barCount; i++) { |
||||||
|
/* |
||||||
|
* loops through each bar from left to right, every frame |
||||||
|
* i represents which bar the loop is currently on |
||||||
|
* there are 128 bars in total |
||||||
|
*/ |
||||||
|
|
||||||
|
let strengthLuminance = 1 //2; // the factor to decide how reactive the brightness of the bar is to its own height, or the strength of that frequency. this number works through decay, and lower numbers mean brighter luminance. (1 to 2 is a much bigger difference than 9 to 10)
|
||||||
|
|
||||||
|
barHeight = (dataArray[i] / 255) * ((maxHeight / 100) * HEIGHT); // sets the bar height based on the music for this frame
|
||||||
|
|
||||||
|
ctx.fillStyle = barColors[i].toRgbString(); // paints the bar for the frame
|
||||||
|
ctx.fillRect(x, HEIGHT - barHeight - minHeight, barWidth - barGap, barHeight + minHeight); |
||||||
|
|
||||||
|
x += barWidth; // sets the gap between bars
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
renderFrame(); |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
html, body, controls { |
||||||
|
padding: 0 0; |
||||||
|
margin: 0 0; |
||||||
|
} |
||||||
|
body { |
||||||
|
position: fixed; |
||||||
|
top: 0; left: 0; |
||||||
|
right: 0; bottom: 0; |
||||||
|
z-index: -1; |
||||||
|
} |
||||||
|
|
||||||
|
.invis {display: none;} |
||||||
|
|
||||||
|
#main { |
||||||
|
margin: 0 0; |
||||||
|
padding: 30px 35px; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
#main, #main * { |
||||||
|
background-color: black; |
||||||
|
color: #dadada; |
||||||
|
border-color: #a3a3a3; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
padding: 8px 10px; |
||||||
|
font-size: 1.2em; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
#controls .ctrl { |
||||||
|
background-color: #3d3d3d; |
||||||
|
border-radius: 3px 3px; |
||||||
|
margin: 10px 6px; |
||||||
|
} |
||||||
|
#controls input { |
||||||
|
padding: 6px 7px; |
||||||
|
margin-left: 0!important; |
||||||
|
} |
||||||
|
|
||||||
|
#controls button:not(.invis) {display: inline-block;} |
||||||
|
#controls input:not(.invis) {display: block;} |
||||||
|
|
||||||
|
#visual-master { |
||||||
|
/*position: fixed; |
||||||
|
left: 0; right: 0; |
||||||
|
height: max-content;*/ |
||||||
|
padding: 0 0; margin: 0 0; |
||||||
|
} |
||||||
|
#visualizer { |
||||||
|
position: fixed; |
||||||
|
left: 0; |
||||||
|
top: 0; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background: 0 0; |
||||||
|
display: none; |
||||||
|
pointer-events: none; |
||||||
|
opacity: 1; |
||||||
|
transition: opacity .75s ease-in; |
||||||
|
} |
Loading…
Reference in new issue