Skip to content

Question: Initial object position OLR vs OBR #21

Description

@trevorknight

Apologies that the following is an AI-generated report. I had to get help understanding how object positions were treated. It seems that objects can have an initial location for loudspeaker outputs (OLR) but they are moved to their initial positions for binaural (OBR).

Is this correct?

======================================

Object Initialization and Movement in liboar

This document explains how object-based audio elements are initialized and how their positions are updated during the first render call.

There is a key difference in behavior between Loudspeaker Rendering (OLR) and Binaural Rendering (OBR).

Summary

  • Loudspeaker Rendering (OLR): Objects can be initialized to their starting position from the first call to render. If oar_update_audio_element_metadata is called before oar_render, the object starts at the configured position immediately with no transition.
  • Binaural Rendering (OBR): Objects will move from a default position to the configured position over the first render call, even if oar_update_audio_element_metadata is called before oar_render.
  • Default Position: The default position is Azimuth: 0.0°, Elevation: 0.0°, Distance: 1.0 (normalized).

Detailed Analysis

1. Loudspeaker Rendering (OLR)

When rendering to loudspeakers (e.g., Stereo, 5.1, 7.1.4), the Open Loudspeaker Renderer (OLR) is used.

Step 1: Element Addition

Calling oar_add_audio_element creates the renderer. For OLR, it does not initialize object positions with defaults in the processing channel. The processor vector remains empty.

  • File: audio_element_renderer.c
  • File: renderer.c (impl->processors is initialized empty).

Step 2: Metadata Update (Before Render)

Calling oar_update_audio_element_metadata before render adds the metadata block to the processing channel.

  • File: olr.c (_metadata_update calls object_audio_renderer_add_metadatas).
  • File: renderer.c (renderer_impl_add_metadatas creates the processor if it doesn't exist and adds metadata).

Step 3: First Render

During the first oar_render, the block processor processes the queued metadata.

  • File: block_processing_channel.c (block_processing_channel_process calls _refil_processing_queue).
  • File: interpret_object_metadata.c (interpret_object_metadata_process handles the block).

Because this is the first block, self->last_block_end is UINT64_MAX (initialized in interpret_object_metadata_create at line 38).

  if (self->last_block_end != UINT64_MAX &&
      start_sample == self->last_block_end) {
    interp_from = self->last_block_gains_to;
  } else {
    target_sample = start_sample;
    interp_from = 0;
  }

Since it goes to the else block, interp_from is NULL and target_sample equals start_sample.
This causes it to return a fixed_gains block instead of interp_gains:

  } else if (target_sample != end_sample) {
    return fixed_gains_create(target_sample, end_sample, interp_to, n);
  }
  • File: processing_block.c (_fixed_gains_process applies the constant gains calculated from the configured position to the entire block).
    Thus, the object starts immediately at the configured position.

2. Binaural Rendering (OBR)

When rendering to binaural (headphones), the Open Binaural Renderer (OBR) is used.

Step 1: Element Addition

Calling oar_add_audio_element triggers OBR to add the element and initialize the DSP.

  • File: obr.c (_set_attribute calls obr_add_audio_element).
  • File: obr_impl.cc (AddAudioElement calls InitializeDsp).
  • File: obr_impl.cc (InitializeDsp calls group.UpdateAmbisonicEncoder).
  • File: processing_group.cc (UpdateAmbisonicEncoder calls ambisonic_encoder_->SetSource with default positions).

The default positions are defined in AudioElementConfig constructor:

  if (type == AudioElementType::kObjectMono) {
    AudioObjectInputChannel input_channel("kMono", 0.0f, 0.0f, 1.0f);
    object_channels_.push_back(input_channel);
  }
  • File: audio_element_config.cc (Default: Azimuth 0.0f, Elevation 0.0f, Distance 1.0f).

In AmbisonicEncoder::SetSource:

  if (sources_.find(input_channel) == sources_.end()) {
    SourceProperties source_properties{};
    source_properties.current = {gain, azimuth, elevation, distance};
    source_properties.target = source_properties.current;
    sources_.insert({input_channel, source_properties});
    return;
  }
  • File: ambisonic_encoder.cc
    This initializes current and target in sources_ to the default position.

Step 2: Metadata Update (Before Render)

Calling oar_update_audio_element_metadata before render updates the position.

  • File: obr_impl.cc (UpdateObjectChannelPosition updates positions in audio_elements_ and calls UpdateAllAmbisonicEncoders -> UpdateAmbisonicEncoder -> SetSource).

In AmbisonicEncoder::SetSource:

  // If the values are unchanged, do nothing.
  SourceProperties& source_properties = sources_.at(input_channel);
  if (source_properties.target.gain == gain && ... ) { return; }

  // Update only the target parameters.
  source_properties.target.gain = gain;
  source_properties.target.azimuth = azimuth;
  ...

Since the source already exists in sources_ (from Step 1), it only updates the target parameters to the configured position. The current parameters remain at the default position.

Step 3: First Render

During the first render call, OBR processes the audio.

  • File: ambisonic_encoder.cc (ProcessPlanarAudioData is called).

It checks if interpolation is needed:

    if (!params_equal(source_properties.current, source_properties.target)) {
      fill_column(source_properties.current, column_start[in_ch]);
      needs_interpolation[in_ch] = true;
    }

Since current (default) != target (configured), needs_interpolation becomes true.
This forces per-frame interpolation:

    for (size_t frame = 0; frame < num_frames; ++frame) {
      float alpha = static_cast<float>(frame) / static_cast<float>(num_frames - 1);
      // ... interpolate from column_start (default) to column_end (configured)
    }

Thus, the object moves from the default position to the configured position over the duration of the first audio block.
After the block is processed, current is caught up to target (line 300), so subsequent blocks start from the configured position.


Conclusion

The behavior depends on the target output layout:

  • For Loudspeaker layouts (OLR), objects can be initialized to their starting position from the first call to render by calling oar_update_audio_element_metadata before oar_render.
  • For Binaural layouts (OBR), it is not possible to avoid the initial transition. The object will always transition from the default position ({0, 0, 1}) to the configured position during the first render block.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions