Skip to content

Sample playing is not idempotent/isolated from what internally the Pool does (i.e. VolumeNode incorrect smoothing) #132

@deavid

Description

@deavid

This project seems to be an audio engine for games, not a DAW. However, in some aspects it seems to be behaving more like a DAW because the internal architecture is leaking out.

When used through bevy_seedling, one would expect that spawning a new sound with an effect chain, creates a sound going through the FX chain in an isolated manner - this is - independent and isolated from what other parts of the code are playing or have played before.

(NOTE: Opening the issue here and not on bevy_seedling repo because it seems that this is a bug of Firewheel and bevy_seedling is just selling the idea of an isolation that does not exist)

In practice, as of 0.10.0, sounds are not isolated from each other or from other sounds played before.

A very simple example for Bevy that would demonstrate the problem is:

commands.spawn((
  SamplePlayer::new(asset_server.load("music.ogg")),
  sample_effects![VolumeNode {
    volume: Volume::Decibels(-40.0),
    smooth_seconds: 0.5,
    ..default()
  }],
  MusicPool,
));

One would expect that the above code just plays the song very softly - maybe to increase the volume dynamically later. This is not what happens. MusicPool is initialized by default to Volume::Linear(1.0), which initializes the worker at this value. When the Sample comes in, it wants -40dB, and then it smooths over half a second - resulting on the first half second to be way louder than intended.

Please note that this is a very specific example, and this bug isn't to fix that exact example.

This bug is to say that Firewheel must uphold a guarantee of isolation between samples, this means that, given a sample plus a chain of effects that is deterministic, Firewheel must always reproduce the same sound regardless on what was played before, regardless of what configuration. (or if there are deviations, they must be imperceptible to a human listener)

This happens because a Pool has N workers, and Firewheel reuses the workers across different sounds. And each worker feels more like a DAW track, a continuous audio stream. So when a second sample reproduces after the first one, it inherits the history of changes made from the previous one.

I am still very new on this, but this feels that it can be divided into two subproblems that would aid selling the idea of true isolation even though it is shared:

  1. A reset signal is probably needed between samples. An effect should be able to receive a "reset" event, and react to it to re-initialize and assume it is going brand new from that particular moment.
    1.b) Note that, from Bevy, we might want to customize the initial sound settings from different systems, on the same frame it is being spawned; such that one system might spawn, and the other one might want to change something just after. Not something I see myself doing, but worth noting. In Bevy, if we haven't sent the frame yet for rendering we mostly assume that still nothing happened. It might be worth withholding the player until the frame rendering starts just so that tweaks within the same frame of the reset are still applied.

  2. A method to manage added tails. For sounds to be played independently it means they need to terminate deterministically in a set amount of time. Therefore a force fade-out must be added once the sample is declared complete. We could add the ability for an effect to tell the chain it will add +X seconds of tail. And/or for the developer to specify this manually.

One thing it is not clear to me is how look-ahead play here, because if an effect that adds a tail is akin to adding a "suffix" to a sound, a look-ahead is akin to a "prefix". But my thought is that probably a reset signal should suffice for plugins to initialize their internal look-ahead ring buffer to zeros.

I think one of the best examples to think are compressors. If I put a compressor on the chain, it shouldn't be changing what it does depending on the timing of the samples. But with the current architecture, the sounds would play differently from playing them with different pacing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions