diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e622cd65b7650db7c3543717e1ad52103ab08e5a..f725a49594ca8b8b1ec5847e2aef9a4ebe89825a 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,15 @@
     <option name="autoReloadType" value="SELECTIVE" />
   </component>
   <component name="ChangeListManager">
-    <list default="true" id="dc2eda89-ce72-4bfb-9c82-f9fb4ab5dde0" name="Changes" comment="Lets start over. (Not bumping version because thats stupid)" />
+    <list default="true" id="dc2eda89-ce72-4bfb-9c82-f9fb4ab5dde0" name="Changes" comment="Add NPM boilerplate">
+      <change afterPath="$PROJECT_DIR$/waves/examples/assets/noise.wav" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/waves/examples/embed_basic.html" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/waves/examples/player_basic.html" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/waves/src/embed.html" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/waves/src/station.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/waves/src/waves.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+    </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
     <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -16,6 +24,7 @@
         <option value="CSS File" />
         <option value="SCSS File" />
         <option value="JavaScript File" />
+        <option value="HTML File" />
       </list>
     </option>
   </component>
@@ -27,6 +36,22 @@
     </option>
     <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
   </component>
+  <component name="GitLabMergeRequestFiltersHistory"><![CDATA[{
+  "lastFilter": {
+    "state": "OPENED",
+    "assignee": {
+      "type": "org.jetbrains.plugins.gitlab.mergerequest.ui.filters.GitLabMergeRequestsFiltersValue.MergeRequestsMemberFilterValue.MergeRequestsAssigneeFilterValue",
+      "username": "bye",
+      "fullname": "Bye"
+    }
+  }
+}]]></component>
+  <component name="GitLabMergeRequestsSettings"><![CDATA[{
+  "selectedUrlAndAccountId": {
+    "first": "https://shinonome.rocks/bye/waves.git",
+    "second": "348e169e-b102-4043-9c6f-b59fa6d1c8d2"
+  }
+}]]></component>
   <component name="ProjectColorInfo">{
   &quot;associatedIndex&quot;: 0
 }</component>
@@ -38,10 +63,11 @@
   <component name="PropertiesComponent">{
   &quot;keyToString&quot;: {
     &quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
+    &quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
     &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
     &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
     &quot;git-widget-placeholder&quot;: &quot;master&quot;,
-    &quot;last_opened_file_path&quot;: &quot;/shop/code/waves&quot;,
+    &quot;last_opened_file_path&quot;: &quot;/shop/code/waves/waves/src/assets&quot;,
     &quot;list.type.of.created.stylesheet&quot;: &quot;SCSS&quot;,
     &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
     &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
@@ -57,9 +83,11 @@
 }</component>
   <component name="RecentsManager">
     <key name="CopyFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/waves/src/assets" />
       <recent name="$PROJECT_DIR$" />
     </key>
     <key name="MoveFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/waves/examples" />
       <recent name="$PROJECT_DIR$/public" />
       <recent name="$PROJECT_DIR$/src" />
       <recent name="$PROJECT_DIR$" />
@@ -120,7 +148,8 @@
       <workItem from="1740323680481" duration="8000" />
       <workItem from="1741975972036" duration="5487000" />
       <workItem from="1742126058949" duration="567000" />
-      <workItem from="1742499282237" duration="423000" />
+      <workItem from="1742499282237" duration="6271000" />
+      <workItem from="1742575186970" duration="7655000" />
     </task>
     <task id="LOCAL-00001" summary="Lets start over. (Not bumping version because thats stupid)">
       <option name="closed" value="true" />
@@ -130,7 +159,15 @@
       <option name="project" value="LOCAL" />
       <updated>1742499316615</updated>
     </task>
-    <option name="localTasksCounter" value="2" />
+    <task id="LOCAL-00002" summary="Add NPM boilerplate">
+      <option name="closed" value="true" />
+      <created>1742499870306</created>
+      <option name="number" value="00002" />
+      <option name="presentableId" value="LOCAL-00002" />
+      <option name="project" value="LOCAL" />
+      <updated>1742499870306</updated>
+    </task>
+    <option name="localTasksCounter" value="3" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">
@@ -149,6 +186,7 @@
   </component>
   <component name="VcsManagerConfiguration">
     <MESSAGE value="Lets start over. (Not bumping version because thats stupid)" />
-    <option name="LAST_COMMIT_MESSAGE" value="Lets start over. (Not bumping version because thats stupid)" />
+    <MESSAGE value="Add NPM boilerplate" />
+    <option name="LAST_COMMIT_MESSAGE" value="Add NPM boilerplate" />
   </component>
 </project>
\ No newline at end of file
diff --git a/waves/examples/assets/noise.wav b/waves/examples/assets/noise.wav
new file mode 100644
index 0000000000000000000000000000000000000000..c185070f3462bc5bc94d66c5c286dbcc1580898f
Binary files /dev/null and b/waves/examples/assets/noise.wav differ
diff --git a/waves/examples/embed_basic.html b/waves/examples/embed_basic.html
new file mode 100644
index 0000000000000000000000000000000000000000..2b4bb5edd039141704824a8c5c4229048f9fee30
--- /dev/null
+++ b/waves/examples/embed_basic.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>waves ~ basic embed</title>
+</head>
+<body>
+
+    <script type="module">
+        import Waves from "../src/waves.js";
+
+        const waves = new Waves();
+
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/waves/examples/player_basic.html b/waves/examples/player_basic.html
new file mode 100644
index 0000000000000000000000000000000000000000..4361383c1418cb6f232f5177d2a32858f020d2eb
--- /dev/null
+++ b/waves/examples/player_basic.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>waves ~ basic player</title>
+</head>
+<body>
+    <script type="module">
+        import Waves from "../src/waves.js";
+        import {URLStation} from "../src/station.js";
+
+        const waves = new Waves();
+        waves.loadingSoundElem.src = "./assets/noise.wav";
+        waves.loadingSoundElem.load();
+
+        const testStation = new URLStation("tilde", "https://tilde.radikan.byecorps.net/listen/tilde/radio.mp3");
+        waves.addStation(testStation);
+
+        waves.setStation("tilde");
+        waves.play();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/waves/src/embed.html b/waves/src/embed.html
new file mode 100644
index 0000000000000000000000000000000000000000..30f86f29be8cfa59e506462ac87ff3b224ebcbe8
--- /dev/null
+++ b/waves/src/embed.html
@@ -0,0 +1,10 @@
+<!--
+
+    waves
+    (c) 2025 bye
+
+    Code for quickly embedding waves on another website.
+
+-->
+
+
diff --git a/waves/src/station.js b/waves/src/station.js
new file mode 100644
index 0000000000000000000000000000000000000000..183c78aad6c03a522a843e625b28ce96ee72c065
--- /dev/null
+++ b/waves/src/station.js
@@ -0,0 +1,61 @@
+
+class Station {
+    constructor(name) {
+        this.name = name;
+        this.player = new Audio();
+
+        this.player.addEventListener("seeking", _ => {
+            console.log(`${this.name}: Started seeking... (${this.player.duration}, ${this.player.currentTime})`);
+        });
+
+        this.player.addEventListener("seeked", _ => {
+            console.log(`${this.name}: Seeked... (${this.player.duration}, ${this.player.currentTime})`);
+        });
+
+        this.metadataSource = undefined;
+    }
+
+    play() {
+        this.player.load();
+
+        this.player.addEventListener("canplay", _=>{
+            this.player.play();
+
+            console.log(`${this.name}: Started playing... (${this.player.duration}, ${this.player.currentTime}, ${this.player.src})`);
+        }, {once: true});
+
+    }
+
+    stop() {
+        this.player.pause();
+    }
+}
+
+class URLStation extends Station {
+    /**
+     * Defines a Station that plays from a streamed file.
+     *
+     * @param props
+     * @param url
+     */
+
+    constructor(props, url) {
+        super(props);
+        this.url = url;
+        this.doCaching = false; // Append a random number to the filename to avoid caching issues.
+
+        this.player.src = this.url;
+        this.player.crossOrigin = "crossorigin";
+    }
+
+    play() {
+        const url = new URL(this.url)
+        url.search = Math.floor(Math.random() * 10000)
+        this.player.src = url.toString()
+
+        super.play()
+    }
+}
+
+export default Station;
+export {URLStation};
diff --git a/waves/src/waves.js b/waves/src/waves.js
new file mode 100644
index 0000000000000000000000000000000000000000..03b5791f0626b71431323078a3abd5c4b522a5ff
--- /dev/null
+++ b/waves/src/waves.js
@@ -0,0 +1,92 @@
+import Station  from "./station.js";
+
+class Waves {
+    constructor() {
+        this.stations = {};
+
+        this.audioContext = new AudioContext();
+        this.gainNode = this.audioContext.createGain();
+
+        this.loadingSoundElem = new Audio(); // Can be set by client.
+        this.loadingSoundElem.loop = true;
+
+        this.currentPlayer  = new Audio();
+        this.currentStation = new Station();
+        this.currentTrack   = this.audioContext.createMediaElementSource(this.currentPlayer);
+        this.currentTrack.connect(this.gainNode).connect(this.audioContext.destination);
+
+        const loadingTrack = this.audioContext.createMediaElementSource(this.loadingSoundElem);
+        loadingTrack.connect(this.gainNode).connect(this.audioContext.destination);
+    }
+
+    setStation(station) {
+        this.currentStation.stop();
+
+        if (!this.stations[station]) {
+            throw ReferenceError("Station not found.");
+        }
+
+        this.currentStation = this.stations[station];
+        this.currentPlayer  = this.currentStation.player;
+        // this.currentPlayer.controls = "controls";
+        delete this.currentTrack;
+        this.currentTrack   = this.audioContext.createMediaElementSource(this.currentPlayer);
+        this.currentTrack.connect(this.gainNode).connect(this.audioContext.destination);
+
+        this.currentPlayer.addEventListener("playing", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.pause();
+        })
+
+        this.currentPlayer.addEventListener("play", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.pause();
+        })
+
+        this.currentPlayer.addEventListener("seeked", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.pause();
+        })
+
+        this.currentPlayer.addEventListener("seeking", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.play();
+        })
+
+        this.currentPlayer.addEventListener("waiting", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.play();
+        })
+
+        this.currentPlayer.addEventListener("stalled", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.play();
+        })
+
+        this.currentPlayer.addEventListener("ended", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.play();
+        })
+
+        this.currentPlayer.addEventListener("error", ev=>{
+            console.debug(`${station}:`, ev)
+            this.loadingSoundElem.play();
+        })
+    }
+
+    addStation(station) {
+        this.stations[station.name] = station;
+        console.log(`Added station ${station.name}`)
+    }
+
+    play() {
+        this.currentStation.play();
+    }
+
+    // Code that deals with embedding waves
+    embed() {
+        document.currentScript.insertAdjacentHTML("beforebegin", EMBED)
+    }
+}
+
+export default Waves;