module SoundGen.Synth open Settings open Oscillator let private getHzBySemitones semi = pitchStandard * (2. ** (1. / 12.)) ** semi let private getSemitoneShiftInternal (rootNote: string) (targetNote: string) : int = // Define arrays to map pitch classes to numeric values and vice versa let pitchClasses = [| "C"; "C#"; "D"; "D#"; "E"; "F"; "F#"; "G"; "G#"; "A"; "A#"; "B" |] // Extract the note number and pitch class for the root note let rootNoteNum = int (rootNote.Substring(rootNote.Length - 1)) let rootPitchClass = Array.findIndex ((=) (rootNote.Substring(0, rootNote.Length - 1))) pitchClasses // Extract the note number and pitch class for the target note let targetNoteNum = int (targetNote.Substring(targetNote.Length - 1)) let targetPitchClass = Array.findIndex ((=) (targetNote.Substring(0, targetNote.Length - 1))) pitchClasses // Calculate the semitone shift using the formula (targetNoteNum - rootNoteNum) * 12 + (targetPitchClass - rootPitchClass) let private freq hz duration (osc: OscillatorParameter list) = 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 -> multiosc { Oscillators = osc; Sample = x } |> (*) 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 getSemitoneShift (targetNote: string) : int = getSemitoneShiftInternal "A4" targetNote let note semitone beats = let hz = getHzBySemitones (semitone) freq hz (beats * beatDuration) [ { Osc = Saw; Freq = hz / 4. } { Osc = Saw; Freq = hz + 0.5 } { Osc = Saw; Freq = hz - 1. } ]