Skip to content

Instantly share code, notes, and snippets.

@Lordmau5
Last active October 26, 2025 15:17
Show Gist options
  • Select an option

  • Save Lordmau5/f9fbf60651c765fc61a50a27ebcf7d4a to your computer and use it in GitHub Desktop.

Select an option

Save Lordmau5/f9fbf60651c765fc61a50a27ebcf7d4a to your computer and use it in GitHub Desktop.

Revisions

  1. Lordmau5 revised this gist Jul 27, 2025. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions userscript.js
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.3.1
    // @downloadURL https://gist.github.com/Lordmau5/f9fbf60651c765fc61a50a27ebcf7d4a/raw/userscript.js
    // @updateURL https://gist.github.com/Lordmau5/f9fbf60651c765fc61a50a27ebcf7d4a/raw/userscript.js
    // @description Various extra functionality and fixes for the amazing site Keygenmusic.tk (Volume slider, Seekbar, Loop toggle, List fixes)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
  2. Lordmau5 revised this gist Aug 10, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion userscript.js
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.3.1
    // @description Volume slider and proper loop functionality (with total played increment)
    // @description Various extra functionality and fixes for the amazing site Keygenmusic.tk (Volume slider, Seekbar, Loop toggle, List fixes)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    // @icon https://www.google.com/s2/favicons?sz=64&domain=keygenmusic.tk
  3. Lordmau5 revised this gist Aug 10, 2023. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion userscript.js
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.3.1
    // @description Various extra functionality and fixes for the amazing site Keygenmusic.tk (Volume slider, Seekbar, Loop toggle, List fixes)
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    // @icon https://www.google.com/s2/favicons?sz=64&domain=keygenmusic.tk
    @@ -14,7 +14,9 @@
    TODO:
    - add download button
    - show current seekbar second when holding down and dragging, so you know where you "drop"
    - try to make audio not lag when scrolling (async-ify onaudioprocess?)
    ^ seems to mostly be fixed?
    */

    (function() {
  4. Lordmau5 revised this gist Dec 9, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion userscript.js
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.3.1
    // @description Volume slider and proper loop functionality (with total played increment)
    // @description Various extra functionality and fixes for the amazing site Keygenmusic.tk (Volume slider, Seekbar, Loop toggle, List fixes)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    // @icon https://www.google.com/s2/favicons?sz=64&domain=keygenmusic.tk
  5. Lordmau5 revised this gist Dec 9, 2022. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion userscript.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // ==UserScript==
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.3
    // @version 1.3.1
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    @@ -12,6 +12,8 @@

    /*
    TODO:
    - add download button
    - show current seekbar second when holding down and dragging, so you know where you "drop"
    - try to make audio not lag when scrolling (async-ify onaudioprocess?)
    */

    @@ -456,6 +458,8 @@ TODO:

    // Add new method to this bad boy
    window.ChiptuneJsPlayer.prototype.reloadConfig = function(config, processNode) {
    if (!processNode || !processNode.modulePtr) return;

    // eslint-disable-next-line
    _openmpt_module_set_repeat_count(processNode.modulePtr, config.repeatCount);
    // eslint-disable-next-line
  6. Lordmau5 revised this gist Mar 17, 2022. 1 changed file with 155 additions and 29 deletions.
    184 changes: 155 additions & 29 deletions userscript.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // ==UserScript==
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.2
    // @version 1.3
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    @@ -12,8 +12,7 @@

    /*
    TODO:
    - localstorage the shuffle state
    - show progress, perhaps even seekbar? (_openmpt_module_get_position_seconds, _openmpt_module_set_position_seconds)
    - try to make audio not lag when scrolling (async-ify onaudioprocess?)
    */

    (function() {
    @@ -23,6 +22,8 @@ TODO:
    let sliderValue = 0.5;
    let currentSong = '';

    let seeking = false;

    function toggleLoop() {
    if (!window.watcher) return;

    @@ -88,6 +89,12 @@ TODO:
    }

    function buildVolumeSlider() {
    /* Get LocalStorage Slider Value */
    const _raw_slider_volume = localStorage.getItem('slider_volume');
    if (!!_raw_slider_volume) {
    sliderValue = Math.max(0, parseFloat(_raw_slider_volume));
    }

    const div = document.createElement('div');
    div.id = 'volume_control';
    div.classList.add('center');
    @@ -105,17 +112,78 @@ TODO:
    input.type = 'range';
    input.min = '0';
    input.max = '100';
    input.value = '50';
    input.value = sliderValue * 100;
    input.oninput = onSliderInput;
    input.classList.add('volume_slider_input');

    innerDiv.appendChild(input);

    strong.innerText = `VOLUME ${input.value}%`;

    div.appendChild(strong);
    div.appendChild(innerDiv);

    return div;
    }

    function onSeekbarInput() {
    seeking = true;
    }

    function onSeekbarChange() {
    seeking = false;

    if (!window.player || !window.player.currentPlayingNode || !window.player.currentPlayingNode.modulePtr) return;

    const seconds = document.querySelector('.seekbar_input').value;
    window._openmpt_module_set_position_seconds(window.player.currentPlayingNode.modulePtr, seconds);
    }

    function onSeekbarMouseUp() {
    seeking = false;
    }

    function buildSeekbar() {
    const div = document.createElement('div');
    div.id = 'seekbar';
    div.classList.add('center');
    div.style = 'margin-top: 15px;';

    const strong = document.createElement('strong');
    strong.style = 'font-size: 19px; color: #a9b7c6;';
    strong.id = 'seekbar_text';
    strong.appendChild(document.createTextNode('00:00 / 00:00'));

    const innerDiv = document.createElement('div');

    const input = document.createElement('input');
    input.style = 'width: 30%;';
    input.type = 'range';
    input.min = '0';
    input.max = '0';
    input.value = 0;
    input.oninput = onSeekbarInput;
    input.onchange = onSeekbarChange;
    input.mouseup = onSeekbarMouseUp;
    input.classList.add('seekbar_input');

    innerDiv.appendChild(input);

    div.appendChild(strong);
    div.appendChild(innerDiv);

    return div;
    }

    function secondsToMinutes(seconds) {
    seconds = Math.floor(seconds);

    const minutes = Math.floor(seconds / 60);
    const remaining_seconds = seconds % 60;

    return `${minutes.toString().padStart(2, '0')}:${remaining_seconds.toString().padStart(2, '0')}`;
    }

    let times_played = 0;
    function loopObserver() {
    if (!window.player.currentPlayingNode || !window.player.currentPlayingNode.modulePtr) {
    @@ -129,6 +197,13 @@ TODO:
    // eslint-disable-next-line
    const song_position = _openmpt_module_get_position_seconds(window.player.currentPlayingNode.modulePtr);

    if (!seeking) {
    document.querySelector('.seekbar_input').value = Math.floor(song_position % song_length);
    }

    const seekbar_text = document.querySelector('#seekbar_text');
    seekbar_text.innerText = `${secondsToMinutes(song_position % song_length)} / ${secondsToMinutes(song_length)}`;

    const played = Math.floor(song_position / song_length);
    if (played > times_played && repeatCount === -1) {
    times_played = played;
    @@ -144,7 +219,7 @@ TODO:
    }

    function hookSearch() {
    if(!window.watcher || !window.watcher.search) {
    if(!window.watcher || !window.watcher.search || !window.search_input) {
    setTimeout(hookSearch, 100);
    return;
    }
    @@ -156,6 +231,10 @@ TODO:
    };
    window.watcher.search = hook_search;

    window.search_input.oninput = function() {
    hook_search(window.search_input.value);
    };

    console.log('[Hooked] window.watcher.search');
    }

    @@ -195,6 +274,10 @@ TODO:
    top: 18px;
    right: 0px;
    }
    #playlist td.heart span {
    color: #9daaaf;
    }
    </style>`);

    const table = document.querySelector('#playlist table');
    @@ -288,11 +371,11 @@ TODO:
    window.UI.renderPlaylist = hook_renderPlaylist;

    const table = document.querySelector('#playlist table');
    table.remove();

    const newTable = document.createElement('table');
    newTable.cellSpacing = '0';
    document.querySelector('#playlist .scroller').appendChild(newTable);
    if (table.firstChild) {
    while(table.firstChild) {
    table.firstChild.remove();
    }
    }

    window.UI.renderPlaylist(window.playList.song_lib, '');
    window.playList.setSelected('');
    @@ -313,19 +396,61 @@ TODO:
    };
    window.UI.scrollToSongInPlaylist = hook_scrollToSongInPlaylist;

    const old_play = window.watcher.play;
    const hook_play = function(song) {
    old_play(song);
    const old_updateSongInfo = window.watcher.updateSongInfo;
    const hook_updateSongInfo = function(song) {
    old_updateSongInfo(song);
    currentSong = song;
    playlistObject.refresh(document.querySelector('#playlist table tbody'), playlistConfig);

    document.querySelector('.seekbar_input').value = 0;
    };
    window.watcher.play = hook_play;
    window.watcher.updateSongInfo = hook_updateSongInfo;

    const observer = new MutationObserver(function(mutationsList, observer) {
    for(const mutation of mutationsList) {
    if (mutation.type === 'childList') {
    for (const tr of mutation.addedNodes) {
    if(!tr.hasAttribute('data-path')) continue;

    if(tr.getAttribute('data-path') === currentSong) {
    tr.classList.add('now_playing');
    }
    else {
    tr.classList.remove('now_playing');
    }
    }
    }
    }
    });

    observer.observe(document.querySelector('#playlist table tbody'), {
    attributes: false,
    childList: true,
    subtree: false
    });
    }

    function injectVolumeSlider() {
    function hookShuffle() {
    if (!window.shuffle || !window.playList || !window.UI) {
    setTimeout(hookShuffle, 1);
    return;
    }

    window.shuffle.addEventListener('change', () => {
    localStorage.setItem('shuffle', window.shuffle.checked);
    });

    /* Get LocalStorage Shuffle Value */
    const _shuffle_status = localStorage.getItem('shuffle');
    if (!!_shuffle_status) {
    window.shuffle.checked = _shuffle_status === 'true';
    window.playList.setCurrentPlayList(window.UI.getShuffleBtnStatus());
    }
    }

    function inject() {
    const player_container = document.querySelector('#player .container');
    if (!player_container || !window.ChiptuneJsPlayer) {
    setTimeout(injectVolumeSlider, 100);
    setTimeout(inject, 100);
    return;
    }

    @@ -339,6 +464,13 @@ TODO:
    _openmpt_module_set_render_param(processNode.modulePtr, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, config.interpolationFilter);
    };

    const old_ChiptuneJsPlayer_play = window.ChiptuneJsPlayer.prototype.play;
    window.ChiptuneJsPlayer.prototype.play = function(buffer) {
    old_ChiptuneJsPlayer_play.call(this, buffer);

    document.querySelector('.seekbar_input').max = Math.floor(window._openmpt_module_get_duration_seconds(window.player.currentPlayingNode.modulePtr));
    };

    // Hook search
    hookSearch();

    @@ -348,26 +480,20 @@ TODO:
    // Observe loops
    loopObserver();

    // Hook shuffle button
    hookShuffle();

    // Add Loop Button
    player_container.appendChild(buildLoopButton());

    // Add Volume Slider
    player_container.appendChild(buildVolumeSlider());

    const _raw_slider_volume = localStorage.getItem('slider_volume');
    if (!!_raw_slider_volume) {
    sliderValue = Math.max(0, parseFloat(_raw_slider_volume));
    }

    const slider = document.querySelector('.volume_slider_input');
    slider.value = sliderValue * 100;
    slider.oninput = onSliderInput;

    const slider_text = document.querySelector('#volume_slider_text');
    slider_text.innerText = `VOLUME ${slider.value}%`;
    // Add Seekbar
    player_container.appendChild(buildSeekbar());

    setInterval(updateGainElement, 10);
    }

    setTimeout(injectVolumeSlider, 100);
    setTimeout(inject, 100);
    })();
  7. Lordmau5 revised this gist Mar 15, 2022. 1 changed file with 48 additions and 12 deletions.
    60 changes: 48 additions & 12 deletions userscript.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // ==UserScript==
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.1
    // @version 1.2
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    @@ -10,11 +10,18 @@
    // @require https://unpkg.com/[email protected]/dist/hyperlist.js
    // ==/UserScript==

    /*
    TODO:
    - localstorage the shuffle state
    - show progress, perhaps even seekbar? (_openmpt_module_get_position_seconds, _openmpt_module_set_position_seconds)
    */

    (function() {
    'use strict';

    let repeatCount = 0;
    let sliderValue = 0.5;
    let currentSong = '';

    function toggleLoop() {
    if (!window.watcher) return;
    @@ -156,8 +163,7 @@
    let playlistConfig = {};
    let playlistSongs = [];
    function hookPlaylist() {
    // TODO: Use HyperList to create virtual list
    if (!window.UI || !window.UI.renderPlaylist) {
    if (!window.UI || !window.UI.renderPlaylist || !window.playList.song_lib || !window.playList.song_lib.length) {
    setTimeout(hookPlaylist, 1);
    return;
    }
    @@ -173,18 +179,18 @@
    }

    document.head.insertAdjacentHTML('beforeend', `<style>
    .track {
    #playlist .track {
    max-width: 420px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: clip;
    }
    .track_number {
    #playlist .track_number {
    min-width: 34px;
    }
    .heart {
    #playlist .heart {
    position: absolute;
    top: 18px;
    right: 0px;
    @@ -210,6 +216,9 @@
    track_element.classList.add('playlist_track');
    track_element.setAttribute('data-path', song.path);
    track_element.style.width = '100%';
    if (currentSong === song.path) {
    track_element.classList.add('now_playing');
    }

    /* Track Number */
    const track_number = document.createElement('td');
    @@ -274,16 +283,43 @@
    const hook_renderPlaylist = function(playlist, listName) {
    hookVirtualList(playlist);

    console.log(playlist);
    console.log(`Loaded playlist "${listName}" with ${playlist.length} songs.`);
    };
    window.UI.renderPlaylist = hook_renderPlaylist;

    const table = document.querySelector('#playlist table');
    if (table && table.firstChild) {
    while(table.firstChild) {
    table.removeChild(table.firstChild);
    table.remove();

    const newTable = document.createElement('table');
    newTable.cellSpacing = '0';
    document.querySelector('#playlist .scroller').appendChild(newTable);

    window.UI.renderPlaylist(window.playList.song_lib, '');
    window.playList.setSelected('');

    const old_scrollToSongInPlaylist = window.UI.scrollToSongInPlaylist;
    const hook_scrollToSongInPlaylist = function(song) {
    let id = -1;
    for (let i = 0; i < window.playList.selected.length; i++) {
    const _s = window.playList.selected[i];
    if (song === _s.path) {
    id = i;
    break;
    }
    }
    }

    if (id === -1) return;
    window.$scroller.scrollTop(id * 60);
    };
    window.UI.scrollToSongInPlaylist = hook_scrollToSongInPlaylist;

    const old_play = window.watcher.play;
    const hook_play = function(song) {
    old_play(song);
    currentSong = song;
    playlistObject.refresh(document.querySelector('#playlist table tbody'), playlistConfig);
    };
    window.watcher.play = hook_play;
    }

    function injectVolumeSlider() {
    @@ -306,7 +342,7 @@
    // Hook search
    hookSearch();

    // Hook Playlist for Virtual List
    // Hook playlist
    hookPlaylist();

    // Observe loops
  8. Lordmau5 revised this gist Mar 15, 2022. 1 changed file with 137 additions and 7 deletions.
    144 changes: 137 additions & 7 deletions userscript.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // ==UserScript==
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.0
    // @version 1.1
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    @@ -49,25 +49,25 @@
    function buildLoopButton() {
    const div = document.createElement('div');
    div.id = 'loop_control';
    div.className = 'center';
    div.classList.add('center');
    div.style = 'margin-top: 15px;';

    const strong = document.createElement('strong');
    strong.style = 'font-size: 19px; color: #a9b7c6;';
    strong.appendChild(document.createTextNode('LOOP'));

    const switchDiv = document.createElement('div');
    switchDiv.className = 'switch';
    switchDiv.classList.add('switch');

    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.id = 'loop';
    checkbox.className = 'switch-check';
    checkbox.classList.add('switch-check');
    checkbox.onclick = toggleLoop;

    const label = document.createElement('label');
    label.htmlFor = 'loop';
    label.className = 'switch-label';
    label.classList.add('switch-label');
    label.appendChild(document.createTextNode('Check'));
    label.appendChild(document.createElement('span'));

    @@ -83,7 +83,7 @@
    function buildVolumeSlider() {
    const div = document.createElement('div');
    div.id = 'volume_control';
    div.className = 'center';
    div.classList.add('center');
    div.style = 'margin-top: 15px;';

    const strong = document.createElement('strong');
    @@ -99,7 +99,7 @@
    input.min = '0';
    input.max = '100';
    input.value = '50';
    input.className = 'volume_slider_input';
    input.classList.add('volume_slider_input');

    innerDiv.appendChild(input);

    @@ -152,8 +152,138 @@
    console.log('[Hooked] window.watcher.search');
    }

    let playlistObject = false;
    let playlistConfig = {};
    let playlistSongs = [];
    function hookPlaylist() {
    // TODO: Use HyperList to create virtual list
    if (!window.UI || !window.UI.renderPlaylist) {
    setTimeout(hookPlaylist, 1);
    return;
    }

    const hookVirtualList = function(playlist) {
    playlistSongs = playlist;

    if (window.__hookedVirtualList) {
    playlistConfig.total = playlistSongs.length;

    playlistObject.refresh(document.querySelector('#playlist table tbody'), playlistConfig);
    return;
    }

    document.head.insertAdjacentHTML('beforeend', `<style>
    .track {
    max-width: 420px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: clip;
    }
    .track_number {
    min-width: 34px;
    }
    .heart {
    position: absolute;
    top: 18px;
    right: 0px;
    }
    </style>`);

    const table = document.querySelector('#playlist table');

    const table_body = document.createElement('tbody');

    playlistConfig = {
    itemHeight: 60,
    total: playlistSongs.length,
    useFragment: true,
    overrideScrollPosition() {
    return document.querySelector('.scroller').scrollTop;
    },
    generate(index) {
    const song = playlistSongs[index];

    // Outer track element
    const track_element = document.createElement('tr');
    track_element.classList.add('playlist_track');
    track_element.setAttribute('data-path', song.path);
    track_element.style.width = '100%';

    /* Track Number */
    const track_number = document.createElement('td');
    track_number.classList.add('track_number');
    track_number.innerText = song.n;
    /* ------------ */

    /* Track */
    const track = document.createElement('td');
    track.classList.add('track');

    const author = document.createElement('span');
    author.classList.add('rg');
    author.innerText = song.rg;

    const name = document.createElement('span');
    name.classList.add('soft');
    name.innerText = song.sn;

    const metadata = document.createElement('span');
    metadata.classList.add('mdt');
    metadata.classList.add('time');
    metadata.innerHTML = song.mdt || '&#8203;';

    track.appendChild(author);
    track.appendChild(document.createElement('br'));
    track.appendChild(name);
    track.appendChild(document.createElement('br'));
    track.appendChild(metadata);
    /* ----- */

    /* Favorite */
    const favorite = document.createElement('td');
    favorite.setAttribute('title', 'Favorite');
    favorite.classList.add('heart');
    if (window.playList.isFavoriteSong(song.path)) {
    favorite.classList.add('favorite');
    }

    const heart_span = document.createElement('span');
    heart_span.innerHTML = '&#10084';

    favorite.appendChild(heart_span);
    /* -------- */

    track_element.appendChild(track_number);
    track_element.appendChild(track);
    track_element.appendChild(favorite);

    return track_element;
    }
    };

    playlistObject = window.HyperList.create(table_body, playlistConfig);

    table.appendChild(table_body);

    window.__hookedVirtualList = true;
    };

    const old_renderPlaylist = window.UI.renderPlaylist;
    const hook_renderPlaylist = function(playlist, listName) {
    hookVirtualList(playlist);

    console.log(playlist);
    };
    window.UI.renderPlaylist = hook_renderPlaylist;

    const table = document.querySelector('#playlist table');
    if (table && table.firstChild) {
    while(table.firstChild) {
    table.removeChild(table.firstChild);
    }
    }
    }

    function injectVolumeSlider() {
  9. Lordmau5 created this gist Mar 15, 2022.
    207 changes: 207 additions & 0 deletions userscript.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,207 @@
    // ==UserScript==
    // @name Keygenmusic.tk Improvements
    // @namespace http://tampermonkey.net/
    // @version 1.0
    // @description Volume slider and proper loop functionality (with total played increment)
    // @author Lordmau5
    // @match https://keygenmusic.tk/
    // @icon https://www.google.com/s2/favicons?sz=64&domain=keygenmusic.tk
    // @grant none
    // @require https://unpkg.com/[email protected]/dist/hyperlist.js
    // ==/UserScript==

    (function() {
    'use strict';

    let repeatCount = 0;
    let sliderValue = 0.5;

    function toggleLoop() {
    if (!window.watcher) return;

    repeatCount = this.checked ? -1 : 0;
    window.player.config.repeatCount = repeatCount;

    window.player.reloadConfig(window.player.config, window.player.currentPlayingNode);
    }

    function onSliderInput() {
    sliderValue = this.value / 100;

    const slider_text = document.querySelector('#volume_slider_text');
    slider_text.innerText = `VOLUME ${this.value}%`;

    localStorage.setItem('slider_volume', sliderValue);
    }

    function updateGainElement() {
    if (!window.player) return;

    if (window.player.config) {
    window.player.config.repeatCount = repeatCount;
    }

    if (window.player.gainNode) {
    window.player.gainNode.gain.value = sliderValue;
    }
    }

    function buildLoopButton() {
    const div = document.createElement('div');
    div.id = 'loop_control';
    div.className = 'center';
    div.style = 'margin-top: 15px;';

    const strong = document.createElement('strong');
    strong.style = 'font-size: 19px; color: #a9b7c6;';
    strong.appendChild(document.createTextNode('LOOP'));

    const switchDiv = document.createElement('div');
    switchDiv.className = 'switch';

    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.id = 'loop';
    checkbox.className = 'switch-check';
    checkbox.onclick = toggleLoop;

    const label = document.createElement('label');
    label.htmlFor = 'loop';
    label.className = 'switch-label';
    label.appendChild(document.createTextNode('Check'));
    label.appendChild(document.createElement('span'));

    switchDiv.appendChild(checkbox);
    switchDiv.appendChild(label);

    div.appendChild(strong);
    div.appendChild(switchDiv);

    return div;
    }

    function buildVolumeSlider() {
    const div = document.createElement('div');
    div.id = 'volume_control';
    div.className = 'center';
    div.style = 'margin-top: 15px;';

    const strong = document.createElement('strong');
    strong.style = 'font-size: 19px; color: #a9b7c6;';
    strong.id = 'volume_slider_text';
    strong.appendChild(document.createTextNode('VOLUME 50%'));

    const innerDiv = document.createElement('div');

    const input = document.createElement('input');
    input.style = 'width: 30%;';
    input.type = 'range';
    input.min = '0';
    input.max = '100';
    input.value = '50';
    input.className = 'volume_slider_input';

    innerDiv.appendChild(input);

    div.appendChild(strong);
    div.appendChild(innerDiv);

    return div;
    }

    let times_played = 0;
    function loopObserver() {
    if (!window.player.currentPlayingNode || !window.player.currentPlayingNode.modulePtr) {
    times_played = 0;
    setTimeout(loopObserver, 100);
    return;
    }

    // eslint-disable-next-line
    const song_length = _openmpt_module_get_duration_seconds(window.player.currentPlayingNode.modulePtr);
    // eslint-disable-next-line
    const song_position = _openmpt_module_get_position_seconds(window.player.currentPlayingNode.modulePtr);

    const played = Math.floor(song_position / song_length);
    if (played > times_played && repeatCount === -1) {
    times_played = played;
    window.user.getTracksPlayed(function (num) {
    const new_count = parseInt(num, 10) + 1;
    window.model.write('tracks_played', new_count);
    window.UI.renderTracksPlayed(new_count);
    window.watcher.handleTotalPlayed();
    });
    }

    setTimeout(loopObserver, 100);
    }

    function hookSearch() {
    if(!window.watcher || !window.watcher.search) {
    setTimeout(hookSearch, 100);
    return;
    }

    const old_search = window.watcher.search;
    const hook_search = function(val) {
    window.UI.renderPlaylist(window.playList.setSelected(val), val);
    window.playList.setCurrentPlayList(window.UI.getShuffleBtnStatus());
    };
    window.watcher.search = hook_search;

    console.log('[Hooked] window.watcher.search');
    }

    function hookPlaylist() {
    // TODO: Use HyperList to create virtual list
    }

    function injectVolumeSlider() {
    const player_container = document.querySelector('#player .container');
    if (!player_container || !window.ChiptuneJsPlayer) {
    setTimeout(injectVolumeSlider, 100);
    return;
    }

    // Add new method to this bad boy
    window.ChiptuneJsPlayer.prototype.reloadConfig = function(config, processNode) {
    // eslint-disable-next-line
    _openmpt_module_set_repeat_count(processNode.modulePtr, config.repeatCount);
    // eslint-disable-next-line
    _openmpt_module_set_render_param(processNode.modulePtr, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, config.stereoSeparation);
    // eslint-disable-next-line
    _openmpt_module_set_render_param(processNode.modulePtr, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, config.interpolationFilter);
    };

    // Hook search
    hookSearch();

    // Hook Playlist for Virtual List
    hookPlaylist();

    // Observe loops
    loopObserver();

    // Add Loop Button
    player_container.appendChild(buildLoopButton());

    // Add Volume Slider
    player_container.appendChild(buildVolumeSlider());

    const _raw_slider_volume = localStorage.getItem('slider_volume');
    if (!!_raw_slider_volume) {
    sliderValue = Math.max(0, parseFloat(_raw_slider_volume));
    }

    const slider = document.querySelector('.volume_slider_input');
    slider.value = sliderValue * 100;
    slider.oninput = onSliderInput;

    const slider_text = document.querySelector('#volume_slider_text');
    slider_text.innerText = `VOLUME ${slider.value}%`;

    setInterval(updateGainElement, 10);
    }

    setTimeout(injectVolumeSlider, 100);
    })();