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