Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: write audio to wav file(s) #3

Merged
merged 1 commit into from
Jan 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
23 changes: 21 additions & 2 deletions cli/src/main/scala/io/zachgray/binauralBeats/cli/BinauralApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 _ =>
}
}
Expand Down Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
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}
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.
*
* @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,
Expand All @@ -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
*
Expand Down Expand Up @@ -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)
)
}
}