Show HN: Boing
Posted by gregsadetsky 10 days ago
Comments
Comment by junon 10 days ago
setInterval(()=>{const canvas=document.getElementById('canvas');const startX=266;const startY=198;const rect=canvas.getBoundingClientRect();const startClientX=rect.left+startX;const startClientY=rect.top+startY;let endClientX,endClientY,distance;do{endClientX=Math.random()*window.innerWidth;endClientY=Math.random()*window.innerHeight;const dx=endClientX-startClientX;const dy=endClientY-startClientY;distance=Math.sqrt(dx*dx+dy*dy)}while(distance<25);const dispatchMouseEvent=(type,target,clientX,clientY)=>{const event=new MouseEvent(type,{view:window,bubbles:true,cancelable:true,clientX:clientX,clientY:clientY,screenX:clientX+window.screenX,screenY:clientY+window.screenY,buttons:type==='mouseup'?0:1,button:0});target.dispatchEvent(event)};dispatchMouseEvent('mousedown',canvas,startClientX,startClientY);setTimeout(()=>{dispatchMouseEvent('mousemove',window,endClientX,endClientY);setTimeout(()=>{dispatchMouseEvent('mouseup',window,endClientX,endClientY)},1);},1);},1);Comment by InsomniacL 9 days ago
Comment by Bewelge 9 days ago
(function () { function rateToDistance(rate) { const minR = 0.09; const maxR = 4.65; if (rate < minR) rate = minR; if (rate > maxR) rate = maxR; const t = (rate - minR) / (maxR - minR); return 400 * t; } function dispatchMouseEvent(type, target, clientX, clientY) { const event = new MouseEvent(type, { view: window, bubbles: true, cancelable: true, clientX, clientY, screenX: clientX + window.screenX, screenY: clientY + window.screenY, buttons: type === "mouseup" ? 0 : 1, button: 0, }); target.dispatchEvent(event); } const canvas = document.getElementById("canvas"); function triggerPull(distance) { const rect = canvas.getBoundingClientRect(); const startX = 266; const startY = 198; const startClientX = rect.left + startX; const startClientY = rect.top + startY; const endClientX = startClientX + distance; const endClientY = startClientY; return new Promise(resolve => { dispatchMouseEvent("mousedown", canvas, startClientX, startClientY); setTimeout(() => { dispatchMouseEvent("mousemove", canvas, endClientX, endClientY); setTimeout(() => { dispatchMouseEvent("mouseup", canvas, endClientX, endClientY); resolve(); }, 50); }, 50); }); } const semitones = 12; const notes = { G: Math.pow(4, -9 / semitones), A: Math.pow(4, -7 / semitones), B: Math.pow(4, -5 / semitones), C2: Math.pow(4, -4 / semitones), D2: Math.pow(4, -2 / semitones), E2: Math.pow(4, -0 / semitones), F2: Math.pow(4, 2 / semitones), G2: Math.pow(4, 4 / semitones), }; async function playWithPitch(rate) { const r = rateToDistance(rate); await triggerPull(r); } async function playScale() { const qrt = 200; const hlf = 400; const fll = 800; const pause = 15; const playNote = async (note, dur) => { await playWithPitch(note); await new Promise(res => setTimeout(res, dur)); }; const loop = async () => { await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.C2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G2, qrt); await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.C2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G2, qrt); await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await new Promise(res => setTimeout(res, pause)); await new Promise(res => setTimeout(res, pause)); }; await loop(); await loop(); await loop(); } playScale(); })();
Comment by port11 9 days ago
Comment by ianberdin 9 days ago
Comment by gregsadetsky 9 days ago
cheers, 3d is glorious.
Comment by ianberdin 5 days ago
Comment by blensor 9 days ago
Comment by junon 9 days ago
Comment by jesse__ 10 days ago
Comment by Bewelge 9 days ago
(function () { function rateToDistance(rate) { const minR = 0.09; const maxR = 4.65; if (rate < minR) rate = minR; if (rate > maxR) rate = maxR; const t = (rate - minR) / (maxR - minR); return 400 * t; } function dispatchMouseEvent(type, target, clientX, clientY) { const event = new MouseEvent(type, { view: window, bubbles: true, cancelable: true, clientX, clientY, screenX: clientX + window.screenX, screenY: clientY + window.screenY, buttons: type === "mouseup" ? 0 : 1, button: 0, }); target.dispatchEvent(event); } const canvas = document.getElementById("canvas"); function triggerPull(distance) { const rect = canvas.getBoundingClientRect(); const startX = 266; const startY = 198; const startClientX = rect.left + startX; const startClientY = rect.top + startY; const endClientX = startClientX + distance; const endClientY = startClientY; return new Promise(resolve => { dispatchMouseEvent("mousedown", canvas, startClientX, startClientY); setTimeout(() => { dispatchMouseEvent("mousemove", window, endClientX, endClientY); setTimeout(() => { dispatchMouseEvent("mouseup", window, endClientX, endClientY); resolve(); }, 50); }, 50); }); } const semitones = 12; const notes = { G: Math.pow(4, -9 / semitones), A: Math.pow(4, -7 / semitones), B: Math.pow(4, -5 / semitones), C2: Math.pow(4, -4 / semitones), D2: Math.pow(4, -2 / semitones), E2: Math.pow(4, -0 / semitones), F2: Math.pow(4, 2 / semitones), G2: Math.pow(4, 4 / semitones), }; async function playWithPitch(rate) { const r = rateToDistance(rate); await triggerPull(r); } async function playScale() { const qrt = 200; const hlf = 400; const fll = 800; const pause = 15; const playNote = async (note, dur) => { await playWithPitch(note); await new Promise(res => setTimeout(res, dur)); }; const loop = async () => { await playNote(notes.C2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.D2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G, hlf); await new Promise(res => setTimeout(res, pause)); await playNote(notes.D2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.E2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G2, qrt); await playNote(notes.F2, qrt); await playNote(notes.E2, qrt); await new Promise(res => setTimeout(res, pause)); await playNote(notes.C2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.D2, fll); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G, fll); await new Promise(res => setTimeout(res, pause)); await new Promise(res => setTimeout(res, pause)); await playNote(notes.G, qrt); await playNote(notes.G, qrt); await playNote(notes.A, qrt); await playNote(notes.C2, qrt); await new Promise(res => setTimeout(res, pause)); await playNote(notes.C2, qrt); await new Promise(res => setTimeout(res, pause)); }; await loop(); await loop(); await loop(); } playScale(); })();
Comment by meesles 10 days ago
Comment by ngkw 10 days ago
Comment by ivanjermakov 9 days ago
while true; do curl -X POST -Ss https://respected-accordion-31461.ondis.co/boing &; sleep 0.1; doneComment by gregsadetsky 9 days ago
Comment by junon 9 days ago
Comment by coffeecoders 10 days ago
It’s basically controlled sloppiness.
Comment by dataflow 9 days ago
Confused. Perfect physics means perfectly simulating reality, not perfectly simulating an unreal idealized formula. Are you saying Hooke's law doesn't feel realistic or are you saying a simulator for a realistic spring doesn't feel realistic?
Comment by luanmuniz 9 days ago
Comment by zamadatix 9 days ago
This leads to what GP was saying: many just cut things off at "Hooke's law simulates a spring, so I'll use that, but the rest is a bit too much to fit so I won't do it" but "Hooke's law simulates a spring but adding a bit of not-physics based fluff approximates all the rest" actually gives far superior results even though it doesn't only use perfect physics equations as the former did.
Comment by stavros 9 days ago
Comment by skrebbel 9 days ago
Comment by dom96 9 days ago
Comment by ngcazz 9 days ago
Comment by apgwoz 9 days ago
Comment by finger 9 days ago
Comment by marginalia_nu 9 days ago
To get somewhat more realistic model of a spring, you a damping term, which turns it into an ODE[1].
Comment by iamflimflam1 10 days ago
Comment by faeyanpiraat 9 days ago
Comment by SAI_Peregrinus 9 days ago
Comment by mythz 9 days ago
There's something therapeutic about door springs, that you just have to stop and play with it.
Comment by brcmthrowaway 9 days ago
Comment by defrost 9 days ago
* I recall when Cheryl had her first baby while at University. I also recall when she had her second.
~ https://en.wikipedia.org/wiki/Cheryl_Praeger
Maybe work on making more insightful and considered comments: https://news.ycombinator.com/threads?id=brcmthrowaway
Comment by analogears 9 days ago
Comment by dfex 10 days ago
Time to recreate the classic: https://www.youtube.com/shorts/pTgJaJYHIAs
Comment by mavamaarten 9 days ago
Comment by mhall 9 days ago
Comment by codeulike 9 days ago
If I bend it right round to one side so the spring is curved I expect it to bounce round to the other side.
Comment by gregsadetsky 9 days ago
thanks!
Comment by codeulike 9 days ago
Comment by reactordev 9 days ago
Comment by gnarlouse 9 days ago
Comment by gregsadetsky 9 days ago
Comment by gnarlouse 9 days ago
Comment by TheAceOfHearts 10 days ago
Comment by gregsadetsky 10 days ago
Comment by mmabbq 9 days ago
Comment by sixtyj 10 days ago
Comment by coffeecoders 10 days ago
Comment by lionkor 10 days ago
Comment by modeless 10 days ago
Comment by jonplackett 10 days ago
Comment by gregsadetsky 9 days ago
Comment by kentiko 9 days ago
Comment by gregsadetsky 9 days ago
In-memory ip address rate limiting.
Hosted and deployed on a ~$20 EC2 server using the open source tool I've been working on, https://disco.cloud/
We were at ~120 requests/second earlier and it took it on, no sweat.
Comment by jonplackett 8 days ago
Comment by jonplackett 8 days ago
Comment by gregsadetsky 8 days ago
Comment by efilife 8 days ago
Comment by doubleorseven 9 days ago
Comment by gregsadetsky 9 days ago
Comment by dmje 10 days ago
Comment by gregsadetsky 9 days ago
Comment by apgwoz 9 days ago
Comment by gregsadetsky 9 days ago
EDIT: done! deploying.
wow ok that was a really good idea.
Comment by dmje 8 days ago
Comment by dmje 9 days ago
Comment by alex440440 9 days ago
Comment by aetherspawn 10 days ago
Comment by gregsadetsky 10 days ago
Comment by pierrec 9 days ago
Ideal springs are a common, simple element in this field, but this kind of spring is very much not that.
You're probably better off improving the sample-based version by fading out the audio when necessary and using different samples based on the way it's triggered. If you have "ultra-dry" samples (maybe taken with a contact mic), you can add a convolution effect with a well-chosen impulse response, this will allow you to sharply cut off or adjust the audio and still have a natural-sounding tail.
Comment by gregsadetsky 9 days ago
If you don't mind humoring me (I'm quite the novice in this field), if I automated the recording of "all" possible positions for a spring (say I had a motor positioned in a way that would let me pull the spring in any polar direction), would that make modeling potentially easier?
There might be a "train an AI, here's 1000 recordings" angle, but I'm not necessarily interested in/asking about that.
Just strictly for modeling, would it help the R&D phase to have a lot of high sample rate recordings? Thanks a lot!
P.S. Also, if you have a good intro to DSP class/book, I'd love to hear it. I know about a few, but a recc is always appreciated
Comment by pierrec 9 days ago
I don't think that recording a large number of starting positions would help that much with creating a (non-ML) model, and I doubt a high sample rate would provide much useful information either. A more common approach would be to try getting separate sounds for the impulse and the resonant body, though they may be impossible to really separate, and the actual model may end up more complex than that.
You probably have a good starting point already with your code for the animated model. I think the sound mostly comes from the collision between coils (collisions not visible in your animated model), and almost entirely from the lowest couple of windings that are against the wall. This is your impulse. The resonant body might be in 2 parts: the wall and the long end of the spring. Your existing model can tell you when to trigger the impulses, and how much force to put into them.
For resources, one of my favorite intros to DSP is the one by Sean Luke: https://cs.gmu.edu/~sean/book/synthesis/
I wrote my own intro to physical modeling, though it focuses on different instruments: https://www.osar.fr/notes/waveguides/
Julius O. Smith has an encyclopedic amount of content on the topic, though it's often condensed into math that can be hard to apply: https://ccrma.stanford.edu/~jos/
Comment by gregsadetsky 9 days ago
Comment by prodigycorp 10 days ago
Comment by ethmarks 10 days ago
Comment by junon 10 days ago
Comment by cons0le 10 days ago
Comment by structuredPizza 9 days ago
Comment by cr125rider 10 days ago
Comment by victorbuilds 10 days ago
Comment by gnarlouse 10 days ago
Comment by gregsadetsky 9 days ago
Comment by gnarlouse 9 days ago
Comment by zer0tonin 9 days ago
Comment by texuf 10 days ago
Comment by gregsadetsky 10 days ago
That, or Settings -> Sounds & Haptics -> Silent Mode ?
Comment by gmac 9 days ago
Comment by satvikpendem 10 days ago
Comment by gregsadetsky 9 days ago
Comment by satvikpendem 9 days ago
Comment by ____tom____ 10 days ago
Safari, Mac.
Comment by mg 10 days ago
When wiggle the spring, keep the mouse inside the white area until it is at rest, press CTRL+u to see the source code, move the mouse to close the source code tab and close it - for some magical reason the spring is moving again for a little bit.
Comment by gregsadetsky 10 days ago
Just fixed, should be live soon.
Comment by egeres 9 days ago
Comment by qwertytyyuu 10 days ago
Comment by e1gen-v 9 days ago
Comment by OuterVale 10 days ago
Comment by sam-cop-vimes 10 days ago
Comment by HelloUsername 10 days ago
Comment by gregsadetsky 10 days ago
Comment by HelloUsername 9 days ago
Comment by gregsadetsky 9 days ago
There will also be no sound there if your phone is in Silence mode. However if Learning Synths works but not mine, then something else is happening.
Thanks!
Comment by HelloUsername 9 days ago
Probably because I have Lockdown mode enabled, and/or NextDNS
Comment by gregsadetsky 9 days ago
Comment by mrlonglong 9 days ago
Comment by cassettelabs 8 days ago
Comment by naich 9 days ago
Comment by uriee 9 days ago
Comment by gregsadetsky 9 days ago
Comment by ianberdin 9 days ago
three.js, audio generating.
Comment by ianberdin 9 days ago
Comment by Quizzical4230 10 days ago
Comment by foobarbecue 9 days ago
Comment by gregsadetsky 9 days ago
try reloading again?
Comment by foobarbecue 9 days ago
Comment by gregsadetsky 9 days ago
Comment by foobarbecue 9 days ago
Comment by abhinavsns 10 days ago
Comment by gregsadetsky 9 days ago
Comment by 29athrowaway 10 days ago
Comment by tdeck 9 days ago
Comment by oyaa52 6 days ago
Comment by xpe 9 days ago
Comment by Ylpertnodi 9 days ago
Comment by gregsadetsky 9 days ago
Comment by nopurpose 10 days ago
Comment by ku1ik 9 days ago
Comment by ProllyInfamous 9 days ago
https://www.decisionproblem.com/paperclips/
You can hate me and/or close the window at any point, friend...
Comment by arbol 9 days ago
Comment by arbol 10 days ago
Comment by ramnik10 10 days ago
Comment by brcmthrowaway 9 days ago
Comment by gregsadetsky 9 days ago
This is not an ad, there's no affiliate link... but the physics & drawing code were one shot by the recently released Gemini 3 Pro. It was pretty incredible to see. Additional tweaks & boing counter server by Claude Code.
Comment by bitcrshr 10 days ago
Comment by rezmason 10 days ago
Comment by thenthenthen 10 days ago
Comment by gregsadetsky 9 days ago
but I agree - I have some other mono-site-ideas like these in mind, and I think that the accelerator could be very fun. thanks for the suggestion!
Comment by jesse__ 10 days ago
Comment by johnwheeler 10 days ago
Comment by akho 10 days ago
Comment by gregsadetsky 9 days ago
This might have to wait for the native app versions ha.
Comment by fHr 9 days ago
Comment by karanveer 10 days ago
Comment by catapart 10 days ago
Comment by doppelgunner 10 days ago
Comment by supareya 10 days ago
Comment by badmonster 7 days ago