From b56520740eb9bb1d1ecb205664d3e6d3cbe38d55 Mon Sep 17 00:00:00 2001 From: HiveBeats <38073817+HiveBeats@users.noreply.github.com> Date: Mon, 31 Oct 2022 08:49:49 +0400 Subject: [PATCH] refactor: separate files --- SoundGen.sln | 16 +++++ SoundGen/Fx.fs | 19 ++++++ SoundGen/PCMWave.fs | 44 ++++++++++++++ SoundGen/Program.fs | 127 +++------------------------------------ SoundGen/Settings.fs | 8 +++ SoundGen/SoundGen.fsproj | 20 ++++++ SoundGen/Synth.fs | 74 +++++++++++++++++++++++ 7 files changed, 188 insertions(+), 120 deletions(-) create mode 100644 SoundGen.sln create mode 100644 SoundGen/Fx.fs create mode 100644 SoundGen/PCMWave.fs create mode 100644 SoundGen/Settings.fs create mode 100644 SoundGen/SoundGen.fsproj create mode 100644 SoundGen/Synth.fs diff --git a/SoundGen.sln b/SoundGen.sln new file mode 100644 index 0000000..e1e0dfd --- /dev/null +++ b/SoundGen.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SoundGen", "SoundGen\SoundGen.fsproj", "{724E3D1E-26BC-45FD-B9D5-D1BE001E916A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {724E3D1E-26BC-45FD-B9D5-D1BE001E916A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {724E3D1E-26BC-45FD-B9D5-D1BE001E916A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {724E3D1E-26BC-45FD-B9D5-D1BE001E916A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {724E3D1E-26BC-45FD-B9D5-D1BE001E916A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/SoundGen/Fx.fs b/SoundGen/Fx.fs new file mode 100644 index 0000000..bddbaea --- /dev/null +++ b/SoundGen/Fx.fs @@ -0,0 +1,19 @@ +module SoundGen.Fx + +type Effect = + | Saturator + | Reverb + +type Reverb = { Wet: float; Room: float } +type Saturator = { Gain: float } + +let saturate (param: Saturator, x: float) = tanh (param.Gain * x) + +// +// let process(effects:Effect list, sound:float seq) = +// let mutable output = sound +// effects +// |> List.iter((fun eff -> +// match eff with +// |Saturator -> output <- (output |> Seq.map saturate eff))) +// diff --git a/SoundGen/PCMWave.fs b/SoundGen/PCMWave.fs new file mode 100644 index 0000000..a16639c --- /dev/null +++ b/SoundGen/PCMWave.fs @@ -0,0 +1,44 @@ +module SoundGen.PCMWave +open Settings +open System.IO + +let toInt16Sample sample = sample |> (*) 32767. |> int16 + + +let pack (d: int16 []) = + let stream = new MemoryStream() + + let writer = + new BinaryWriter(stream, System.Text.Encoding.ASCII) + + let dataLength = Array.length d * 2 + + // RIFF + writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF")) + writer.Write(Array.length d) + writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVE")) + + // fmt + let sr = sampleRate |> int + writer.Write(System.Text.Encoding.ASCII.GetBytes("fmt ")) + writer.Write(16) + writer.Write(1s) // PCM + writer.Write(1s) // mono + writer.Write(sr) // sample rate + writer.Write(sr * 16 / 8) // byte rate + writer.Write(2s) // bytes per sample + writer.Write(16s) // bits per sample + + // data + writer.Write(System.Text.Encoding.ASCII.GetBytes("data")) + writer.Write(dataLength) + + let data: byte [] = + Array.zeroCreate dataLength + + System.Buffer.BlockCopy(d, 0, data, 0, dataLength) + writer.Write(data) + stream + +let createWAV(wave: float seq) = + wave |> Seq.map (fun x -> x |> toInt16Sample) |> Seq.toArray |> pack \ No newline at end of file diff --git a/SoundGen/Program.fs b/SoundGen/Program.fs index 39d6ced..c046707 100644 --- a/SoundGen/Program.fs +++ b/SoundGen/Program.fs @@ -1,88 +1,9 @@ -module SoundGen + open System.IO - -let sampleRate = 48000. -let bpm = 120. -let beatDuration = 60. / bpm -let pitchStandard = 440. -let volume = 0.5 -let attackMs = 100. - -let getHzBySemitones semi = - pitchStandard * (2. ** (1. / 12.)) ** semi - -let getSemitonesByNote (str: string) = - let defaultOctave = 4 - - let notes = - [ "A" - "A#" - "B" - "C" - "C#" - "D" - "D#" - "E" - "F" - "F#" - "G" - "G#" ] - |> List.toArray - - let octave = - str.Substring(str.Length - 1) |> int - - let noteShift = - Array.findIndex (fun e -> e = str.Substring(0, str.Length - 1)) notes - - (octave - defaultOctave - 1) * 12 + noteShift - -let saturate x = - tanh (6. * x) - -let toInt16Sample sample = sample |> (*) 32767. |> int16 - -let freq hz duration = - let samples = - seq { 0.0 .. (duration * sampleRate) } - - let attack = - let samplesToRise = - (sampleRate * (0.001 * attackMs)) - - let risingDelta = 1. / samplesToRise - let mutable i = 0. - - seq { - while true do - i <- i + risingDelta - yield min i 1. - } - - let output = - Seq.map - (fun x -> - x - |> (*) (2. * System.Math.PI * hz / sampleRate) - |> sin - |> (*) volume - ) - samples - - let adsrLength = Seq.length output - - let attackArray = - attack |> Seq.take adsrLength - - let release = Seq.rev attackArray - - Seq.zip3 release attackArray output - |> Seq.map (fun (x, y, z) -> (x * y * z) |> saturate) - - -let note semitone beats = - let hz = getHzBySemitones semitone - freq hz (beats * beatDuration) +open SoundGen +open PCMWave +open Fx +open Synth let song = [ note 3 0.5 @@ -126,47 +47,13 @@ let song = note 15 0.5 ] |> Seq.concat -let pack (d: int16 []) = - let stream = new MemoryStream() - let writer = - new BinaryWriter(stream, System.Text.Encoding.ASCII) - - let dataLength = Array.length d * 2 - - // RIFF - writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF")) - writer.Write(Array.length d) - writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVE")) - - // fmt - let sr = sampleRate |> int - writer.Write(System.Text.Encoding.ASCII.GetBytes("fmt ")) - writer.Write(16) - writer.Write(1s) // PCM - writer.Write(1s) // mono - writer.Write(sr) // sample rate - writer.Write(sr * 16 / 8) // byte rate - writer.Write(2s) // bytes per sample - writer.Write(16s) // bits per sample - - // data - writer.Write(System.Text.Encoding.ASCII.GetBytes("data")) - writer.Write(dataLength) - - let data: byte [] = - Array.zeroCreate dataLength - - System.Buffer.BlockCopy(d, 0, data, 0, dataLength) - writer.Write(data) - stream - -let write (ms: MemoryStream) = +let writeToFile (ms: MemoryStream) = use fs = new FileStream(Path.Combine(__SOURCE_DIRECTORY__, "test.wav"), FileMode.Create) ms.WriteTo(fs) -song |> Seq.map (fun x -> x |> toInt16Sample) |> Seq.toArray |> pack |> write +song |> createWAV |> writeToFile diff --git a/SoundGen/Settings.fs b/SoundGen/Settings.fs new file mode 100644 index 0000000..3b9714b --- /dev/null +++ b/SoundGen/Settings.fs @@ -0,0 +1,8 @@ +module SoundGen.Settings + +let sampleRate = 48000. +let bpm = 120. +let beatDuration = 60. / bpm +let pitchStandard = 440. +let volume = 0.5 +let attackMs = 100. \ No newline at end of file diff --git a/SoundGen/SoundGen.fsproj b/SoundGen/SoundGen.fsproj new file mode 100644 index 0000000..5ff2e23 --- /dev/null +++ b/SoundGen/SoundGen.fsproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + diff --git a/SoundGen/Synth.fs b/SoundGen/Synth.fs new file mode 100644 index 0000000..82ca0ca --- /dev/null +++ b/SoundGen/Synth.fs @@ -0,0 +1,74 @@ +module SoundGen.Synth +open Settings + +let getHzBySemitones semi = + pitchStandard * (2. ** (1. / 12.)) ** semi + +let getSemitonesByNote (str: string) = + let defaultOctave = 4 + + let notes = + [ "A" + "A#" + "B" + "C" + "C#" + "D" + "D#" + "E" + "F" + "F#" + "G" + "G#" ] + |> List.toArray + + let octave = + str.Substring(str.Length - 1) |> int + + let noteShift = + Array.findIndex (fun e -> e = str.Substring(0, str.Length - 1)) notes + + (octave - defaultOctave - 1) * 12 + noteShift + + +let freq hz duration = + let samples = + seq { 0.0 .. (duration * sampleRate) } + + let attack = + let samplesToRise = + (sampleRate * (0.001 * attackMs)) + + let risingDelta = 1. / samplesToRise + let mutable i = 0. + + seq { + while true do + i <- i + risingDelta + yield min i 1. + } + + let output = + Seq.map + (fun x -> + x + |> (*) (2. * System.Math.PI * hz / sampleRate) + |> sin + |> (*) volume + ) + samples + + let adsrLength = Seq.length output + + let attackArray = + attack |> Seq.take adsrLength + + let release = Seq.rev attackArray + + Seq.zip3 release attackArray output + |> Seq.map (fun (x, y, z) -> (x * y * z)) + + +let note semitone beats = + let hz = getHzBySemitones semitone + freq hz (beats * beatDuration) \ No newline at end of file