From aef4eb4f37777a52c5d69e4b6a1cb86bd82d9f00 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 20 Mar 2025 18:51:08 -0400 Subject: [PATCH] major update commit --- .gitmodules | 6 + desktop-framework-dotnet | 1 + editor-dotnet.sln | 115 +++++- .../MBS.Editor.Web.Server.csproj | 15 + .../src/app/MBS.Editor.Web.Server/Program.cs | 102 +++++ .../src/app/MBS.Editor.Web.Server/TODO.txt | 12 + .../src/app/MBS.Editor/MBS.Editor.csproj | 2 +- editor-dotnet/src/install-engines.sh | 23 ++ .../src/lib/MBS.Editor.Core/DataFormat.cs | 59 ++- .../DataFormats/Chunked/ChunkedDataFormat.cs | 142 +++++++ .../FileSystem/IFF/IFFDataFormat.cs | 45 +++ .../FileSystem/IFF/RIFFDataFormat.cs | 27 ++ .../PropertyList/INI/INIDataFormat.cs | 205 ++++++++++ .../PropertyList/INI/INIDataFormatToken.cs | 9 + .../src/lib/MBS.Editor.Core/IO/Reader.cs | 27 +- .../MBS.Editor.Core/IO/ReaderWriterBase.cs | 25 +- .../src/lib/MBS.Editor.Core/ObjectModel.cs | 11 +- .../ObjectModelNotSupportedException.cs | 14 +- .../ObjectModels/Chunked/Chunk.cs | 63 ++++ .../Chunked/ChunkedObjectModel.cs | 23 ++ .../ObjectModels/Chunked/DataChunk.cs | 29 ++ .../ObjectModels/Chunked/GroupChunk.cs | 29 ++ .../ObjectModels/Chunked/IChunkContainer.cs | 23 ++ .../ObjectModels/FileSystem/FileSource.cs | 8 + .../FileSources/EmbeddedFileSource.cs | 2 +- .../FileSystem/FileSystemItemCollection.cs | 10 + .../PropertyList/IPropertyListContainer.cs | 9 + .../PropertyList/PropertyListComment.cs | 6 + .../PropertyList/PropertyListGroup.cs | 14 + .../PropertyList/PropertyListItem.cs | 58 +++ .../PropertyList/PropertyListObjectModel.cs | 18 + .../PropertyList/PropertyListProperty.cs | 41 ++ .../Schema/PropertyDataTypeDefinition.cs | 27 ++ .../PropertyList/Schema/PropertyDefinition.cs | 41 ++ .../PropertyList/Schema/PropertyListSchema.cs | 14 + .../EmbeddedResourceTest.cs | 65 ++++ .../MBS.Editor.Testing.csproj | 19 + .../EditorApplication.cs | 34 +- .../MBS.Editor.UserInterface/EditorWindow.cs | 20 + .../MBS.Editor.UserInterface.csproj | 2 +- .../lib/MBS.Editor.Web/Controls/PageHeader.cs | 31 ++ .../lib/MBS.Editor.Web/MBS.Editor.Web.csproj | 14 + .../src/lib/MBS.Editor.Web/Pages/MainPage.cs | 123 ++++++ .../MBS.Editor.Plugins.Kronosaur/Class1.cs | 6 + .../MBS.Editor.Plugins.Kronosaur.csproj | 9 + .../DataFormats/Mechanic/MEKDataFormat.cs | 70 ++++ .../PropertyList/MEKBaseDataFormat.cs | 92 +++++ .../PropertyList/MEKPropertyType.cs | 10 + .../MBS.Editor.Plugins.Mekada.csproj | 14 + .../Mechanic/MechanicObjectModel.cs | 11 + .../Audio/Synthesized/MIDI/MIDICommand.cs | 143 +++++++ .../Audio/Synthesized/MIDI/MIDICommandType.cs | 38 ++ .../Audio/Synthesized/MIDI/MIDIDataFormat.cs | 353 ++++++++++++++++++ .../Audio/Synthesized/MIDI/MIDIEventType.cs | 46 +++ .../Synthesized/MIDI/MIDIFileFormatType.cs | 46 +++ .../Synthesized/MIDI/MIDIMetaEventType.cs | 46 +++ .../Audio/Synthesized/RMI/RMIDataFormat.cs | 114 ++++++ .../Picture/ILBM/ILBMCompression.cs | 27 ++ .../Picture/ILBM/ILBMDataFormat.cs | 164 ++++++++ .../DataFormats/Picture/ILBM/ILBMMasking.cs | 46 +++ .../ObjectModels/Audio/AudioMetadata.cs | 26 ++ .../Audio/AudioObjectModelBase.cs | 26 ++ .../Synthesized/SynthesizedAudioCommand.cs | 114 ++++++ .../SynthesizedAudioCommandNote.cs | 82 ++++ .../SynthesizedAudioCommandRest.cs | 29 ++ .../SynthesizedAudioCommandTempo.cs | 49 +++ .../SynthesizedAudioCommandText.cs | 48 +++ .../SynthesizedAudioCommandTimeSignature.cs | 66 ++++ .../SynthesizedAudioObjectModel.cs | 109 ++++++ .../SynthesizedAudioPredefinedNote.cs | 98 +++++ .../SynthesizedAudioStylePlugin.cs | 63 ++++ .../Synthesized/SynthesizedAudioTrack.cs | 101 +++++ .../SynthesizedAudioVibratoType.cs | 30 ++ .../ObjectModels/Palette/PaletteEntry.cs | 71 ++++ .../Palette/PaletteObjectModel.cs | 85 +++++ .../Picture/PictureObjectModel.cs | 55 +++ .../FileSystem/IFF/IFFDataFormatTests.cs | 49 +++ .../MBS.Editor.Core.Tests.csproj | 5 + .../FileSystemItemCollectionTests.cs | 2 +- .../PropertyList/INIDataFormatTests.cs | 80 ++++ .../DataFormats/FileSystem/IFF/VENUS.IFF | Bin 0 -> 26978 bytes .../PropertyList/INI/CompleteTest.ini | 16 + .../DataFormats/CPK/CPKDataFormatTests.cs | 25 +- .../Database/UTF/UTFDataFormatTests.cs | 8 + .../MBS.Editor.Plugins.CRI.Tests.csproj | 1 + .../DataFormats/MEK/MEKDataFormatTests.cs | 59 +++ .../GlobalUsings.cs | 1 + .../MBS.Editor.Plugins.Mekada.Tests.csproj | 39 ++ .../Resources/TestData/Mechanics/0.mek | Bin 0 -> 389 bytes .../Resources/TestData/Mechanics/1.mek | Bin 0 -> 16561 bytes .../TestData/Mechanics/WithCarInLot.mek | Bin 0 -> 72760 bytes .../Synthesized/RMI/RMIDataFormatTests.cs | 117 ++++++ .../Picture/ILBM/ILBMDataFormatTests.cs | 62 +++ .../GlobalUsings.cs | 1 + ...MBS.Editor.Plugins.Multimedia.Tests.csproj | 42 +++ .../Audio/Synthesized/RMI/Mountain.rmi | Bin 0 -> 38444 bytes .../TestData/DataFormats/Picture/ILBM/BLK.IFF | Bin 0 -> 2564 bytes .../DataFormats/Picture/ILBM/VENUS.IFF | Bin 0 -> 26978 bytes framework-dotnet | 2 +- web-framework-dotnet | 1 + 100 files changed, 4278 insertions(+), 82 deletions(-) create mode 160000 desktop-framework-dotnet create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt create mode 100755 editor-dotnet/src/install-engines.sh create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj create mode 100644 editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/1.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF create mode 160000 web-framework-dotnet diff --git a/.gitmodules b/.gitmodules index 479b021..3dc70c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "framework-dotnet"] path = framework-dotnet url = git@gitea.azcona-becker.net:alcetech/framework-dotnet +[submodule "desktop-framework-dotnet"] + path = desktop-framework-dotnet + url = git@gitea.azcona-becker.net:alcetech/desktop-framework-dotnet +[submodule "web-framework-dotnet"] + path = web-framework-dotnet + url = git@gitea.azcona-becker.net:alcetech/web-framework-dotnet diff --git a/desktop-framework-dotnet b/desktop-framework-dotnet new file mode 160000 index 0000000..57316e3 --- /dev/null +++ b/desktop-framework-dotnet @@ -0,0 +1 @@ +Subproject commit 57316e3557c1c3a566d1a24bd60675c02d1f8744 diff --git a/editor-dotnet.sln b/editor-dotnet.sln index 499bead..a0cc482 100644 --- a/editor-dotnet.sln +++ b/editor-dotnet.sln @@ -14,8 +14,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor.UserInterface", "editor-dotnet\src\lib\MBS.Editor.UserInterface\MBS.Editor.UserInterface.csproj", "{C4316562-555A-4A79-9D71-15737976DF8B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{4ED8C38B-47EF-4368-9965-CF627465B45A}" - ProjectSection(SolutionItems) = preProject - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor", "editor-dotnet\src\app\MBS.Editor\MBS.Editor.csproj", "{A936C411-0184-43F8-A343-0DE8C3B7B42E}" EndProject @@ -27,8 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BDC147D8-4D9 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{80A728D5-7C00-4B59-A37E-321C54CC554F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Desktop", "framework-dotnet\framework-dotnet\src\lib\MBS.Desktop\MBS.Desktop.csproj", "{4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Core", "framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj", "{7565CFB4-9761-4064-B18F-5E2644730BC0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{451AD529-16B4-4049-9D0C-0C79B3DDFA52}" @@ -45,6 +41,46 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Core.Tests", "ed EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.CRI.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.CRI.Tests\MBS.Editor.Plugins.CRI.Tests.csproj", "{2747FFC9-55AA-4A76-B0E9-D8A839E94E47}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "desktop-framework-dotnet", "desktop-framework-dotnet", "{1BEBE6E9-D723-4A76-8210-D470DEB8C9C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "desktop-framework-dotnet", "desktop-framework-dotnet", "{68FC9B7D-A168-47CE-9106-A2501F5E4814}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9C1544A0-92FF-46F3-A8F3-93A6EB599EDD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{418A36B7-1856-4945-AB9D-D66956A6A549}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Desktop", "desktop-framework-dotnet\desktop-framework-dotnet\src\lib\MBS.Desktop\MBS.Desktop.csproj", "{11862DE3-B214-42C5-9CFE-72FFE7F29F09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engines", "engines", "{C91C4319-D6B5-448C-BC28-3F5F65EC4EB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Desktop.Engines.GTK3", "desktop-framework-dotnet\desktop-framework-dotnet\src\engines\MBS.Desktop.Engines.GTK3\MBS.Desktop.Engines.GTK3.csproj", "{A738CB41-831D-4BB3-A7F5-87DB19FAD87B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Mekada", "editor-dotnet\src\plugins\MBS.Editor.Plugins.Mekada\MBS.Editor.Plugins.Mekada.csproj", "{9D1F7A94-B165-4581-B68D-4E2CF9F83D30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Mekada.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.Mekada.Tests\MBS.Editor.Plugins.Mekada.Tests.csproj", "{55865BF2-E3C3-4823-A295-C85F6D98901F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Testing", "editor-dotnet\src\lib\MBS.Editor.Testing\MBS.Editor.Testing.csproj", "{93A3D49C-A6EC-4F19-A410-F222DE1C9358}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Kronosaur", "editor-dotnet\src\plugins\MBS.Editor.Plugins.Kronosaur\MBS.Editor.Plugins.Kronosaur.csproj", "{5D627852-158F-460D-9E5F-56F80BDB9A0B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D5423E32-5CD5-4207-8B56-235D470E7BBB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Core.Tests", "framework-dotnet\framework-dotnet\src\tests\MBS.Core.Tests\MBS.Core.Tests.csproj", "{13F3D325-9EB8-4479-BB3F-1D663F924A8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Multimedia.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.Multimedia.Tests\MBS.Editor.Plugins.Multimedia.Tests.csproj", "{BDE1CD42-771E-4902-93DB-5B17C616C172}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Web.Server", "editor-dotnet\src\app\MBS.Editor.Web.Server\MBS.Editor.Web.Server.csproj", "{A74A8939-9E5A-4811-9D03-7B53E164A979}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web-framework-dotnet", "web-framework-dotnet", "{B24A3EC9-CAC1-4162-A71F-4E085E828DAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5A57B76D-C7E1-48A4-AB10-23E804590287}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{91E8E5EA-8098-4929-9E87-6C2D55F35A02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Web", "web-framework-dotnet\src\lib\MBS.Web\MBS.Web.csproj", "{09CD7984-6CB9-42BC-9A9E-31FA1C21697F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Web", "editor-dotnet\src\lib\MBS.Editor.Web\MBS.Editor.Web.csproj", "{C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,10 +99,6 @@ Global {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.Build.0 = Release|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.Build.0 = Release|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,13 +123,55 @@ Global {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Debug|Any CPU.Build.0 = Debug|Any CPU {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.ActiveCfg = Release|Any CPU {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.Build.0 = Release|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Release|Any CPU.Build.0 = Release|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Release|Any CPU.Build.0 = Release|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Release|Any CPU.Build.0 = Release|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Release|Any CPU.Build.0 = Release|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Release|Any CPU.Build.0 = Release|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Release|Any CPU.Build.0 = Release|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Release|Any CPU.Build.0 = Release|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Release|Any CPU.Build.0 = Release|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Release|Any CPU.Build.0 = Release|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} - {451AD529-16B4-4049-9D0C-0C79B3DDFA52} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} = {75210F45-D690-4A61-9CD8-96B09E5DAAC5} {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {8FFB417A-2CDC-429F-ABE0-19B3015530D3} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} @@ -107,13 +181,32 @@ Global {B9747AFE-160D-4807-B989-B3F0ACCA3634} = {CC86007D-8193-4EAA-932D-A96B5F09847E} {BDC147D8-4D97-4663-9408-BC822E1E0B3C} = {B9747AFE-160D-4807-B989-B3F0ACCA3634} {80A728D5-7C00-4B59-A37E-321C54CC554F} = {BDC147D8-4D97-4663-9408-BC822E1E0B3C} - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143} = {80A728D5-7C00-4B59-A37E-321C54CC554F} {7565CFB4-9761-4064-B18F-5E2644730BC0} = {80A728D5-7C00-4B59-A37E-321C54CC554F} + {451AD529-16B4-4049-9D0C-0C79B3DDFA52} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {5978938E-19F6-42AE-B588-7719A65ABCA7} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF} = {4ED8C38B-47EF-4368-9965-CF627465B45A} {78B11A3E-1371-48D8-9B8E-AE6ED2380A50} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {7A349FC6-BCE7-465D-ADBC-7A21242E2C78} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} {2747FFC9-55AA-4A76-B0E9-D8A839E94E47} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {68FC9B7D-A168-47CE-9106-A2501F5E4814} = {1BEBE6E9-D723-4A76-8210-D470DEB8C9C7} + {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} = {68FC9B7D-A168-47CE-9106-A2501F5E4814} + {418A36B7-1856-4945-AB9D-D66956A6A549} = {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} + {11862DE3-B214-42C5-9CFE-72FFE7F29F09} = {418A36B7-1856-4945-AB9D-D66956A6A549} + {C91C4319-D6B5-448C-BC28-3F5F65EC4EB7} = {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B} = {C91C4319-D6B5-448C-BC28-3F5F65EC4EB7} + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {55865BF2-E3C3-4823-A295-C85F6D98901F} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {93A3D49C-A6EC-4F19-A410-F222DE1C9358} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} + {5D627852-158F-460D-9E5F-56F80BDB9A0B} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {D5423E32-5CD5-4207-8B56-235D470E7BBB} = {BDC147D8-4D97-4663-9408-BC822E1E0B3C} + {13F3D325-9EB8-4479-BB3F-1D663F924A8D} = {D5423E32-5CD5-4207-8B56-235D470E7BBB} + {BDE1CD42-771E-4902-93DB-5B17C616C172} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {A74A8939-9E5A-4811-9D03-7B53E164A979} = {4ED8C38B-47EF-4368-9965-CF627465B45A} + {5A57B76D-C7E1-48A4-AB10-23E804590287} = {B24A3EC9-CAC1-4162-A71F-4E085E828DAD} + {91E8E5EA-8098-4929-9E87-6C2D55F35A02} = {5A57B76D-C7E1-48A4-AB10-23E804590287} + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F} = {91E8E5EA-8098-4929-9E87-6C2D55F35A02} + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D0B64EB-14E9-4013-AA33-33716704909B} diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj b/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj new file mode 100644 index 0000000..7ebf669 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs b/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs new file mode 100644 index 0000000..6154355 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs @@ -0,0 +1,102 @@ +using System.Text.Json.Nodes; +using System.Xml; +using MBS.Core; +using MBS.Editor.Web.Pages; +using MBS.Web; + +public class Program : WebApplication +{ + protected override int DefaultPort => 54920; + + public Program() + { + Commands.Add(new Command("File", "_File", new CommandItem[] + { + new CommandReferenceCommandItem("FileOpen") + })); + Commands.Add(new Command("FileOpen", "_Open")); + } + + protected override WebServer CreateWebServer() + { + WebServer ws = base.CreateWebServer(); + ws.Routes.Add(new WebRoute("/", new WebHandler(delegate(WebContext ctx) + { + ctx.Response.ContentType = "application/xhtml+xml"; + + MainPage page = new MainPage(); + page.Title = "MBS Editor"; + + MBS.Web.UI.WebControls.CommandBar toolbar = new MBS.Web.UI.WebControls.CommandBar(); + toolbar.Items.Add(new CommandReferenceCommandItem("FileOpen")); + page.Controls.Add(toolbar); + + XmlWriter sw = XmlWriter.Create(ctx.Response.Stream); + page.Render(sw); + sw.Close(); + }))); + ws.Routes.Add(new WebRoute("/api/local/browse", new WebHandler(delegate(WebContext ctx) + { + StreamWriter sw = new StreamWriter(ctx.Response.Stream); + + string relativePath = ""; + if (ctx.Request.Query.ContainsKey("path")) + { + List oPath = ctx.Request.Query["path"]; + if (oPath.Count > 0) + { + relativePath = oPath[0]; + } + } + + JsonObject json = new JsonObject(); + + string homeDirectory = "/home/beckermj/Documents"; + string absolutePath = String.Join("/", new string[] { homeDirectory, relativePath }); + if (!System.IO.Directory.Exists(absolutePath)) + { + json.Add("result", "failure"); + json.Add("message", "path not found"); + + sw.Write(json.ToJsonString()); + sw.Close(); + return; + } + + json.Add("result", "success"); + + JsonArray ary = new JsonArray(); + string[] files = System.IO.Directory.GetFiles(absolutePath, "*", SearchOption.TopDirectoryOnly); + string[] folders = System.IO.Directory.GetDirectories(absolutePath, "*", SearchOption.TopDirectoryOnly); + foreach (string file in folders) + { + JsonObject o = new JsonObject(); + string title = System.IO.Path.GetFileName(file); + o.Add("title", title); + o.Add("type", "folder"); + o.Add("path", file); + ary.Add(o); + } + foreach (string file in files) + { + JsonObject o = new JsonObject(); + string title = System.IO.Path.GetFileName(file); + o.Add("title", title); + o.Add("type", "file"); + o.Add("path", file); + ary.Add(o); + } + + json.Add("items", ary); + + sw.Write(json.ToJsonString()); + sw.Close(); + }))); + return ws; + } + + public static void Main(string[] args) + { + (new Program()).Start(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt b/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt new file mode 100644 index 0000000..5232bbe --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt @@ -0,0 +1,12 @@ +This is MBS Editor Web Server. + +example URL: + +Edit existing registered Document: +https://localhost:54920/doc/61b30ad3c7754ae2bc7abf949ee83289/edit + +Create document and register it: +https://localhost:54920/doc/new + +- redirects to /doc/.../edit when document is created + diff --git a/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj b/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj index c84f015..76d4962 100644 --- a/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj +++ b/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj @@ -3,8 +3,8 @@ - + diff --git a/editor-dotnet/src/install-engines.sh b/editor-dotnet/src/install-engines.sh new file mode 100755 index 0000000..9617e51 --- /dev/null +++ b/editor-dotnet/src/install-engines.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +CONFIDENCE=Debug +NET_VERSION=net8.0 + +if [ ! -d app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines ]; then + + mkdir app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines + +fi + +for dir in ../../desktop-framework-dotnet/desktop-framework-dotnet/src/engines/* ; do + + echo "Building $dir" + + pushd $dir + dotnet build + popd + + echo "Copying $dir" + cp $dir/bin/$CONFIDENCE/$NET_VERSION/*.dll app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines + +done diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs index 566e0ea..24b2df8 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs @@ -1,22 +1,73 @@ +// +// DataFormat.cs - translates ObjectModel to serialized data in a particular format +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2024 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + namespace MBS.Editor.Core; public abstract class DataFormat { + protected virtual void BeforeLoadInternal(Stack objectModels) + { + } + protected virtual void AfterLoadInternal(Stack objectModels) + { + } + public void Load(ObjectModel objectModel, Stream stream) { - LoadInternal(objectModel, stream); + Stack stack = new Stack(); + stack.Push(objectModel); + BeforeLoadInternal(stack); + + ObjectModel omb = stack.Pop(); + + LoadInternal(omb, stream); + + stack.Push(omb); + AfterLoadInternal(stack); } protected abstract void LoadInternal(ObjectModel objectModel, Stream stream); + protected virtual void BeforeSaveInternal(Stack objectModels) + { + } + protected virtual void AfterSaveInternal(Stack objectModels) + { + } + public void Save(ObjectModel objectModel, Stream stream) { - SaveInternal(objectModel, stream); + Stack stack = new Stack(); + stack.Push(objectModel); + BeforeSaveInternal(stack); + + ObjectModel omb = stack.Pop(); + + SaveInternal(omb, stream); + + stack.Push(omb); + AfterSaveInternal(stack); } protected abstract void SaveInternal(ObjectModel objectModel, Stream stream); - - public static T FromType() where T : DataFormat, new() { T objectModel = new T(); diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs new file mode 100644 index 0000000..281cf7b --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Core; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.DataFormats.FileSystem.Chunked; + +public class ChunkedDataFormat : DataFormat +{ + protected virtual int KeyLength { get; } = 4; + + /// + /// Gets or sets the endianness of multibyte values. For IFF and AIFF, set to + /// . For RIFF, set to . + /// + /// + public Endianness Endianness { get; set; } = Endianness.LittleEndian; + + protected virtual int ChunkAlignment { get; } = 0; + protected virtual bool IsGroupChunk(string name) + { + return false; + } + + private void ReadChunk(Reader reader, IChunkContainer parent) + { + string chunkName = reader.ReadFixedLengthString(4); + int chunkLength = reader.ReadInt32(); + + if (IsGroupChunk(chunkName)) + { + string typeName = reader.ReadFixedLengthString(4); + + // is group chunk + GroupChunk folder = new GroupChunk(chunkName, typeName); + + while (!reader.BaseStream.EndOfStream()) + { + ReadChunk(reader, folder); + } + + parent.Chunks.Add(folder); + } + else + { + long offset = reader.BaseStream.Position; + + DataChunk file = new DataChunk(chunkName); + file.Source = new EmbeddedFileSource(reader.BaseStream, offset, chunkLength); + parent.Chunks.Add(file); + + // CMAP=color map, 768 bytes, 3x256 colors, rgb + + reader.BaseStream.Seek(chunkLength, SeekOrigin.Current); + reader.Align(ChunkAlignment); + } + } + + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModel); + + Reader reader = new Reader(stream); + reader.Endianness = Endianness; + while (!reader.BaseStream.EndOfStream()) + { + ReadChunk(reader, chunked); + } + } + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModel); + + Writer writer = new Writer(stream); + writer.Endianness = Endianness; + foreach (Chunk chunk in chunked.Chunks) + { + WriteChunk(writer, chunk); + } + } + + private void WriteChunk(Writer writer, Chunk chunk) + { + writer.WriteFixedLengthString(chunk.Name, KeyLength); + writer.WriteInt32(CalculateChunkSize(chunk)); + + if (chunk is GroupChunk g) + { + writer.WriteFixedLengthString(g.TypeName, KeyLength); + foreach (Chunk chunk2 in g.Chunks) + { + WriteChunk(writer, chunk2); + } + } + else if (chunk is DataChunk d) + { + if (d.Source != null) + { + writer.WriteBytes(d.Source.GetData()); + } + } + } + + private int CalculateChunkSize(Chunk chunk) + { + int sz = 0; + if (chunk is GroupChunk g) + { + sz += 4; + foreach (Chunk c in g.Chunks) + { + sz += CalculateChunkSize(c); + } + } + else if (chunk is DataChunk d) + { + sz = 0; + if (d.Source != null) + { + sz = (int)d.Source.Length; + } + } + return sz; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs new file mode 100644 index 0000000..05a249a --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Core; +using MBS.Editor.Core.DataFormats.FileSystem.Chunked; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.DataFormats.FileSystem.IFF; + +public class IFFDataFormat : ChunkedDataFormat +{ + public List GroupChunkNames { get; } = new List(); + public IFFDataFormat() + { + Endianness = Endianness.BigEndian; + GroupChunkNames.Add("FORM"); + GroupChunkNames.Add("LIST"); + GroupChunkNames.Add("CAT"); + } + + protected override bool IsGroupChunk(string name) + { + return GroupChunkNames.Contains(name); + } + + protected override int ChunkAlignment => 2; +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs new file mode 100644 index 0000000..d040eab --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.DataFormats.FileSystem.IFF; + +public class RIFFDataFormat : IFFDataFormat +{ + public RIFFDataFormat() + { + GroupChunkNames.Add("RIFF"); + Endianness = IO.Endianness.LittleEndian; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs new file mode 100644 index 0000000..d66b347 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs @@ -0,0 +1,205 @@ + +using System.Formats.Asn1; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Text.RegularExpressions; +using MBS.Core.Settings; + +namespace MBS.Editor.Core; + +public class INIDataFormat : DataFormat +{ + public char CommentPrefix { get; set; } = ';'; + + private static DataFormatMetadata? _dfr = null; + public static DataFormatMetadata Metadata + { + get + { + if (_dfr == null) + { + _dfr = new DataFormatMetadata(); + + SettingsGroup sg = new SettingsGroup("General", new Setting[] + { + new TextSetting("CommentPrefix", "Comment Prefix", ";") + }); + _dfr.ExportSettings.SettingsGroups.Add(sg); + } + return _dfr; + } + } + + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + IPropertyListContainer currentGroup = plom; + INIDataFormatToken insideToken = INIDataFormatToken.None; + StringBuilder currentString = new StringBuilder(); + bool escaped = false; + + string currentPropertyName = ""; + string currentGroupName = ""; + + StreamReader sr = new StreamReader(stream); + while (!sr.EndOfStream) + { + int val = sr.Read(); + if (val == -1) + break; + + char c = (char)val; + if (insideToken == INIDataFormatToken.Group) + { + if (c == ']') + { + insideToken = INIDataFormatToken.None; + } + else + { + currentGroupName += c; + } + continue; + } + + if (c == CommentPrefix) + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.Comment; + } + else + { + currentString.Append(c); + } + } + else if (c == '"') + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.String; + } + else if (insideToken == INIDataFormatToken.String && !escaped) + { + insideToken = INIDataFormatToken.None; + } + else + { + currentString.Append(c); + } + } + else if (c == '\\') + { + if (insideToken == INIDataFormatToken.None || insideToken == INIDataFormatToken.Comment) + { + currentString.Append(c); + } + else if (insideToken == INIDataFormatToken.String) + { + if (!escaped) + { + escaped = true; + } + else + { + currentString.Append(c); + } + } + } + else if (c == '\n') + { + if (currentGroupName != "") + { + PropertyListGroup group = new PropertyListGroup(currentGroupName); + currentGroupName = ""; + + plom.Items.Add(group); + currentGroup = group; + } + else + { + PropertyListProperty property = new PropertyListProperty(currentPropertyName, currentString.ToString()); + currentString.Clear(); + + currentGroup.Items.Add(property); + } + } + else if (c == '=') + { + if (insideToken == INIDataFormatToken.None) + { + currentPropertyName = currentString.ToString(); + currentString.Clear(); + } + else + { + currentString.Append(c); + } + } + else if (c == '[') + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.Group; + continue; + } + else + { + currentString.Append(c); + } + } + else if (c == ']') + { + if (insideToken != INIDataFormatToken.None) + { + currentString.Append(c); + } + } + else + { + currentString.Append(c); + } + } + } + + private void WriteItem(StreamWriter sw, PropertyListItem item) + { + if (item is PropertyListProperty p) + { + sw.WriteLine(String.Format("{0}={1}", p.Name, p.Value)); + } + else if (item is PropertyListGroup g) + { + sw.WriteLine(String.Format("[{0}]", g.Name)); + foreach (PropertyListItem item2 in g.Items) + { + WriteItem(sw, item2); + } + } + else if (item is PropertyListComment c) + { + string[] lines = c.Value.Split(System.Environment.NewLine); + foreach (string line in lines) + { + sw.WriteLine(String.Format("{0} {1}", CommentPrefix, line)); + } + } + } + + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + StreamWriter sw = new StreamWriter(stream); + foreach (PropertyListItem item in plom.Items) + { + WriteItem(sw, item); + } + sw.Flush(); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs new file mode 100644 index 0000000..845dd01 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core; + +public enum INIDataFormatToken +{ + None = 0, + Comment, + String, + Group +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs index 305afe3..f925c44 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs @@ -609,7 +609,7 @@ public class Reader : ReaderWriterBase public int ReadInt24() { byte[] buffer = ReadBytes((uint)3); - byte[] _buffer = new byte[3]; + byte[] _buffer = new byte[4]; if (base.Endianness == Endianness.LittleEndian) { _buffer[0] = buffer[0]; @@ -1798,31 +1798,6 @@ public class Reader : ReaderWriterBase return line; } } - - /// - /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. - /// - public void Close() - { - BaseStream.Close(); - } - - /// - /// Aligns the to the specified number of bytes. If the current - /// position of the is not a multiple of the specified number of bytes, - /// the position will be increased by the amount of bytes necessary to bring it to the - /// aligned position. - /// - /// The number of bytes on which to align the . - /// Any additional padding bytes that should be included after aligning to the specified boundary. - public void Align(int alignTo, int extraPadding = 0) - { - long paddingCount = ((alignTo - (BaseStream.Position % alignTo)) % alignTo); - paddingCount += extraPadding; - - if (BaseStream.Position + paddingCount < BaseStream.Length) - BaseStream.Position += paddingCount; - } public string ReadStringUntilAny(char[] anyOf) { diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs index 74aa39d..7ee3cc5 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs @@ -66,14 +66,25 @@ public class ReaderWriterBase long paddingCount = ((alignTo - (_st.Position % alignTo)) % alignTo); paddingCount += extraPadding; - if (_st.Position == _st.Length) + if (paddingCount > 0) { - byte[] buffer = new byte[paddingCount]; - _st.Write(buffer, 0, buffer.Length); - } - else - { - _st.Position += paddingCount; + if (_st.Position == _st.Length) + { + byte[] buffer = new byte[paddingCount]; + _st.Write(buffer, 0, buffer.Length); + } + else + { + _st.Position += paddingCount; + } } } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public void Close() + { + BaseStream.Close(); + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs index c342e97..35bf2eb 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs @@ -32,5 +32,14 @@ public class ObjectModel } return objectModel; } - + + public static T CastOrThrow(ObjectModel? objectModel) where T : ObjectModel + { + T om = objectModel as T; + if (om == null) + { + throw new ObjectModelNotSupportedException(typeof(T), objectModel?.GetType()); + } + return om; + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs index 80e9a03..a1334e7 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs @@ -1,3 +1,5 @@ +using MBS.Core; + namespace MBS.Editor.Core; public class ObjectModelNotSupportedException : Exception @@ -29,9 +31,19 @@ public class ObjectModelNotSupportedException : Exception public Type? ExpectedObjectModelType { get; } public Type? ActualObjectModelType { get; } - public ObjectModelNotSupportedException(string? message, Type expectedObjectModelType, Type actualObjectModelType) + private static Dictionary __c1(Type expected, Type actual) { + Dictionary dict = new Dictionary(); + dict["expected"] = expected.FullName; + dict["actual"] = actual.FullName; + return dict; + } + public ObjectModelNotSupportedException(Type expectedObjectModelType, Type? actualObjectModelType) : this("The object model is not supported (expected $(expected), got $(actual))", expectedObjectModelType, actualObjectModelType) { } + public ObjectModelNotSupportedException(string? message, Type expectedObjectModelType, Type? actualObjectModelType) : base(StringExtensions.Format(message, __c1(expectedObjectModelType, actualObjectModelType))) + { + ExpectedObjectModelType = expectedObjectModelType; + ActualObjectModelType = actualObjectModelType; } } \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs new file mode 100644 index 0000000..1808ca2 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public abstract class Chunk +{ + public class ChunkCollection + : System.Collections.ObjectModel.Collection + { + + public Chunk? this[string name] + { + get + { + foreach (Chunk item in this) + { + if (item.Name == name) + return item; + } + return null; + } + } + + public Chunk? this[string name, string typeName] + { + get + { + foreach (Chunk item in this) + { + if (item is GroupChunk g) + { + if (g.Name == name && g.TypeName == typeName) + return g; + } + } + return null; + } + } + } + + public Chunk(string name) + { + Name = name; + } + public string Name { get; set; } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs new file mode 100644 index 0000000..3dff116 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class ChunkedObjectModel : ObjectModel, IChunkContainer +{ + public Chunk.ChunkCollection Chunks { get; } = new Chunk.ChunkCollection(); +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs new file mode 100644 index 0000000..a97ffd7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Editor.Core.ObjectModels.FileSystem; + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class DataChunk : Chunk +{ + public DataChunk(string name) : base(name) + { + } + public FileSource? Source { get; set; } = null; +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs new file mode 100644 index 0000000..c0b67e0 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class GroupChunk : Chunk, IChunkContainer +{ + public GroupChunk(string name, string typeName) : base(name) + { + TypeName = typeName; + } + public string TypeName { get; set; } + + public Chunk.ChunkCollection Chunks { get; } = new Chunk.ChunkCollection(); +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs new file mode 100644 index 0000000..4e5aa4d --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public interface IChunkContainer +{ + Chunk.ChunkCollection Chunks { get; } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs index 34cd688..c9da54b 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs @@ -1,3 +1,4 @@ + namespace MBS.Editor.Core.ObjectModels.FileSystem; public abstract class FileSource @@ -22,4 +23,11 @@ public abstract class FileSource */ return msInput.ToArray(); } + + public Stream GetStream() + { + byte[] data = GetData(); + MemoryStream msInput = new MemoryStream(data); + return msInput; + } } \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs index 9327a10..c4a08db 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs @@ -13,7 +13,7 @@ public class EmbeddedFileSource : FileSource } protected override byte[] GetDataInternal(long offset, long length) { - if (Offset + offset + length >= Stream.Length) + if (Offset + offset + length > Stream.Length) { throw new ArgumentOutOfRangeException("embedded file offset + requested offset + requested length extends past the actual length of the underlying stream"); } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs index 38cfef2..39c4c41 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs @@ -178,4 +178,14 @@ public class FileSystemItemCollection } return list.ToArray(); } + + public T? GetChild(string v) where T : FileSystemItem + { + FileSystemItem? item = this[v]; + if (item is T) + { + return (T)item; + } + return null; + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs new file mode 100644 index 0000000..932fe29 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core; + +public interface IPropertyListContainer +{ + + PropertyListItem.PropertyListItemCollection Items { get; } + IPropertyListContainer? Parent { get; } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs new file mode 100644 index 0000000..06c20d3 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Core; + +public class PropertyListComment : PropertyListItem +{ + public string Value { get; set; } = ""; +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs new file mode 100644 index 0000000..2fceee0 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs @@ -0,0 +1,14 @@ +namespace MBS.Editor.Core; + +public class PropertyListGroup : PropertyListItem, IPropertyListContainer +{ + + public PropertyListItem.PropertyListItemCollection Items { get; } + + public PropertyListGroup(string name) + { + Name = name; + Items = new PropertyListItemCollection(this); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs new file mode 100644 index 0000000..c62338e --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs @@ -0,0 +1,58 @@ +namespace MBS.Editor.Core; + +public abstract class PropertyListItem +{ + + public class PropertyListItemCollection + : System.Collections.ObjectModel.Collection + { + private IPropertyListContainer _Parent = null; + + public PropertyListItemCollection(IPropertyListContainer parent) + { + _Parent = parent; + } + + protected override void ClearItems() + { + foreach (PropertyListItem item in this) + { + item.Parent = null; + } + base.ClearItems(); + } + protected override void InsertItem(int index, PropertyListItem item) + { + base.InsertItem(index, item); + item.Parent = _Parent; + } + protected override void RemoveItem(int index) + { + this[index].Parent = null; + base.RemoveItem(index); + } + + } + + public string Name { get; set; } = ""; + public IPropertyListContainer? Parent { get; internal set; } + + public PropertyListObjectModel? ParentObjectModel + + { + get + { + IPropertyListContainer? parent = Parent; + while (parent != null) + { + if (parent is PropertyListObjectModel) + { + return (parent as PropertyListObjectModel); + } + parent = parent.Parent; + } + return null; + } + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs new file mode 100644 index 0000000..d0f7f43 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs @@ -0,0 +1,18 @@ +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +namespace MBS.Editor.Core; + +public class PropertyListObjectModel : ObjectModel, IPropertyListContainer +{ + + public PropertyListItem.PropertyListItemCollection Items { get; } + public PropertyListSchema? Schema { get; set; } = null; + + public IPropertyListContainer? Parent => null; + + public PropertyListObjectModel() + { + Items = new PropertyListItem.PropertyListItemCollection(this); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs new file mode 100644 index 0000000..4e2ae02 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs @@ -0,0 +1,41 @@ +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +namespace MBS.Editor.Core; + +public class PropertyListProperty : PropertyListItem +{ + + private object? _Value = null; + public object? Value + { + get { return _Value; } + set { _Value = value; } + } + + public T? GetValueAs() + { + PropertyListObjectModel? plom = ParentObjectModel; + if (plom != null) + { + PropertyListSchema? schema = plom.Schema; + if (schema != null) + { + foreach (PropertyDefinition def in schema.PropertyDefinitions) + { + if (def.PropertyName == Name) + { + //return def.ConvertTo(def.DataType, Value); + } + } + } + } + return default(T); + } + + public PropertyListProperty(string name, object value) + { + Name = name; + Value = value; + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs new file mode 100644 index 0000000..4c0d881 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs @@ -0,0 +1,27 @@ +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyValueConverter +{ + public class PropertyValueConverterCollection + : System.Collections.ObjectModel.Collection + + { + + } +} +public class PropertyValueConverter : PropertyValueConverter +{ + private Func __convertTo = null; + private Func __convertFrom = null; + public PropertyValueConverter(Func convertTo, Func convertFrom) + { + __convertTo = convertTo; + __convertFrom = convertFrom; + } + + public TOutput? ConvertTo(TInput value) + { + return default; + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs new file mode 100644 index 0000000..c1e0bfc --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs @@ -0,0 +1,41 @@ + +using System.Diagnostics.Contracts; +using System.Text.Json.Serialization; +using MBS.Core.Collections.Generic; + +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyDefinition +{ + public class PropertyDefinitionCollection + : System.Collections.ObjectModel.Collection + { + + } + + public string PropertyName { get; } + public Type DataType { get; } + public PropertyValueConverter.PropertyValueConverterCollection Converters { get; } = new PropertyValueConverter.PropertyValueConverterCollection(); + + public PropertyDefinition(string propertyName, string displayTitle, Type dataType, PropertyValueConverter[]? valueConverters = null) + { + PropertyName = propertyName; + DataType = dataType; + if (valueConverters != null) + { + Converters.AddRange(valueConverters); + } + } + + public TTo ConvertTo(TFrom value) + { + foreach (PropertyValueConverter converter in Converters) + { + if (converter is PropertyValueConverter cvt) + { + return cvt.ConvertTo(value); + } + } + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs new file mode 100644 index 0000000..01422a9 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs @@ -0,0 +1,14 @@ +using MBS.Core.Collections.Generic; + +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyListSchema +{ + public PropertyDefinition.PropertyDefinitionCollection PropertyDefinitions { get; } = new PropertyDefinition.PropertyDefinitionCollection(); + + public PropertyListSchema(PropertyDefinition[] propertyDefinitions) + { + PropertyDefinitions.AddRange(propertyDefinitions); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs b/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs new file mode 100644 index 0000000..b7c91ea --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; + +namespace MBS.Editor.Testing; + +public class EmbeddedResourceTest +{ + private System.Collections.Generic.Dictionary testDataStreams = new System.Collections.Generic.Dictionary(); + + [SetUp] + public void SetUp() + { + InitializeResources(); + } + + protected void InitializeResources() + { + MBS.Core.Reflection.ManifestResourceStream[] strms = MBS.Core.Reflection.ManifestResourceStream.GetManifestResourceStreamsForAssembly(this.GetType().Assembly); + for (int i = 0; i < strms.Length; i++) + { + testDataStreams[strms[i].Name] = strms[i].Stream; + } + } + + protected Stream? CreateResourceStream(string name) + { + if (TryCreateResourceStream(name, out Stream? st)) + { + return st; + } + return null; + } + + protected bool TryCreateResourceStream(string name, out System.IO.Stream? st) + { + st = null; + if (testDataStreams.ContainsKey(name)) + { + System.IO.Stream TEST_STREAM = testDataStreams[name]; + st = TEST_STREAM; + return true; + } + else + { + Console.Error.WriteLine("test data stream not found: '{0}'", name); + } + return false; + } + + protected void SampleStreamTest(string streamName) + { + if (TryCreateResourceStream(streamName, out System.IO.Stream? st)) + { + SampleStreamTest(st); + } + else + { + Assert.Ignore(); + } + } + + protected void SampleStreamTest(Stream st) + { + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj b/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj new file mode 100644 index 0000000..e5eafa1 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + net8.0 + enable + enable + false + false + + + diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs index f9f2537..033e05e 100644 --- a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs @@ -1,14 +1,38 @@ namespace MBS.Editor.UserInterface; +using System; +using MBS.Core; using MBS.Desktop; +using MBS.Desktop.Controls; public class EditorApplication : DesktopApplication { + protected override void OnStartup(EventArgs e) + { + base.OnStartup(e); - protected override int StartInternal() - { - return 0; - } + Console.WriteLine("editor: OnStartup"); + + this.Commands.Add(new Command("FileExit", "E_xit")); + this.Commands.Add(new Command("File", "_File", new CommandItem[] + { + new CommandReferenceCommandItem("FileExit") + })); + + this.AttachCommandEventHandler("FileExit", delegate (object sender, CommandEventArgs e) + { + this.Stop(); + }); + } + + protected override void OnActivated(ApplicationActivatedEventArgs e) + { + base.OnActivated(e); + + Console.WriteLine("editor: OnActivated"); + + EditorWindow window = new EditorWindow(); + window.Show(); + } - } diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs new file mode 100644 index 0000000..3d56548 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs @@ -0,0 +1,20 @@ +namespace MBS.Editor.UserInterface; + +using System; +using MBS.Core; +using MBS.Desktop; +using MBS.Desktop.Controls; + +// [ContainerLayout(typeof(EditorWindow), "MBS.Editor.UserInterface.EditorWindow.glade")] +public class EditorWindow : MainWindow +{ + + public EditorWindow() + { + Title = "MBS Editor"; + DefaultSize = new Core.Drawing.Dimension2D(1000, 700); + + Controls.Add(new Label("Strong Bad!") { UseMarkup = true }); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj b/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj index dd82f9d..4e87b11 100644 --- a/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj @@ -2,7 +2,7 @@ - + diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs b/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs new file mode 100644 index 0000000..3ee6c62 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Web.UI; + +namespace MBS.Editor.Web.Controls; + +public class PageHeader : WebControl +{ + protected override string TagName => "div"; + protected override IEnumerable GetStyleClasses() + { + return new string[] { "uwt-page-header" }; + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj b/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj new file mode 100644 index 0000000..c987729 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs b/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs new file mode 100644 index 0000000..b726c65 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs @@ -0,0 +1,123 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Web; +using MBS.Web.UI; + +using MBS.Editor.Web.Controls; +using MBS.Web.UI.HtmlControls; +using System.Text; +using MBS.Core.Drawing; +using MBS.Web.UI.WebControls; +using MBS.Core; + +namespace MBS.Editor.Web.Pages; + +public class MainPage : WebPage +{ + private class WebStyleSheet2 + { + + + } + + + public MainPage() + { + Dictionary> dict = new Dictionary>(); + /* + WebStyleSheet2 wss = new WebStyleSheet2(); + + WebStyleSheetSelector selector = WebStyleSheetSelector.Parse("div.uwt-page-header"); + + wss.Selectors.Add("div", "uwt-page-header"); + */ + + dict["div.uwt-page-header"] = new Dictionary(); + dict["div.uwt-page-header"]["border-bottom"] = new object[] { Measurement.Parse("1px"), "solid", Color.FromString("#E0E0E0") }; + + StyleSheets.Add(WebStyleSheet.FromContent("text/css", FormatCss(dict))); + } + + protected override IEnumerable GetHeaderControls() + { + return new Control[] + { + + + }; + } + + private string FormatCss(Dictionary> dict) + { + StringBuilder sb = new StringBuilder(); + foreach (KeyValuePair> kvp in dict) + { + sb.Append(kvp.Key); + sb.Append(" { "); + foreach (KeyValuePair kvp2 in kvp.Value) + { + sb.Append(kvp2.Key); + sb.Append(": "); + + if (kvp2.Value is Color) + { + sb.Append(((Color)kvp2.Value).ToHexadecimalHTML()); + } + else if (kvp2.Value is string) + { + sb.Append("\""); + sb.Append(kvp2.Value); + sb.Append("\""); + } + else if (kvp2.Value is object[]) + { + object[] objs = (object[])kvp2.Value; + for (int i = 0; i < objs.Length; i++) + { + sb.Append(objs[i]); + if (i < objs.Length - 1) + { + sb.Append(' '); + } + } + } + else + { + sb.Append(kvp2.Value); + } + } + sb.Append(" } "); + } + return sb.ToString(); + } + + protected override IEnumerable GetBodyControls() + { + CommandBar cb = new CommandBar(); + cb.ClientId = "mainToolbar"; + cb.Items.Add(new CommandReferenceCommandItem("FileOpen")); + + return new Control[] + { + new PageHeader(), + cb + }; + } + + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs new file mode 100644 index 0000000..63247f8 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Plugins.Kronosaur; + +public class Class1 +{ + +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs new file mode 100644 index 0000000..1b354d2 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs @@ -0,0 +1,70 @@ +using MBS.Editor.Core; +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; +using MBS.Editor.Plugins.Mekada.DataFormats.PropertyList; + +namespace MBS.Editor.Plugins.Mekada.DataFormats.Mechanic; + +public class MEKDataFormat : MEKBaseDataFormat +{ + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + + PropertyListObjectModel plom = new PropertyListObjectModel(); + plom.Schema = new PropertyListSchema(new PropertyDefinition[] + { + new PropertyDefinition("1403", "Money", typeof(float)), + new PropertyDefinition("1405", "Total Time Played", typeof(int), new PropertyValueConverter[] + { + new PropertyValueConverter(delegate (int value) + { + TimeSpan ts = new TimeSpan(0, 0, value); + return ts; + }, + delegate (TimeSpan value) + { + int seconds = (value.Hours * 3600) + (value.Minutes * 60) + (value.Seconds); + return seconds; + }) + }) + }); + objectModels.Push(plom); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + PropertyListObjectModel? plom = objectModels.Pop() as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), plom?.GetType()); + + Console.WriteLine("{ "); + foreach (PropertyListItem item in plom.Items) + { + RenderItem(item); + } + Console.WriteLine(" }"); + + } + + private void RenderItem(PropertyListItem item) + { + if (item is PropertyListGroup grp) + { + Console.WriteLine("\"" + grp.Name + "\": { "); + foreach (PropertyListItem item2 in grp.Items) + { + RenderItem(item2); + if (grp.Items.IndexOf(item2) < grp.Items.Count - 1) + { + Console.Write(", "); + } + } + Console.WriteLine(" }"); + } + else if (item is PropertyListProperty prop) + { + Console.Write("\"" + item.Name + "\": \"" + prop.Value.ToString() + "\""); + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs new file mode 100644 index 0000000..387bffb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs @@ -0,0 +1,92 @@ +using System.Text.RegularExpressions; +using MBS.Editor.Core; +using MBS.Editor.Core.IO; + +namespace MBS.Editor.Plugins.Mekada.DataFormats.PropertyList; + +public class MEKBaseDataFormat : DataFormat +{ + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + Reader r = new Reader(stream); + + while (true) + { + PropertyListItem item = ReadItem(r, plom); + if (item == null) + break; + + plom.Items.Add(item); + } + } + + private PropertyListItem? ReadItem(Reader r, IPropertyListContainer parent) + { + long start = r.BaseStream.Position; + + ushort index = r.ReadUInt16(); + MEKPropertyType type = (MEKPropertyType) r.ReadUInt16(); + int length = r.ReadInt32(); + if (index == 0 && type == MEKPropertyType.None && length == 0) + return null; + + if (type == MEKPropertyType.Group) + { + PropertyListGroup group = new PropertyListGroup(index.ToString()); + + while (r.BaseStream.Position < length + start) + { + PropertyListItem? item = ReadItem(r, group); + if (item != null) + { + group.Items.Add(item); + } + } + + return group; + } + else if (type == MEKPropertyType.Float) + { + if (length == 4) + { + float value = r.ReadSingle(); + return new PropertyListProperty(index.ToString(), value); + } + else + { + throw new NotImplementedException(); + } + } + else if (type == MEKPropertyType.Int32) + { + if (length == 4) + { + int value = r.ReadInt32(); + return new PropertyListProperty(index.ToString(), value); + } + else + { + throw new NotImplementedException(); + } + } + else if (type == MEKPropertyType.String) + { + string value = r.ReadFixedLengthString(length); + return new PropertyListProperty(index.ToString(), value); + } + else + { + byte[] data = r.ReadBytes(length); + return new PropertyListProperty(index.ToString(), data); + } + } + + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + throw new NotImplementedException(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs new file mode 100644 index 0000000..7e2e7f0 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs @@ -0,0 +1,10 @@ +namespace MBS.Editor.Plugins.Mekada; + +public enum MEKPropertyType : ushort +{ + None = 0, + Int32 = 8192, + Float = 16384, + String = 24576, + Group = 32768 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj new file mode 100644 index 0000000..d7fe524 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs new file mode 100644 index 0000000..bc5692e --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs @@ -0,0 +1,11 @@ +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Mekada; + +public class MechanicObjectModel : ObjectModel +{ + public string Name { get; set; } = ""; + + public float Money { get; set; } = 0.0f; + +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs new file mode 100644 index 0000000..4f64d69 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs @@ -0,0 +1,143 @@ +// +// MIDICommand.cs - represents a command in a MIDI synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Represents a command in a MIDI synthesized audio file. +/// +public class MIDICommand +{ + /// + /// Gets or sets the type of the to send. + /// + /// The type of the to send. + public MIDICommandType CommandType + { + get + { + MIDICommandType result; + switch (Command) + { + case 0: + { + result = MIDICommandType.None; + return result; + } + case 8: + { + result = MIDICommandType.NoteOff; + return result; + } + case 9: + { + result = MIDICommandType.NoteOn; + return result; + } + case 10: + { + result = MIDICommandType.KeyAfterTouch; + return result; + } + case 11: + { + result = MIDICommandType.ControlChange; + return result; + } + case 12: + { + result = MIDICommandType.ProgramChange; + return result; + } + case 13: + { + result = MIDICommandType.ChannelAfterTouch; + return result; + } + case 14: + { + result = MIDICommandType.PitchWheelChange; + return result; + } + } + result = MIDICommandType.Unknown; + return result; + } + set + { + switch (value) + { + case MIDICommandType.None: + { + Command = 0; + return; + } + case MIDICommandType.NoteOff: + { + Command = 8; + return; + } + case MIDICommandType.NoteOn: + { + Command = 9; + return; + } + case MIDICommandType.KeyAfterTouch: + { + Command = 10; + return; + } + case MIDICommandType.ControlChange: + { + Command = 11; + return; + } + case MIDICommandType.ProgramChange: + { + Command = 12; + return; + } + case MIDICommandType.ChannelAfterTouch: + { + Command = 13; + return; + } + case MIDICommandType.PitchWheelChange: + { + Command = 14; + return; + } + } + Command = 0; + } + } + + /// + /// Gets or sets the channel on which to send this . + /// + /// The channel on which to send this . + public byte Channel { get; set; } = 0; + /// + /// Gets or sets the value of the to send. + /// + /// The value of the to send. + public byte Command { get; set; } = 0; +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs new file mode 100644 index 0000000..7f3f2b9 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs @@ -0,0 +1,38 @@ +// +// MIDICommandType.cs - indicates the type of MIDI command +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI command. +/// +public enum MIDICommandType +{ + Unknown = -1, + None, + NoteOff = 8, + NoteOn, + KeyAfterTouch, + ControlChange, + ProgramChange, + ChannelAfterTouch, + PitchWheelChange +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs new file mode 100644 index 0000000..6d24af3 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs @@ -0,0 +1,353 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using System.Globalization; +using System.Reflection; +using MBS.Core; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.Chunked; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +public class MIDIDataFormat : ChunkedDataFormat +{ + public MIDIDataFormat() + { + Endianness = Endianness.BigEndian; + } + + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModels.Pop()); + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + System.Collections.Generic.Dictionary notesForNoteNumber = new System.Collections.Generic.Dictionary(); + foreach (Chunk c in chunked.Chunks) + { + if (c.Name == "MThd") + { + Stream s = ((DataChunk)c).Source.GetStream(); + Reader r = new Reader(s); + r.Endianness = Endianness.BigEndian; + + ushort format = r.ReadUInt16(); + ushort trackCount = r.ReadUInt16(); + ushort tickdiv = r.ReadUInt16(); + } + else if (c.Name == "MTrk") + { + int ofs = 0; + Stream s = null; + try + { + s = ((DataChunk)c).Source.GetStream(); + } + catch (Exception ex) + { + return; + } + + Reader r = new Reader(s); + r.Endianness = Endianness.BigEndian; + + SynthesizedAudioTrack track = new SynthesizedAudioTrack(); + + while (!r.BaseStream.EndOfStream()) + { + int deltaTime = r.ReadVariableLengthInt32(); + MIDIEventType commandAndChannel = (MIDIEventType) r.ReadByte(); + byte channel = (byte)((byte)commandAndChannel & (byte)MIDIEventType.MIDIChannelMask); + MIDIEventType command = (MIDIEventType)((byte)commandAndChannel >> 4); + + if (commandAndChannel == MIDIEventType.Meta) + { + // this is special cased because it does not include a channel number + MIDIMetaEventType metaEventType = (MIDIMetaEventType)r.ReadByte(); + int length = r.ReadVariableLengthInt32(); + switch (metaEventType) + { + case MIDIMetaEventType.Text: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + track.Commands.Add(new SynthesizedAudioCommandText(text)); + break; + } + case MIDIMetaEventType.CopyrightNotice: + { + break; + } + case MIDIMetaEventType.SequenceName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + track.Name = text; + break; + } + case MIDIMetaEventType.ProgramName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + break; + } + case MIDIMetaEventType.DeviceName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + break; + } + case MIDIMetaEventType.EndOfTrack: + { + audio.Tracks.Add(track); + break; + } + case MIDIMetaEventType.TimeSignature: + { + byte numerator = r.ReadByte(); + byte denominatorPower = r.ReadByte(); + byte denominator = (byte)Math.Pow(2.0, denominatorPower); + byte ticksPerMetronomeClick = r.ReadByte(); + byte numberOf32ndNotesPerQuarterNote = r.ReadByte(); + track.Commands.Add(new SynthesizedAudioCommandTimeSignature(numerator, denominator, ticksPerMetronomeClick, numberOf32ndNotesPerQuarterNote)); + break; + } + case MIDIMetaEventType.SetTempo: + { + int tempo = (int)r.ReadInt24(); + track.Commands.Add(new SynthesizedAudioCommandTempo((double)tempo)); + break; + } + case MIDIMetaEventType.KeySignature: + { + byte sf = r.ReadByte(); // -7 = 7 flats, -1 = 1 flat, 0 = none (key of C), 1 = + bool minorKey = r.ReadBoolean(); + break; + } + default: + { + Console.WriteLine("ue: MIDI: warning: meta event type {0} ({1}) [{2} bytes] unhandled", metaEventType, (byte)metaEventType, length); + r.BaseStream.Seek(length, SeekOrigin.Current); + break; + } + } + } + else + { + switch (command) + { + case MIDIEventType.ProgramChange: + { + byte programNumber = r.ReadByte(); + break; + } + case MIDIEventType.NoteOn: + { + byte noteNumber = r.ReadByte(); + byte velocity = r.ReadByte(); + + if (!notesForNoteNumber.ContainsKey(noteNumber)) + { + notesForNoteNumber[noteNumber] = new SynthesizedAudioCommandNote(); + } + SynthesizedAudioCommandNote note = notesForNoteNumber[noteNumber]; + note.Frequency = noteNumber; + note.Position = deltaTime + ofs; + note.Intensity = velocity; + ofs += deltaTime; + track.Commands.Add(note); + break; + } + case MIDIEventType.NoteOff: + { + byte noteNumber = r.ReadByte(); + byte velocity = r.ReadByte(); + + if (notesForNoteNumber.ContainsKey(noteNumber)) + { + notesForNoteNumber[noteNumber].Length = deltaTime; + notesForNoteNumber.Remove(noteNumber); + } + break; + } + case MIDIEventType.ControlChange: + { + byte controllerNumber = r.ReadByte(); + byte value = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandControlChange(controllerNumber, value)); + break; + } + case MIDIEventType.PolyphonicKeyPressureAftertouch: + { + byte noteNumber = r.ReadByte(); + byte pressure = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandPolyphonicKeyPressureAftertouch(controllerNumber, value)); + break; + } + case MIDIEventType.ChannelPressureAftertouch: + { + byte pressure = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandChannelPressureAftertouch(controllerNumber, value)); + break; + } + case MIDIEventType.PitchWheelChange: + { + short value = r.ReadInt16(); + // track.Commands.Add(new SynthesizedAudioCommandPitchWheel(value)); + break; + } + } + } + } + } + } + } + + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType) + { + WriteMIDIMetaEvent(w, deltaTime, metaType, (Action?)null); + } + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType, string data) + { + WriteMIDIMetaEvent(w, deltaTime, metaType, delegate (Writer w2) + { + w2.WriteFixedLengthString(data); + }); + } + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType, Action? dataFunc = null) + { + byte[] data; + if (dataFunc == null) + { + data = new byte[0]; + } + else + { + MemoryStream ms = new MemoryStream(); + Writer w2 = new Writer(ms); + + dataFunc(w2); + + ms.Close(); + data = ms.ToArray(); + } + + w.WriteVariableLengthInt32(deltaTime); + w.WriteByte((byte)MIDIEventType.Meta); + w.WriteByte((byte)metaType); + w.WriteVariableLengthInt32(data.Length); + w.WriteBytes(data); + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + ChunkedObjectModel chunked = new ChunkedObjectModel(); + + DataChunk MThd = new DataChunk("MThd"); + chunked.Chunks.Add(MThd); + + foreach (SynthesizedAudioTrack track in audio.Tracks) + { + DataChunk MTrk = new DataChunk("MTrk"); + + MemoryStream ms = new MemoryStream(); + Writer w = new Writer(ms); + + if (track.Name != null) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.SequenceName, track.Name); + } + + foreach (SynthesizedAudioCommand command in track.Commands) + { + WriteCommand(w, command); + } + + + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.EndOfTrack); + + ms.Close(); + + MTrk.Source = new ByteArrayFileSource(ms.ToArray()); + chunked.Chunks.Add(MTrk); + } + + objectModels.Push(chunked); + } + + private void WriteCommand(Writer w, SynthesizedAudioCommand command) + { + if (command is SynthesizedAudioCommandNote note) + { + WriteMIDIEvent(w, 0, MIDIEventType.NoteOn, delegate(Writer w) + { + byte noteNumber = (byte)(note.Frequency); + byte velocity = 0; + w.WriteByte(noteNumber); + w.WriteByte(velocity); + }); + } + else if (command is SynthesizedAudioCommandTempo tempo) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.SetTempo, delegate (Writer w) + { + w.WriteInt24((int)(tempo.Tempo)); + }); + } + else if (command is SynthesizedAudioCommandTimeSignature ts) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.TimeSignature, delegate (Writer w) + { + byte numerator = (byte)ts.Numerator; + w.WriteByte(numerator); + + byte denominatorPower = (byte)Math.Log2((byte)ts.Denominator); + w.WriteByte(denominatorPower); + + byte ticksPerMetronomeClick = (byte)ts.TicksPerMetronomeClick; + w.WriteByte(ticksPerMetronomeClick); + + byte numberOf32ndNotesPerQuarterNote = (byte)ts.NumberOf32ndNotesPerQuarterNote; + w.WriteByte(numberOf32ndNotesPerQuarterNote); + }); + } + } + + private void WriteMIDIEvent(Writer w, int deltaTime, MIDIEventType eventType, Action func) + { + w.WriteVariableLengthInt32(deltaTime); + + byte channel = 0; + MIDIEventType type = MIDIEventType.NoteOn; + byte commandAndChannel = (byte)type; + w.WriteByte(commandAndChannel); + + func(w); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs new file mode 100644 index 0000000..d33e7d4 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs @@ -0,0 +1,46 @@ +// +// MIDIEventType.cs - indicates the type of MIDI event +// +// Author: +// Michael Becker +// +// Copyright (c) 2019 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI event. +/// +public enum MIDIEventType : byte +{ + None, + + MIDIChannelMask = 0x0F, + MIDIEventMask = 0xF0, + + NoteOff = 0x8, + NoteOn = 0x9, + PolyphonicKeyPressureAftertouch = 0xA, + ControlChange = 0xB, + ProgramChange = 0xC, + ChannelPressureAftertouch = 0xD, + PitchWheelChange = 0xE, + + + SysEx = 0xF0, + Escape = 0xF7, + Meta = 0xFF +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs new file mode 100644 index 0000000..9e0d5c9 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs @@ -0,0 +1,46 @@ +// +// MIDIFileFormatType.cs - indicates the type of MIDI file format +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI file format. +/// +public enum MIDIFileFormatType : short +{ + /// + /// Single track (format 0). Consists of a header-chunk and a single track-chunk. The single track chunk will contain all the + /// note and tempo information. + /// + SingleTrack = 0, + /// + /// Simultaneous multi-track (format 1). Consists of a header-chunk and one or more track-chunks, with all tracks being played + /// simultaneously. The first track of a Format 1 file is special, and is also known as the 'Tempo Map'. It should contain all + /// meta-events of the types Time Signature, and Set Tempo. The meta-events Sequence/Track Name, Sequence Number, Marker, and + /// SMTPE Offset. should also be on the first track of a Format 1 file. + /// + SimultaneousMultitrack = 1, + /// + /// Independent multi-track (format 2). Consists of a header-chunk and one or more track-chunks, where each track represents an + /// independent sequence. + /// + IndependentMultitrack = 2 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs new file mode 100644 index 0000000..ee2eb1c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs @@ -0,0 +1,46 @@ +// +// MIDIMetaEventType.cs - indicates the type of MIDI meta event +// +// Author: +// Michael Becker +// +// Copyright (c) 2019 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI meta event. +/// +public enum MIDIMetaEventType : byte +{ + SequenceNumber = 0x00, + Text = 0x01, + CopyrightNotice = 0x02, + SequenceName = 0x03, + InstrumentName = 0x04, + Lyric = 0x05, + Marker = 0x06, + CuePoint = 0x07, + ProgramName = 0x08, + DeviceName = 0x09, + ChannelPrefix = 0x20, + EndOfTrack = 0x2F, + SetTempo = 0x51, + SMPTEOffset = 0x54, + TimeSignature = 0x58, + KeySignature = 0x59, + SequencerSpecific = 0x7F +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs new file mode 100644 index 0000000..b67fba8 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs @@ -0,0 +1,114 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Core; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.RMI; + +public class RMIDataFormat : RIFFDataFormat +{ + + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + // f8ffdcb7-e2ec-40b1-aa8a-beeb5076c1c9 + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModels.Pop()); + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + if (chunked.Chunks.Count == 0) + { + throw new InvalidDataFormatException("RIFF-MIDI document does not contain any RIFF chunks"); + } + + GroupChunk? root = chunked.Chunks["RIFF", "RMID"] as GroupChunk; + if (root == null) + { + throw new InvalidDataFormatException("RIFF-MIDI document does not contain an RIFF-RMID group chunk"); + } + + DataChunk? filData = root.Chunks["data"] as DataChunk; + if (filData != null) + { + byte[]? data = filData.Source?.GetData(); + if (data != null) + { + MIDIDataFormat mid = new MIDIDataFormat(); + Document.Load(audio, mid, filData.Source?.GetStream()); + } + } + + GroupChunk info = (GroupChunk)root.Chunks["LIST", "INFO"]; + if (info != null) + { + DataChunk? filIART = info.Chunks["IART"] as DataChunk; + if (filIART != null) + { + byte[]? data = filIART.Source?.GetData(); + if (data != null) + { + string artist = System.Text.Encoding.UTF8.GetString(data).TrimNull(); + audio.Metadata.Artist = artist; + } + } + DataChunk? filICOP = info.Chunks["ICOP"] as DataChunk; + if (filICOP != null) + { + byte[]? data = filICOP.Source?.GetData(); + if (data != null) + { + string copyright = System.Text.Encoding.UTF8.GetString(data).TrimNull(); + audio.Metadata.Copyright = copyright; + } + } + } + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + ChunkedObjectModel chunked = new ChunkedObjectModel(); + + GroupChunk rmid = new GroupChunk("RIFF", "RMID"); + + MemoryStream ms = new MemoryStream(); + MIDIDataFormat mid = new MIDIDataFormat(); + Document.Save(audio, mid, ms); + + DataChunk data = new DataChunk("data"); + data.Source = new StreamFileSource(ms); + rmid.Chunks.Add(data); + + chunked.Chunks.Add(rmid); + objectModels.Push(chunked); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs new file mode 100644 index 0000000..2abd25c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +/// +/// Choice of compression algorithm applied to the rows of all source and mask planes. +/// +public enum ILBMCompression : byte +{ + None = 0x00, + ByteRun1 = 0x01 +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs new file mode 100644 index 0000000..9787549 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs @@ -0,0 +1,164 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Palette; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +public class ILBMDataFormat : IFFDataFormat +{ + public ILBMMasking Masking { get; set; } = ILBMMasking.None; + public ILBMCompression Compression { get; set; } = ILBMCompression.None; + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ObjectModel om = objectModels.Pop(); + ChunkedObjectModel? fsom = om as ChunkedObjectModel; + if (fsom == null) + throw new ObjectModelNotSupportedException(typeof(ChunkedObjectModel), om.GetType()); + + om = objectModels.Pop(); + PictureObjectModel? pic = om as PictureObjectModel; + if (pic == null) + throw new ObjectModelNotSupportedException(typeof(PictureObjectModel), om.GetType()); + + + GroupChunk? fldFORM = fsom.Chunks[0] as GroupChunk; + if (fldFORM?.Name != "FORM" || fldFORM?.TypeName != "ILBM") + throw new InvalidDataFormatException(); + + #region BMHD + { + DataChunk? filBMHD = fldFORM.Chunks[0] as DataChunk; + if (filBMHD?.Name != "BMHD" || filBMHD?.Source == null) + throw new InvalidDataFormatException(); + + MemoryStream ms = new MemoryStream(filBMHD.Source.GetData()); + Reader reader = new Reader(ms); + reader.Endianness = Endianness.BigEndian; + + // thanks http://etwright.org/lwsdk/docs/filefmts/ilbm.html + + // raster width & height in pixels + ushort width = reader.ReadUInt16(); + ushort height = reader.ReadUInt16(); + pic.Width = width; + pic.Height = height; + + // pixel position for this image + ushort x = reader.ReadUInt16(); + ushort y = reader.ReadUInt16(); + + byte nPlanes = reader.ReadByte(); + ILBMMasking masking = (ILBMMasking)reader.ReadByte(); + ILBMCompression compression = (ILBMCompression)reader.ReadByte(); + byte pad1 = reader.ReadByte(); // should be 0 + ushort transparentColor = reader.ReadUInt16(); + byte xAspect = reader.ReadByte(); + byte yAspect = reader.ReadByte(); + ushort pageWidth = reader.ReadUInt16(); + ushort pageHeight = reader.ReadUInt16(); + } + #endregion + #region CMAP + { + DataChunk? filCMAP = fldFORM.Chunks["CMAP"] as DataChunk; + if (filCMAP != null) + { + byte[]? data = filCMAP.Source?.GetData(); + if (data != null) + { + pic.Palette = new PaletteObjectModel(); + for (int i = 0; i < data.Length; i += 3) + { + byte r = data[i]; + byte g = data[i + 1]; + byte b = data[i + 2]; + + Color color = Color.FromRGBAByte(r, g, b); + pic.Palette.Entries.Add(color); + } + } + } + } + #endregion + + #region BODY + { + + System.IO.MemoryStream ms = new MemoryStream(); + Writer w = new Writer(ms); + + DataChunk? filBODY = fldFORM.Chunks["BODY"] as DataChunk; + if (filBODY != null) + { + byte[]? data = filBODY.Source?.GetData(); + if (data != null) + { + for (int i = 0; i < data.Length; i += 3) + { + sbyte n = (sbyte) data[i]; + if (n >= 0 && n <= 127) + { + int count = n + 1; + for (int j = 0; j < count; j++) + { + byte next = data[i + 1]; + w.WriteByte(next); + i++; + } + } + else if (n == -128) + { + // nop + } + else + { + int count = -n; + byte next = data[i + 1]; + for (int j = 0; j < count; j++) + { + w.WriteByte(next); + } + i++; + } + } + } + } + } + #endregion + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs new file mode 100644 index 0000000..cfbdb41 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +/// +/// Choice of masking technique. +/// +public enum ILBMMasking : byte +{ + /// + /// Designates an opaque rectangular image. + /// + None = 0x00, + /// + /// Mask plane is interleaved with the bitplanes in the BODY chunk. + /// + Mask = 0x01, + /// + /// Pixels in the source planes matching transparentColor are to be considered "transparent." + /// + /// + /// transparentColor isn't a "color number" since it's matched with numbers formed by the source bitmap rather than the possibly + /// deeper destination bitmap. Note that having a transparent color implies ignoring one of the color registers. + /// + TransparentColor = 0x02, + /// + /// Indicates the reader may construct a mask by lassoing the image as in MacPaint. To do this, put a 1 pixel border of + /// transparentColor around the image rectangle. Then do a seed fill from this border. Filled pixels are to be transparent. + /// + Lasso = 0x03 +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs new file mode 100644 index 0000000..27e70eb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; + +public class AudioMetadata +{ + public string Title { get; set; } = ""; + public string Artist { get; set; } = ""; + public string Copyright { get; set; } = ""; + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs new file mode 100644 index 0000000..dabade1 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; + +public abstract class AudioObjectModelBase : ObjectModel +{ + public AudioMetadata Metadata { get; set; } = new AudioMetadata(); + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs new file mode 100644 index 0000000..bfd3352 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs @@ -0,0 +1,114 @@ +// +// SynthesizedAudioCommand.cs - represents a command (e.g. note, control change) in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.ObjectModel; +using MBS.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a command (e.g. note, control change) in a synthesized audio file. +/// +public class SynthesizedAudioCommand : ICloneable +{ + public class SynthesizedAudioCommandCollection : Collection + { + public SynthesizedAudioCommandRest Add(double length) + { + SynthesizedAudioCommandRest rest = new SynthesizedAudioCommandRest(); + rest.Length = length; + + base.Add(rest); + return rest; + } + public SynthesizedAudioCommandNote Add(SynthesizedAudioPredefinedNote note, double length, int octave, float volume) + { + SynthesizedAudioCommandNote command = new SynthesizedAudioCommandNote(); + command.Length = length; + command.Frequency = SynthesizedAudioPredefinedNoteConverter.GetFrequency(note, octave); + + base.Add(command); + return command; + } + + public event EventHandler ItemsChanged; + protected virtual void OnItemsChanged(EventArgs e) + { + ItemsChanged?.Invoke(this, e); + } + protected override void InsertItem(int index, SynthesizedAudioCommand item) + { + base.InsertItem(index, item); + OnItemsChanged(EventArgs.Empty); + } + + protected override void RemoveItem(int index) + { + base.RemoveItem(index); + OnItemsChanged(EventArgs.Empty); + } + protected override void ClearItems() + { + base.ClearItems(); + OnItemsChanged(EventArgs.Empty); + } + protected override void SetItem(int index, SynthesizedAudioCommand item) + { + base.SetItem(index, item); + OnItemsChanged(EventArgs.Empty); + } + + public T NextOfType(T item, SeekDirection direction = SeekDirection.Forward) where T : SynthesizedAudioCommand + { + int index = IndexOf(item); + if (index < 0) index = 0; + if (index > Count - 1) index = 0; + + if (direction == SeekDirection.Backward) + { + if (index < 0) index = Count - 1; + if (index > Count - 1) index = Count - 1; + + for (int i = index - 1; i >= 0; i--) + { + if (this[i] is T) + return (T)this[i]; + } + return (T)this[Count - 1]; + } + else + { + for (int i = index + 1; i < Count; i++) + { + if (this[i] is T) + return (T)this[i]; + } + return (T)this[0]; + } + } + } + + public virtual object Clone() + { + return base.MemberwiseClone(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs new file mode 100644 index 0000000..1e4e8eb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs @@ -0,0 +1,82 @@ +// +// SynthesizedAudioCommandNote.cs - represents a note command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a note command in a synthesized audio file. +/// +public class SynthesizedAudioCommandNote : SynthesizedAudioCommand +{ + public bool Protected { get; set; } = false; + public int Position { get; set; } = 0; + public double Length { get; set; } = 0; + public string Phoneme { get; set; } = null; + public string Lyric { get; set; } = null; + public double Frequency { get; set; } = 0; + public int PreUtterance { get; set; } = 0; + public int VoiceOverlap { get; set; } = 0; + public int Intensity { get; set; } = 0; + public int Modulation { get; set; } = 0; + public int PBType { get; set; } = 0; + public double[] Pitches { get; set; } = new double[0]; + public string[] Envelope { get; set; } = new string[0]; + public double[] VBR { get; set; } = new double[0]; + public int Accent { get; set; } = 50; + public int PitchBendDepth { get; set; } = 8; + public int PitchBendLength { get; set; } = 0; + public int Decay { get; set; } = 50; + public bool PortamentoFalling { get; set; } = false; + public int Opening { get; set; } = 127; + public bool PortamentoRising { get; set; } = false; + public int VibratoLength { get; set; } = 0; + public SynthesizedAudioVibratoType VibratoType { get; set; } = SynthesizedAudioVibratoType.None; + + public override object Clone() + { + return new SynthesizedAudioCommandNote + { + Envelope = this.Envelope.Clone() as string[], + Intensity = this.Intensity, + Length = this.Length, + Lyric = this.Lyric, + Modulation = this.Modulation, + PBType = this.PBType, + Phoneme = this.Phoneme, + Pitches = this.Pitches.Clone() as double[], + PortamentoFalling = this.PortamentoFalling, + PortamentoRising = this.PortamentoRising, + PreUtterance = this.PreUtterance, + Protected = this.Protected, + Position = this.Position, + Frequency = this.Frequency, + VBR = this.VBR.Clone() as double[], + VoiceOverlap = this.VoiceOverlap + }; + } + + public override string ToString() + { + return String.Format("{0} [{1}] <{2}, {3}>", Lyric, Phoneme, Position, Length); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs new file mode 100644 index 0000000..62c46ed --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs @@ -0,0 +1,29 @@ +// +// SynthesizedAudioCommandRest.cs - represents a rest in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a rest in a synthesized audio file. +/// +public class SynthesizedAudioCommandRest : SynthesizedAudioCommandNote +{ +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs new file mode 100644 index 0000000..b1e25bc --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs @@ -0,0 +1,49 @@ +// +// SynthesizedAudioCommandTempo.cs - represents a tempo change command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a tempo change command in a synthesized audio file. +/// +public class SynthesizedAudioCommandTempo : SynthesizedAudioCommand +{ + public double Tempo { get; set; } = 0.0; + + public SynthesizedAudioCommandTempo() + { + } + public SynthesizedAudioCommandTempo(double tempo) + { + this.Tempo = tempo; + } + public override string ToString() + { + return "MM = " + this.Tempo.ToString() + " BPM"; + } + public override object Clone() + { + return new SynthesizedAudioCommandTempo + { + Tempo = this.Tempo + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs new file mode 100644 index 0000000..2e1841c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs @@ -0,0 +1,48 @@ +// +// SynthesizedAudioCommandText.cs - represents a text command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a text command in a synthesized audio file. +/// +public class SynthesizedAudioCommandText : SynthesizedAudioCommand +{ + public string Text { get; set; } = string.Empty; + public SynthesizedAudioCommandText() + { + } + public SynthesizedAudioCommandText(string text) + { + this.Text = text; + } + public override string ToString() + { + return "\"" + this.Text + "\""; + } + public override object Clone() + { + return new SynthesizedAudioCommandText + { + Text = this.Text.Clone() as string + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs new file mode 100644 index 0000000..b5d255a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs @@ -0,0 +1,66 @@ +// +// SynthesizedAudioCommandTimeSignature.cs - represents a time signature change command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a time signature change command in a synthesized audio file. +/// +public class SynthesizedAudioCommandTimeSignature : SynthesizedAudioCommand +{ + public byte Numerator { get; set; } = 0; + public byte Denominator { get; set; } = 0; + public byte TicksPerMetronomeClick { get; set; } = 0; + public byte NumberOf32ndNotesPerQuarterNote { get; set; } = 0; + + public SynthesizedAudioCommandTimeSignature() + { + } + public SynthesizedAudioCommandTimeSignature(byte numerator, byte denominator, byte ticksPerMetronomeClick, byte numberOf32ndNotesPerQuarterNote) + { + this.Numerator = numerator; + this.Denominator = denominator; + this.TicksPerMetronomeClick = ticksPerMetronomeClick; + this.NumberOf32ndNotesPerQuarterNote = numberOf32ndNotesPerQuarterNote; + } + public override string ToString() + { + return string.Concat(new string[] + { + "TS = ", + this.Numerator.ToString(), + "/", + this.Denominator.ToString(), + "; ♪ = ", + this.NumberOf32ndNotesPerQuarterNote.ToString() + }); + } + public override object Clone() + { + return new SynthesizedAudioCommandTimeSignature + { + Denominator = this.Denominator, + NumberOf32ndNotesPerQuarterNote = this.NumberOf32ndNotesPerQuarterNote, + Numerator = this.Numerator, + TicksPerMetronomeClick = this.TicksPerMetronomeClick + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs new file mode 100644 index 0000000..f43ec15 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs @@ -0,0 +1,109 @@ +// +// SynthesizedAudioObjectModel.cs - provides an ObjectModel for manipulating synthesized audio files (e.g. MIDI, VSQ, etc.) +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using MBS.Editor.Core; +// using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Voicebank; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Provides an for manipulating synthesized audio files (e.g. MIDI, VSQ, etc.). +/// +public class SynthesizedAudioObjectModel : AudioObjectModelBase +{ + /* + private static ObjectModelReference _omr = null; + protected override ObjectModelReference MakeReferenceInternal() + { + if (_omr == null) + { + _omr = base.MakeReferenceInternal(); + _omr.Path = new string[] { "Multimedia", "Audio", "Synthesized Audio" }; + } + return _omr; + } + */ + + public short ChannelCount { get; set; } = 2; + + public string Name { get; set; } = string.Empty; + + public double Tempo { get; set; } = 120.0; + + public SynthesizedAudioTrack.SynthesizedAudioTrackCollection Tracks { get; } = new SynthesizedAudioTrack.SynthesizedAudioTrackCollection(); + // public VoicebankObjectModel.VoicebankObjectModelCollection Voices { get; } = new VoicebankObjectModel.VoicebankObjectModelCollection(); + + /* + private CriteriaObject[] _CriteriaObjects = null; + + private CriteriaProperty PROPERTY_LYRIC = new CriteriaProperty("Lyric", typeof(string)); + protected override CriteriaObject[] GetCriteriaObjectsInternal() + { + if (_CriteriaObjects == null) + { + _CriteriaObjects = new CriteriaObject[] + { + new CriteriaObject("Note", new CriteriaProperty[] + { + PROPERTY_LYRIC + }) + }; + } + return _CriteriaObjects; + } + protected override CriteriaResult[] FindInternal(CriteriaQuery query) + { + List list = new List(); + for (int i = 0; i < Tracks.Count; i++) + { + for (int j = 0; j < Tracks[i].Commands.Count; j++) + { + if (Tracks[i].Commands[j] is SynthesizedAudioCommandNote) + { + if (query.Check(PROPERTY_LYRIC, (Tracks[i].Commands[j] as SynthesizedAudioCommandNote).Lyric)) + { + list.Add(new CriteriaResult(Tracks[i].Commands[j])); + } + } + } + } + return list.ToArray(); + } + */ + + public override void CopyTo(ObjectModel destination) + { + SynthesizedAudioObjectModel clone = destination as SynthesizedAudioObjectModel; + clone.Name = (this.Name.Clone() as string); + clone.Tempo = this.Tempo; + foreach (SynthesizedAudioTrack track in this.Tracks) + { + clone.Tracks.Add(track.Clone() as SynthesizedAudioTrack); + } + } + public override void Clear() + { + this.Name = string.Empty; + this.Tempo = 120.0; + this.Tracks.Clear(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs new file mode 100644 index 0000000..5b268cd --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs @@ -0,0 +1,98 @@ +// +// SynthesizedAudioPredefinedNote.cs - defines various commonly-used note pitches for synthesized audio files +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Defines various commonly-used note pitches for synthesized audio files. +/// +public enum SynthesizedAudioPredefinedNote +{ + A = 0, + ASharp, + BFlat = ASharp, + B, + C, + CSharp, + DFlat = CSharp, + D, + DSharp, + EFlat = DSharp, + E, + F, + FSharp, + GFlat = FSharp, + G, + GSharp, + AFlat = GSharp +} +/// +/// Converts to and from values and their associated frequencies. +/// +public static class SynthesizedAudioPredefinedNoteConverter +{ + public static double GetFrequency(SynthesizedAudioPredefinedNote note, int octave) + { + return GetFrequency((int)note, octave); + } + public static double GetFrequency(int note, int octave) + { + return GetFrequency(note + ((octave - 4) * 12)); + } + public static double GetFrequency(int note) + { + /* + C5 = the C an octave above middle C. This is 3 half steps above A4 and so the frequency is + f3 = 440 * (1.059463..)^3 = 523.3 Hz + If your calculator does not have the ability to raise to powers, then use the fact that + (1.059463..)^3 = (1.059463..)*(1.059463..)*(1.059463..) + That is, you multiply it by itself 3 times. + + Middle C is 9 half steps below A4 and the frequency is: + f -9 = 440 * (1.059463..)^(-9) = 261.6 Hz + If you don't have powers on your calculator, remember that the negative sign on the power means you divide instead of multiply. For this example, you divide by (1.059463..) 9 times. + */ + + double d = Math.Pow(2, (double)1 / 12); + int halfSteps = note; + double value = 440 * Math.Pow(d, halfSteps); + return value; + } + + public static int GetNote(double frequency) + { + double d = Math.Pow(2, (double)1 / 12); + double halfSteps = Math.Pow(frequency / 440, (1 / d)); + int note = (int)Math.Round((halfSteps - 1) * 12) + 1; + return note; + } + + public static double ChangeFrequency(double oldFrequency, int detuneBy) + { + if (detuneBy == 0) return oldFrequency; + + int note = GetNote(oldFrequency); + double freq = GetFrequency(note + detuneBy); + return freq; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs new file mode 100644 index 0000000..219b54a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs @@ -0,0 +1,63 @@ +// +// SynthesizedAudioStylePlugin.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +public class SynthesizedAudioStylePlugin +{ + private Guid mvarID = Guid.Empty; + private string mvarName = string.Empty; + private Version mvarVersion = new Version(1, 0); + public Guid ID + { + get + { + return this.mvarID; + } + set + { + this.mvarID = value; + } + } + public string Name + { + get + { + return this.mvarName; + } + set + { + this.mvarName = value; + } + } + public Version Version + { + get + { + return this.mvarVersion; + } + set + { + this.mvarVersion = value; + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs new file mode 100644 index 0000000..5693d9a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs @@ -0,0 +1,101 @@ +// +// SynthesizedAudioTrack.cs - represents a track in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +using MBS.Core.Drawing; + +// using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Voicebank; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a track in a synthesized audio file. +/// +public class SynthesizedAudioTrack : ICloneable +{ + public class SynthesizedAudioTrackCollection : Collection + { + private Dictionary tracksByID = new Dictionary(); + public SynthesizedAudioTrack this[string ID] + { + get + { + SynthesizedAudioTrack result; + if (this.tracksByID.ContainsKey(ID)) + { + result = this.tracksByID[ID]; + } + else + { + result = null; + } + return result; + } + } + + protected override void InsertItem(int index, SynthesizedAudioTrack item) + { + base.InsertItem(index, item); + + if (!string.IsNullOrEmpty(item.ID)) + tracksByID[item.ID] = item; + } + protected override void RemoveItem(int index) + { + if (!string.IsNullOrEmpty(this[index].ID)) + tracksByID.Remove(this[index].ID); + + base.RemoveItem(index); + } + protected override void ClearItems() + { + base.ClearItems(); + tracksByID.Clear(); + } + } + + public string ID { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Comment { get; set; } = string.Empty; + public SynthesizedAudioCommand.SynthesizedAudioCommandCollection Commands { get; } = new SynthesizedAudioCommand.SynthesizedAudioCommandCollection(); + // public VoicebankObjectModel Synthesizer { get; set; } = null; + public bool IsMuted { get; set; } = false; + public bool IsSolo { get; set; } = false; + public byte Panpot { get; set; } = 64; + public byte Volume { get; set; } = 0; + public object Clone() + { + SynthesizedAudioTrack clone = new SynthesizedAudioTrack(); + foreach (SynthesizedAudioCommand command in this.Commands) + { + clone.Commands.Add(command.Clone() as SynthesizedAudioCommand); + } + clone.ID = (this.ID.Clone() as string); + clone.Name = (this.Name.Clone() as string); + return clone; + } + + public double Tempo { get; set; } = 120; + public Color Color { get; set; } = Color.Empty; +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs new file mode 100644 index 0000000..987d672 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs @@ -0,0 +1,30 @@ +// +// SynthesizedAudioVibratoType.cs - indicates the type of vibrato applied to a note in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Indicates the type of vibrato applied to a note in a synthesized audio file. +/// +public enum SynthesizedAudioVibratoType +{ + None +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs new file mode 100644 index 0000000..a4f999f --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs @@ -0,0 +1,71 @@ +// +// PaletteEntry.cs - represents a color entry in a palette +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using MBS.Core.Drawing; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Palette +{ + /// + /// Represents a color entry in a palette. + /// + public class PaletteEntry : ICloneable + { + public class PaletteEntryCollection + : System.Collections.ObjectModel.Collection + { + public PaletteEntry Add(Color color, string colorName = "") + { + PaletteEntry entry = new PaletteEntry(); + entry.Name = colorName; + entry.Color = color; + + Add(entry); + return entry; + } + } + + public PaletteEntry(Color color = default(Color), string name = "") + { + Name = name; + Color = color; + } + + /// + /// Gets or sets the name of this . + /// + /// The name of this . + public string Name { get; set; } = String.Empty; + /// + /// Gets or sets the color of this . + /// + /// The color of this . + public Color Color { get; set; } = Color.Empty; + + public object Clone() + { + PaletteEntry clone = new PaletteEntry(); + clone.Name = Name; + clone.Color = Color; + return clone; + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs new file mode 100644 index 0000000..7a7a7a6 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs @@ -0,0 +1,85 @@ +// +// PaletteObjectModel.cs - provides an ObjectModel for manipulating color palettes +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Palette +{ + /// + /// Provides an for manipulating color palettes. + /// + public class PaletteObjectModel : ObjectModel + { + /* + private static ObjectModelReference _omr = null; + protected override ObjectModelReference MakeReferenceInternal() + { + if (_omr == null) + { + _omr = new ObjectModelReference(GetType(), new Guid("{c852b1d7-d034-43b4-b850-00e583e47fef}")); + _omr.Path = new string[] { "Multimedia", "Color palette" }; + } + return _omr; + } + */ + + public PaletteObjectModel() + { + } + public PaletteObjectModel(PaletteEntry[] entries) + { + if (entries != null) + { + for (int i = 0; i < entries.Length; i++) + { + Entries.Add(entries[i]); + } + } + } + + public override void Clear() + { + Entries.Clear(); + } + + public override void CopyTo(ObjectModel where) + { + PaletteObjectModel clone = (where as PaletteObjectModel); + clone.Name = (Name.Clone() as string); + for (int i = 0; i < Entries.Count; i++) + { + clone.Entries.Add(Entries[i].Clone() as PaletteEntry); + } + } + + /// + /// Gets or sets the name of this palette. + /// + /// The name of this palette. + public string Name { get; set; } = String.Empty; + /// + /// Gets a collection of instances representing the color entries in this palette. + /// + /// The color entries in this palette. + public PaletteEntry.PaletteEntryCollection Entries { get; } = new PaletteEntry.PaletteEntryCollection(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs new file mode 100644 index 0000000..3af1a35 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs @@ -0,0 +1,55 @@ +// +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . +// + +using System.Numerics; +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Palette; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; + +public class PictureObjectModel : ObjectModel +{ + private Dictionary pixels = new Dictionary(); + + public PaletteObjectModel? Palette { get; set; } = null; + + public int Width { get; set; } + public int Height { get; set; } + + public IEnumerable> GetPixels() + { + return pixels; + } + + public Color GetPixel(int x, int y) + { + Vector2D key = new Vector2D(x, y); + if (pixels.ContainsKey(key)) + { + return pixels[key]; + } + return Color.Empty; + } + + public void SetPixel(int x, int y, Color color) + { + pixels[new Vector2D(x, y)] = color; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs new file mode 100644 index 0000000..80104ec --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Testing; + +namespace MBS.Editor.Core.Tests.DataFormats.FileSystem.IFF; + +[TestFixture] +public class IFFDataFormatTests : EmbeddedResourceTest +{ + + [Test] + public void CheckVenusTotalSize() + { + if (TryCreateResourceStream("MBS.Editor.Core.Tests.Resources.TestData.DataFormats.FileSystem.IFF.VENUS.IFF", out Stream? st)) + { + if (st == null) + return; + + ChunkedObjectModel fsom = new ChunkedObjectModel(); + + + IFFDataFormat iff = new IFFDataFormat(); + Document.Load(fsom, iff, st); + + Assert.That(fsom.Chunks.Count, Is.EqualTo(1)); + Assert.That((fsom.Chunks[0] as GroupChunk).Chunks.Count, Is.EqualTo(3)); + } + } + + +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj index 050e4ca..92af4a8 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj @@ -6,6 +6,10 @@ false true + + + + @@ -15,5 +19,6 @@ + \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs index de922a8..5f68bd9 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs @@ -5,7 +5,7 @@ namespace MBS.Editor.Core.Tests.ObjectModels.FileSystem; public class FileSystemItemCollectionTests { - [ Test()] + [Test()] public static void AddFolderWithEmptyNameTest() { // the reason this test exists diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs new file mode 100644 index 0000000..087f57b --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs @@ -0,0 +1,80 @@ +namespace MBS.Editor.Core.Tests.ObjectModels.PropertyList; + +using System.Text; +using MBS.Editor.Core.ObjectModels.PropertyList; +using MBS.Editor.Testing; + +public class INIDataFormatTests : EmbeddedResourceTest +{ + private PropertyListObjectModel CreateSimplePropertyListWithSections() + { + PropertyListObjectModel plom = new PropertyListObjectModel(); + + PropertyListProperty prop = new PropertyListProperty("Test 1", 3127); + plom.Items.Add(prop); + + prop = new PropertyListProperty("String Property", "This is a Test"); + plom.Items.Add(prop); + + PropertyListGroup group = new PropertyListGroup("Section 2"); + group.Items.Add(new PropertyListProperty("Property 2", 923.1072)); + plom.Items.Add(group); + + return plom; + } + + [Test()] + public void INIDataFormatTest() + { + PropertyListObjectModel plom = CreateSimplePropertyListWithSections(); + INIDataFormat ini = new INIDataFormat(); + + int old_count = plom.Items.Count; + + MemoryStream ms = new MemoryStream(); + Document.Save(plom, ini, ms); + ms.Flush(); + + byte[] data = ms.ToArray(); + ms = new MemoryStream(data); + + plom = new PropertyListObjectModel(); + ini = new INIDataFormat(); + + Document.Load(plom, ini, ms); + + Assert.That(plom.Items.Count, Is.EqualTo(old_count)); + } + + [Test()] + public void ReallyBadINIDataFormatTest() + { + if (TryCreateResourceStream("Resources.TestData.ObjectModels.PropertyList.INI.CompleteTest.ini", out Stream? st)) + { + if (st == null) + return; + + PropertyListObjectModel plom = new PropertyListObjectModel(); + INIDataFormat ini = new INIDataFormat(); + Document.Load(plom, ini, st); + + Assert.That(plom.Items.Count == 4); // comments are included by default + Assert.That(plom.Items[0], Is.InstanceOf()); + + Assert.That(plom.Items[1], Is.InstanceOf()); + + PropertyListGroup? g = plom.Items[1] as PropertyListGroup; + if (g != null) + { + Assert.That(g.Items.Count, Is.EqualTo(3)); + Assert.That(g.Items[0].Name, Is.EqualTo("Property1")); + + PropertyListProperty? prop = g.Items[1] as PropertyListProperty; + Assert.That(prop, Is.Not.Null); + Assert.That(prop.Value, Is.Not.Null); + Assert.That(prop.Value, Is.EqualTo("This is a property with quotes.")); + } + } + } + +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF new file mode 100644 index 0000000000000000000000000000000000000000..443778ef421211b00c1b80bb693f82a1c75e6669 GIT binary patch literal 26978 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 HlQE0m8OL)a literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini new file mode 100644 index 0000000..a96d07c --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini @@ -0,0 +1,16 @@ +; this is a really bad INI file which should be able +; to be completely parsed by MBS Editor + +[Section1] +Property1=Value2 +PropertyWithQuotes="This is a property with quotes." +PropertyWithoutQuotes=Quotes really do not matter in INI files. + +[Section with a space in its name] +; this should work, and this comment should be included +; in the PropertyListObjectModel +Property Key with Space=Another unquoted value +What happens when=there are multiple equal=values it should be=just the first one + +[This is [probably a [really bad] Idea]] +Version=1.0.50726.42 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs index a2678c9..03b310e 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs @@ -5,22 +5,17 @@ using MBS.Editor.Core; using MBS.Editor.Core.ObjectModels.FileSystem; using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; using MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; +using MBS.Editor.Testing; using NUnit.Framework.Internal; namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.CPK; -public class CPKDataFormatTests +public class CPKDataFormatTests : EmbeddedResourceTest { - private System.Collections.Generic.Dictionary testDataStreams = new System.Collections.Generic.Dictionary(); - [SetUp] public void Setup() { - MBS.Core.Reflection.ManifestResourceStream[] strms = MBS.Core.Reflection.ManifestResourceStream.GetManifestResourceStreamsForAssembly(System.Reflection.Assembly.GetExecutingAssembly()); - for (int i = 0; i < strms.Length; i++) - { - testDataStreams[strms[i].Name] = strms[i].Stream; - } + InitializeResources(); } [Test(Description = "Ensures the CPKDataFormat can store exactly one file, uncompressed, with no filename masking.")] @@ -57,20 +52,6 @@ public class CPKDataFormatTests Assert.That(System_dat.Source.GetData(2, 1)[0], Is.EqualTo(0xBA)); } - - private void SampleStreamTest(string streamName) - { - if (testDataStreams.ContainsKey(streamName)) - { - System.IO.Stream TEST_STREAM = testDataStreams[streamName]; - SampleStreamTest(TEST_STREAM); - } - else - { - Console.Error.WriteLine("test data stream not found: '" + streamName + "'"); - Assert.Ignore(); - } - } private void SampleStreamTest(System.IO.Stream stream) { stream.Seek(0, System.IO.SeekOrigin.Begin); diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs new file mode 100644 index 0000000..27416c1 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs @@ -0,0 +1,8 @@ +using System; + +namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.Database.UTF; + +public class UTFDataFormatTests +{ + +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj index cd028c0..8a81931 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs new file mode 100644 index 0000000..c28e646 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs @@ -0,0 +1,59 @@ +using MBS.Editor.Core; +using MBS.Editor.Plugins.Mekada.DataFormats.Mechanic; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Mekada.Tests; + +public class MEKDataFormatTests : EmbeddedResourceTest +{ + [SetUp] + public void Setup() + { + InitializeResources(); + } + + [Test] + public void Test1() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.0.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void Test2() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.1.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void WithCarInLot() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.WithCarInLot.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj new file mode 100644 index 0000000..98421d3 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek new file mode 100644 index 0000000000000000000000000000000000000000..7930b12db286aa974a0bcab6ffeb72b4acd9b11c GIT binary patch literal 389 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 IhW}UKKM2k0Q2+n{ literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek new file mode 100644 index 0000000000000000000000000000000000000000..222a921e5c1d0495c73529e79561d4b9c789b31c GIT binary patch literal 72760 zcmeI$dsLI>9S8828mhC{vLm*2<#;=Ei)*a6*rgSYcnqsuBib$Mrg*{01W_{a78P3* z?Xh?%7G^b4tCo#)Mo|mFL9kd!wGt~>>O@^BUTRb(%%)}4SoXXX?C<2qbIkwya*ptt z*>@uZ1d^x`!1dqWv-TeI6H&4H19$Ywp$PA+8U!U#rg_ zY`=a;Tz{asS39fkri#E^x+K?y*-4FmIvk*NpiH_;MCu+dS8ndbmmEO-woR03asaoHMVVK+mQiTpqo-la1e;AnHv{ozB{MOHz-H`}emFu$)CwkGiDZ@v?Tx)*r1G znpIK}=+Yu-C#~o1Tg2KK%(^w0RK0cFNW}ZRcFSjn(^e5cRZUjt5cT5w7rNAYhF^?X$33H)$OVomoqOu@9rp~{hCXA@ zvo%8ZMw+92zP;v;tj|fL?KDU0ecdaU<<#dp<^s)$hwrcDlef3BcGA6y=4ie5M(v0R zbfJ5rEN3d-$nG`WgJ|dcJJ;Blrb=?Oo!yUwvYc%E#IWCY8MZZv^)E$IkB;AG6%VnT zY#eBA;PhwN_ysdxmONX>#!o#+bvEv_Uca74SvzIpPFuG90M`g~`5K=RuPv!Zi| z=EPgvpL?&QPGz|?@qEXa(j2wAVdi_z0!fa}g_M+HW=z)ztw(boj&iVe(m6|Ww4FoF z9ANe6`H$wP%dfBPW;uG!p*gw-Bu<^p_5gbRqdD5Yv;CV`?k5sY+RpbrTEp5YK8AEU zezcxFzM1WfUb04LJ8A#yJLa=`bZyCU9}nBYa`gO1bF`grOtmr>dXA$x>QepMK~_&T zel)ju^9w9DTjEJ`v%Ggk2CiGW$I03`{q^H9fjQZ{rY=kB%UC<PRjJFbzu{$jxQ z4eDzjG_H$kpN|R5sqO46N9WP2MeA8k-FF|9JGbb!(Sh~oJyzyinccG@Fh|buGscjx*L zmXqzPw13xrH99769Ozkx_K)U1>+59oWakr_i&=NW6pE>3+8^Kn>v<_ zJN^7f`$y~DzE#9rWcvW^-}B4zS#EI<{p(&{%GxPAx6yh#j*MrX>hlj;w@Le|Sv~q$ zQRXs!UnR@Q_HpX@cFuG(a6a*esBVMBw>V)NR)E$)_PcXPF~ zy0x*@8hxlB<5aEds#a^aSlmu$VHqvT?V zv(dTNy|=!l-kM=8C@i>r_x9cI@7A8KwYm%ir-Evm(MF?jTtdQZ zpU?k0^R&YKpTA`K{YS?euU`Ll*229DRvlcq@o@ImkGK8p)WM4LrS7kyU!3yE+2;By z-(2_k@4bQ1WHOn}`3{FULA=dupWm;E(hMCnG)fm`PB0tCjWJDp`KN<>JYyJ`v*pb` zhTevFeNt9}qu8Fm)3$I?dVXD0;_RtmVH%w-%49N{&8CC|b5)hg@AqByXuOxoM(A~4 zdeSe~=3Mz_;mMMu`V-r$@*-S$zi_@g?L@_vzwWlR`kGGXN0hDqSyf)7C%@mW4O7O( z>O6T7jfH(Zd69M7`?&L-F3XPEl0LwmsV!ZvtH^!s{k4Xe;gMAvdV97FDp@~b$*hrA zPQG)t&f~p&q5K~uZ(B#t8vo2HOZ+M4x-o;g_kW`6@LxXFc;wAqk7_0v2cC3f}cUjAd9haOd_vJ41RJp2L#h&9;P1nA8qe6&V>B5fKq4G&P#6 YP_Y6+#3Be$fB*mftO-^6Y1+I0Kj{Xy{Qv*} literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs new file mode 100644 index 0000000..ef609ef --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs @@ -0,0 +1,117 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.RMI; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Multimedia.Tests.DataFormats.Audio.Synthesized.RMI; + +public class RMIDataFormatTests : EmbeddedResourceTest +{ + [Test] + public void LoadMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + Assert.That(audio.Metadata.Artist, Is.EqualTo("Edvard Grieg")); + Assert.That(audio.Metadata.Copyright, Is.EqualTo("1995 Midisoft Corporation")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void LoadTrackMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + Assert.That(audio.Tracks.Count, Is.EqualTo(12)); + Assert.That(audio.Tracks[1].Name, Is.EqualTo("High Strings")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void SaveTrackMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + MemoryStream ms = new MemoryStream(); + Document.Save(audio, rmi, ms); + + audio = new SynthesizedAudioObjectModel(); + ms.Seek(0, SeekOrigin.Begin); + Document.Load(audio, rmi, ms); + + Assert.That(audio.Tracks.Count, Is.EqualTo(12)); + Assert.That(audio.Tracks[1].Name, Is.EqualTo("High Strings")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void SaveTrackCommandsTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + MemoryStream ms = new MemoryStream(); + Document.Save(audio, rmi, ms); + + int cc = audio.Tracks[1].Commands.Count; + + audio = new SynthesizedAudioObjectModel(); + ms.Seek(0, SeekOrigin.Begin); + Document.Load(audio, rmi, ms); + + Assert.That(audio.Tracks[1].Commands.Count, Is.EqualTo(cc)); + } + else + { + Assert.Ignore(); + } + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs new file mode 100644 index 0000000..f47eb3b --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs @@ -0,0 +1,62 @@ +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Multimedia.Tests.DataFormats.Picture.ILBM; + +public class ILBMDataFormatTests : EmbeddedResourceTest +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Venus_Size_EqualTo_300x150() + { + if (!TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Picture.ILBM.VENUS.IFF", out Stream? st)) + { + Assert.Ignore(); + return; + } + if (st == null) + { + Assert.Ignore(); + return; + } + + PictureObjectModel pic = new PictureObjectModel(); + + ILBMDataFormat iff = new ILBMDataFormat(); + Document.Load(pic, iff, st); + + Assert.That(pic.Width, Is.EqualTo(300)); + Assert.That(pic.Height, Is.EqualTo(150)); + } + + [Test] + public void Venus_Palettte_Contains_ColorMap() + { + if (!TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Picture.ILBM.VENUS.IFF", out Stream? st)) + { + Assert.Ignore(); + return; + } + if (st == null) + { + Assert.Ignore(); + return; + } + + PictureObjectModel pic = new PictureObjectModel(); + + ILBMDataFormat iff = new ILBMDataFormat(); + Document.Load(pic, iff, st); + + Assert.That(pic.Palette, Is.Not.Null); + Assert.That(pic.Palette.Entries.Count, Is.EqualTo(256)); + Assert.That(pic.Palette.Entries[1].Color, Is.EqualTo(Color.FromString("#FFAF17"))); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj new file mode 100644 index 0000000..3f65521 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj @@ -0,0 +1,42 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi new file mode 100644 index 0000000000000000000000000000000000000000..da79d91c9b63301340c43f4d8166a2d09837c829 GIT binary patch literal 38444 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 literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF new file mode 100644 index 0000000000000000000000000000000000000000..1bddef9a5bd306371e9691dc4890a3fe72467d7e GIT binary patch literal 2564 zcmZ?s5AtPT;QZt1*2z{z#zhKf?)!XW?*Ar1d`0mAilG&V*rq201|I#VEF(4 zKLZ2;`5;b1L&Jds2O2=KAbGH&2B3tqqpv%V$HKtC!QkZY5(%_x!gB@)WUT-H>OTXN k0}>h)7!8lnWiMhk<{!eF#8pmoCt0G*nYCIA2c literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF new file mode 100644 index 0000000000000000000000000000000000000000..443778ef421211b00c1b80bb693f82a1c75e6669 GIT binary patch literal 26978 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 HlQE0m8OL)a literal 0 HcmV?d00001 diff --git a/framework-dotnet b/framework-dotnet index a24ae26..223c34c 160000 --- a/framework-dotnet +++ b/framework-dotnet @@ -1 +1 @@ -Subproject commit a24ae26d5775be4387a58f61a3dc26057e0c1171 +Subproject commit 223c34c734ee60b4f4738b48181afb7221e18c39 diff --git a/web-framework-dotnet b/web-framework-dotnet new file mode 160000 index 0000000..732daae --- /dev/null +++ b/web-framework-dotnet @@ -0,0 +1 @@ +Subproject commit 732daae00ebeb6bb31454bd954bdd8174ef1fd0e