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

I think you misunderstood the comment you're replying to.

They meant (like Wikipedia says) that traditionally a tetrachord spans a perfect fourth (i.e., that's the interval between the first and last notes), not that it's a stack of perfect fourths.

As to the original point, any discussion about dissonance of chords is meaningless without mention of temperament: in the modern tuning system (equal temperament[1]) both major and minor triads are pretty dissonant, look at the thirds at the table in the section named "Comparison with just intonation" in that Wikipedia page.

We have been dealing with the tension between keeping thirds and fifths pleasant for centuries: very roughly, people used meantone temperaments[2] in the Renaissance (which had perfectly tuned thirds), then came the era of well temperament[3], and eventually we settled for the simpler equal temperament we use today with its nasty thirds (it has several other advantages, but "having pleasant major and minor triads" is not one of them).

[1] https://en.wikipedia.org/wiki/Equal_temperament

[2] https://en.wikipedia.org/wiki/Meantone_temperament

[3] https://en.wikipedia.org/wiki/Well_temperament


Thanks for filling in the detail.

It's explained in the article, but I want to emphasize that "well temperament" was not a single thing, but various separate attempts at compromise. Bach wrote https://en.wikipedia.org/wiki/The_Well-Tempered_Clavier seemingly to celebrate the idea of tunings that sound acceptable in every key (without the wolf fifths of meantone temperaments), but it's not at all clear what specific tuning he had in mind, if any:

> During much of the 20th century it was presumed, possibly mistakenly, that Bach intended equal temperament, which after Bach's death became popular as the standard keyboard tuning, and had been described by theorists and musicians for at least a century before Bach's birth. Accounts of Bach's own tuning practice are few and inexact. …Despite the presumption of equal temperament, research has continued into various unequal systems contemporary with Bach's career; there is debate whether Bach might have meant a range of similar temperaments, perhaps altered slightly in practice from piece to piece, or possibly some single, specific, "well-tempered" solution for all purposes…

Personally, while I know on some academic level that our 12edo minor and major thirds are off by something like 14 cents, they don't sound unpleasant to me. I think it takes a specific sort of ear training to become really, er, attuned to purely consonant intervals and to notice dissonance on that level (which only produces real clashes in the higher harmonics).


Even in reasonable time zones, significant distance between noon and 12:00 is inevitable. For example, Las Vegas and San Francisco are in the same time zone but one sees the Sun rise about half an hour earlier than the other.

Worrying about a couple of seconds or even minutes is completely insane for almost everyone.


> our only option is to not care about aligning clock time to the sun

No, an easy option is to simply shift every timezone by an hour and keep UTC intact. A ton of places already do that twice a year for daylight savings.

I never understood why we mess with UTC so much when we already have another widely used mechanism to keep the sun and clocks aligned.


Sure, but political timezones shifted by more than 12 hours 65,000 years in the future seems like the sort of thing that would really screw up our existing code. (I wonder what happens if you put such an entry in tzdata right now? Maybe we should put Null Island at +37:00 or something crazy like +1000:00 to start hardening now)

And maybe we should stop touching political timezones by then anyways.


In less than half a century, we've created 3 different time standards (TAI, GPS, and now UTC) that tick identically and differ only by arbitrary constant offsets (+37, +18 and +0 seconds). If we haven't come up with an elegant solution by then it's likely 65000 years from now most computing power will be spent running time-conversion libraries.


Time zones change all the time, often on amazingly short notice.

Random local government people declare it is to be, and ... as an industry we largely figure it out.

The more responsible governments give amble notice and as an industry we are very very good at that (you not realizing it, is an example).

And good riddance to leap seconds! It was too hard for us to figure out, and they didn't do anything useful for anyone.


Right, we have the capability to update timezone definitions in a standardized automatable way right now, but 14 hours is the furthest timezone offset a political timezone has ever had. The distant future will require the offset to drift by multiple earth rotations, which puts a wrench in the flexibility of existing systems even if the tzdata database compiles properly. For example, this would break Java's ZonedDateTime as it only supports offsets up to 18 hours.


Just define new epoch at the turn of a century. Like a "UTD" or "UT2" that begins like 2200/01/01 00:00 that is actually UTC 2199/12/31 11:43:21 or something. Then hardcode the magic TAI number in the headers along the gravity constant and such. Solves the time_t problem too if done every centuries; 2^32 seconds = 136.102208 years. The biggest concern will be that kids at that time will start asking why there was never been UTB and UTA, which our oldest grandkids wouldn't actually know why.


There is a bit of a fine line between caring about the future and not screwing them over, versus just being so arrogant as to think we know now the right thing to do for them.

But given the amount of time it is going to take to accumulate so much as another leap minute's worth of leap time, let alone an hour, I'm pretty confident we're in the "arrogant" branch if we start declaring what they need.

P(nobody cares about UTC anymore anyhow | someone had to solve the problem of incorporating non-terrestrial time which is way harder than leap seconds) would seem to me to approach 100%. You can decide which branch you want to take, but P(things just keep on keeping on without much change from today for the next several thousand years) seems not large enough to start trying to make lots of plans for.


He works in the engine/tool side of things. He worked on some widely used libraries, mainly Bink 2 (video codec) and Granny 3D (3D animation) used in a ton of shipped games.


Lisa's dictionary is different from widely-used real dictionaries. Here's what Merriam-Webster says[1]:

    Some assert that only envious is correctly used to describe someone who feels or shows a very strong desire for something that belongs to someone else, but in truth both envious and jealous commonly carry this meaning. Jealous alone is used to describe someone who tends to suspect unfaithfulness.
[1] https://www.merriam-webster.com/dictionary/envious


This problem of what exactly a color value means is mostly inconsequential when you have 8 bits per component, the difference in the denominator being either 255 or 256 makes the errors tiny, you must have really good color perception and get really close to the screen to see any difference at all, and your monitor/phone screen is probably not calibrated anyway, so who cares.

It becomes a pain in the ass when you're generating a VGA signal with a microcontroller with 8 color output pins (3 red, 3 green, 2 blue). The meaning of a color value is very real in this setup: it corresponds to a voltage level you must send to the VGA monitor, 0V-0.7V.

So the blue channel will map (0->0V, 1->0.23V, 2->0.47V, 3->0.7V), and the red/green will map (0->0V, 1->0.1V, ..., 7->0.7V). Notice how none of the blue voltages match any of the red/green ones (other than the extremes)? That means you don't get to see any pure grays -- the closest ones will have bit of blue or yellow tint, depending on the direction of the difference.

Not only that, any gradients at all (other than the ones not mixing blue with the other channels) will be noticeable off: for example, the closest colors in the line between pure red to pure white will all be slightly orange or purple.

Code for VGA output in 8-bit color with double-buffered 320x240 framebuffer for the Raspberry Pi Pico 2 here, if anyone cares: https://github.com/moefh/pico-vga-8bit-demo


> That means you don't get to see any pure grays -- the closest ones will have bit of blue or yellow tint, depending on the direction of the difference.

OMG I remember as a kid staring at static-y CRT displays, and seeing these faint blue and yellow lines at the borders of them. I’d always wondered why they appeared and why they were specifically blue and yellow. I finally know! (at least, assuming those specific artifacts are due to the same thing)


specifically on the edges? I would guess that is the phosphor layout, that the 'gray' beam is hitting a blue phosphor but not the red & green, or vice-versa.


You forgot about gamma correction. Before converting a value in the range of 0-255 into a voltage, PCs typically raise that value to the power of 2.2. This makes the difference between small values and large values far more apparent:

2^2.2 = 4.595, 255^2.2 = 196,964.699


Differences between small and large values are irrelevant to the point being made here, though. Much more relevant is the difference between nearby values, and the gamma just gets that closer to logarithmic perception, instead of perceptual steps being disproportionately large for small values.

(This may be more apparent when you frame gamma as being applied in the 0-1 range, so it doesn’t really turn 2 into 4.595 and 255 into ~200k; it turns (2/255)≈0.00784 into (2/255)^2.2 ≈ 0.0000233, and leaves (255/255)=1 as is.)


Dithering in time seems like the best solution to this problem. Delta sigma modulation per pixel can be done reasonably easily.

Changing at 30Hz I doubt a human can tell the difference between slightly blue and slightly yellow.


This is called frame rate control (FRC) in display terminology, at least when used for LCD displays where the panel has fewer distinct levels than the manufacturer wants to advertise.


PWM and FRC aka headache generators deluxe


> Notice how none of the blue voltages match any of the red/green ones (other than the extremes)? That means you don't get to see any pure grays

I assume this is why RGBI color was so common in the 80s.


I think they meant to write "There are about 4 billion TIMES more 64 bit integers than 32 bit integers".


Indeed, edited the mistake


Pretty nifty. As of now, the code doesn't compile: there's some stray "span" stuff in codegen.rs[1], and it's trying to format `Warning` which doesn't implement `Display` in main.rs[2].

Fixing these, it runs mostly as advertised, but it seems to assume that one-letter types are always generic parameters, so it's impossible to (for example) generate this:

    struct X;
    enum A {
        P(X),
        Q
    }
Trying this:

    (struct X)
    (enum A (P X) Q)
produces this:

    struct X;
    enum A<P, X> { Q }
while using a multi-letter type like `String`:

    (enum A (P String) Q)
produces the expected:

    enum A { P(String), Q }
One way to solve this would be to always require the generic annotation, and let it be empty when there are no generics, but when I tried that it did something weird:

    (struct X)
    (enum A () (P X) Q)
produces:

    struct X;
    enum A {
        _ /* List([], Some(Span { start: 54, end: 56 })) */,
        P(X),
        Q
    }
I have no idea where the `_` and the comment came from.

[1] https://github.com/ThatXliner/rust-but-lisp/blob/70c51a107b2...

[2] https://github.com/ThatXliner/rust-but-lisp/blob/70c51a107b2...


Nice write-up.

Let me offer a nitpck: in the "Gradual underflow" section it says this about subnormal numbers:

    Bonus: we have now acquired extra armour against a division by zero:

    if ( x != y ) z = 1.0 / ( x - y );
But that's not that useful: just because you're not dividing by zero doesn't mean the result won't overflow to infinity, which is what you get when you do divide by zero.

Think about it this way: the smallest subnormal double is on the order of 10^-324, but the largest double is on the order of 10^308. If `x - y` is smaller than 10^-308, `1.0 / (x - y)` will be larger than 10^308, which can't be represented and must overflow to infinity.

This C program demonstrates this:

    #include <stdio.h>
    #include <float.h>
    #include <math.h>

    // return a (subnormal) number that results in zero when divided by 2:
    double calc_tiny(void)
    {
        double x = DBL_MIN; // the normal number closest to 0.0
        while (1) {
            double next = x / 2.0;
            if (next == 0.0) {
                return x;
            }
            x = next;
        }
    }

    int main(void)
    {
        double y = calc_tiny();
        double x = 2.0 * y;
        if (x != y) {
            double z = 1.0 / (x - y);
            printf("division is safe: %g\n", z);
        } else {
            printf("refusing to divide by zero\n");
        }
    }
(It will print something like "division is safe: inf", or however else your libc prints infinity)


Other things worth noting about denormal numbers:

- It’s not just ‘old’ FPUs that handle them verrry slowly. Benchmark this aspect of your target processors if this detail matters.

- Most modern processors provide a “flush to zero” denormal handling mode (and sometimes you can separately specify “denormals are zero”, relevant when e.g. loading old data). However, math libraries usually haven’t been written with this mode in mind, so you need to be careful with this.


It's really annoying that IEEE set the exponent bias wrong. x!=0 => 1/x!=Inf was a totally achievable property if they had wanted it (by tweaking the implicit bias)


I'm not sure how this would be useful in Rust, but macros and tail calls are what allows one to (for example) write iterative loops in Scheme, which doesn't have a native loop syntax.

Maybe the same idea can be used in Rust where some constructs are easier to write in recursive form instead of a loop?

In any case, here's a silly example of a `for-loop` macro in Scheme using a tail call:

    (define-syntax for-loop
      (syntax-rules ()
        ((for-loop var start end body ...)
         (letrec ((loop (lambda (var)
                          (unless (>= var end)
                            body ...
                            (loop (+ var 1))))))  ; <-- tail call
           (loop start)))))
And here's how you'd use it to print the numbers 0 to 9:

    (for-loop i 0 10
              (display i)
              (newline))
This macro expands to a function that calls itself to loop. Since Scheme is guaranteed to have proper tail calls, the calls are guaranteed to not blow the stack.

(Note that you'll probably never see a `letrec` used like this: people would use a named `let`, which is syntax sugar for that exact usage of `letrec`. I wrote it the `letrec` way to make the function explicit).


Interesting, my lack of real experience in Scheme will make this take a bit more work for me to fully work through the implications of. It's not immediately clear to what this would mean for Rust, since there is already a loop construct (well, three of them, although two of them are syntactic sugar for the first). You could define a macro around it in Rust today, but it would be fairly uninteresting: https://play.rust-lang.org/?version=stable&mode=debug&editio...


Yes, I agree. Like I said, it might be useful when dealing with something that is easier to express in (tail) recursion form instead of an iteration.

Anyway, here's something more-or-less equivalent in Rust, which will blow the stack if made to loop too many times: https://play.rust-lang.org/?version=stable&mode=debug&editio...

(There may be a way to use a closure instead of a function to avoid hard-coding the type of `$i` in the macro, but I can't find an easy way to write a recursive closure call in Rust).


Consider applying for YC's Winter 2027 batch! Applications are open till November 2.

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

Search: