Creating audio from raw bits in Scala

I was curious recently if it was possible to create sound in pure Java / Scala, without using some third-party package, when I stumbled across this old code snippet on the Oracle forums which did just that.

With some cleanup and a few small bug fixes,…


This content originally appeared on DEV Community and was authored by DEV Community

I was curious recently if it was possible to create sound in pure Java / Scala, without using some third-party package, when I stumbled across this old code snippet on the Oracle forums which did just that.

With some cleanup and a few small bug fixes, I was able to get it working nicely in Scala.

The core of the example is this Note class

case class Note(frequency: Double, msecs: Double, volume: Double = 128.0, fade: Boolean = true) {

  // this (mostly) eliminates "crackling" / "popping" at the beginning / end of each tone
  def fadeVolume(sampleIndex: Int, nSamples: Int): Double = {
    val fadedSamples = 0.1 * nSamples // 10% fade in/out

    if (sampleIndex < fadedSamples) { // fade in
      val x = sampleIndex / fadedSamples // [0, 1]
      x * x * volume
    } else if ((nSamples - sampleIndex) < fadedSamples) { // fade out
      val x = (nSamples - sampleIndex) / fadedSamples // [0, 1]
      x * x * volume
    } else volume
  }

  val wavelength: Double = 2.0 * Math.PI * frequency

  def bytes(sampleRate: Int): Array[Byte] = {
    val nSamples = (msecs * sampleRate / 1000.0).toInt
    (0 to nSamples).map({ sampleIndex =>
      val angle = wavelength * sampleIndex / sampleRate
      val fadedVolume = if (fade) fadeVolume(sampleIndex, nSamples) else volume
      (Math.sin(angle) * fadedVolume).toByte
    }).toArray
  }
}

...where, for a given frequency and duration in msecs, we literally build a tone bit-by-bit, including fading the tone in and out to avoid "crackly" discontinuities at the start and end of the tone.

Creating a Tune out of multiple Notes is then pretty straightforward

class Tune(val sampleRate: Int, audioFormat: AudioFormat) {

  private[this] var sourceDataLine: Option[SourceDataLine] = None
  private[this] var ready = false

  private var bytes = Array[Byte]()

  def start(): Unit = {
    sourceDataLine = Some(AudioSystem.getSourceDataLine(audioFormat))
    sourceDataLine.get.open(audioFormat)
    sourceDataLine.get.flush() // this eliminates "crackling" / "popping" at the beginning of the tune
    sourceDataLine.get.start()
    ready = true
  }

  def addNote(note: Note): Unit = {
    bytes ++= note.bytes(sampleRate)
  }

  def play(): Unit = {
    if (!ready) start()
    sourceDataLine.get.write(bytes, 0, bytes.length)
    sourceDataLine.get.drain() // this causes the "crackling" / "popping" at the end of the tune
  }

  def close(): Unit = {
    sourceDataLine.foreach(_.flush())
    sourceDataLine.foreach(_.stop())
    sourceDataLine.foreach(_.close())
    ready = false
  }
}

Add the Notes to a buffer one at a time, then when you want to play the tune, simply copy the buffer to the SourceDataLine and drain the line's buffer.

I wrote a simple tune to test this... can you tell what it is without playing it?

object Main extends App {

  val G  = 196.00 // Hz
  val Eb = 155.56
  val F  = 174.61
  val D  = 146.83

  val bpm = 108.0
  val quarter = 1000.0 * 60.0 / bpm
  val triplet = quarter / 3.0
  val half = quarter * 2.0

  val quarterRest = Note(0, quarter, 0)
  val tripletG = Note(G, triplet)
  val halfEb = Note(Eb, half)
  val tripletF = Note(F, triplet)
  val halfD = Note(D, half)

  val bars12: List[Note] = List(quarterRest, tripletG, tripletG, tripletG, halfEb)
  val bars34: List[Note] = List(quarterRest, tripletF, tripletF, tripletF, halfD, quarterRest)

  val tune = Tune.empty

  (bars12 ++ bars34).foreach(tune.addNote)

  tune.play()
  tune.close()
}

P.S. if anyone has any ideas for eliminating the "crackling" at the end of the tune, please let me know! Fading out doesn't seem to help, nor does trimming the end of the buffer. Even when only playing a bit of silence, there's still some crackling at the end.


This content originally appeared on DEV Community and was authored by DEV Community


Print Share Comment Cite Upload Translate Updates
APA

DEV Community | Sciencx (2021-11-06T22:44:13+00:00) Creating audio from raw bits in Scala. Retrieved from https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/

MLA
" » Creating audio from raw bits in Scala." DEV Community | Sciencx - Saturday November 6, 2021, https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/
HARVARD
DEV Community | Sciencx Saturday November 6, 2021 » Creating audio from raw bits in Scala., viewed ,<https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/>
VANCOUVER
DEV Community | Sciencx - » Creating audio from raw bits in Scala. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/
CHICAGO
" » Creating audio from raw bits in Scala." DEV Community | Sciencx - Accessed . https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/
IEEE
" » Creating audio from raw bits in Scala." DEV Community | Sciencx [Online]. Available: https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/. [Accessed: ]
rf:citation
» Creating audio from raw bits in Scala | DEV Community | Sciencx | https://www.scien.cx/2021/11/06/creating-audio-from-raw-bits-in-scala/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.