Awesome project! As a professional audio developer, I was really blown away that this was the author's first project working with audio.
For anyone interested, I'd recommend checking out "The Infinite Jukebox", which has a similar goal, but perhaps a more robust approach: http://infinitejukebox.playlistmachinery.com
If I had to guess at why your approach didn't work well on recorded music, it's probably because most of the time, there is more than a single event happening at a time, so picking out just the highest FFT bin is probably not a very robust "fingerprint" of that part of the music. The infinite jukebox uses timbral features as the fingerprint, rather than just a single note.
Thanks for the clarification. I guess like all trade-offs, it's context dependent. I see the advantages of having a realtime usable non-optimized build for debugging. Since I use modern libraries like Eigen, that option has not been available to me for some time.
With "modern" techniques, the performance ceiling is a bit higher - whether that benefit is worth it depends on a lot of factors.
I work using C++17 for high performance applications, and I can relate to a lot of these gripes. I think it's a fair point that C++ is unreasonably complex as a language, and it's been a serious problem in the community for a long time.
One part that really struck me as odd is the focus on non-optimized performance. To me, this is an important consideration, but not nearly as important as optimized performance. Using techniques like ranges can definitely slow down debug performance, but much of the time it _dramatically increases_ optimized performance vs. naive techniques.
How do ranges speed up optimized builds? One of the best techniques for very high performance code is separation of specifying the algorithm and scheduling the computation. What I mean by this is techniques like [eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page) and [halide](http://halide-lang.org) where you can control _what_ gets done and _how_ it gets done separately. Being able to modify execution orders like this is critical for ensuring that you're using your single-core parallelism and cache space in an efficient way. This sort of control is exactly what you get out of range view transformers.
This was done on a much larger scale (without primes) as a lottery game in Sweden called "Limbo" or "LUPI" (lowest unique positive integer). Several game theorists have analyzed the data with some interesting results:
Calculating the equillibrium strategy for rational actors is difficult because each player doesn't know how many other players there are. In the paper above, game theorists calculate it and show that the distributions seen in the lottery match up fairly well to a rational strategy.
Multi-armed bandit isn't an algorithm, it's a model of how to view the problem. Like it or not, the problem web designers face fits the multi-armed bandit model pretty well. The algorithm called "MAB" in the article is one of many that have been developed for multi-armed bandit problems. Traditionally, the "MAB" of this article is known as "epsilon-greedy".
The point of multi-armed bandit situations is that there is a trade-off to be made between gaining new knowledge and exploiting existing knowledge. This comes up in your charts - the "MAB"s always have better conversion rates, because they balance between the two modes. The "A/B testing" always gain more information quickly because they ignore exploitation and only focus on exploration.
I should say also that multi-armed bandit algorithms also aren't supposed to be run as a temporary "campaign" - they are "set it and forget it". In epsilon-greedy, you never stop exploring, even after the campaign is over. In this way, you don't need to achieve "statistical significance" because you're never taking the risk of choosing one path for all time. In traditional A/B testing, there's always the risk of picking the wrong choice.
You aren't comparing A/B testing to a multi-armed bandit algorithm because both are multi-armed bandit algorithms. You're in a bandit situation either way. The strategy you were already using for your A/B tests is a different common bandit strategy called "epsilon-first" by wikipedia, and there is a bit of literature on how it compares to epsilon-greedy.
I find it interesting that there is no mention of using dependently typed languages or proof engines for this application. Something like Coq, where you write the formal proof of correctness as you write the program, would fit the bill nicely if you really care about safety over ease of implementation.
I have mixed feelings about this book. It's how I learned electronics, so I can't knock it too much. However, sometimes the explanations, which tend to be intuitive rather than logical, can be quite hard to follow for someone who is more methodically-minded. So, for the nitty-gritty analog stuff in the bipolar transistor chapter of AoE, I much prefer "Design of Analog Integrated Circuits" by Gray and Meyer. It works for discrete circuits, too, even though integrated is in the name.
64 bit x86 is very similar to 32 bit. The differences are covered in on slides 191-193 in this deck.
The biggest difference for me is the difference in the calling convention. In 32 bit, all arguments generally are placed on the stack for "standard" calls. In 64 bit, different OSes have different conventions:
While I agree that this is totally possible, are there any shipping, non-buggy optimizers that do this? Sometimes C/C++ seems purposely designed to make the compiler/optimizer's job difficult.
As a thought experiment, let's consider a pulse that has been band-limited to 20kHz. Are you arguing that the analog output of a (filtered, idealized) DAC would look different depending on whether the dac was running at 44.1kHz vs 192kHz? If so, I don't think many people would agree with you.
Any difference in the "timing" of the output wave would have to come from energy that falls above nyquist of the slower sample rate. So, while I agree with you that the timing would be sharper, this is exactly caused by "higher frequencies", not by some other sort of timing improvement.
This is completely incorrect, by shannon (http://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_samplin...). The sampling frequency determines the maximum frequency that can be captured, not the temporal resolution. That said, a transient containing higher frequencies will be sharper than a transient that doesn't, but its onset time resolution will not be determined at all by the sample rate.
Said another way, two band limited pulse signals with different onset times, no matter how arbitrarily close, will result in different sampled signals.
I completely agree. It's hard for beginners to learn that a monad isn't a thing, it's an property that can apply to many different things. A monad is so general it's hard to have a single intuition about it.
I think perhaps another reason why beginners find monads so hard to understand, especially those coming from a math background, is that >>= is a bizarre presentation of monads. I think, for example, defining a monad as an applicative with a join operation would have been a much better idea - most monads, like Maybe or [], make the most sense to me as "containers" that can be joined. The bind operator never really made intuitive sense to me when thinking about a monad as a container with a structure. Plus, to me, m (m a) -> m a is a lot easier to recognize than m a -> (a -> m b) -> m b.
This is especially true since applicatives are now becoming a very popular language idiom. It seems unfair to beginners to make them try to understand applicatives and then make them learn an unrelated typeclass for monads, when mathematically the two are very similar objects.
Neither solution is great. Excessive use of return and continue is an incredibly fast way of making code completely incomprehensible.
Such complicated control flows should be treated as a major, fundamental problem. Keeping them in place will cause bugs and prevent future contributors from having confidence that their changes are correct. The problem of complicated control flow is too deep to be solved with a band-aid like this, although perhaps syntactically it looks a bit nicer. In fact, this is exactly what people did before the so-called "structured programming adepts" came around, except using gotos instead of continues. It didn't work so well back then, and it doesn't work so well now.
Sure, having a virtual human try to solve the problem would be pretty good. However, even the virtual human would never be able to check the specification correctly for every possible program due to the halting problem. In particular, no matter what the specification was, a malicious user could create a program that mimics the virtual human's response and then does the opposite thing (it's the same basic proof that the halting problem is undecidable). By construction then, the virtual human would answer incorrectly when asked whether or not the program matches the specification.
So, academically, even the "virtual human" approach doesn't work since it doesn't answer correctly for every single program. Practically, you're right, having a virtual human would be good enough for most uses.
That said, I wouldn't want to use a virtual human for this due to ethical concerns :-).
I agree with the sentiment that the problem the OP brings up is difficult, but bringing up the halting problem here seems pretty pedantic.
If you restrict yourself to a turing incomplete language, checking a specification no longer reduces to the halting problem. In fact, there are many systems that do exactly this, and most of these systems are perfectly capable of sorting a list and proving that the list is sorted.
However, I think the real point is that there are at least dozens of smart people who are working on this exact problem all day, every day. The OP shows neither a deep understanding of why previous attempts at this have failed or really, any knowledge at all about the state of the field of programming language research.
For those interested in an open source design embodying these techniques, I have an open source hardware design here: https://github.com/russellmcc/dco with a blog write-up here: https://www.russellmcc.com/posts/2013-12-01-DCO.html .
Also of relevance is an open source hardware design for the later roland alpha juno digital oscillator design: https://github.com/russellmcc/alphaosc https://www.russellmcc.com/posts/2019-06-14-Alpha-DCO.html