refactor: separate files
This commit is contained in:
16
SoundGen.sln
Normal file
16
SoundGen.sln
Normal file
@@ -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
|
||||||
19
SoundGen/Fx.fs
Normal file
19
SoundGen/Fx.fs
Normal file
@@ -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)))
|
||||||
|
//
|
||||||
44
SoundGen/PCMWave.fs
Normal file
44
SoundGen/PCMWave.fs
Normal file
@@ -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
|
||||||
@@ -1,88 +1,9 @@
|
|||||||
module SoundGen
|
|
||||||
open System.IO
|
open System.IO
|
||||||
|
open SoundGen
|
||||||
let sampleRate = 48000.
|
open PCMWave
|
||||||
let bpm = 120.
|
open Fx
|
||||||
let beatDuration = 60. / bpm
|
open Synth
|
||||||
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)
|
|
||||||
|
|
||||||
let song =
|
let song =
|
||||||
[ note 3 0.5
|
[ note 3 0.5
|
||||||
@@ -126,47 +47,13 @@ let song =
|
|||||||
note 15 0.5 ]
|
note 15 0.5 ]
|
||||||
|> Seq.concat
|
|> Seq.concat
|
||||||
|
|
||||||
let pack (d: int16 []) =
|
|
||||||
let stream = new MemoryStream()
|
|
||||||
|
|
||||||
let writer =
|
let writeToFile (ms: MemoryStream) =
|
||||||
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) =
|
|
||||||
use fs =
|
use fs =
|
||||||
new FileStream(Path.Combine(__SOURCE_DIRECTORY__, "test.wav"), FileMode.Create)
|
new FileStream(Path.Combine(__SOURCE_DIRECTORY__, "test.wav"), FileMode.Create)
|
||||||
|
|
||||||
ms.WriteTo(fs)
|
ms.WriteTo(fs)
|
||||||
|
|
||||||
song |> Seq.map (fun x -> x |> toInt16Sample) |> Seq.toArray |> pack |> write
|
song |> createWAV |> writeToFile
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
SoundGen/Settings.fs
Normal file
8
SoundGen/Settings.fs
Normal file
@@ -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.
|
||||||
20
SoundGen/SoundGen.fsproj
Normal file
20
SoundGen/SoundGen.fsproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Settings.fs" />
|
||||||
|
<Compile Include="Synth.fs" />
|
||||||
|
<Compile Include="Fx.fs" />
|
||||||
|
<Compile Include="PCMWave.fs" />
|
||||||
|
<Compile Include="Program.fs" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
74
SoundGen/Synth.fs
Normal file
74
SoundGen/Synth.fs
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user