All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.io7m.jarabica.demo.MainController Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2022 Mark Raynsford  https://www.io7m.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


package com.io7m.jarabica.demo;

import com.io7m.jarabica.api.JAAbstractGraphListener;
import com.io7m.jarabica.api.JABufferFormat;
import com.io7m.jarabica.api.JABufferType;
import com.io7m.jarabica.api.JAContextType;
import com.io7m.jarabica.api.JADeviceType;
import com.io7m.jarabica.api.JAException;
import com.io7m.jarabica.api.JASourceBufferLink;
import com.io7m.jarabica.api.JASourceOrBufferType;
import com.io7m.jarabica.api.JASourceType;
import com.io7m.jarabica.extensions.efx.JAEFXEffectEAXReverbParameters;
import com.io7m.jarabica.extensions.efx.JAEFXEffectEAXReverbType;
import com.io7m.jarabica.extensions.efx.JAEFXType;
import com.io7m.jarabica.extensions.efx.JAEXFEffectsSlotType;
import com.io7m.jarabica.lwjgl.JALWDeviceFactory;
import com.io7m.jtensors.core.unparameterized.vectors.Vector3D;
import com.io7m.jtensors.core.unparameterized.vectors.Vectors3D;
import com.io7m.jwheatsheaf.api.JWFileChooserAction;
import com.io7m.jwheatsheaf.api.JWFileChooserConfiguration;
import com.io7m.jwheatsheaf.api.JWFileChoosersType;
import com.io7m.jwheatsheaf.ui.JWFileChoosers;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Point2D;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import org.jgrapht.Graph;
import org.jgrapht.event.GraphVertexChangeEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 * The main controller.
 */

public final class MainController implements Initializable
{
  private static final Logger LOG =
    LoggerFactory.getLogger(MainController.class);

  private static final Font FONT =
    Font.font("Monospaced Bold", 12.0);

  private static final double DISTANCE_SCALE_FACTOR = 20.0;

  private final JALWDeviceFactory devices;
  private final JWFileChoosersType choosers;
  private final HashMap sourceShapes;

  @FXML private Pane listenerPane;
  @FXML private Ellipse listener;
  @FXML private Label listenerText;
  @FXML private Pane sourceControls;
  @FXML private TextField sourcePosition;
  @FXML private TextField sourceVelocity;
  @FXML private Slider sourcePitch;
  @FXML private Slider sourceGain;
  @FXML private TextField sourceId;
  @FXML private Slider reverbDensity;
  @FXML private Slider reverbDiffusion;
  @FXML private Slider reverbGain;
  @FXML private Slider reverbGainHF;
  @FXML private Slider reverbGainLF;
  @FXML private Slider reverbDecay;
  @FXML private Slider reverbDecayHF;
  @FXML private Slider reverbDecayLF;
  @FXML private Slider reverbReflectionsGain;
  @FXML private Slider reverbReflectionsDelay;
  @FXML private Slider reverbLateGain;
  @FXML private Slider reverbLateDelay;
  @FXML private Slider reverbEchoTime;
  @FXML private Slider reverbEchoDepth;
  @FXML private Slider reverbModulationTime;
  @FXML private Slider reverbModulationDepth;
  @FXML private Slider reverbAirAbsorption;

  private JADeviceType device;
  private JAContextType context;
  private JAEFXType efx;
  private Graph sourceGraph;
  private JAEXFEffectsSlotType mainEffectSlot;
  private SimpleObjectProperty reverbEAXEffectParameters;
  private JAEFXEffectEAXReverbType reverbEAXEffect;
  private SimpleObjectProperty sourceSelected;

  /**
   * The main controller.
   */

  public MainController()
  {
    this.devices = new JALWDeviceFactory();
    this.choosers = JWFileChoosers.create();
    this.sourceShapes = new HashMap<>();
  }

  private static JABufferFormat formatFor(
    final AudioFormat format)
  {
    return switch (format.getChannels()) {
      case 1 -> switch (format.getSampleSizeInBits()) {
        case 8 -> JABufferFormat.AUDIO_8_BIT_MONO;
        case 16 -> JABufferFormat.AUDIO_16_BIT_MONO;
        default -> throw new UnsupportedOperationException();
      };
      case 2 -> switch (format.getSampleSizeInBits()) {
        case 8 -> JABufferFormat.AUDIO_8_BIT_STEREO;
        case 16 -> JABufferFormat.AUDIO_16_BIT_STEREO;
        default -> throw new UnsupportedOperationException();
      };
      default -> throw new UnsupportedOperationException();
    };
  }

  @Override
  public void initialize(
    final URL location,
    final ResourceBundle resources)
  {
    final var descriptions =
      this.devices.enumerateDevices();
    if (descriptions.isEmpty()) {
      throw new UnsupportedOperationException();
    }

    try {
      this.device =
        this.devices.openDevice(descriptions.get(0));
      this.context =
        this.device.createContext();
      this.efx =
        this.context.extension(JAEFXType.class)
          .orElseThrow();

      this.sourceGraph =
        this.context.sourceBufferGraph();
      this.sourceSelected =
        new SimpleObjectProperty<>();

      this.mainEffectSlot =
        this.efx.createEffectsSlot();

      this.reverbEAXEffectParameters = new SimpleObjectProperty<>();
      this.reverbEAXEffectParameters.set(
        new JAEFXEffectEAXReverbParameters(
          1.0,
          1.0,
          0.32,
          0.89,
          0.0,
          1.49,
          0.83,
          1.0,
          0.05,
          0.007,
          1.26,
          0.011,
          0.25,
          0.0,
          0.25,
          0.0,
          0.994,
          5000.0,
          250.0,
          0.0,
          true
        )
      );

      this.reverbEAXEffect =
        this.efx.createEffectEAXReverb(this.reverbEAXEffectParameters.get());

      this.efx.attachEffectToEffectsSlot(
        this.reverbEAXEffect,
        this.mainEffectSlot);

      this.context.addSourceBufferGraphListener(
        new SourceBufferGraphListener());

    } catch (final JAException e) {
      throw new IllegalStateException(e);
    }

    this.listenerText.setText("");

    this.listenerPane.widthProperty()
      .addListener((o, old, newValue) -> this.onListenerPaneSizeChanged());
    this.listenerPane.heightProperty()
      .addListener((o, old, newValue) -> this.onListenerPaneSizeChanged());

    this.configureReverbSliders();

    this.reverbEAXEffectParameters.addListener((observable, oldValue, newValue) -> {
      try {
        this.reverbEAXEffect.setParameters(newValue);
      } catch (final JAException e) {
        LOG.error("AL: ", e);
      }
    });

    this.sourceSelected.addListener((observable, oldValue, newValue) -> {
      this.onSourceSelectionChanged(oldValue, newValue);
    });

    this.sourcePitch.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.onSourcePitchChanged(newValue.doubleValue());
      });
    this.sourceGain.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.onSourceGainChanged(newValue.doubleValue());
      });
  }

  private void configureReverbSliders()
  {
    this.reverbDensity.setValue(
      this.reverbEAXEffectParameters.get()
        .density()
    );
    this.reverbDiffusion.setValue(
      this.reverbEAXEffectParameters.get()
        .diffusion()
    );
    this.reverbGain.setValue(
      this.reverbEAXEffectParameters.get()
        .gain()
    );
    this.reverbGainHF.setValue(
      this.reverbEAXEffectParameters.get()
        .gainHF()
    );
    this.reverbGainLF.setValue(
      this.reverbEAXEffectParameters.get()
        .gainLF()
    );
    this.reverbDecay.setValue(
      this.reverbEAXEffectParameters.get()
        .decaySeconds()
    );
    this.reverbDecayHF.setValue(
      this.reverbEAXEffectParameters.get()
        .decayHFRatio()
    );
    this.reverbDecayLF.setValue(
      this.reverbEAXEffectParameters.get()
        .decayLFRatio()
    );
    this.reverbReflectionsGain.setValue(
      this.reverbEAXEffectParameters.get()
        .reflectionsGain()
    );
    this.reverbReflectionsDelay.setValue(
      this.reverbEAXEffectParameters.get()
        .reflectionsDelaySeconds()
    );
    this.reverbLateGain.setValue(
      this.reverbEAXEffectParameters.get()
        .lateReverbGain()
    );
    this.reverbLateDelay.setValue(
      this.reverbEAXEffectParameters.get()
        .lateReverbDelaySeconds()
    );
    this.reverbEchoTime.setValue(
      this.reverbEAXEffectParameters.get()
        .echoTime()
    );
    this.reverbEchoTime.setValue(
      this.reverbEAXEffectParameters.get()
        .echoDepth()
    );
    this.reverbModulationTime.setValue(
      this.reverbEAXEffectParameters.get()
        .modulationTime()
    );
    this.reverbModulationDepth.setValue(
      this.reverbEAXEffectParameters.get()
        .modulationDepth()
    );
    this.reverbAirAbsorption.setValue(
      this.reverbEAXEffectParameters.get()
        .airAbsorptionHFGain()
    );

    this.reverbDensity.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withDensity(newValue.doubleValue())
        );
      });

    this.reverbDiffusion.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withDiffusion(newValue.doubleValue())
        );
      });

    this.reverbGain.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withGain(newValue.doubleValue())
        );
      });

    this.reverbGainHF.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withGainHF(newValue.doubleValue())
        );
      });

    this.reverbGainLF.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withGainLF(newValue.doubleValue())
        );
      });

    this.reverbDecay.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withDecaySeconds(newValue.doubleValue())
        );
      });

    this.reverbDecayHF.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withDecayHFRatio(newValue.doubleValue())
        );
      });

    this.reverbDecayLF.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withDecayLFRatio(newValue.doubleValue())
        );
      });

    this.reverbReflectionsGain.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withReflectionsGain(newValue.doubleValue())
        );
      });

    this.reverbReflectionsDelay.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withReflectionsDelaySeconds(newValue.doubleValue())
        );
      });

    this.reverbLateGain.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withLateReverbGain(newValue.doubleValue())
        );
      });

    this.reverbLateDelay.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withLateReverbDelaySeconds(newValue.doubleValue())
        );
      });

    this.reverbEchoTime.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withEchoTime(newValue.doubleValue())
        );
      });

    this.reverbEchoDepth.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withEchoDepth(newValue.doubleValue())
        );
      });

    this.reverbModulationTime.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withModulationTime(newValue.doubleValue())
        );
      });

    this.reverbModulationDepth.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withModulationDepth(newValue.doubleValue())
        );
      });

    this.reverbAirAbsorption.valueProperty()
      .addListener((observable, oldValue, newValue) -> {
        this.reverbEAXEffectParameters.set(
          this.reverbEAXEffectParameters.get()
            .withAirAbsorptionHFGain(newValue.doubleValue())
        );
      });
  }

  private void onSourceGainChanged(
    final double gain)
  {
    final var source = this.sourceSelected.get();
    if (source == null) {
      return;
    }

    try {
      source.setGain(gain);
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  private void onSourcePitchChanged(
    final double pitch)
  {
    final var source = this.sourceSelected.get();
    if (source == null) {
      return;
    }

    try {
      source.setPitch(pitch);
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  private void onSourceSelectionChanged(
    final JASourceType oldSource,
    final JASourceType newSource)
  {
    if (oldSource != null) {
      final var shape =
        this.sourceShapes.get(oldSource);

      if (shape != null) {
        shape.shape.setStroke(Color.BLACK);
      }
    }

    if (newSource == null) {
      this.sourceId.setText("");
      this.sourceControls.setDisable(true);
      this.sourcePosition.setText("");
      this.sourceVelocity.setText("");
      this.sourcePitch.setValue(1.0);
      this.sourceGain.setValue(1.0);
      return;
    }

    final var shape = this.sourceShapes.get(newSource);
    if (shape != null) {
      shape.shape.setStroke(Color.CYAN);
    }

    this.sourceControls.setDisable(false);

    try {
      this.sourceId.setText(String.valueOf(newSource.handle()));
      final var position = newSource.position();
      this.sourcePosition.setText(String.format(
        "%.2f %.2f",
        position.x(),
        position.z()));
      final var velocity = newSource.velocity();
      this.sourceVelocity.setText(String.format(
        "%.2f %.2f",
        velocity.x(),
        velocity.z()));
      this.sourcePitch.setValue(newSource.pitch());
      this.sourceGain.setValue(newSource.gain());
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  private void onSourceDeleted(
    final JASourceType source)
  {
    final var shape = this.sourceShapes.get(source);
    this.listenerPane.getChildren()
      .remove(shape.shape);
    this.listenerPane.getChildren()
      .remove(shape.label);
  }

  private void onSourceCreated(
    final JASourceType source)
  {
    try {
      final var shape = new Ellipse(8.0, 8.0);
      shape.setFill(
        Color.rgb(
          (source.hashCode() >> 16 & 0xff),
          (source.hashCode() >> 8 & 0xff),
          (source.hashCode() & 0xff)
        )
      );
      shape.setStroke(Color.BLACK);

      final var label = new Label();
      label.setTextFill(Color.WHITE);
      label.setFont(FONT);

      final var shapeState =
        new SourceShapeState(
          source,
          shape,
          label,
          new AtomicReference<>(source.position())
        );

      this.sourceShapes.put(source, shapeState);

      shape.onMouseDraggedProperty()
        .set(event -> this.onSourceDragged(event, shapeState));
      shape.onMouseDragReleasedProperty()
        .set(event -> this.onSourceDragReleased(event, shapeState));
      shape.onMouseReleasedProperty()
        .set(event -> this.onSourceMouseReleased(event, shapeState));

      this.listenerPane.getChildren()
        .add(shape);
      this.listenerPane.getChildren()
        .add(label);

      final var position = this.alWorldToLocal(source.position());
      shape.setLayoutX(position.getX());
      shape.setLayoutY(position.getY());

      this.sourceSelected.set(source);
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  private void onSourceMouseReleased(
    final MouseEvent event,
    final SourceShapeState state)
  {
    if (event.getButton() == MouseButton.PRIMARY) {
      this.sourceSelected.set(state.source);
      return;
    }

    if (event.getButton() == MouseButton.SECONDARY) {
      if (Objects.equals(this.sourceSelected.get(), state.source)) {
        this.sourceSelected.set(null);
      }

      try {
        state.source.stop();
        state.source.detachBuffer();
        state.source.close();
      } catch (final JAException e) {
        LOG.error("AL: ", e);
      }

      final var nodes =
        Set.copyOf(this.sourceGraph.vertexSet());

      for (final var node : nodes) {
        if (node instanceof JABufferType buffer) {
          if (this.sourceGraph.outDegreeOf(buffer) == 0) {
            try {
              buffer.close();
            } catch (final JAException e) {
              LOG.error("AL: ", e);
            }
          }
        }
      }
    }
  }

  private void onSourceDragReleased(
    final MouseDragEvent event,
    final SourceShapeState state)
  {
    try {
      this.sourceSelected.set(state.source);
      state.source.setVelocity(Vectors3D.zero());
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  private void onSourceDragged(
    final MouseEvent event,
    final SourceShapeState state)
  {
    this.sourceSelected.set(state.source);

    final var eventSceneX =
      event.getSceneX();
    final var eventSceneY =
      event.getSceneY();
    final var panePosition =
      this.listenerPane.sceneToLocal(eventSceneX, eventSceneY);

    final var alPositionLast =
      state.positionLast().get();
    final var alPosition =
      this.localToALWorld(panePosition);
    final var diff =
      Vectors3D.scale(Vectors3D.subtract(alPosition, alPositionLast), 1.0);

    state.shape.setLayoutX(panePosition.getX());
    state.shape.setLayoutY(panePosition.getY());
    state.label.setLayoutX(panePosition.getX() + 16);
    state.label.setLayoutY(panePosition.getY() - 8);
    state.label.setText(String.format(
      "[%.2f, %.2f]",
      alPosition.x(),
      alPosition.z()));

    this.sourceSelected.set(state.source);

    try {
      state.source.setPosition(alPosition);
      state.source.setVelocity(diff);
    } catch (final JAException e) {
      LOG.error("AL: ", e);
    }
  }

  /**
   * Convert a pane-local position to OpenAL world coordinates.
   *
   * @param position The position
   *
   * @return The OpenAL position
   */

  private Vector3D localToALWorld(
    final Point2D position)
  {
    final var centerX =
      this.listenerPane.getWidth() / 2.0;
    final var centerY =
      this.listenerPane.getHeight() / 2.0;

    final var centerRelative =
      new Point2D(
        position.getX() - centerX,
        centerY - position.getY()
      );

    final var centerScaled =
      new Point2D(
        centerRelative.getX() / DISTANCE_SCALE_FACTOR,
        centerRelative.getY() / DISTANCE_SCALE_FACTOR
      );

    return Vector3D.of(
      centerScaled.getX(),
      0.0,
      centerScaled.getY()
    );
  }

  /**
   * Convert OpenAL world coordinates to a pane-local position.
   *
   * @param position The OpenAL position
   *
   * @return The position
   */

  private Point2D alWorldToLocal(
    final Vector3D position)
  {
    final var x = position.x() * DISTANCE_SCALE_FACTOR;
    final var y = position.z() * DISTANCE_SCALE_FACTOR;

    final var centerX =
      this.listenerPane.getWidth() / 2.0;
    final var centerY =
      this.listenerPane.getHeight() / 2.0;

    return new Point2D(
      centerX + x,
      centerY + y
    );
  }

  @FXML
  private void onListenerMouseDragged(
    final MouseEvent event)
    throws JAException
  {
    final var eventSceneX =
      event.getSceneX();
    final var eventSceneY =
      event.getSceneY();

    var panePosition =
      this.listenerPane.sceneToLocal(eventSceneX, eventSceneY);

    panePosition =
      new Point2D(
        Math.min(
          this.listenerPane.getWidth() - 16.0,
          Math.max(16.0, panePosition.getX())),
        Math.min(
          this.listenerPane.getHeight() - 16.0,
          Math.max(16.0, panePosition.getY()))
      );

    final var alPosition =
      this.localToALWorld(panePosition);

    this.context.listener()
      .setPosition(alPosition);

    final var position =
      this.context.listener()
        .position();

    this.listener.setLayoutX(panePosition.getX());
    this.listener.setLayoutY(panePosition.getY());
    this.listenerText.setText(
      String.format("[%.2f, %.2f]", position.x(), position.z())
    );
    this.listenerText.setLayoutX(panePosition.getX() + 16);
    this.listenerText.setLayoutY(panePosition.getY() - 8);
  }

  private void onListenerPaneSizeChanged()
  {
    try {
      final var centerX =
        this.listenerPane.getWidth() / 2.0;
      final var centerY =
        this.listenerPane.getHeight() / 2.0;

      final var position =
        this.context.listener()
          .position();

      this.listener.setLayoutX(centerX + position.x());
      this.listener.setLayoutY(centerY + position.z());

      this.listenerPane.setClip(
        new Rectangle(
          this.listenerPane.getWidth(),
          this.listenerPane.getHeight()
        )
      );
    } catch (final JAException e) {
      throw new IllegalStateException(e);
    }
  }

  @FXML
  private void onCreateSourceSelected()
    throws IOException, UnsupportedAudioFileException, JAException
  {
    final var chooser =
      this.choosers.create(
        this.listenerPane.getScene().getWindow(),
        JWFileChooserConfiguration.builder()
          .setAction(JWFileChooserAction.OPEN_EXISTING_SINGLE)
          .build()
      );

    final var file = chooser.showAndWait();
    if (file.isEmpty()) {
      return;
    }

    try (var stream =
           AudioSystem.getAudioInputStream(file.get(0).toFile())) {
      final var format =
        stream.getFormat();
      final var data =
        stream.readAllBytes();
      final var dataBuffer =
        ByteBuffer.allocateDirect(data.length);
      dataBuffer.put(data);
      dataBuffer.rewind();

      final var buffer =
        this.context.createBuffer();
      buffer.setData(
        formatFor(format),
        (int) format.getSampleRate(),
        dataBuffer
      );

      final var source = this.context.createSource();
      this.efx.attachSourceDirectToEffectsSlot(source, this.mainEffectSlot);

      source.setPosition(0.0, 0.0, 0.0);
      source.setBuffer(buffer);
      source.setGain(0.5);
      source.setLooping(true);
      source.play();
    }
  }

  @FXML
  private void onQuit()
  {
    Platform.exit();
  }

  record SourceShapeState(
    JASourceType source,
    Ellipse shape,
    Label label,
    AtomicReference positionLast)
  {

  }

  private final class SourceBufferGraphListener
    extends JAAbstractGraphListener
  {
    SourceBufferGraphListener()
    {

    }

    @Override
    public void vertexAdded(
      final GraphVertexChangeEvent e)
    {
      final var v = e.getVertex();
      if (v instanceof JASourceType source) {
        MainController.this.onSourceCreated(source);
      }
    }

    @Override
    public void vertexRemoved(
      final GraphVertexChangeEvent e)
    {
      final var v = e.getVertex();
      if (v instanceof JASourceType source) {
        MainController.this.onSourceDeleted(source);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy