The thing is it is not so easy to continue after a while, this might help if you have a
handy..
* Install the script (google for "tempermonkey" or "greasemonkey" you will find it how)
* Open the tease on
oeos.art
How it works in a sentence:
It watches the html dom and if a new sound gets added / removed, it checks the filename if it has *bpm / *bps / *ps in the filename it sets the handy via api, so it would reach that speed / stops it.
(it might work with other teases if the audio filename matches..)
If it bothers you that the handy stops when the page changes, there is a comment in the code: remove the next 3 lines
Just remove them.
Note: you can use the buttons on the handy to stop / move the stoke zone, but it will be changed in the next page.
Code: Select all
// ==UserScript==
// @name EOS
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://oeos.art/?id=*
// @icon https://www.google.com/s2/favicons?domain=oeos.art
// @grant none
// ==/UserScript==
(function() {
'use strict';
const maxStrokeSpeed = 400;
const maxStrokeLength = 190;
const apiKey = 'YOUR_HANDY_API_KEY'; // api key
const api = "https://www.handyfeeling.com/api/handy/v2";
let lastSpeed = 0;
const sendRequest = (url, data, verb, onResponse) => {
let myHeaders = new Headers();
if (verb.trim().toLowerCase() !== 'option'){
myHeaders.append('X-Connection-Key', apiKey);
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Accept', 'application/json');
}
const options = {
method: verb,
headers: myHeaders,
mode: 'cors',
}
if (data) {
options.body = JSON.stringify(data);
}
console.log(url, 'request: ', options);
fetch(url, options)
.then(function(response) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json().then((r)=>{
console.log('response: ', r);
onResponse(r);
});
})
}
sendRequest(api + "/mode", {mode: 0}, 'PUT',(request) => {
sendRequest(api + "/slide", {min: 0, max: maxStrokeLength}, 'PUT',(request) => {
sendRequest(api + "/hamp/velocity", {velocity: 20}, 'PUT',()=>{
console.log('handy set');
});
});
});
/**
* Calculates the stroke speed and distance from the specified beats per second.
*
* Lookup Table
* ====================
* Speed / Stroke = BPS
* 400 / 80 = 5
* 400 / 100 = 4
* 400 / 133 = 3
* 400 / 200 = 2
* 300 / 200 = 1.5
* 200 / 200 = 1
* 100 / 200 = 0.5
* 50 / 200 = 0.25
*/
function getStrokeSpeedAndDistance(bps, maxSpeed = 400, maxLength = 200) {
maxSpeed = Math.min(maxStrokeSpeed, maxSpeed);
maxLength = Math.min(maxStrokeLength, maxLength);
let speed = maxSpeed;
let length = maxLength;
const fastestBPS = maxSpeed / maxLength;
// Decrease stroke speed to increase BPS
if (bps < fastestBPS) {
speed = maxLength * bps;
}
// Shorten stroke distance to increase BPS
if (bps > fastestBPS) {
length = maxSpeed / bps;
}
return {
speed,
stroke: length,
};
}
function changeSpeed(newState) {
sendRequest(api + "/hamp/state", null, 'GET',(response)=>{
console.log('state',response);
if (response.state !== 2){
sendRequest(api + "/hamp/start", null, 'PUT',(response)=>{
console.log('start')
sendRequest(api + "/hamp/velocity", {velocity: newState.speed / 4}, 'PUT',(request)=>{
console.log(request.speed)
sendRequest(api + "/slide", {min: 0, max: newState.stroke/2}, 'PUT',(request)=>console.log(request.speed));
});
});
} else {
if (lastSpeed !== newState.speed){
if (lastSpeed > newState.speed){
sendRequest(api + "/hamp/velocity", {velocity: newState.speed / 4}, 'PUT',(request)=>{
console.log(request.speed)
sendRequest(api + "/slide", {min: 0, max: newState.stroke/2}, 'PUT',(request)=>console.log(request.speed));
});
} else {
sendRequest(api + "/slide", {min: 0, max: newState.stroke/2}, 'PUT',(request)=>{
console.log(request.speed)
sendRequest(api + "/hamp/velocity", {velocity: newState.speed / 4}, 'PUT',(request)=>console.log(request.speed));
});
}
}
}
lastSpeed = newState.speed;
});
}
const mainWrapperCallback = (records, ob) => {
records.forEach((record)=>{
if (record.target.id === 'oeos-sounds'){
const data = record.target.querySelector('data');
if (data){
const match = data.value.match(/([1-9].+)bpm/);
if (match){
const bpm = parseInt(match[1],10);
console.log('bpm',bpm);
const newState = getStrokeSpeedAndDistance(bpm/60);
changeSpeed(newState);
} else {
const bpsMatch = data.value.match(/([1-9].+)bps/);
if (bpsMatch){
const bps = parseInt(bpsMatch[1],10);
const newState = getStrokeSpeedAndDistance(bps);
changeSpeed(newState);
} else {
const bpsMatch = data.value.match(/([1-9].+)ps/);
if (bpsMatch){
const bps = parseInt(bpsMatch[1],10);
const newState = getStrokeSpeedAndDistance(bps);
changeSpeed(newState);
}
}
}
} else {
// remove the next 3 lines
sendRequest(api + "/hamp/stop", null, 'PUT',(response)=>{
console.log('stop', response);
});
}
}
});
};
const mainWrapper = document.querySelector('.v-main__wrap');
// Create an observer instance linked to the callback function
const wrapperObserver = new MutationObserver(mainWrapperCallback);
// Start observing the target node for configured mutations
wrapperObserver.observe(mainWrapper, { attributes: false, childList: true, subtree: true });
})();