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

eu.joaocosta.minart.audio.AudioClip.scala Maven / Gradle / Ivy

package eu.joaocosta.minart.audio

/** Audio Clip represented by a wave and a duration.
  *
  * @param wave audio wave storing this clip
  * @param duration duration of this audio wave in seconds
  */
final case class AudioClip(
    wave: AudioWave,
    duration: Double
) { outer =>

  /** Gets the amplitude at a certain point in time
    *  @param t time in seconds
    *  @return amplitude
    */
  def getAmplitude(t: Double): Option[Double] =
    if (t < 0 || t > duration) None
    else Some(wave(t))

  /** Gets the amplitude at a certain point in time, falling back to a default value when out of bounds.
    * Similar to `getAmplitude(t).getOrElse(fallback)`, but avoids an allocation.
    *  @param t time in seconds
    *  @return amplitude
    */
  def getAmplitudeOrElse(t: Double, fallback: Double = 0.0): Double =
    if (t < 0 || t > duration) fallback
    else wave(t)

  /** Returns a new Audio Clip with the first `time` seconds of this audio clip
    */
  def take(time: Double): AudioClip = {
    val newDuration = math.max(0.0, math.min(time, duration))
    AudioClip(wave, newDuration)
  }

  /** Returns a new Audio Clip without the first `time` seconds of this audio clip
    */
  def drop(time: Double): AudioClip = {
    val delta = math.max(0.0, math.min(time, duration))
    AudioClip(wave.drop(delta), duration - delta)
  }

  /** Maps the values of this wave.
    */
  def map(f: Double => Double): AudioClip =
    AudioClip(wave.map(f), duration)

  /** Flatmaps the wave of this clip. The duration stays unchanged */
  def flatMap(f: Double => AudioWave): AudioClip =
    AudioClip(wave.flatMap(f), duration)

  /** Contramaps the values of the wave of this clip */
  def contramap(f: Double => Double): AudioWave =
    wave.contramap(f)

  /** Combines this clip with another by combining their values using the given function.
    */
  def zipWith(that: AudioClip, f: (Double, Double) => Double): AudioClip = {
    val newDuration = math.min(this.duration, that.duration)
    AudioClip(this.wave.zipWith(that.wave, f), newDuration)
  }

  /** Combines this clip with a wave by combining their values using the given function.
    */
  def zipWith(that: AudioWave, f: (Double, Double) => Double): AudioClip =
    AudioClip(this.wave.zipWith(that, f), duration)

  /** Coflatmaps this clip with a AudioClip => Double function.
    * Effectively, each sample of the new clip is computed from a translated clip, which can be used to
    * implement convolutions.
    */
  def coflatMap(f: AudioClip => Double): AudioClip =
    AudioClip(
      new AudioWave {
        def getAmplitude(t: Double): Double = f(outer.drop(t))
      },
      duration
    )

  /** Appends an AudioClip to this one */
  def append(that: AudioClip): AudioClip =
    AudioClip(
      AudioWave.fromFunction(t =>
        if (t <= this.duration) this.wave(t)
        else that.wave(t - this.duration)
      ),
      this.duration + that.duration
    )

  /** Returns a reversed version of this wave */
  def reverse: AudioClip =
    contramap(t => duration - t).take(duration)

  /** Speeds up/down this clip according to a multiplier */
  def changeSpeed(multiplier: Double): AudioClip =
    contramap(t => multiplier * t).take(duration / multiplier)

  /** Returns an audio wave that repeats this clip forever */
  def repeating: AudioWave =
    if (duration <= 0) AudioWave.silence
    else
      new AudioWave {
        def getAmplitude(t: Double): Double = wave.getAmplitude(AudioClip.floorMod(t, duration))
      }

  /** Returns an audio wave that repeats this clip a certain number of times */
  def repeating(times: Int): AudioClip =
    repeating.take(duration * times)

  /** Returns an audio wave that clamps this clip when out of bounds */
  def clamped: AudioWave =
    if (duration <= 0) AudioWave.silence
    else contramap(t => AudioClip.clamp(0.0, t, duration))

  override def toString = s"AudioClip($wave,$duration)"
}

object AudioClip {

  /** Empty audio clip */
  val empty = silence(0.0)

  /** Audio clip with just silence for the specified duration */
  def silence(duration: Double) = AudioWave.silence.take(duration)

  /** Generates an audio clip for a sequence of samples.
    *
    * @param data indexed sequence of samples
    * @param sampleRate sample rate used in the sequence
    */
  def fromIndexedSeq(data: IndexedSeq[Double], sampleRate: Double): AudioClip = {
    val duration = data.size / sampleRate
    AudioWave.fromIndexedSeq(data, sampleRate).take(duration)
  }

  private def floorMod(x: Double, y: Double): Double = {
    val rem = x % y
    if (rem >= 0) rem
    else rem + y
  }

  private def clamp(minValue: Double, value: Double, maxValue: Double): Double =
    math.max(0, math.min(value, maxValue))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy