MeadowlarkDAW / rainout

Cross-platform audio and MIDI IO
Other
49 stars 10 forks source link

Comments on DESIGN_DOC.md #4

Open ollpu opened 2 years ago

ollpu commented 2 years ago

As of 705d2f

Enumeration

Configuration

Startup/run

Wild idea

Doing input and output syncrhonized in the same callback prevents a certain feature that may or may not be desired.

When recording, it is often more important that the recording is flawless, while the live playback (through effects and such) is less important and if it does have dropouts, there's not much that can be done about it. If input and output are instead run asynchronous of each other, it is possible to always keep recording without dropouts regardless of how slowly the effects chain is running.

More specifically, we could have it so that the input side runs on schedule and sends data to be stored on disk (or memory), and also sends it to the output side for further processing. The output side can then slow down and drop out as much as it likes without affecting the input side.

Something akin to this is provided by JACK2 asynchronous mode, though the recording would have to happen in a separate client from the effects. With JACK, it is explicitly stated that asynchronous mode increases latency by one period. Some additional latency is inevitable, so I suppose this should be configurable.

Earlier I thought this might be a common feature in DAWs, but my experience might have been incidental. Even with synchronized I/O, there is some room for the output to drop out before any input data is lost. From what I was able to test (and read code), none of Bitwig, Ardour or Reaper have this feature. So I guess we can happily ignore it.

BillyDM commented 2 years ago
  • Maybe the backend could be described and selected as an enum? All variants should be available in the enum regardless if support for them is compiled in (unlike how it is in CPAL).

From the perspective of a platform-agnostic settings GUI that isn't concerned with what platform it is running on, it wouldn't make sense to expose all the backend types on every system as an option.

  • I don't think the backends should be split according to OS.

I mean, isn't that exactly what a platform-agnostic abstraction does? Like CoreAudio would only make sense in MacOS. The only exception maybe could be Jack, but even then there still might be platform-specific detail differences.

  • Returning all device info for all backends at once seems problematic. Some backends may take a while to "start up" and enumerate devices. Some may even hang in some cases.

Oh yeah, that is a great point. Yeah I'll probably change it so it only scans one backend/device at a time.

  • What happens if the set of available devices changes between enumeration and startup? It might be possible to accidentally try to open a completely different device, since they are only correlated by name.

Do you mean we should use device IDs or something? A lot of the time the name of the device is the ID, but I suppose adding the actual ID of a device wouldn't hurt.

  • I think there should be an option to skip input/output functionality. Or is that indicated by an empty vector?

Right, I suppose that could be confusing. I'll probably use a custom enum in place of Option then.

  • Should connecting to devices from different backends be supported?

Hmm, I suppose this might be possible. But is it really necessary?

  • In ALSA/Jack we can create anonymous clients that aren't necessarily tied to any available device.

The behavior is already to use "fake" buffers filled with silence for ports and controllers that can't be found. I suppose some backends could just register any invalid controllers and ports with unconnected ports/controllers in the audio server.

  • I suppose changing the MIDI config on the fly should also be allowed?

Right, definitely. I forgot to add that one.

  • Is the tuple necessary here instead of Variant(u32, u32)?

Rust marks it as an error if it's not a tuple.

  • Reporting estimated round-trip latency mainly serves to inform the end-user with a ballpark estimate. Sometimes that is all you can do of course, but some kind of timestamps are needed to properly correlate MIDI/other events with the audio clock.

Right, the purpose is just to be an estimate. I suppose I can do some name changing to make it more obvious.

When recording, it is often more important that the recording is flawless, while the live playback (through effects and such) is less important and if it does have dropouts, there's not much that can be done about it. If input and output are instead run asynchronous of each other, it is possible to always keep recording without dropouts regardless of how slowly the effects chain is running.

Well, a large goal of this is to have everything synced into a single process() callback, but I do get your point. Supporting this might depend on the backend and the device. I suppose later down the road we could add an extra option to record the inputs to disk on demand in a separate thread? Another option is to leave it up to the user to spawn their own high priority thread and sync buffers between the main audio thread and their own thread (which will create some delay of course).

ollpu commented 2 years ago

From the perspective of a platform-agnostic settings GUI that isn't concerned with what platform it is running on, it wouldn't make sense to expose all the backend types on every system as an option.

I suppose that makes sense. The enum wouldn't be meant to be iterated over though, just something that can be more cleanly matched against programmatically (the UI might need to be slightly different for each backend or you might want to add a textual explanation like in Bitwig). My gripe with CPAL's design is that it is a nightmare to programmatically select a backend that isn't always available, as evidenced by the dance that the examples have to do to support JACK. Even string identifiers is certainly a step up from that.

I mean, isn't that exactly what a platform-agnostic abstraction does? Like CoreAudio would only make sense in MacOS. The only exception maybe could be Jack, but even then there still might be platform-specific detail differences.

Mostly they would be dictated by OS of course, but I disagree with the way you've split the code into modules by OS currently. I'm thinking specifically of ASIO. It's a pain to compile, so it probably shouldn't be enabled by default. Same with JACK to some extent. So some backends aren't necessarily available even if the OS is appropriate. I doubt there's much code to be shared between backends on the same OS. CPAL's design seems reasonable in this regard.

Do you mean we should use device IDs or something? A lot of the time the name of the device is the ID, but I suppose adding the actual ID of a device wouldn't hurt.

Possibly, but maybe it'd make sense to keep device handles open in the driver. That definitely poses its own challenges though.

[separate MIDI backends] Hmm, I suppose this might be possible. But is it really necessary?

I don't know.

Right, the purpose is just to be an estimate. I suppose I can do some name changing to make it more obvious.

My main concern with the latency thing is it's not enough: timestamps for audio input and output times are necessary, as they are for MIDI events.

I see you've now added a delta on the MIDI events "The amount of time passed, in frames, relative to the start of the process cycle." However, I think that's misguided. The MIDI events might be older than the current audio input buffer, or they may even be newer. There's no guarantee of them being inside the buffer, and live playback either needs to add systematic latency to avoid issues like this, or be slightly off. I don't think the IO lib should worry about such buffering, but rather just provide the events when they are available. Recorded MIDI events should be as precise as they can be, though.

BillyDM commented 2 years ago

the UI might need to be slightly different for each backend or you might want to add a textual explanation like in Bitwig

Yeah, that's fair. I suppose we could return enum values instead of the name of the backend as a String.

It's a pain to compile, so it probably shouldn't be enabled by default.

Right, backends like Jack and ASIO will be an optional feature.

The MIDI events might be older than the current audio input buffer, or they may even be newer.

Hmm, good point. I suppose this ties into whatever Bitwig calls the "MIDI clock". Do you have more insight on the proper way to do this?

BillyDM commented 2 years ago

Okay, I commited a new API.

Still need to figure out the MIDI clock thing though.