From 34d6e1f0cbd354088a04dc6b3824a6ae23bc1f52 Mon Sep 17 00:00:00 2001 From: Zach Gray Date: Sat, 11 Jan 2020 14:48:41 +1100 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20write=20audio=20to=20wav?= =?UTF-8?q?=20file(s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use of "-f ~/somefile" flag will write the binaural beat audio to file. use of "-s" with this flag will create separate output files for later usage. ✅ Closes: #2 --- WORKSPACE | 4 +- .../binauralBeats/cli/BinauralApp.scala | 23 +++++++- .../engine/BinauralBeatsConfiguration.scala | 8 ++- .../binauralBeats/engine/BinauralEngine.scala | 56 ++++++++++++++++--- 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 5fd866c..d865f70 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -80,7 +80,7 @@ pinned_maven_install() git_repository( name = "zachgrayio_scalaudio", - commit = "6d7dfabd71392a6b15ff3eea8b86945b12ad20f5", + commit = "a0aa2daccdfee2fc78b60c1a898249c0427b6a7a", remote = "http://github.com/zachgrayio/scalaudio.git", - shallow_since = "1578573315 +1100" + shallow_since = "1578711564 +1100" ) diff --git a/cli/src/main/scala/io/zachgray/binauralBeats/cli/BinauralApp.scala b/cli/src/main/scala/io/zachgray/binauralBeats/cli/BinauralApp.scala index 541a14d..8019ef3 100644 --- a/cli/src/main/scala/io/zachgray/binauralBeats/cli/BinauralApp.scala +++ b/cli/src/main/scala/io/zachgray/binauralBeats/cli/BinauralApp.scala @@ -12,7 +12,16 @@ object BinauralApp extends App { */ override def main(args: Array[String]): Unit = { parseBinauralBeatsConfiguration(args) match { - case Some(config) => BinauralEngine.play(config) + // valid config + case Some(config) => { + config.fileName match { + // filename option is provided, output to file + case Some(str) => BinauralEngine.writeToFile(config) + // no filename flag so invoke playback on default audio device + case None => BinauralEngine.play(config) + } + } + // invalid config case _ => } } @@ -67,7 +76,17 @@ object BinauralApp extends App { opt[Int]('d', "duration") .action((x, c) => c.copy(duration = x)) - .text("the duration for which to play the binaural audio - an integer property") + .text("the duration for which to play the binaural audio - an integer property"), + + opt[String]('f', "file") + .optional() + .action((x, c) => c.copy(fileName = Some(x))) + .text("the output filename - a string property"), + + opt[Unit]('s', "separate_files") + .optional() + .action((_, c) => c.copy(separateFiles = true)) + .text("split left and right audio into separate files - a boolean property") ) } OParser.parse(optionsParser, args, BinauralBeatsConfiguration()) diff --git a/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralBeatsConfiguration.scala b/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralBeatsConfiguration.scala index a160377..6a65cf7 100644 --- a/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralBeatsConfiguration.scala +++ b/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralBeatsConfiguration.scala @@ -1,3 +1,9 @@ package io.zachgray.binauralBeats.engine -case class BinauralBeatsConfiguration(pitch: Double = 300, binauralPitch: Double = 10, duration: Int = 30) +case class BinauralBeatsConfiguration( + pitch: Double = 300, + binauralPitch: Double = 10, + duration: Int = 30, + fileName: Option[String] = null, + separateFiles: Boolean = false +) diff --git a/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralEngine.scala b/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralEngine.scala index 9e21400..9be8220 100644 --- a/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralEngine.scala +++ b/engine/src/main/scala/io/zachgray/binauralBeats/engine/BinauralEngine.scala @@ -1,7 +1,8 @@ package io.zachgray.binauralBeats.engine +import io.zachgray.binauralBeats.engine.BinauralEngine.{createBinauralFrameFunction, createDualBinauralFrameFunctions} import scalaudio.core.types.{Frame, Pitch} -import scalaudio.core.{AudioContext, ScalaudioConfig} +import scalaudio.core.{AudioContext, DefaultAudioContext, ScalaudioConfig} import scalaudio.units.AmpSyntax import scalaudio.units.filter.{GainFilter, StereoPanner} import scalaudio.units.ugen.{OscState, Sine} @@ -9,7 +10,7 @@ import scalaz.Scalaz._ import scala.concurrent.duration._ -object BinauralEngine extends AmpSyntax { +object BinauralEngine extends AmpSyntax with DefaultAudioContext { /** * Creates a stereo audio context and invokes playback for the specified configuration. @@ -17,7 +18,6 @@ object BinauralEngine extends AmpSyntax { * @param binauralBeatConfiguration the configuration of the binaural beats which should be generated. */ def play(binauralBeatConfiguration: BinauralBeatsConfiguration): Unit = { - implicit val audioContext: AudioContext = AudioContext(ScalaudioConfig(nOutChannels = 2)) playback( frameFunc = createBinauralFrameFunction( pitch = binauralBeatConfiguration.pitch, @@ -27,6 +27,37 @@ object BinauralEngine extends AmpSyntax { ) } + /** + * Creates a stereo audio context and invokes `record` for the specified configuration. + * + * @param config the configuration of the binaural beats which should be generated. + */ + def writeToFile(config: BinauralBeatsConfiguration): Unit = { + if (config.separateFiles) { + writeToSeparateFiles(config) + } else { + record( + fileName = config.fileName.get, + frameFunc = createBinauralFrameFunction( + pitch = config.pitch, + binauralPitch = config.binauralPitch + ), + duration = config.duration seconds + ) + } + } + + private def writeToSeparateFiles(config: BinauralBeatsConfiguration): Unit = { + val ffs = createDualBinauralFrameFunctions(config.pitch, config.binauralPitch) + for((ff, index) <- ffs.zipWithIndex) { + record( + fileName = config.fileName.get + "_" + index, + frameFunc = ff, + duration = config.duration seconds + ) + } + } + /** * Creates a sine frame function with the specified parameters * @@ -60,14 +91,25 @@ object BinauralEngine extends AmpSyntax { * @return the binaural frame function */ def createBinauralFrameFunction(pitch: Double, binauralPitch: Double)(implicit audioContext: AudioContext): () => Frame = { - val frameFunctions = Seq( - createSineWaveFrameFunction(pitch, 0, 1), - createSineWaveFrameFunction(pitch + binauralPitch, 1, 1) - ) + val frameFunctions = createDualBinauralFrameFunctions(pitch, binauralPitch) var interleave = false () => { interleave = !interleave frameFunctions(interleave.compare(false))() } } + + /** + * + * @param pitch The baseline pitch (in Hz) of the binaural beat + * @param binauralPitch The pitch of the binaural frequency which should be generated + * @param audioContext The audio context + * @return the binaural frame functions + */ + def createDualBinauralFrameFunctions(pitch: Double, binauralPitch: Double)(implicit audioContext: AudioContext): Seq[() => Frame] = { + Seq( + createSineWaveFrameFunction(pitch, 0, 1), + createSineWaveFrameFunction(pitch + binauralPitch, 1, 1) + ) + } }