Hacker Newsnew | past | comments | ask | show | jobs | submit | Bewelge's commentslogin

Very cool! Love the simplicity of this. Built a similar mode in Midiano (also a midi webapp, but focused on playing MIDI files). But this is much cleaner for a quick exercise. Mine never really got past the prototype stage though. Safe to say, I'll be coming back to this thread for ideas when I do get around working on it again :)

Thanks Bewelge! I took a look at Midiano - it's very polished! Were you inspired by Synthesia? I know it has a combination of falling note + sheet notation as well~

https://synthesiagame.com


Definitely inspired by it - I had been using Synthesia myself to play piano. But it started more as a "Oh you can use MIDI on the web?"-realisation and grew from there

To all non-Germans who might get scared by this blogpost:

While there's valid issues to complain about, this blogpost is really hyperbole. And frankly, to someone who has lived in the area, it reads purposefully disingenuous.

Just enter the places he mentions on Google Maps. Everything in NRW is so close together that to travel between cities you can often choose between international trains, regional trains or even just public transport.

The connection he needed, is serviced several times per hour by several different train lines.

Why did he stay on that specific train when he heard his stop would be skipped? The only reason I can think of is to write this blogpost. Since he's local to the area he should have known better.

Also it's worth noting that driving that same route by car, at that time, just a couple of hours before everyone starts their Christmas dinner, might've taken even longer.

I'm not trying to deny common issues with the DB but the author tried to travel through the densest urban area in the whole of Europe during the busiest 2-hour window of the whole year. AND he made a bad judgement call. To leave the transportation hub, staying on a long distance train which was already being re-routed.

Funnily enough, the fact that every "Kuhdorf" needs to be connected by train is one of the difficulties the DB faces for which there is no easy solution. And if a long-distance train needs to decide between dropping some stops which can also be reached by short-distance trains or delaying the whole train, I think that dropping those short-distance stops is absolutely the correct choice.


> Why did he stay on that specific train when he heard his stop would be skipped? The only reason I can think of is to write this blogpost. Since he's local to the area he should have known better.

Because the train didn't stop?


Only the first few notes

(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(); })();


Almost gave up getting this to work...

(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(); })();


Are you sure they are exempt? I was always under the impression that their practice is pretty obviously illegal. I just did a quick google search and didn't find anything about exemption. So they are as exempt from the GDPR as much as Al Capone was exempt from taxes ;)

What they seem to be exempt from is getting consent if they require the data for journalistic purposes.

IANAL, but I think they are simply not following the law and waiting for a definitive decision by a court.

ed: So I kept reading and from my understanding it's TBD whether the practice is lawful. The European Data Protection Board has issued an opinion against it a year ago.


the edpb did not. that was explicitly -- in the very first paragraph -- under the DSA, not GDPR:

> The scope of this opinion is indeed limited to the implementation by large online platforms

Separately, in the first couple of paragraphs, they basically complain that they don't like the alternative that platforms can legally implement of paywalls for all. :shrug: Which they may not like, but is legal. So consent or pay is essentially a realpolitik deal to not implement paywalls.


Can you explain how you mean this in the context of software?

What you describe sounds like the paradox of intolerance but I fail to see how that can be applied to free software.

Freedom in general: You can't have absolute freedom because that includes the freedom to take the freedom from others.

In software: You can't have absolutely free software because ... ? I fail to see how free software might infringe on the freedom of others.


Sure.

If you MIT license a critical piece of software instead of GPL it, what happens is someone will have a business idea on top of it. They implement their idea, then close source the new implementation.

They then bring in revenue. That revenue then enables them to amplify and accelerate their modifications. Eventually, they start patenting and commodifying their changes. More resources accelerate their closed-source implementation that quickly obsoletes the open source implementation.

Now, people are forced to use the closed source implementation or be left behind with an inferior tech. Because these things tend to optimize or otherwise accelerate them, they have no choice but to pay or trust this closed source implementation, and that creates a feedback loop.

They get more resources, build more closed source stuff, and rarely contribute back to the open backbone that enabled them to begin with.

You an extrapolate the pattern from here.


My experience has been that installing AdGuard on my iPhone made no noticeable difference. To be fair, I barely browse on my phone. Basically only news sites and Reddit/HN. But apart from HN I see ads on all of those pages.

So I am just a puzzled by your point of view :) May I ask which App you are using? I would love to be proven wrong and have an ad-free browsing experience in the future.


Not sure what to troubleshoot with AdGuard, but from consulting mine that's working well, I'd ensure that both "Safari protection" and "Advanced protection" are enabled in its app, and that all of its Safari extensions in the system Settings app are enabled (and the main one is set to "All Websites: Allow").


Ah thanks! I only have Safari protection enabled, the advanced one requires me to pay (though I don't remember that from when I installed it a couple of months ago).

Saw someone else in this thread mention the Orion browser - I will give that a try for now. If I'm not satisfied I'll try paying for AdGuard. Thanks for the reply though!


How would you implement that scrolling background? Particularly the mouse interaction, where it scrolls with a dynamic speed ( depending on how far to the side the mouse it) and even has some smooth damping when you stop scrolling.

And what part uses the visibility state? The grayscale seems to be triggered by focus which I think would be doable with CSS


> How would you implement that scrolling background?

At a glance I'm not sure exactly how I'd implement it, but my guess is that it's possible with some workarounds and hacks, at the very least.

Here's an article showing some demos (at the bottom) on how you can make the mouse cursor affect CSS: https://css-tricks.com/how-to-map-mouse-position-in-css/


Ah, I forgot that (everything except for the interaction should be okay, though). For the visibility state however, checking for focus is insufficient because you can have elements focused without the document being visible.


Scrolling using actual scroll - instead of following the pointer and automatically scrolling - would make the page better anyway IMHO


To be fair, for me at least, that weird chat bot only appears on https://help.shopify.com/ while the technical documentation is on shopify.dev/.

Everytime I land on help.shopify.com I get the feeling it's one of those "Doc pages for sales people". Like it's meant to show "We have great documentation and you can do all these things" but never actually explains how to do anything.

I tried that bot a couple of months ago and it was utterly useless:

question: When using discountRedeemCodeBulkAdd there's a limit to add 100 codes to a discount. Is this a limit on the API or on the discount? So can I add 100 codes to the same discount multiple times?

answer: I wasn't able to find any results for that. Can you tell me a little bit more about what you're looking for?

Telling it more did not help. To me that seemed like the bot didn't even have access to the technical documentation. Finding it hard to believe that any search engine can miss a word like discountRedeemCodeBulkAdd if it actually is in the dataset: https://shopify.dev/docs/api/admin-graphql/latest/mutations/...

So it's a bit like asking sales people technical questions.

edit: Okay, I should have tried that before commenting. They seem to have updated it. When I ask the same question now it answers correctly (weirdly in German) :

Die Begrenzung von 100 Codes bei der Verwendung von discountRedeemCodeBulkAdd bezieht sich auf die Anzahl der Codes, die Sie in einem einzelnen API-Aufruf hinzufügen können, nicht auf die Gesamtanzahl der Codes, die einem Rabatt zugeordnet werden können. Ein Rabattcode kann bis zu 20.000.000 eindeutige Rabattcodes enthalten. Daher können Sie mehrfach jeweils 100 Codes zum selben Rabatt hinzufügen, bis Sie das Limit von 20.000.000 Codes erreicht haben. Beachten Sie, dass Drittanbieter-Apps oder benutzerdefinierte Lösungen dieses Limit nicht umgehen oder erhöhen können.

~= It's a limit on the API endpoint, you can add up to 20M to a single discount.


> weirdly in German

I keep seeing bots wrongly prompted with both the browser language and the text "reply in the user's language". So I write to a bot in English and I get a Spanish answer.


> So it's a bit like asking sales people technical questions.

Maybe that's the best anthropomorphic analogy of LLMs. Like good sales people completely disconnected from reality, but finely tuned to give you just the answer you want.


Well no, the problem was that the bot didn't give them the answer they wanted. It's more like "finely tuned to waffle around pretending to be knowledgeable, but lacking technical substance".

Kind of like a bad salesperson, the best salespeople I've had the pleasure of knowing were not afraid to learn the technical background of their products.


The best anthropomorphic analogy for LLMs is no anthropomorphic analogy :)


Anthropomorphizing sales people involves the same constraints, so I'd allow it.


to be fair?


Not sure if I'm missing something but the way I'd always test orders is generate some 100% discount. You don't need any payment info then. I only ever needed a CC if I wanted to actually test something relating to payment. And on test stores you can mock a CC


That's a good way too for most cases. Unless you need there to be an amount


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: