@pro-gram/flux 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Linq;
4
+ using System.Text;
5
+ using System.Threading;
6
+ using System.Collections.Generic;
7
+ using System.Windows;
8
+ using System.Windows.Media;
9
+ using System.Windows.Media.Imaging;
10
+
11
+ namespace DrumBuddy
12
+ {
13
+ public static class EditorToolsTests
14
+ {
15
+ private static readonly List<string> log = new List<string>();
16
+ private static void Check(bool value, string text) { if (!value) throw new Exception(text); log.Add("PASS " + text); }
17
+ private static long Frame(double seconds) { return (long)Math.Round(seconds * Audio.Rate); }
18
+ private static float[] Render(EditorSession session, IDictionary<string, Clip> assets)
19
+ {
20
+ using (var mix = new TimelineMix(session, assets)) { var output = new float[checked((int)session.End * 2)]; int n = 0, read; while ((read = mix.Read(output, n, output.Length - n)) > 0) n += read; return output; }
21
+ }
22
+ private static void Picture(FrameworkElement view, string file)
23
+ {
24
+ view.Measure(new Size(1100, 650)); view.Arrange(new Rect(0, 0, 1100, 650)); view.UpdateLayout();
25
+ var bitmap = new RenderTargetBitmap(1100, 650, 96, 96, PixelFormats.Pbgra32); bitmap.Render(view);
26
+ var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap)); using (var output = File.Create(file)) encoder.Save(output);
27
+ }
28
+ public static int Run(string[] args)
29
+ {
30
+ string root = Path.GetFullPath(args[0]); Directory.CreateDirectory(root); log.Clear(); var preferences = InterfacePreferences.Current;
31
+ try {
32
+ InterfacePreferences.Current = InterfacePreferences.Defaults(); InterfacePreferences.Current.SnapToGrid = InterfacePreferences.Current.SnapToEdges = false;
33
+ string file = Path.Combine(root, "Original float.wav"); var samples = Enumerable.Repeat(1.5f, Audio.Rate * 4).ToArray();
34
+ using (var writer = new FloatWaveWriter(file, 1, Audio.Rate)) writer.Write(samples, samples.Length, 1);
35
+ byte[] original = File.ReadAllBytes(file);
36
+ var clip = new Clip { Source = file, Cache = file, Name = "Original float.wav", Channels = 1, Duration = 4, Peak = 1.5, Waveform = Enumerable.Repeat(1.5f, 900).ToArray(), Ready = true };
37
+ var assets = new Dictionary<string, Clip> { { file, clip } };
38
+ var fade = new EditorSession(); fade.Regions.Add(new TimelineRegion { Id = "fade", Source = file, Length = 100, FadeIn = 20, FadeOut = 20 });
39
+ var rendered = Render(fade, assets);
40
+ Check(rendered[0] == 0 && rendered[40] == 1.5f && rendered[198] == 0, "Apply clip fade-in/out to actual float samples while retaining headroom");
41
+ var split = fade.Copy(); TimelineEdits.Split(split, 0, false, 10); split.Validate(); Check(rendered.SequenceEqual(Render(split, assets)), "Splitting inside a fade preserves the exact audible envelope");
42
+ var copied = TimelineEdits.Copy(fade, 0, false, 5, 85); var pasted = new EditorSession(); TimelineEdits.Paste(pasted, copied, 0, 0);
43
+ Check(Render(pasted, assets).SequenceEqual(rendered.Skip(10).Take(160)), "Copy/paste part of a faded clip preserves its original sample levels");
44
+ var muted = fade.Copy(); EditorTools.ToggleMute(muted, new HashSet<string> { "fade" }); Check(Render(muted, assets).All(v => v == 0), "Clip mute silences playback and mix output without changing source audio");
45
+ var cross = new EditorSession(); cross.Regions.Add(new TimelineRegion { Id = "a", Source = file, Length = 100 }); cross.Regions.Add(new TimelineRegion { Id = "b", Source = file, Start = 80, Length = 100 });
46
+ EditorTools.Crossfade(cross, new HashSet<string> { "a", "b" });
47
+ Check(Render(cross, assets).Skip(160).Take(40).All(v => Math.Abs(v - 1.5f) < 1e-6), "Crossfade overlapping clips with complementary levels across the overlap");
48
+ string exported = Path.Combine(root, "Faded mix.wav"); TimelineRender.Export(fade, assets, exported, 32, false, 0, 0, CancellationToken.None, null);
49
+ using (var wave = new NAudio.Wave.WaveFileReader(exported)) { var bytes = new byte[checked((int)wave.Length)]; wave.Read(bytes, 0, bytes.Length); var actual = new float[bytes.Length / 4]; Buffer.BlockCopy(bytes, 0, actual, 0, bytes.Length); Check(actual.SequenceEqual(rendered), "Save the same clip fades heard in playback into a float mix export"); }
50
+ string project = Path.Combine(root, "Fades.dbedit"); split.Regions[0].Muted = true; split.Save(project); var loaded = EditorSession.Load(project);
51
+ Check(loaded.Regions[0].Muted && loaded.Regions[1].FadeInOffset == 10, "Persist clip mute, fades and split-envelope offsets in project files");
52
+ var state = new EditorSession { Name = "Clip tools" };
53
+ state.Regions.Add(new TimelineRegion { Id = "first", Name = "Move / trim / slip", Source = file, Start = Frame(1), SourceStart = Frame(1), Length = Frame(2) });
54
+ state.Regions.Add(new TimelineRegion { Id = "second", Name = "Group item", Source = file, Track = 1, Start = Frame(2), Length = Frame(1) });
55
+ var owner = new Window(); ThemePalette.Apply(true); ThemePalette.Install(owner);
56
+ using (var editor = new EditorView(owner, Path.Combine(root, "cache"), delegate { }, delegate { })) {
57
+ owner.Content = editor; editor.SetForTest(state, assets); editor.ZoomForTest(250); Picture(editor, Path.Combine(root, "clip-tools-start.png"));
58
+ editor.PointerDownForTest(Frame(1.25), 0, false); editor.PointerUpForTest(Frame(1.25)); editor.PointerDownForTest(Frame(2.25), 1, true);
59
+ editor.ToolDownForTest("first", "Move", Frame(1.5)); editor.ToolMoveForTest(Frame(3.5), 2); editor.ToolEndForTest(false);
60
+ var moved = editor.SessionForTest;
61
+ Check(moved.Regions.Single(r => r.Id == "first").Start == Frame(3) && moved.Regions.Single(r => r.Id == "second").Start == Frame(4) && moved.Regions.Single(r => r.Id == "first").Track == 2 && moved.Regions.Single(r => r.Id == "second").Track == 3, "Move a selected clip group in time and across tracks without losing relative placement");
62
+ editor.ExecuteAction("undo").GetAwaiter().GetResult(); Check(editor.SessionForTest.Regions.Single(r => r.Id == "first").Start == Frame(1), "Undo a complete clip drag as one edit");
63
+ editor.SetForTest(state, assets); editor.ToolDownForTest("first", "Move", Frame(1.5)); editor.ToolMoveForTest(Frame(2.5), 2); editor.ToolEndForTest(true);
64
+ Check(!editor.Dirty && editor.SessionForTest.Regions.Single(r => r.Id == "first").Track == 0 && editor.SessionForTest.Regions.Single(r => r.Id == "first").Start == Frame(1), "Cancel a clip drag without changing the session or dirty state");
65
+ editor.ToolDownForTest("first", "TrimLeft", Frame(1)); editor.ToolMoveForTest(Frame(1.5), 0); editor.ToolEndForTest(false);
66
+ var trimmed = editor.SessionForTest.Regions.Single(r => r.Id == "first"); Check(trimmed.Start == Frame(1.5) && trimmed.SourceStart == Frame(1.5) && trimmed.Length == Frame(1.5), "Trim the left edge while advancing the exact source offset");
67
+ editor.SetForTest(state, assets); editor.ToolDownForTest("first", "TrimRight", Frame(3)); editor.ToolMoveForTest(Frame(8), 0); editor.ToolEndForTest(false);
68
+ Check(editor.SessionForTest.Regions.Single(r => r.Id == "first").End == Frame(4), "Clamp edge extension to available source audio");
69
+ editor.SetForTest(state, assets); editor.ToolDownForTest("first", "Slip", Frame(1.5)); editor.ToolMoveForTest(Frame(1.25), 0); editor.ToolEndForTest(false);
70
+ var slipped = editor.SessionForTest.Regions.Single(r => r.Id == "first"); Check(slipped.SourceStart == Frame(1.25) && slipped.Start == Frame(1) && slipped.Length == Frame(2), "Slip source contents while retaining clip start and end");
71
+ editor.ToolDownForTest("first", "FadeIn", Frame(1)); editor.ToolMoveForTest(Frame(1.5), 0); editor.ToolEndForTest(false); editor.ToolDownForTest("first", "FadeOut", Frame(3)); editor.ToolMoveForTest(Frame(2.5), 0); editor.ToolEndForTest(false);
72
+ Check(editor.SessionForTest.Regions.Single(r => r.Id == "first").FadeIn == Frame(.5) && editor.SessionForTest.Regions.Single(r => r.Id == "first").FadeOut == Frame(.5), "Commit both fade handles as audible, undoable clip properties");
73
+ editor.ExecuteAction("zoomSelection").GetAwaiter().GetResult(); Picture(editor, Path.Combine(root, "clip-tools-fades.png"));
74
+ editor.SetForTest(state, assets); editor.PointerDownForTest(Frame(1.25), 0, false); editor.PointerUpForTest(Frame(1.25)); editor.ExecuteAction("duplicate").GetAwaiter().GetResult();
75
+ Check(editor.SessionForTest.Regions.Any(r => r.Id != "first" && r.Track == 0 && r.Start == Frame(3) && r.SourceStart == Frame(1)), "Duplicate selected audio directly after its original duration");
76
+ editor.SetForTest(state, assets); editor.ShowSelectionForTest(Frame(1.25), Frame(1.75)); editor.ContextForTest(Frame(1.5), 0);
77
+ Check(editor.SelectedClipsForTest == 0, "Keep a dragged time range active when opening its context menu");
78
+ editor.ExecuteAction("clearFades").GetAwaiter().GetResult();
79
+ Check(!editor.Dirty && editor.SessionForTest.Regions.Count == 2, "Roll back temporary splits when a range operation makes no audio change");
80
+ }
81
+ owner.Content = null; owner.Close();
82
+ var repeats = state.Copy(); EditorTools.Repeat(repeats, new HashSet<string> { "first", "second" }, 3);
83
+ Check(repeats.Regions.Count == 8 && repeats.Regions.Any(r => r.Track == 1 && r.Start == Frame(8)), "Repeat selected multitrack clips with their timing and source windows intact");
84
+ bool rejected = false; int before = repeats.Regions.Count; try { EditorTools.Repeat(repeats, new HashSet<string> { "first" }, 65); } catch (IOException) { rejected = true; }
85
+ Check(rejected && repeats.Regions.Count == before, "Reject excessive repeats before partially editing the project");
86
+ Check(original.SequenceEqual(File.ReadAllBytes(file)), "Keep original audio byte-for-byte unchanged across all new clip tools");
87
+ File.WriteAllLines(Path.Combine(root, "tools-test-results.txt"), log, Encoding.UTF8); return 0;
88
+ }
89
+ catch (Exception ex) { log.Add("FAIL " + ex); File.WriteAllLines(Path.Combine(root, "tools-test-results.txt"), log, Encoding.UTF8); return 1; }
90
+ finally { InterfacePreferences.Current = preferences; ThemePalette.Apply(preferences.Theme == "Dark"); }
91
+ }
92
+ }
93
+ }