DSPPipeline.java

package org.hammer.audio.dsp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.core.AudioBlock;

/**
 * Composable chain of {@link DSPProcessor} stages.
 *
 * <p>A pipeline applies its processors in declared order, threading the output of each stage into
 * the next:
 *
 * <pre>{@code
 * DSPPipeline pipeline = DSPPipeline.of(
 *     new GainProcessor(0.5f),
 *     new DCBlockProcessor(),
 *     new HighPassProcessor(80.0f));
 * AudioBlock processed = pipeline.process(rawBlock);
 * }</pre>
 *
 * <p>The pipeline itself is immutable: stages are captured at construction time. To change the
 * topology at runtime, build a new pipeline.
 *
 * @author refactoring
 */
public final class DSPPipeline implements DSPProcessor {

  private final DSPProcessor[] stages;

  private DSPPipeline(DSPProcessor[] stages) {
    this.stages = stages;
  }

  /**
   * Create a new pipeline from an explicit list of stages.
   *
   * @param stages ordered list of processors; must not be {@code null} and must not contain {@code
   *     null} entries
   * @return a new pipeline
   */
  public static DSPPipeline of(DSPProcessor... stages) {
    Objects.requireNonNull(stages, "stages");
    DSPProcessor[] copy = new DSPProcessor[stages.length];
    for (int i = 0; i < stages.length; i++) {
      copy[i] = Objects.requireNonNull(stages[i], "stages[" + i + "]");
    }
    return new DSPPipeline(copy);
  }

  /**
   * Create a new pipeline from a list of stages.
   *
   * @param stages ordered list of processors; must not be {@code null} and must not contain {@code
   *     null} entries
   * @return a new pipeline
   */
  public static DSPPipeline of(List<? extends DSPProcessor> stages) {
    Objects.requireNonNull(stages, "stages");
    return of(stages.toArray(new DSPProcessor[0]));
  }

  /**
   * @return an empty pipeline that returns its input unchanged
   */
  public static DSPPipeline empty() {
    return new DSPPipeline(new DSPProcessor[0]);
  }

  @Override
  public AudioBlock process(AudioBlock block) {
    AudioBlock current = Objects.requireNonNull(block, "block");
    for (DSPProcessor stage : stages) {
      current = Objects.requireNonNull(stage.process(current), "stage produced null block");
    }
    return current;
  }

  /**
   * @return number of stages in this pipeline
   */
  public int size() {
    return stages.length;
  }

  /**
   * @return an unmodifiable view of the stages in declaration order
   */
  public List<DSPProcessor> stages() {
    return Collections.unmodifiableList(new ArrayList<>(Arrays.asList(stages)));
  }
}