diff --git a/ProjectObsidian/ProjectObsidian.csproj b/ProjectObsidian/ProjectObsidian.csproj index 6f42d64..462344f 100644 --- a/ProjectObsidian/ProjectObsidian.csproj +++ b/ProjectObsidian/ProjectObsidian.csproj @@ -3,7 +3,7 @@ Project-Obsidian Project-Obsidian net48 - 11 + 12 Copyright © 2024 Project-Obsidian diff --git a/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs b/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs new file mode 100644 index 0000000..6f2be63 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs @@ -0,0 +1,162 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; +using Elements.Assets; +using Elements.Core; +using System.Linq; +using System.Collections.Generic; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Math +{ + [DataModelType] + public enum NoteScale + { + Chromatic, + Major, + Minor, + Blues, + Dorian, + Phrygian, + Lydian, + Mixolydian, + Locrian + } + + [NodeCategory("Obsidian/Math")] + public class FrequencyQuantize : VoidNode + { + public readonly ValueInput Frequency; + public readonly ValueInput Scale; + public readonly ValueInput RoundToNearest; + + public readonly ValueInput Offset; + + public readonly ValueOutput QuantizedFrequency; + public readonly ValueOutput InScale; + + public static readonly List ChromaticScale = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + public static readonly List MajorScale = [0, 2, 4, 5, 7, 9, 11]; + public static readonly List MinorScale = [0, 2, 3, 5, 7, 8, 10]; + public static readonly List BluesScale = [0, 3, 5, 6, 7, 10]; + public static readonly List DorianScale = [0, 2, 3, 5, 7, 9, 10]; + public static readonly List PhrygianScale = [0, 1, 3, 5, 7, 8, 10]; + public static readonly List LydianScale = [0, 2, 4, 6, 7, 9, 11]; + public static readonly List MixolydianScale = [0, 2, 4, 5, 7, 9, 10]; + public static readonly List LocrianScale = [0, 1, 3, 5, 6, 8, 10]; + + private static int FreqToNearestMidi(float freq) + { + var n = 12f * (float)System.Math.Log(freq / 440f, 2f) + 69f; + return (int)MathX.Round(n, MidpointRounding.AwayFromZero); + } + + private static float MidiToFreq(int midi) + { + return 440f * MathX.Pow(2, (midi - 69f) / 12f); + } + + protected override void ComputeOutputs(FrooxEngineContext context) + { + var freq = Frequency.Evaluate(context); + + var offset = Offset.Evaluate(context); + var rootNote = 69 + offset; + + var midi = FreqToNearestMidi(freq); + + bool inScale = false; + List scaleList; + switch (Scale.Evaluate(context)) + { + case NoteScale.Chromatic: + scaleList = ChromaticScale; + break; + case NoteScale.Major: + scaleList = MajorScale; + break; + case NoteScale.Minor: + scaleList = MinorScale; + break; + case NoteScale.Blues: + scaleList = BluesScale; + break; + case NoteScale.Dorian: + scaleList = DorianScale; + break; + case NoteScale.Phrygian: + scaleList = PhrygianScale; + break; + case NoteScale.Lydian: + scaleList = LydianScale; + break; + case NoteScale.Mixolydian: + scaleList = MixolydianScale; + break; + case NoteScale.Locrian: + scaleList = LocrianScale; + break; + default: + scaleList = ChromaticScale; + break; + } + foreach (var note in scaleList) + { + var scaleDegree = (midi - rootNote) % 12; + if (scaleDegree < 0) scaleDegree += 12; + if (note == scaleDegree) + { + inScale = true; + break; + } + } + if (!inScale) + { + if (RoundToNearest.Evaluate(context)) + { + int scaleDegree = (midi - rootNote) % 12; + if (scaleDegree < 0) + scaleDegree += 12; + + int closestSemitone = scaleList[0]; + int minDifference = int.MaxValue; + + foreach (int scaleSemitone in scaleList) + { + int difference = MathX.Abs(scaleDegree - scaleSemitone); + if (difference < minDifference) + { + minDifference = difference; + closestSemitone = scaleSemitone; + } + } + + int octave = (midi - rootNote) / 12; + + midi += closestSemitone - scaleDegree; + + //UniLog.Log($"midi: {midi} octave: {octave} rootNote: {rootNote} closestSemitone: {closestSemitone} scaleDegree: {scaleDegree}"); + + inScale = true; + } + } + InScale.Write(inScale, context); + if (inScale) + { + var res = MidiToFreq(midi); + QuantizedFrequency.Write(res, context); + } + else + { + QuantizedFrequency.Write(0f, context); + } + } + + public FrequencyQuantize() + { + QuantizedFrequency = new ValueOutput(this); + InScale = new ValueOutput(this); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Math/MIDI_NoteFrequency.cs b/ProjectObsidian/ProtoFlux/Math/MIDI_NoteFrequency.cs new file mode 100644 index 0000000..78e7f61 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Math/MIDI_NoteFrequency.cs @@ -0,0 +1,21 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; +using Elements.Assets; +using Elements.Core; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Math +{ + [NodeCategory("Obsidian/Math")] + public class MIDI_NoteFrequency : ValueFunctionNode + { + public readonly ValueInput NoteNumber; + protected override float Compute(FrooxEngineContext context) + { + var note = NoteNumber.Evaluate(context); + return 440f * MathX.Pow(2, (note - 69f) / 12f); + } + } +} \ No newline at end of file