Local Music Artist Discovers That Playing Back Own Song Requires Advanced Degree in Apple Science

The breakthrough, which required circumventing multiple layers of what Bliziński described as “surreal user experience design,” came after a six-hour journey through what industry experts are calling “the pinnacle of digital hazing rituals.”

DUBLIN, IRELAND — Music artist Maciej Bliziński reportedly achieved enlightenment Tuesday after successfully tricking his iPhone into playing a song he created, sources confirmed.

The breakthrough, which required circumventing multiple layers of what Bliziński described as “surreal user experience design,” came after a six-hour journey through what industry experts are calling “the pinnacle of digital hazing rituals.”

“I mixed a song with birds positioned behind the listener,” said Bliziński, staring at his $500 AirPods Max with the thousand-yard stare of a war veteran. “Apparently, hearing it required becoming a systems engineer.”

The ordeal began when Bliziński discovered that his professionally mixed Dolby Atmos file could only be played back after converting it through proprietary software that costs more than most people’s cars, then following official Dolby instructions to compress the audio into a zip file “for reasons that remain classified by the Department of Homeland Security.”

Upon receiving the resulting MP4 file, the iPhone would cheerfully stop the song mid-chorus every time the screen dimmed. Researchers are calling this phenomenon “the QuickTime Paradox.”

“MP4 equals video player,” Bliziński recited with glazed eyes. “No video equals screen sleep. Screen sleep equals stop playing because video player. It’s flawless logic if you’re a computer having an existential crisis.”

When Bliziński attempted to outsmart the system using alternative software, he discovered that iOS had prepared a masterclass in psychological warfare. Upon detecting a non-Apple player, the system would cheerfully obliterate his spatial audio while maintaining the illusion that everything was functioning normally.

“iOS sees the now-stereo mix and goes, ‘Oh look, head tracking! Let me spatialize this corpse!'” Bliziński explained, his left eye developing a persistent twitch. “You turn your head and something happens, giving you the confidence that spatial audio is working, while your rear-positioned sounds are being quietly executed in a digital back alley.”

Bliziński confirmed he spent four weeks convinced he had broken his mixing software, repeatedly fixing a problem that existed only in his playback method.

“The system gaslights you with surgical precision,” he noted. “It’s like having a waiter who throws away half your order, brings you silverware with a smile, then asks if everything tastes okay.”

The solution, discovered after what Bliziński describes as “a vision quest through Apple’s Byzantine file system,” involved manually changing the file extension from .mp4 to .m4a—causing iOS to treat the identical file as if it had undergone digital gender reassignment.

“Same container, same audio, but different magical incantation,” Bliziński said. “Apparently in 2025, file extensions wield more power than examining actual file contents. It’s like putting gasoline in an electric car because it has racing stripes.”

Industry experts hailed Apple’s innovative approach to professional audio.

“This represents a quantum leap in user confusion,” said Dr. Sarah Chen, Professor of Digital Masochism at Dublin Institute of Technology. “Apple has successfully weaponized cognitive dissonance. The harder you try to do professional work, the more the computer fights you. Only the strongest survive, keeping the music producer herd healthy and lean. It’s Darwinian genius.”

Tech analyst Mike Rodriguez concurred: “Most companies just make their software work. Apple has created an entire philosophy around making you question your life choices, your career, and the fundamental nature of reality itself.”

At press time, Bliziński was reportedly listening to his three-minute song for the 47th consecutive time while researching whether other professions require this level of technological combat engineering to practice their craft.

“The birds sound correct now,” he confirmed, “though I’m no longer certain birds are real or if I’ve been hallucinating this entire timeline.”


Apple declined to comment, as they were reportedly busy designing a new feature that requires users to complete a doctoral dissertation before accessing their own photos.

Irish Rail Celebrates 30 Years of Platform Signs That Technically Instruct Commuters to Stand on Train Tracks

Transit authority maintains that “behind” definitely means what they think it means

DUBLIN—In a ceremony marking three decades of successfully endangering the linguistically literal, Irish Rail officials gathered Tuesday at Connolly Station to commemorate their iconic “Keep behind this line” platform warnings, which grammatically direct passengers to position themselves directly in the path of oncoming trains.

“We’re tremendously proud of our safety signage,” said Irish Rail spokesperson Siobhán O’Malley, standing confidently on the wrong side of the yellow line to demonstrate. “Sure, if you interpret ‘behind’ according to basic spatial relationships and the English language, it tells you to stand on the tracks. But that’s really more of a ‘you’ problem, isn’t it?”

The signs, which have graced DART platforms since 1994, represent what transit historians call “the most sustained commitment to semantic chaos in public transportation history.” When facing the tracks, being “behind” the yellow line would indeed place commuters in the path of trains traveling at speeds up to 100 km/h.

“Look, we’ve only had 47 incidents of confused philosophy students wandering onto the tracks while debating prepositional logic,” O’Malley added. “That’s well within acceptable margins.”

Dr. Fiona Walsh, a linguistics professor at Trinity College, praised Irish Rail’s dedication to creating what she termed “a daily epistemological crisis for anyone who actually reads signs.”

“It’s brilliant, really,” Walsh explained. “They’ve managed to create a safety warning that only works if you ignore what it says. It’s like Schrödinger’s cat, but for public transit—you’re simultaneously safe and in mortal danger depending on your relationship with language.”

Irish Rail’s commitment to confusing directional guidance extends beyond platform signage. Internal documents obtained by reporters reveal rejected alternatives including “Stand where you’re not supposed to not be” and “Remain in the area that isn’t where trains are, unless you’re reading this literally, in which case do the opposite.”

“We conducted extensive testing,” said senior engineer Paddy Murphy, who headed the original signage committee. “We tried ‘Stay platform-side of this line,’ but that was too clear. ‘Keep away from the platform edge’ made too much sense. We needed something that would really make people think, you know? Maybe panic a little. Keeps them alert.”

The transit authority has steadfastly refused calls to update the signage, with officials citing “tradition” and “the prohibitive cost of paint.” A 2019 proposal to change the wording to “Stay on the platform side of this line” was rejected after Irish Rail’s board determined it “lacked character” and “failed to provide commuters with their daily logical paradox.”

Meanwhile, the Dublin Association for Literal Thinkers continues to meet monthly in the Connolly Station car park, having been banned from platform areas after what officials described as “an epidemic of doing exactly what the signs say.”

“We’ve tried explaining that ‘behind’ means ‘in front of’ in this context,” said association president Michael O’Brien, “but then they asked us why we don’t just write ‘in front of’ if that’s what we mean, and honestly, we had no good answer.”

Irish Rail recently announced plans to expand their confounding safety program with new initiatives, including emergency exits marked “Do Not Use This Door” and fire extinguishers labeled “In Case of Fire, Panic.”

“If it ain’t broke, don’t fix it,” O’Malley concluded, gesturing toward a platform where confused tourists were engaged in an animated debate about spatial reasoning. “And even if it is broke, sure, people figure it out eventually. The ones that don’t, well… natural selection, innit?”

At press time, Irish Rail was reportedly considering updating their slogan from “We’re not there yet” to “We’re not here yet,” citing a desire to “maintain brand consistency with our signage philosophy.“​​​​​​​​​​​​​​​​

A solid block of solder

I was born in Warsaw in the 1970s, when Poland was still a communist country. Supplies of almost everything were incredibly scarce. People would queue for hours just to buy toilet paper. Electronics were simply unavailable. At the butcher shop, you’d often see empty hooks where sausages once hung.

When I was a child, my father wasn’t around much. He had his desk and equipment at home, but most of the time he was away working on cruise ships, earning money to build his home studio. For one of my birthdays, he bought me a small hi-fi system—a kid-sized setup with an amplifier, cassette player, and a pair of stereo speakers so I could play music in my room. Everything was connected with the kind of plugs that don’t exist anymore.

The problem with this equipment was that the cables broke quite easily, and I needed to fix them. With my father away, I’d go to his desk and find his soldering iron to try repairing them myself. I knew you needed tin solder for the job, so I used what was there. There was also a bottle of sulfuric acid that he used for something, but I didn’t understand what for. I decided not to use it because I knew it was dangerous. So I just tried to make do with this big block of solder that barely wanted to melt and the soldering iron, which was large and unwieldy. But I’d seen my father fix cables with it, so I thought, why shouldn’t I be able to?

The problem was that none of my solder joints held. I would try and try for hours—countless frustrated attempts at soldering. Just when I thought I was getting somewhere, and the connection seemed to stick, it would fall apart again. I didn’t know what to do. Sometimes I’d finally manage to solder something that would hold for a while, but then it would break again after just a few plugs and unplugs. I could kind of make it work, but it was incredibly frustrating.

Eventually, I thought, “Well, I’m just terrible at soldering.” That’s all there was to it—I was simply bad at it. I had to accept that fact and move on.

Then came 1989, a pivotal year when both my father passed away and communism ended in Poland. Suddenly we had something called pluralism—the ability to have more than one political party in the country. That was amazing news. You could also start businesses. Beginning in 1990, new shops started opening everywhere throughout Poland, including in my neighborhood.

In my neighborhood, a new hardware store opened. This was fascinating because there I saw a small soldering iron and a more modern type of solder to go with it—solder that included flux inside. It came in a small, transparent plastic tube with a blue cap, and the solder was coiled inside in the shape of a shiny helix. This solder was very soft and easy to work with, melting easily and allowing you to use just the right amount.

When I’d saved a little money, I went there and bought it. I must have been 15 or 16 years old at the time. Feeling a little anxious, I sat down at my desk to solder a broken plug.

And you know what happened? I soldered it perfectly on the first try. And it worked forever.

I couldn’t believe what I was seeing. I then proceeded to solder everything that needed soldering, or anything I knew was a little shaky—I’d just desolder it and solder it again. Everything was perfectly soldered within minutes. I couldn’t believe what had happened.

The next day at high school, I told my friends, “Hey, does anyone have anything to solder? Because I turned into a god of soldering overnight.” A few classmates gave me cables to fix from time to time.

That experience taught me something important. Sometimes frustration and failure do build skill—that’s one lesson. But there’s a second lesson: your skill is still useless until you find the right soldering iron and the right type of solder.

Core Audio API design: The Joy of Implementing C… in C

Designing an API is like writing a novel: each function is a chapter, with clear intent and a purpose all its own. You expect elegance, clarity, and narrative flow. But then, somewhere in the deepest, darkest corner of Cupertino, the Core Audio team must have collectively said, “Why use plain C when we can, in fact, make it feel like we’re re-inventing the wheel every step of the way?”

Enter Core Audio, where functions like AudioObjectGetPropertyData exist to taunt your common sense and break the sacred bond of function specificity. You see, when you design an API, the expectation is that each function does something distinct. You call getVolume(), and what do you expect? It gets the volume! And that’s the end of the story. No convoluted treasure hunt through void pointers, no obscure flags buried in a 200-page reference manual. But in Core Audio? Oh, no—this is a far more “adventurous” endeavor.

Imagine trying to get property data from an audio device. In any other universe, this would be a simple function with a descriptive name, arguments relevant to the task at hand, and—God forbid—a sensible return value. But no, in the bizarre alternate dimension that is Core Audio, we have AudioObjectGetPropertyData. You don’t just ask it to give you the damn data; you ask it, “Hey, here’s a void pointer, and a vague idea of what I want, now figure it out.” You’re basically passing a wish to a genie and hoping that the stars align.

It’s like asking someone to fetch an item from a warehouse by handing them a pile of unlabeled boxes, a cryptic scroll, and a flashlight with dead batteries. By the time you manage to get the function to work, you’ve lost three hours, a sizable chunk of your sanity, and all faith in the core principles of software design. Oh, and don’t forget the constant—you always need some bizarre constant to tell the function what you actually meant in the first place, because apparently naming functions by what they do is just too pedestrian.

This is C in C, ladies and gentlemen. Not the clear, crisp, and procedural C that Dennis Ritchie gave us, but some convoluted cousin that huffs paint thinner and wanders through life perpetually confused. You’re not writing code; you’re deciphering hieroglyphs, hoping that the vague shape you carve out of a void pointer will eventually give you something remotely useful. Every function is a mystery wrapped in an enigma wrapped in “please, for the love of all that is holy, just let me get my audio buffer.”

And can we talk about documentation? Sure, if you can find it. It reads like the fever dream of someone who spent too much time on the bitwise operations section of the C standard. “Here’s a function, and here’s 20 parameters, some of which might be void pointers. What do they do? We’ll let you figure that out.” Oh, and don’t even get me started on debugging. If you’re lucky, the whole thing won’t crash in the middle of rendering audio, but hey, hope is not a strategy, right?

In conclusion, the Core Audio API is like taking a road trip without a map, using a broken GPS, and every time you stop for directions, the locals give you answers in riddles. It’s a testament to the fact that just because you can design something a certain way doesn’t mean you should. Honestly, I’d like to send the Core Audio authors back to programming school with a simple assignment: “Write a C program that doesn’t make its users scream into the void (pun intended).”

Until then, keep a bottle of aspirin handy—you’re going to need it.

C++ Google coding style in 2024

When write C++, I stick to the Google C++ style guide and follow the ToTW tips. This includes not using exceptions, and instead reporting errors via a utility class such as absl::Status. Another favorite programming technique of mine, in C++, is RAII. Let’s review what are the currently available options, and how to program using those.

In my current tool chain, I’ve got GCC 12.2.0. This gives me the option to use -std=c++2b, that is C+23 with slightly incomplete implementation. For example, std::expected is available, but the monadic API like and_then and transform, are not available. Here’s a couple of examples of things I wanted to do and how I solved them for now. This isn’t to say that I necessarily think it’s the best way, but rather a diary of what I recently did. If you have a better idea, please share it!

At my job at Google, I used absl::Status and absl::StatusOr. While the Abseil project is publicly available, I realized that it’s quite a big dependency, and it would force me to use either CMake or Bazel for my builds, and I’m not keen on changing my build system just yet. I asked the upstream if there’s a chance of getting absl::Status as a standalone library; it’s not in the cards unfortunately. I asked ChatGPT to write me a replacement, and it did a pretty good job at it. It’s not half as versatile as absl::Status, but it works well enough for me. For the purpose of this blog entry, I’ll use an enum class.

There were more things I liked to use, like the CHECK macro. That I haven’t found a replacement for just yet.

Problem #1: Returning errors

enum class Error { InvalidArgument, Unavailable };

Now, let’s say that we call a function which can return either a value or an error. In C++23 we can do it using the std::expected library.

I like complete examples you can compile and run, so here goes:

// Compile with g++ -std=c++2b example_1.cc -o example_1
// Run with ./example_1

#include <iostream>
#include <expected>

enum class Error { InvalidArgument, Unavailable };

std::string ToString(const Error& e) {
  if (e == Error::InvalidArgument) {
    return "InvalidArgument";
  }
  if (e == Error::Unavailable) {
    return "InvalidArgument";
  }
  return "Unknown";
}

std::expected<int, Error> GetInt(bool want_success) {
  if (want_success) {
    return 42;
  }
  return std::unexpected(Error::Unavailable);
}

void Example(bool want_success) {
  auto result = GetInt(want_success);
  std::cout << "Got ";
  if (result.has_value()) {
    std::cout << result.value() << std::endl;
  } else {
    std::cout << ToString(result.error()) << std::endl;
  }
}

int main(int argc, char* argv[]) {
  Example(true);
  Example(false);
  return 0;
}

In a real code, you’d use something more powerful than a simple enum; you’d use something that can hold both the type of error and an error string.

Problem #2: Complex initialization

If we’re not using exceptions, we don’t have a way of returning an error from a constructor. After some searching I found a pretty good way of handling it. The rationale is well explained in https://abseil.io/tips/42.

The main wrinkle was that I can only make objects wrapped in std::unique_ptr, rather than values. The idea is to make the constructor private, and only allow creating objects via a factory function. The answer I saw (I can’t find it now) deleted the copy constructors and returned a std::unique_ptr<Foo>. This is annoying, because the factory function returns a std::expected<std::unique_ptr<Foo>, Error>, and to use my Foo instance, I have to go through two layers of indirection every time. Functions that take Foo& as an argument (e.g. DoSomething(Foo& foo)), require me to type DoSomething(*foo.value()); to invoke them, and that looks confusing. Calling methods takes the form of foo.value()->Name();. This is again confusing, because we call a function named “value”, and then we use the arrow -> on it, which is used on pointers, not on values! Of course, what we got from .value() is a std::unique_ptr, which is technically a value, so it does technically make sense, but the code sure does look confusing.

#include <expected>
#include <iostream>
#include <memory>

enum class Error { InvalidArgument, Unavailable };

std::string ToString(const Error& e) {
  if (e == Error::InvalidArgument) {
    return "InvalidArgument";
  }
  if (e == Error::Unavailable) {
    return "InvalidArgument";
  }
  return "Unknown";
}

class Foo {
 public:
  static std::expected<std::unique_ptr<Foo>, Error> Create(bool want_success) {
    // Acquiring resources.
    if (want_success) {
      return std::unique_ptr<Foo>(new Foo());
    }
    return std::unexpected(Error::Unavailable);
  }
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;
  ~Foo() {
    // Possibly cleanup, since it's RAII.
    resource_ = 0;
  }
  std::string Name() { return "Foo " + std::to_string(resource_); }

 private:
  Foo() : resource_(42) {}

  int resource_ = 0;
};

void Example(bool want_success) {
  auto foo = Foo::Create(want_success);
  if (foo.has_value()) {
    std::cout << "Got " << foo.value()->Name() << std::endl;
  } else {
    std::cout << "Got " << ToString(foo.error()) << std::endl;
  }
}

int main(int argc, char* argv[]) {
  Example(true);
  Example(false);
  return 0;
}

Risky workaround: Move semantics

A possible way to get rid of std::unique_ptr in your factory function is enabling move semantics for your class. There isn’t an easy way to do it; while Foo(Foo&&) = default; is correct syntax, it will not do what you need. You’ll need to write logic to your destructor which will only free the resources when necessary, and you need to implement a move constructor and an assignment constructor, which will be responsible for moving resources between two objects. It’s relatively easy to get those wrong, and it’s easy to introduce bugs later, if you happen to add another resource to your class but forget to handle it in the move constructor. It’s likely better to return std::expected<std::unique_ptr<Foo>, Error> and suffer confusing-looking accesses rather than bugs in resource management.

Problem #3: Polymorphism and RAII

Building on the two earlier examples, what if we had a few subclasses and we wanted a factory function for all of these? I’d like to write something like this:

std::expected<AbstractFoo, Error> CreateAbstractFoo(bool want_success) {
  return Foo::Create(want_success);
}

Alas, the compiler tells us we can’t do that. std::expected can’t hold an abstract class. We could change the factory function signature to return std::unique_ptr<AbstractFoo>, and that would work, but we would be back to using *abstract_foo.value(). A slightly ugly way of doing it that I found, was to create an intermediate class, holding a std::unique_ptr<AbstractFoo> and forwarding calls to it. Here’s a complete example:

#include <expected>
#include <iostream>
#include <memory>

enum class Error { InvalidArgument, Unavailable };

std::string ToString(const Error& e) {
  if (e == Error::InvalidArgument) {
    return "InvalidArgument";
  }
  if (e == Error::Unavailable) {
    return "InvalidArgument";
  }
  return "Unknown";
}

class AbstractFoo {
 public:
  virtual std::string Name() = 0;
};

class Foo : public AbstractFoo {
 public:
  static std::expected<std::unique_ptr<Foo>, Error> Create(bool want_success) {
    // Acquiring resources.
    if (want_success) {
      return std::unique_ptr<Foo>(new Foo());
    }
    return std::unexpected(Error::Unavailable);
  }
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;
  Foo(Foo&&) = default;  // Allows returning Foo rather than
                         // std::unique_ptr<Foo>.
  ~Foo() {
    // Possibly cleanup, since it's RAII.
    resource_ = 0;
  }
  std::string Name() override { return "Foo " + std::to_string(resource_); }

 private:
  Foo() : resource_(42) {}

  int resource_ = 0;
};

class ConcreteFoo : public AbstractFoo {
 public:
  template <typename T>
  ConcreteFoo(std::unique_ptr<T> a_foo) : a_foo_(std::move(a_foo)) {}
  std::string Name() override {
    return a_foo_->Name();
  }

 private:
  std::unique_ptr<AbstractFoo> a_foo_;
};

std::expected<ConcreteFoo, Error> CreatePolymorphicFoo(bool want_success) {
  return Foo::Create(want_success);
}

void Example(bool want_success) {
  auto foo = CreatePolymorphicFoo(want_success);
  if (foo.has_value()) {
    std::cout << "Got " << foo.value().Name() << std::endl;
  } else {
    std::cout << "Got " << ToString(foo.error()) << std::endl;
  }
}

int main(int argc, char* argv[]) {
  Example(true);
  Example(false);
  return 0;
}

The constructor of ConcreteFoo is a template, so that it can be invoked with various classes, as long as they are movable / convertible to AbstractFoo.

One wrinkle to check is to add the keyword explicit to the constructor of ConcreteFoo, like so:

  template <typename T>
  explicit ConcreteFoo(std::unique_ptr<T> a_foo) : a_foo_(std::move(a_foo)) {}

If you do that, the code no longer compiles, you get error: could not convert a ‘Foo::Create(bool)()’ from ‘expected<std::unique_ptr<Foo>,[…]>’ to ‘expected<ConcreteFoo,[…]>’. The error message is pointing at the only line in the body of CreatePolymorphicFoo(). This is where the compiler implicitly inserts the constructor of ConcreteFoo. It’s convenient, as long as we’re aware of it. Another detail here is that we can’t get rid of std::move in the initializer. This is where we’re converting our smart pointer from the derived type to the base (abstract) type.

The ConcreteFoo class might be annoying; if you change the methods of AbstractFoo, that’s yet another class that you have to update. But it’s not without advantages. If you want, you can utilize it in testing, by writing a stub class, and injecting it into ConcreteFoo.

That’s all I have for now, the next step for me will be waiting until and_then is available.

Problem #4: text formatting

std::format seems unavailable on my system; I ended up using fmt::format. It works well, except when formatting std::chrono::time_point values on macOS.

Problem #5: C++ wrappers around C APIs

I use SDL2 in my code, which is a C API and doesn’t use RAII. In most cases, C++ wrappers are only responsible for calling one function at the end of a life of an object. For example, if you use SDL_Texture* texture, you need to run SDL_DestroyTexture(texture); to avoid leaking memory. You can write full classes to wrap SDL pointer types, but there’s a simpler way: std::unique_ptr with a custom deleter. Looking online I found a few ways of doing it, and the easiest to use was one using a struct.

namespace sdl {
  struct TextureDeleter {
    void operator()(SDL_Texture* tex) { SDL_DestroyTexture(tex); }
  };
  using Texture = std::unique_ptr<SDL_Texture, TextureDeleter>;
}  // namespace sdl

I like the elegance of this solution. Instantiating the wrapper is easy:

sdl::Texture texture(SDL_CreateTexture(renderer, ...));
if (texture == nullptr) {
  // error handling here
}

When you need to pass a raw pointer to your texture when using other SDL API functions, you use texture.get().

Conclusion

  • absl::Status replacement: had ChatGPT write me a subset of functionality
  • absl::StatusOr replacement: std::expected is slightly more verbose, but it works
  • CHECK macro: had ChatGPT write me a subset of functionality
  • RAII and complex initialization: factory functions
  • Text formatting (absl::Sprintf replacement): fmt::format

Tempos for dancing and moving

If you jump gently in place like when dancing or at a rally, you’ll notice that there’s a tempo at which jumping is comfortable. Slower than that, you’d need to jump too high and it’s tiring. Faster than that, you . For me it’s about 160 bpm. The tempo will be similar for most people, because it does not depend on body size or weight, but only on how high you’re jumping.

So if you take a person whose natural response to rhythmical music is jumping, and you play them a beat in 113 bpm, they will be confused. They might try jumping and realize that they have to leap up about 20cm into the air and it’s exhausting. Double the tempo (226bpm) will be too fast, it would be more like shaking rather than jumping.

But there are also people who are more used to ballroom dancing, which is based on stepping rather than jumping. Normal walking would be around 60-115 steps per minute. Step-based dances tempos generally range from about 70 bpm till 135 bpm, or 140-270 steps per minute. This covers almost the entire circle. I have some experience dancing forró; tempos around 70 bpm would be for training, and for dancing are around 80-120 bpm.

There are people who like swaying. My natural tempo for swaying would be around 70 bpm. And the most “unswayable” tempo would be on the opposite side of the circle, around 100 bpm. I tried swaying at 100 bpm, I can kind of do it, but it feels forced, unnatural, and tiring.

I tried to map the most basic types of moving to the music onto the circle of tempos, and here’s what I came up with.

Please take the above with a pinch of salt, every person is different, and I’m sure there’s someone who jumps at 113 bpm and sways at 95 bpm. But overall, I think these are the general tendencies.

So when you are working on a new piece of music, you can use the above to have an idea what kind of moves will be the most comfortable when listening to your tune.

The Circle of Tempos

It’s similar to the Circle of Fifths, and relates to musical tempos, in beats per minute.

The Circle of Tempos. 12 notches equals doubling of halving the tempo.

Let’s see some examples of metric modulations, using a simple drum beat.

A “3 over 2” metric modulation

A”3 over 2″ metric modulation moves us approximately 210°, or 7 notches on the circle, to the right. For example, from 60 bpm to 90 bpm, or from 90 bpm to 135 bpm.

A “2 over 3” metric modulation

The “2 over 3” modulation slows the beat down, for example from 120 bpm to 80 bpm, or from 90 bpm to 60 bpm. It’s the reverse of the “3 over 2” modulation, moving us 210° to the left.

A “group of 3” metric modulation

This type of modulation moves us 150°, or 5 notches on the circle, to the right, for example from 60 bpm to 80 bpm, or from 90 bpm to 120 bpm.

The “5 over 4” modulation moves us approximately 4 notches to the right.

There’s math behind it, but I’m not going to bother you with it. You can work it out yourself. But that math tells us that a metric modulation to move just one notch is too complex to be practical. It would have to been something like a “16 over 15” or something silly like that. I can more or less do “3 over something”, and just barely “5 over something”. Similarly with jumping to the opposite side of the circle, 6 notches.

Type of modulationTempo change (approx)
groups of 3 (from 4)5 notches faster
3 over 27 notches faster
2 over 37 notches slower
5 over 44 notches faster
4 over 54 notches slower
6 over 53 notches faster
5 over 63 notches slower

Starting a new tune in the tempo you want

Metric modulations can be very useful when playing live. A common problem is when the band gets engrossed in one tempo, and it’s hard to break out of it. If left unchecked, the band might carry over tempo from one tune to the other.

If you’ve just finished a tune in 120 bpm and you want to start the next one in 80 bpm, you can do the “2 over 3” modulation in your mind and you’ll be in the vicinity of the tempo you wanted, without having to consult the metronome.

Running a standalone JACK daemon after migration to PipeWire

I’ve recently had a bunch of problems with my audio setup on Linux after the migration to PipeWire. Here’s a summary for those who might run into similar problems.

  • System: Debian testing
  • Audio interface: Steinberg UR44C

First, jackd would no longer start. I worked around it by compiling jack2. But that also stopped working: the output volume dropped to almost zero. So I tried the PipeWire compatibility layer. But then I couldn’t open 44.1kHz sessions, and the 48kHz sessions would glitch. In total, I was stuck for about three weeks.

After resolving all the issues, I’m using standalone jack daemon for audio work. I was under the impression that audio was often glitching when using PipeWire as backend. But I also experienced some of glitching when working with jackd, so my assessment might be wrong. It could be because of audio plugins, or something else entirely. I’ll give PipeWire a go again soon.

Problem 1: Can’t start jackd, RequestRelease is not implemented

This was the first problem I encountered and the last problem I solved.

The main error message was “Problems starting jackd: Method RequestRelease is not implemented”. Asking on Reddit helped, I finally found out that the fix is to configure PipeWire to not reserve audio devices:

properties {
    alsa.reserve = false
}

By default, there’s no configure file where you can put this line, so first you need to copy the example config into /etc and edit it:

sudo cp /usr/share/pipewire/media-session.d/alsa-monitor.conf \
    /etc/pipewire/media-session.d/alsa-monitor.conf
sudo vim /etc/pipewire/media-session.d/alsa-monitor.conf

Then find the line with “alsa.reserve” and set it to false (no quotes). Save the file, and restart the pipewire service.

systemctl --user restart pipewire

I think the error message should be more friendly, for example, if PipeWire won’t release the device, it should say so explicitly and point the user at instructions on how to disable the reservation.

Problem 2: pw-jack ardour can’t open a 44.1kHz project

Solution: pw-metadata -n settings 0 clock.force-rate 44100

This is when working with Ardour and PipeWire as backend. I tried to open an Ardour project in 44.1kHz, using PipeWire compatibility mode.

pw-jack /opt/Ardour-6.9.0/bin/ardour6 /path/to/my_project.ardour

Ardour would say:

This session was created with a sample rate of 44100 Hz, but Ardour is currently running at 48000 Hz. If you load this session, audio may be played at the wrong sample rate.

Ardour, early 21st century

If you type “pw-jack -h”, it will tell you that it accepts “-s” as a command line argument to set the sample rate, but in my experience this doesn’t work. I asked about it on Ardour forums, and found out the solution, which is to temporarily override the sample rate for the PipeWire daemon.

pw-metadata -n settings 0 clock.force-rate 44100

This can be done as a regular user. To return to the default, use a 0 instead of 44100.

Maybe the JACK compatibility layer in PipeWire is incomplete or buggy.

Problem 3: Locally compiled jackd is almost mute

Solution: Compile version 1.9.19 and not 1.9.17 (default).

Before I figured out the alsa.reserve = false thing, I was trying different things to run a standalone jackd. For example, I downloaded jack2 sources, compiled them, and got a locally built jackd which didn’t throw the “Method RequestRelease is not implemented” error. (Not sure why, maybe it just didn’t try to do the DBus negotiation. Maybe it simply opened the audio device.)

This worked for a time, but recently the locally compiled jackd would output audio at a very low volume. Ardour output meters would show a loud output, but I had to turn the headphone volume knob all the way to the right to hear a distant echo of my recording.

The UR44C audio interface doesn’t seem to have an ALSA volume control. If I run alsamixer, press F6 and select UR44C, I get “This sound device does not have any controls.”

After some trial and error I realized that the problems seems to be specific to jack2 version 1.9.17, which is currently the default (branch “develop” is set to the “v1.9.17” tag), but there’s also a version 1.9.19 on github, and this one, compiled locally, worked just fine. Unfortunately I didn’t get to the bottom of it.


I hope this helps, if anyone runs into the same problems. Drop me a line if you figured out any further details!

My recorded track won’t sync

In the pandemic times many people try online music collaboration, and most people run into track synchronization issues. They’d play the backing track on one device (say, laptop) and record on another (say, phone). The recorded track would for the love of Zeus not want to synchronize with the backing track, no matter how much they dragged it left or right. It’s baffling and frustrating.

What causes this? Using two unsynchronized clocks [devices] does.

And how to do it correctly? Use one clock [device]. Make sure that that the same device plays the backing track(s) and records your performance.

Examples of setups that work:

  • A Digital Audio Workstation on a laptop or PC
  • A multitrack app on the phone (wired headphones recommended)
  • An audio recorder with the overdub function

Examples of setups that don’t work:

  • Any situation when you listen from one device and record on another.

You might think that clocks run at a constant speed, but it isn’t true. You can see it for yourself. Take any recording, about 5 minutes long, and import it into your DAW. Then play it back, and record it on your phone. Then import the recording from the phone into your DAW.

It won’t align.

It won’t align no matter how much you drag it back and forth. Either the beginning is in sync and the ending isn’t, or vice versa.

You might get away with short recordings, up to maybe 1 minute or minute and a half. They will still drift, just not enough for the drift to be perceptible.

Let’s say this represents the backing track:

A sequence of evenly spaced vertical lines on square paper.
You can imagine that lines are bar lines, or beats. Musical time.

Then you record your track on top of it, and you hope that it looks like this:

A sequence of evenly spaced vertical lines, which do not align with the grid of the squared paper.
The lines are off grid. You could drag them a little to the left and they would align again. But…

But in reality, your recorded track looks like this:

A sequence of unevenly spaced vertical lines, which also do not align with the grid.
Look closely. Lines are not spaced evenly. Even worse, the number of lines is different!

Compare it to the original:

The unevenly spaced sequence drawn next to the evenly spaced sequence. Corresponding vertical lines (number 1, number 2 and so on) are connected to show that misalignment grows to the right.
Comparison with the backing track shows that vertical lines are increasingly off grid.

How to fix this? You can’t easily fix a track that has been recorded this way. You would need to cut it into pieces and align each piece individually. I wholeheartedly advise against it. You will never be 100% sure if you aligned each piece correctly. You’ll also destroy any subtle timing properties of the recording. Maybe the musician wanted this phrase to be slightly behind the beat? It’s best to record the track again, sorry.

The solution is to record audio on the same device that plays the backing track.

It’s a subtle problem. Minuscule differences in clock speeds accumulate over time. A 0.02% imperfection in clock speed will be perceptible in a recording. You might think I’m crazy or pedantic saying that clocks of our phones and laptops are that inaccurate. But they really are! We are used to phones and laptops showing accurate time, but this is only because they synchronize time over the network. The source of accurate time is a set of atomic clocks.

If my laptop’s or phone’s clock speed is wobbly, how is it possible to ever record anything in sync? When you’re using a single device for recording, it will still speed up and slow down, but both playback and recording speed up and slow down together, and don’t drift apart.

I hope this article sheds some light on the problem, and that you see how the root of the issue is based in basic physics. I also hope I convinced you that if you want to record, you need to invest some time into learning a simple DAW, or get an audio recorder with the overdub function.

Problems logging into PSN

In the spirit of xkcd 979, let me describe something that recently happened.

My PS4 could not log into PSN, the PlayStation Network. It would show various error codes:

NW-31473-8
CE-38599-4

Then I logged out, and tried to log in by inputting the password. I saw a new error code:

NP-40831-6

I tried logging into PSN from my laptop, from the same home network. I saw:

Error
This page can't be displayed. Contact support for additional information.
The incident ID is: N/A.

Sometimes I’d see a long incident number. It would change with each attempt.

I called the PlayStation support center. They didn’t have any information about the error codes I saw. They checked that my IP address was not blocked. They suggested setting up port forwarding. I thought it would be an odd way of dealing with the logging in problem.

I rang up my ISP. The support person said that there’s a problem with my broadband connection and they’re going to send a technician. The technician detected that some cable outside of my house got rusty and the signal level went down from -1dB to -11dB. The technician fixed this. I waited maybe 3 days and my PS4 started logging into PSN without problems.

So, what was it? Why a weak broadband connection would result in PSN refusing logins from my home network? I don’t know! I could speculate but I have so little information that I would be almost certainly wrong.

Design a site like this with WordPress.com
Get started