diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 37cfc99..61f156b 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -1,53 +1,154 @@
-name: Build and Publish Artifacts
+name: Build Drum Midi Remapper (Windows/macOS/Linux)
on:
push:
- branches: [ main ]
tags:
- - 'v*'
-
-permissions:
- contents: write
+ - 'v*.*.*'
+ pull_request:
+ branches: [ main ]
jobs:
- build:
+ version:
+ name: 🏷️ Generate Version
runs-on: ubuntu-latest
- strategy:
- matrix:
- runtime: [win-x64, osx-x64, osx-arm64, linux-x64]
-
+ outputs:
+ version: ${{ steps.set-version.outputs.version }}
steps:
- - name: Checkout repository
- uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- - name: Setup .NET 9 SDK
- uses: actions/setup-dotnet@v3
+ - name: 🏷️ Generate semantic version from Git tag
+ id: set-version
+ run: |
+ TAG=$(git describe --tags --abbrev=0)
+ VERSION_BASE=${TAG#v}
+ COUNT=$(git rev-list --count ${TAG}..HEAD)
+ VERSION="$VERSION_BASE.$COUNT"
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "📦 Using version: $VERSION"
+
+ build-windows:
+ name: Build Windows Desktop + CLI
+ runs-on: windows-latest
+ needs: version
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
with:
- dotnet-version: '9.0.x'
+ dotnet-version: 8.0.x
+
+ - name: Install MAUI workloads
+ run: |
+ dotnet workload update
+ dotnet workload install maui
+ dotnet workload install maui-windows
- name: Restore dependencies
run: dotnet restore
- - name: Publish single-file executable for ${{ matrix.runtime }}
+ - name: Build Windows MSIX
+ run: |
+ dotnet publish ./src/GUI/GUI.csproj -f net8.0-windows10.0.19041.0 -c Release `
+ -p:Version=${{ needs.version.outputs.version }} `
+ -p:WindowsPackageType=MSIX `
+ -p:GenerateAppxPackageOnBuild=true
+
+ - name: Build CLI
+ run: dotnet publish ./src/CLI/CLI.csproj -c Release -r win-x64 --self-contained true -p:Version=${{ needs.version.outputs.version }} -o ./cli-publish
+
+ - name: Package Windows MSIX
+ shell: pwsh
run: |
- dotnet publish src/CLI \
- -c Release \
- -r ${{ matrix.runtime }} \
- --self-contained true \
- /p:PublishSingleFile=true \
- /p:IncludeAllContentForSelfExtract=true \
- -o ./publish/${{ matrix.runtime }}
-
- - name: Upload artifact for ${{ matrix.runtime }}
- uses: actions/upload-artifact@v4
+ $msix = Get-ChildItem -Path ./src/GUI/bin/Release/** -Recurse -Include "*.msix" | Select-Object -First 1
+ Compress-Archive -Path $msix.FullName -DestinationPath DrumMidiRemapper-Windows-${{ needs.version.outputs.version }}.zip
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: DrumMidiRemapper-Windows-${{ needs.version.outputs.version }}
+ path: |
+ DrumMidiRemapper-Windows-${{ needs.version.outputs.version }}.zip
+ ./cli-publish/**
+
+ build-macos:
+ name: Build macOS Desktop + CLI
+ runs-on: macos-latest
+ needs: version
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
with:
- name: DrumMidiRemapper-${{ matrix.runtime }}
- path: ./publish/${{ matrix.runtime }}
+ dotnet-version: 8.0.x
- - name: Upload Release Asset
- if: startsWith(github.ref, 'refs/tags/v')
- uses: softprops/action-gh-release@v2
+ - uses: maxim-lobanov/setup-xcode@v1
with:
- files: ./publish/${{ matrix.runtime }}/CLI*
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ xcode-version: 'latest'
+
+ - name: Install MAUI workloads
+ run: |
+ dotnet workload update
+ dotnet workload install maui
+ dotnet workload install maui-maccatalyst
+
+ - name: Restore dependencies
+ run: dotnet workload restore
+
+ - name: Build MacCatalyst App
+ run: dotnet publish ./src/GUI/GUI.csproj -f net8.0-maccatalyst -c Release -p:Version=${{ needs.version.outputs.version }}
+
+ - name: Create DMG Installer
+ run: |
+ brew install create-dmg
+ APP_PATH=$(find ./src/GUI/bin/Release/net8.0-maccatalyst -name "*.app" | head -n 1)
+ echo "Found app: $APP_PATH"
+
+ if [[ "$APP_PATH" == *" "* ]]; then
+ APP_DIR=$(dirname "$APP_PATH")
+ APP_NEW="$APP_DIR/DrumMidiRemapper.app"
+ echo "Renaming app to: $APP_NEW"
+ mv "$APP_PATH" "$APP_NEW"
+ APP_PATH="$APP_NEW"
+ fi
+
+ create-dmg \
+ --volname "DrumMidiRemapper" \
+ --window-pos 200 120 \
+ --window-size 600 400 \
+ --no-internet-enable \
+ --skip-jenkins \
+ --icon-size 100 \
+ --icon "$APP_PATH" 175 190 \
+ --app-drop-link 425 190 \
+ "DrumMidiRemapper-${{ needs.version.outputs.version }}.dmg" \
+ "$APP_PATH"
+
+ - name: Build CLI
+ run: dotnet publish ./src/CLI/CLI.csproj -c Release -r osx-x64 --self-contained true -p:Version=${{ needs.version.outputs.version }} -o ./cli-publish
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: DrumMidiRemapper-macOS-${{ needs.version.outputs.version }}
+ path: |
+ DrumMidiRemapper-${{ needs.version.outputs.version }}.dmg
+ ./cli-publish/**
+
+ # build-linux:
+ # name: Build Linux CLI only
+ # runs-on: ubuntu-latest
+ # needs: version
+ # steps:
+ # - uses: actions/checkout@v4
+ # - uses: actions/setup-dotnet@v4
+ # with:
+ # dotnet-version: 8.0.x
+
+ # - name: Restore dependencies
+ # run: dotnet restore ./src/CLI/CLI.csproj
+
+ # - name: Build CLI Linux
+ # run: dotnet publish ./src/CLI/CLI.csproj -c Release --framework net8.0 --self-contained true -p:Version=${{ needs.version.outputs.version }} -o ./cli-publish --no-restore
+
+ # - uses: actions/upload-artifact@v4
+ # with:
+ # name: DrumMidiRemapper-LinuxCLI-${{ needs.version.outputs.version }}
+ # path: ./cli-publish/**
\ No newline at end of file
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
deleted file mode 100644
index 5a19686..0000000
--- a/.github/workflows/pr-validation.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: PR Validation
-
-on:
- pull_request:
-
-jobs:
- build-and-validate:
- name: Build, Test & Validate
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: '9.0.x'
-
- - name: Restore dependencies
- run: dotnet restore
-
- - name: Check code formatting
- run: dotnet format --verify-no-changes
-
- - name: Build with analyzers
- run: dotnet build --configuration Release -warnaserror
-
- - name: Run unit tests
- run: dotnet test --configuration Release --verbosity normal
-
- - name: Validate mapping JSON files
- run: |
- for file in $(find ./Services/Resources/Maps -name "*.json"); do
- echo "Validating $file"
- jq empty "$file"
- done
-
- - name: Check for vulnerable packages
- run: dotnet list package --vulnerable
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 083aead..7820328 100644
--- a/.gitignore
+++ b/.gitignore
@@ -483,4 +483,4 @@ $RECYCLE.BIN/
# Vim temporary swap files
*.swp
-midis/**
+**/midis/**
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 574dcbd..e3ac9b9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,29 +1,30 @@
{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
- "name": ".NET Core Attach",
- "type": "coreclr",
- "request": "attach"
- },
- {
- "name": ".NET Core Launch (console)",
+ "name": "Launch CLI",
"type": "coreclr",
"request": "launch",
- "preLaunchTask": "build",
- "program": "${workspaceFolder}/src/CLI/bin/Debug/net9.0/CLI.dll",
- "args": [],
+ "program": "${workspaceFolder}/src/CLI/bin/Debug/net8.0/CLI",
"cwd": "${workspaceFolder}/src/CLI",
- "console": "internalConsole",
- "stopAtEntry": false
+ "args": [
+ "GuitarPro",
+ "StevenSlate",
+ "${workspaceFolder}/midis/test.mid"
+ ],
+ "console": "integratedTerminal"
},
{
- "name": ".NET Core Attach",
+ "name": "Launch GUI (Mac Catalyst)",
"type": "coreclr",
- "request": "attach"
+ "request": "launch",
+ "preLaunchTask": "build GUI (Mac Catalyst)",
+ "program": "${workspaceFolder}/src/GUI/bin/Debug/net8.0-maccatalyst/maccatalyst-arm64/DrumMidiRemapper.app/Contents/MacOS/DrumMidiRemapper",
+ "cwd": "${workspaceFolder}/src/GUI",
+ "internalConsoleOptions": "openOnSessionStart",
+ "env": {
+ "DOTNET_ROOT": "/usr/local/share/dotnet"
+ }
}
]
}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index f99d588..a021bb2 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,38 +2,14 @@
"version": "2.0.0",
"tasks": [
{
- "label": "build",
+ "label": "build GUI (Mac Catalyst)",
"command": "dotnet",
"type": "process",
"args": [
"build",
- "${workspaceFolder}/drum-midi-remapper.sln",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "publish",
- "command": "dotnet",
- "type": "process",
- "args": [
- "publish",
- "${workspaceFolder}/drum-midi-remapper.sln",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "--project",
- "${workspaceFolder}/drum-midi-remapper.sln"
+ "${workspaceFolder}/src/GUI/GUI.csproj",
+ "-f",
+ "net8.0-maccatalyst"
],
"problemMatcher": "$msCompile"
}
diff --git a/README.md b/README.md
index 1e1673b..5174499 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Drum MIDI Remapper
-A **.NET 9** cross-platform tool for remapping MIDI drum notes between different standards and custom mappings. Designed to help musicians and producers adapt MIDI drum tracks for compatibility with various drum kits, DAWs, and hardware.
+A **.NET 8** cross-platform tool for remapping MIDI drum notes between different standards and custom mappings. Designed to help musicians and producers adapt MIDI drum tracks for compatibility with various drum kits, DAWs, and hardware.


@@ -22,7 +22,7 @@ dotnet run --project src/CLI -- GuitarPro StevenSlate midis/test.mid
## Requirements
-- [.NET 9.0 SDK or newer](https://dotnet.microsoft.com/download)
+- [.NET 8.0 SDK or newer](https://dotnet.microsoft.com/download)
- Compatible with Windows, macOS, and Linux
---
@@ -32,7 +32,7 @@ dotnet run --project src/CLI -- GuitarPro StevenSlate midis/test.mid
- Remap MIDI drum notes using customizable JSON mapping files
- Support for popular drum mapping standards (e.g., GuitarPro, StevenSlate, LogicPro, ProTools)
- Batch processing of MIDI files via CLI
-- Cross-platform support powered by .NET 9
+- Cross-platform support powered by .NET 8
- Modular architecture with Dependency Injection for easy extensibility
---
@@ -112,7 +112,7 @@ dotnet run --project src/CLI -- GuitarPro StevenSlate midis/test.mid
## Troubleshooting & FAQ
-- **Build errors:** Ensure .NET 9 SDK is installed and your environment is configured correctly.
+- **Build errors:** Ensure .NET 8 SDK is installed and your environment is configured correctly.
- **Mapping not found:** Verify spelling and that JSON mapping files exist in **Services/Resources/Maps/**.
- **MIDI file issues:** Confirm your input file is a valid MIDI file and accessible.
diff --git a/drum-midi-remapper.sln b/drum-midi-remapper.sln
index 1f8712f..9958861 100644
--- a/drum-midi-remapper.sln
+++ b/drum-midi-remapper.sln
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Models.Tests", "tests\Model
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Tests", "tests\Services.Tests\Services.Tests.csproj", "{3CECB972-EFFD-422B-9109-4D0D5AB6441E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUI", "src\GUI\GUI.csproj", "{06266691-6BB7-4ADC-9F06-0AA0553CF26D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -115,6 +117,18 @@ Global
{3CECB972-EFFD-422B-9109-4D0D5AB6441E}.Release|x64.Build.0 = Release|Any CPU
{3CECB972-EFFD-422B-9109-4D0D5AB6441E}.Release|x86.ActiveCfg = Release|Any CPU
{3CECB972-EFFD-422B-9109-4D0D5AB6441E}.Release|x86.Build.0 = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|x64.Build.0 = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Debug|x86.Build.0 = Debug|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|x64.ActiveCfg = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|x64.Build.0 = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|x86.ActiveCfg = Release|Any CPU
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -127,5 +141,6 @@ Global
{04A8B018-1954-48B8-BAAF-E17385114845} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{C3C581BD-E5DE-4CC4-B9A5-9F76602C69A4} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{3CECB972-EFFD-422B-9109-4D0D5AB6441E} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {06266691-6BB7-4ADC-9F06-0AA0553CF26D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
diff --git a/src/CLI/CLI.csproj b/src/CLI/CLI.csproj
index 060ef51..73c3f37 100644
--- a/src/CLI/CLI.csproj
+++ b/src/CLI/CLI.csproj
@@ -6,9 +6,14 @@
Exe
- net9.0
+ net8.0
enable
enable
+
+ drummidi
+ DrumMidiRemapper.CLI
+ Drum Midi Remapper CLI
+ 1.0.0
-
+
\ No newline at end of file
diff --git a/src/Managers/Contracts/ICliArgumentManager.cs b/src/CLI/Managers/Contracts/ICliArgumentManager.cs
similarity index 75%
rename from src/Managers/Contracts/ICliArgumentManager.cs
rename to src/CLI/Managers/Contracts/ICliArgumentManager.cs
index 6f9e270..afaaf8a 100644
--- a/src/Managers/Contracts/ICliArgumentManager.cs
+++ b/src/CLI/Managers/Contracts/ICliArgumentManager.cs
@@ -1,6 +1,6 @@
using Models;
-namespace Managers.Contracts;
+namespace CLI.Managers.Contracts;
public interface ICliArgumentManager
{
diff --git a/src/Managers/Implementations/CliArgumentsManager.cs b/src/CLI/Managers/Implementation/CliArgumentsManager.cs
similarity index 61%
rename from src/Managers/Implementations/CliArgumentsManager.cs
rename to src/CLI/Managers/Implementation/CliArgumentsManager.cs
index 25d4cbf..b0cb3a0 100644
--- a/src/Managers/Implementations/CliArgumentsManager.cs
+++ b/src/CLI/Managers/Implementation/CliArgumentsManager.cs
@@ -1,7 +1,7 @@
-using Managers.Contracts;
+using CLI.Managers.Contracts;
using Models;
-namespace Managers.Implementations;
+namespace CLI.Managers.Implementations;
public class CliArgumentManager : ICliArgumentManager
{
@@ -23,22 +23,10 @@ public Task Execute(string[] args)
var targetArg = args[1];
var midiPath = args[2];
- if (!Enum.TryParse(sourceArg, true, out var sourceMapType))
- {
- PrintAvailableMapTypes();
- throw new ArgumentException($"Invalid source map: '{sourceArg}'");
- }
-
- if (!Enum.TryParse(targetArg, true, out var targetMapType))
- {
- PrintAvailableMapTypes();
- throw new ArgumentException($"Invalid target map: '{targetArg}'");
- }
-
return Task.Run(() => new RemapVariables
{
- SourceMapType = sourceMapType,
- TargetMapType = targetMapType,
+ SourceMapType = sourceArg,
+ TargetMapType = targetArg,
MidiPath = midiPath
});
}
@@ -46,7 +34,7 @@ public Task Execute(string[] args)
private static void PrintAvailableMapTypes()
{
Console.WriteLine(AVAILABLE_MAPS);
- foreach (string name in Enum.GetNames())
+ foreach (string name in Enum.GetNames())
{
Console.WriteLine($"- {name}");
}
diff --git a/src/CLI/Managers/ServiceCollectionEx.cs b/src/CLI/Managers/ServiceCollectionEx.cs
new file mode 100644
index 0000000..356a26b
--- /dev/null
+++ b/src/CLI/Managers/ServiceCollectionEx.cs
@@ -0,0 +1,15 @@
+using CLI.Managers.Contracts;
+using CLI.Managers.Implementations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace CLI.Managers;
+
+public static class ServiceCollectionEx
+{
+ public static IServiceCollection AddCLIManagers(this IServiceCollection services)
+ {
+ services.AddTransient();
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/CLI/Program.cs b/src/CLI/Program.cs
index ed3fa00..97465f5 100644
--- a/src/CLI/Program.cs
+++ b/src/CLI/Program.cs
@@ -2,6 +2,8 @@
using Managers.Contracts;
using Services;
using Managers;
+using CLI.Managers;
+using CLI.Managers.Contracts;
namespace CLI;
@@ -15,6 +17,7 @@ private static async Task Main(string[] args)
services.AddServices();
services.AddManagers();
+ services.AddCLIManagers();
ServiceProvider provider = services.BuildServiceProvider();
@@ -25,7 +28,7 @@ private static async Task Main(string[] args)
{
Models.RemapVariables variables = await argumentManager.Execute(args);
- await manager.RemapMidi(variables);
+ await manager.RemapMidi(variables.SourceMapType, variables.TargetMapType, variables.MidiPath);
Console.WriteLine(SUCCESS_MESSAGE);
}
catch (Exception ex)
diff --git a/src/GUI/App.xaml b/src/GUI/App.xaml
new file mode 100644
index 0000000..ba90a5f
--- /dev/null
+++ b/src/GUI/App.xaml
@@ -0,0 +1,76 @@
+
+
+
+
+
+ #6366f1
+
+ #4f46e5
+ #f8f9fa
+
+ #ffffff
+
+ #f9fafb
+
+ #212529
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GUI/App.xaml.cs b/src/GUI/App.xaml.cs
new file mode 100644
index 0000000..66b01d5
--- /dev/null
+++ b/src/GUI/App.xaml.cs
@@ -0,0 +1,22 @@
+using GUI.Views;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+
+namespace GUI;
+
+public partial class App : Application
+{
+ private readonly MainPage _mainPage;
+
+ public App(MainPage mainPage)
+ {
+ InitializeComponent();
+
+ _mainPage = mainPage;
+ }
+
+ protected override Window CreateWindow(IActivationState? activationState)
+ {
+ return new Window(new NavigationPage(_mainPage));
+ }
+}
\ No newline at end of file
diff --git a/src/GUI/AppShell.xaml b/src/GUI/AppShell.xaml
new file mode 100644
index 0000000..33d21e0
--- /dev/null
+++ b/src/GUI/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/src/GUI/AppShell.xaml.cs b/src/GUI/AppShell.xaml.cs
new file mode 100644
index 0000000..33d4585
--- /dev/null
+++ b/src/GUI/AppShell.xaml.cs
@@ -0,0 +1,11 @@
+using Microsoft.Maui.Controls;
+
+namespace GUI;
+
+public partial class AppShell : Shell
+{
+ public AppShell()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/GUI/GUI.csproj b/src/GUI/GUI.csproj
new file mode 100644
index 0000000..431bb74
--- /dev/null
+++ b/src/GUI/GUI.csproj
@@ -0,0 +1,57 @@
+
+
+
+ net8.0-maccatalyst;net8.0-windows10.0.19041.0
+ Exe
+ true
+ enable
+ true
+ enable
+ true
+
+ Drum Midi Remapper
+ DrumMidiRemapper
+ DrumMidiRemapper
+ com.abstractize.drumremapper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime;native;build;contentfiles
+
+
+
+
+ true
+ None
+
+
+
\ No newline at end of file
diff --git a/src/GUI/MauiProgram.cs b/src/GUI/MauiProgram.cs
new file mode 100644
index 0000000..431a4c2
--- /dev/null
+++ b/src/GUI/MauiProgram.cs
@@ -0,0 +1,31 @@
+using GUI.ViewModels;
+using GUI.Views;
+using Managers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Maui.Controls.Hosting;
+using Microsoft.Maui.Hosting;
+using Services;
+using CommunityToolkit.Maui;
+
+namespace GUI;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp()
+ .UseMauiCommunityToolkit()
+ .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); });
+
+ builder.Services.AddServices();
+ builder.Services.AddManagers();
+
+ // ViewModels
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ return builder.Build();
+ }
+}
\ No newline at end of file
diff --git a/src/GUI/Platforms/MacCatalyst/AppDelegate.cs b/src/GUI/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 0000000..dd6386a
--- /dev/null
+++ b/src/GUI/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,11 @@
+using Foundation;
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+
+namespace GUI;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/src/GUI/Platforms/MacCatalyst/Entitlements.plist b/src/GUI/Platforms/MacCatalyst/Entitlements.plist
new file mode 100644
index 0000000..e3af334
--- /dev/null
+++ b/src/GUI/Platforms/MacCatalyst/Entitlements.plist
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.network.client
+
+
+ com.apple.security.files.downloads.read-write
+
+ com.apple.security.files.user-selected.read-write
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Platforms/MacCatalyst/Info.plist b/src/GUI/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 0000000..df8aae5
--- /dev/null
+++ b/src/GUI/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,55 @@
+
+
+
+
+
+ ITSAppUsesNonExemptEncryption
+
+
+
+ LSApplicationCategoryType
+ public.app-category.music
+
+
+ LSSupportsOpeningDocumentsInPlace
+
+ UIFileSharingEnabled
+
+
+
+ NSDocumentsFolderUsageDescription
+ This app requires access to your Documents folder to open and save MIDI files.
+ NSDownloadsFolderUsageDescription
+ This app requires access to your Downloads folder to open and save MIDI files.
+
+
+ UIDeviceFamily
+
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+ UIBackgroundModes
+
+ fetch
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Platforms/MacCatalyst/Program.cs b/src/GUI/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 0000000..4c1a9cd
--- /dev/null
+++ b/src/GUI/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace GUI;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/src/GUI/Platforms/Windows/App.xaml b/src/GUI/Platforms/Windows/App.xaml
new file mode 100644
index 0000000..56e418e
--- /dev/null
+++ b/src/GUI/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/GUI/Platforms/Windows/App.xaml.cs b/src/GUI/Platforms/Windows/App.xaml.cs
new file mode 100644
index 0000000..833431f
--- /dev/null
+++ b/src/GUI/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace GUI.WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : MauiWinUIApplication
+{
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/src/GUI/Platforms/Windows/Package.appxmanifest b/src/GUI/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 0000000..40d53dd
--- /dev/null
+++ b/src/GUI/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ $placeholder$
+ User Name
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GUI/Platforms/Windows/app.manifest b/src/GUI/Platforms/Windows/app.manifest
new file mode 100644
index 0000000..e508f66
--- /dev/null
+++ b/src/GUI/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/src/GUI/Properties/launchSettings.json b/src/GUI/Properties/launchSettings.json
new file mode 100644
index 0000000..f4c6c8d
--- /dev/null
+++ b/src/GUI/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "Project",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GUI/Resources/AppIcon/appicon.svg b/src/GUI/Resources/AppIcon/appicon.svg
new file mode 100644
index 0000000..5f04fcf
--- /dev/null
+++ b/src/GUI/Resources/AppIcon/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/GUI/Resources/AppIcon/appiconfg.svg b/src/GUI/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 0000000..62d66d7
--- /dev/null
+++ b/src/GUI/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Resources/Fonts/OpenSans-Regular.ttf b/src/GUI/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 0000000..33b3e0d
Binary files /dev/null and b/src/GUI/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/src/GUI/Resources/Fonts/OpenSans-Semibold.ttf b/src/GUI/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 0000000..a1f8571
Binary files /dev/null and b/src/GUI/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/src/GUI/Resources/Images/dotnet_bot.png b/src/GUI/Resources/Images/dotnet_bot.png
new file mode 100644
index 0000000..1d1b981
Binary files /dev/null and b/src/GUI/Resources/Images/dotnet_bot.png differ
diff --git a/src/GUI/Resources/Raw/AboutAssets.txt b/src/GUI/Resources/Raw/AboutAssets.txt
new file mode 100644
index 0000000..f22d3bf
--- /dev/null
+++ b/src/GUI/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,15 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+
+
+These files will be deployed with your package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/src/GUI/Resources/Splash/splash.svg b/src/GUI/Resources/Splash/splash.svg
new file mode 100644
index 0000000..62d66d7
--- /dev/null
+++ b/src/GUI/Resources/Splash/splash.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Resources/Styles/Colors.xaml b/src/GUI/Resources/Styles/Colors.xaml
new file mode 100644
index 0000000..22f0a67
--- /dev/null
+++ b/src/GUI/Resources/Styles/Colors.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ #512BD4
+ #ac99ea
+ #242424
+ #DFD8F7
+ #9880e5
+ #2B0B98
+
+ White
+ Black
+ #D600AA
+ #190649
+ #1f1f1f
+
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #212121
+ #141414
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Resources/Styles/Styles.xaml b/src/GUI/Resources/Styles/Styles.xaml
new file mode 100644
index 0000000..f6319b1
--- /dev/null
+++ b/src/GUI/Resources/Styles/Styles.xaml
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GUI/ViewModels/MainViewModel.cs b/src/GUI/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..a5c23fd
--- /dev/null
+++ b/src/GUI/ViewModels/MainViewModel.cs
@@ -0,0 +1,108 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CommunityToolkit.Maui.Storage;
+using Managers.Contracts;
+using Models;
+
+namespace GUI.ViewModels;
+
+public partial class MainViewModel : ObservableObject
+{
+ private readonly IMidiMapManager _midiManager;
+
+ public MainViewModel(IMidiMapManager midiManager)
+ {
+ _midiManager = midiManager;
+ MapTypes = [.. Enum.GetValues()];
+ }
+
+ [ObservableProperty]
+ private DrumMapTypes sourceMap;
+
+ [ObservableProperty]
+ private DrumMapTypes targetMap;
+
+ [ObservableProperty]
+ private Stream? selectedFile;
+ [ObservableProperty]
+ private string? fileName;
+
+ public List MapTypes { get; }
+
+
+ [RelayCommand]
+ private async Task PickFile()
+ {
+ var options = new PickOptions
+ {
+ PickerTitle = "Select a MIDI file",
+ FileTypes = new FilePickerFileType(new Dictionary>
+ {
+ { DevicePlatform.MacCatalyst, ["public.audio"] },
+ { DevicePlatform.WinUI, [".mid", ".midi"] }
+ })
+ };
+
+ FileResult? result = await FilePicker.Default.PickAsync(options);
+ if (result != null)
+ {
+ SelectedFile = await result.OpenReadAsync();
+ fileName = result.FileName;
+ }
+ else
+ {
+ await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "No file selected.", "OK");
+ }
+
+ }
+
+ [RelayCommand]
+ private async Task Remap()
+ {
+ try
+ {
+ if (SelectedFile == null)
+ {
+ await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Please select a MIDI file.", "OK");
+ return;
+ }
+
+ Console.WriteLine($"Source Map: {SourceMap}, Target Map: {TargetMap}");
+
+ Stream tempFile = await _midiManager.RemapMidi(SourceMap.ToString(), TargetMap.ToString(), SelectedFile);
+
+ var saveResult = await FileSaver.Default.SaveAsync("remapped.mid", tempFile);
+
+ if (saveResult.IsSuccessful)
+ await Application.Current!.Windows[0].Page!.DisplayAlert("Success", "✅ MIDI remapped and saved!", "OK");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error during remapping: {ex.Message}");
+ await Application.Current!.Windows[0].Page!.DisplayAlert("Error", $"An error occurred: {ex.Message}", "OK");
+ }
+ }
+
+ private static string GetDownloadsPath()
+ {
+ string downloadsPath = string.Empty;
+ if (OperatingSystem.IsWindows())
+ {
+ downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads";
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/Downloads";
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/Downloads";
+ }
+ else
+ {
+ throw new PlatformNotSupportedException("Unsupported platform for Downloads path.");
+ }
+
+ return downloadsPath;
+ }
+}
\ No newline at end of file
diff --git a/src/GUI/Views/MainPage.xaml b/src/GUI/Views/MainPage.xaml
new file mode 100644
index 0000000..f5133bb
--- /dev/null
+++ b/src/GUI/Views/MainPage.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GUI/Views/MainPage.xaml.cs b/src/GUI/Views/MainPage.xaml.cs
new file mode 100644
index 0000000..b7045f4
--- /dev/null
+++ b/src/GUI/Views/MainPage.xaml.cs
@@ -0,0 +1,13 @@
+using GUI.ViewModels;
+using Microsoft.Maui.Controls;
+
+namespace GUI.Views;
+
+public partial class MainPage : ContentPage
+{
+ public MainPage(MainViewModel vm)
+ {
+ InitializeComponent();
+ BindingContext = vm;
+ }
+}
\ No newline at end of file
diff --git a/src/Managers/Contracts/IMidiMapManager.cs b/src/Managers/Contracts/IMidiMapManager.cs
index 1f45927..3674ea0 100644
--- a/src/Managers/Contracts/IMidiMapManager.cs
+++ b/src/Managers/Contracts/IMidiMapManager.cs
@@ -4,5 +4,6 @@ namespace Managers.Contracts;
public interface IMidiMapManager
{
- Task RemapMidi(RemapVariables variables);
+ Task RemapMidi(string sourceMapArg, string targetMapArg, Stream midiStream);
+ Task RemapMidi(string sourceMapArg, string targetMapArg, string midiPath);
}
diff --git a/src/Managers/Implementations/MidiMapManager.cs b/src/Managers/Implementations/MidiMapManager.cs
index 11659d2..f0c0853 100644
--- a/src/Managers/Implementations/MidiMapManager.cs
+++ b/src/Managers/Implementations/MidiMapManager.cs
@@ -1,19 +1,49 @@
using Services.Contracts;
using Managers.Contracts;
using Models;
+using System.IO;
+using System.Threading.Tasks;
namespace Managers.Implementations;
-public class MidiMapManager(IMapLoaderService mapLoader, IMidiFileService midiFileService) : IMidiMapManager
+public class MidiMapManager : IMidiMapManager
{
- private readonly IMapLoaderService _mapLoader = mapLoader;
- private readonly IMidiFileService _midiFileService = midiFileService;
+ private readonly IMapLoaderService _mapLoader;
+ private readonly IMidiFileService _midiFileService;
- public async Task RemapMidi(RemapVariables variables)
+ public MidiMapManager(IMapLoaderService mapLoader, IMidiFileService midiFileService)
{
- DrumMap sourceMap = await _mapLoader.LoadAsync(variables.SourceMapType);
- DrumMap targetMap = await _mapLoader.LoadAsync(variables.TargetMapType);
+ _mapLoader = mapLoader;
+ _midiFileService = midiFileService;
+ }
+
+ public async Task RemapMidi(string sourceMapArg, string targetMapArg, Stream midiStream)
+ {
+ if (!Enum.TryParse(sourceMapArg, true, out var sourceMapType))
+ throw new ArgumentException($"Invalid source map: '{sourceMapArg}'");
+
+ if (!Enum.TryParse(targetMapArg, true, out var targetMapType))
+ throw new ArgumentException($"Invalid target map: '{targetMapArg}'");
+
+ DrumMap sourceMap = await _mapLoader.LoadAsync(sourceMapType);
+ DrumMap targetMap = await _mapLoader.LoadAsync(targetMapType);
+
+ return await _midiFileService.RemapAsync(sourceMap, targetMap, midiStream);
+ }
+
+ public async Task RemapMidi(string sourceMapArg, string targetMapArg, string midiPath)
+ {
+ using var fileStream = File.OpenRead(midiPath);
+ Stream stream = await RemapMidi(sourceMapArg, targetMapArg, fileStream);
+
+ string tempFilePath = Path.GetTempFileName();
+
+ using (var outputStream = File.OpenWrite(tempFilePath))
+ {
+ await stream.CopyToAsync(outputStream);
+ }
- await _midiFileService.RemapAsync(sourceMap, targetMap, variables.MidiPath);
+ stream.Dispose();
+ return tempFilePath;
}
}
\ No newline at end of file
diff --git a/src/Managers/Managers.csproj b/src/Managers/Managers.csproj
index 084d28f..b272877 100644
--- a/src/Managers/Managers.csproj
+++ b/src/Managers/Managers.csproj
@@ -1,14 +1,14 @@
-
-
-
-
-
- net9.0
+ net8.0
enable
enable
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Managers/ServiceCollectionEx.cs b/src/Managers/ServiceCollectionEx.cs
index 895f9c4..2d9446f 100644
--- a/src/Managers/ServiceCollectionEx.cs
+++ b/src/Managers/ServiceCollectionEx.cs
@@ -9,7 +9,6 @@ public static class ServiceCollectionEx
public static IServiceCollection AddManagers(this IServiceCollection services)
{
services.AddTransient();
- services.AddTransient();
return services;
}
diff --git a/src/Models/DrumMapType.cs b/src/Models/DrumMapType.cs
index fef0bf3..7ea7e66 100644
--- a/src/Models/DrumMapType.cs
+++ b/src/Models/DrumMapType.cs
@@ -1,6 +1,6 @@
namespace Models;
-public enum DrumMapType
+public enum DrumMapTypes
{
StevenSlate,
GuitarPro,
diff --git a/src/Models/Models.csproj b/src/Models/Models.csproj
index 125f4c9..e1b91f9 100644
--- a/src/Models/Models.csproj
+++ b/src/Models/Models.csproj
@@ -1,9 +1,9 @@
- net9.0
+ net8.0
enable
enable
-
+
\ No newline at end of file
diff --git a/src/Models/RemapVariables.cs b/src/Models/RemapVariables.cs
index b1737fa..27d71f6 100644
--- a/src/Models/RemapVariables.cs
+++ b/src/Models/RemapVariables.cs
@@ -2,7 +2,7 @@ namespace Models;
public record RemapVariables
{
- public DrumMapType SourceMapType { get; set; }
- public DrumMapType TargetMapType { get; set; }
+ public required string SourceMapType { get; set; }
+ public required string TargetMapType { get; set; }
public required string MidiPath { get; set; }
}
diff --git a/src/Services/Contracts/IMapLoaderService.cs b/src/Services/Contracts/IMapLoaderService.cs
index 4b6e2a3..355dd32 100644
--- a/src/Services/Contracts/IMapLoaderService.cs
+++ b/src/Services/Contracts/IMapLoaderService.cs
@@ -4,5 +4,5 @@ namespace Services.Contracts;
public interface IMapLoaderService
{
- abstract Task LoadAsync(DrumMapType type);
+ abstract Task LoadAsync(DrumMapTypes type);
}
diff --git a/src/Services/Contracts/IMidiFileService.cs b/src/Services/Contracts/IMidiFileService.cs
index 3201199..65879f0 100644
--- a/src/Services/Contracts/IMidiFileService.cs
+++ b/src/Services/Contracts/IMidiFileService.cs
@@ -4,5 +4,5 @@ namespace Services.Contracts;
public interface IMidiFileService
{
- public Task RemapAsync(DrumMap sourceMap, DrumMap targetMap, string midiFilePath);
+ public Task RemapAsync(DrumMap sourceMap, DrumMap targetMap, Stream midiStream);
}
diff --git a/src/Services/Extensions/MidiFileExtensions.cs b/src/Services/Extensions/MidiFileExtensions.cs
index 9eee2ec..1352c84 100644
--- a/src/Services/Extensions/MidiFileExtensions.cs
+++ b/src/Services/Extensions/MidiFileExtensions.cs
@@ -1,3 +1,5 @@
+#if !MACCATALYST
+
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Models;
@@ -50,3 +52,5 @@ public static void RemapNotes(this MidiFile midiFile, DrumMap sourceMap, DrumMap
}
}
}
+
+#endif
\ No newline at end of file
diff --git a/src/Services/Implementations/MapLoaderService.cs b/src/Services/Implementations/MapLoaderService.cs
index 36c146f..eef23b0 100644
--- a/src/Services/Implementations/MapLoaderService.cs
+++ b/src/Services/Implementations/MapLoaderService.cs
@@ -9,7 +9,7 @@ public class MapLoaderService : IMapLoaderService
{
private readonly Assembly _assembly = Assembly.GetExecutingAssembly();
- public async Task LoadAsync(DrumMapType type)
+ public async Task LoadAsync(DrumMapTypes type)
{
var resourceName = $"Services.Resources.Maps.{type}.json";
diff --git a/src/Services/Implementations/MidiFileService.cs b/src/Services/Implementations/MidiFileService.cs
deleted file mode 100644
index 1c23abd..0000000
--- a/src/Services/Implementations/MidiFileService.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Melanchall.DryWetMidi.Core;
-using Models;
-using Services.Contracts;
-using Services.Extensions;
-
-namespace Services.Implementations;
-
-public class MidiFileService : IMidiFileService
-{
- public async Task RemapAsync(DrumMap sourceMap, DrumMap targetMap, string midiFilePath)
- {
- if (!File.Exists(midiFilePath))
- throw new FileNotFoundException($"MIDI file not found: {midiFilePath}");
-
- MidiFile midiFile = await ReadMidiAsync(midiFilePath);
-
- midiFile.RemapNotes(sourceMap, targetMap);
-
- string fileName = Path.GetFileName(midiFilePath);
- string outputPath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
-
- await WriteMidiAsync(midiFile, outputPath);
-
- Console.WriteLine($"Remapped MIDI file saved at: {outputPath}");
- }
-
- private static async Task ReadMidiAsync(string path)
- {
- return await Task.Run(() =>
- {
- return MidiFile.Read(path);
- });
- }
-
- private static async Task WriteMidiAsync(MidiFile midi, string path)
- {
- await Task.Run(() =>
- {
- midi.Write(path);
- });
- }
-}
\ No newline at end of file
diff --git a/src/Services/Implementations/MidiFileServiceCoreMidi.cs b/src/Services/Implementations/MidiFileServiceCoreMidi.cs
new file mode 100644
index 0000000..aab6558
--- /dev/null
+++ b/src/Services/Implementations/MidiFileServiceCoreMidi.cs
@@ -0,0 +1,71 @@
+#if MACCATALYST
+using CoreMidi;
+using Models;
+using Services.Contracts;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Services.Implementations
+{
+ public class MidiFileServiceCoreMidi : IMidiFileService
+ {
+ public async Task RemapAsync(DrumMap sourceMap, DrumMap targetMap, Stream midiStream)
+ {
+ // Leer todos los bytes del stream
+ using var ms = new MemoryStream();
+ await midiStream.CopyToAsync(ms);
+ byte[] midiData = ms.ToArray();
+
+ // Procesar y remapear
+ RemapMidiEvents(midiData, sourceMap, targetMap);
+
+ return new MemoryStream(midiData);
+ }
+
+ // (Resto métodos igual...)
+ private static void RemapMidiEvents(byte[] midiData, DrumMap sourceMap, DrumMap targetMap)
+ {
+ Dictionary mapping = BuildMapping(sourceMap, targetMap);
+
+ for (int i = 0; i < midiData.Length - 2; i++)
+ {
+ byte status = midiData[i];
+ if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80)
+ {
+ byte note = midiData[i + 1];
+ if (mapping.TryGetValue(note, out int mappedNote))
+ midiData[i + 1] = (byte)mappedNote;
+ }
+ }
+ }
+
+ private static Dictionary BuildMapping(DrumMap sourceMap, DrumMap targetMap)
+ {
+ var mapping = new Dictionary();
+ foreach (var kvp in sourceMap.Mapping)
+ {
+ if (targetMap.Mapping.TryGetValue(kvp.Key, out int targetNote))
+ mapping[kvp.Value] = targetNote;
+ }
+ return mapping;
+ }
+
+ private static void SendToCoreMidi(byte[] midiData)
+ {
+ var client = new MidiClient("RemapperClient");
+ var outputPort = client.CreateOutputPort("OutputPort");
+ var dest = MidiEndpoint.GetDestination(0);
+
+ if (dest == null)
+ {
+ Console.WriteLine("⚠️ No external MIDI device found.");
+ return;
+ }
+
+ var packet = new MidiPacket(0, midiData);
+ outputPort.Send(dest, new[] { packet });
+ Console.WriteLine("🎹 Sent remapped MIDI events to external MIDI device.");
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Services/Implementations/MidiFileServiceDryWetMidi.cs b/src/Services/Implementations/MidiFileServiceDryWetMidi.cs
new file mode 100644
index 0000000..e34b4f6
--- /dev/null
+++ b/src/Services/Implementations/MidiFileServiceDryWetMidi.cs
@@ -0,0 +1,39 @@
+#if !MACCATALYST
+using Melanchall.DryWetMidi.Core;
+using Models;
+using Services.Contracts;
+using Services.Extensions;
+namespace Services.Implementations
+{
+ public class MidiFileServiceDryWetMidi : IMidiFileService
+ {
+ public async Task RemapAsync(DrumMap sourceMap, DrumMap targetMap, Stream midiStream)
+ {
+ MidiFile midiFile = await ReadMidiAsync(midiStream);
+
+ midiFile.RemapNotes(sourceMap, targetMap);
+
+ var outputStream = new MemoryStream();
+
+ await Task.Run(() =>
+ {
+ midiFile.Write(outputStream);
+ });
+
+ outputStream.Position = 0;
+
+ Console.WriteLine($"Remapped MIDI processed in memory.");
+
+ return outputStream;
+ }
+
+ private static async Task ReadMidiAsync(Stream stream)
+ {
+ return await Task.Run(() =>
+ {
+ return MidiFile.Read(stream);
+ });
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Services/ServiceCollectionEx.cs b/src/Services/ServiceCollectionEx.cs
index 691bded..f7db114 100644
--- a/src/Services/ServiceCollectionEx.cs
+++ b/src/Services/ServiceCollectionEx.cs
@@ -8,7 +8,11 @@ public static class ServiceCollectionEx
{
public static IServiceCollection AddServices(this IServiceCollection services)
{
- services.AddSingleton();
+#if MACCATALYST
+ services.AddSingleton();
+#else
+ services.AddSingleton();
+#endif
services.AddSingleton();
return services;
diff --git a/src/Services/Services.csproj b/src/Services/Services.csproj
index 854ae43..8300275 100644
--- a/src/Services/Services.csproj
+++ b/src/Services/Services.csproj
@@ -1,22 +1,30 @@
+
+
+ net8.0;net8.0-maccatalyst
+ enable
+ enable
+
+
+
+
+
+
+
+
+
-
+
-
-
- net9.0
- enable
- enable
-
-
+
\ No newline at end of file
diff --git a/tests/Managers.Tests/Implementations/CliArgumentManagerTest.cs b/tests/Managers.Tests/Implementations/CliArgumentManagerTest.cs
deleted file mode 100644
index 315bb9a..0000000
--- a/tests/Managers.Tests/Implementations/CliArgumentManagerTest.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using Models;
-
-namespace Managers.Tests.Implementations
-{
- public class CliArgumentManagerTest
- {
- [Fact]
- public async Task Execute_ValidArguments_ReturnsRemapVariables()
- {
- var manager = new CliArgumentManager();
- var args = new[] { "GuitarPro", "LogicPro", "test.mid" };
-
- var result = await manager.Execute(args);
-
- Assert.Equal(DrumMapType.GuitarPro, result.SourceMapType);
- Assert.Equal(DrumMapType.LogicPro, result.TargetMapType);
- Assert.Equal("test.mid", result.MidiPath);
- }
-
- [Fact]
- public async Task Execute_InsufficientArguments_ThrowsNullReferenceException()
- {
- var manager = new CliArgumentManager();
- var args = new[] { "GuitarPro", "LogicPro" };
-
- var ex = await Assert.ThrowsAsync(() => manager.Execute(args));
- Assert.Contains("Insufficient arguments", ex.Message);
- }
-
- [Fact]
- public async Task Execute_InvalidSourceMap_ThrowsArgumentException()
- {
- var manager = new CliArgumentManager();
- var args = new[] { "InvalidMap", "LogicPro", "test.mid" };
-
- var ex = await Assert.ThrowsAsync(() => manager.Execute(args));
- Assert.Contains("Invalid source map", ex.Message);
- }
-
- [Fact]
- public async Task Execute_InvalidTargetMap_ThrowsArgumentException()
- {
- var manager = new CliArgumentManager();
- var args = new[] { "GuitarPro", "InvalidMap", "test.mid" };
-
- var ex = await Assert.ThrowsAsync(() => manager.Execute(args));
- Assert.Contains("Invalid target map", ex.Message);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/Managers.Tests/Implementations/MidiMapManagerTests.cs b/tests/Managers.Tests/Implementations/MidiMapManagerTests.cs
index b2243bd..60ff654 100644
--- a/tests/Managers.Tests/Implementations/MidiMapManagerTests.cs
+++ b/tests/Managers.Tests/Implementations/MidiMapManagerTests.cs
@@ -6,8 +6,8 @@ namespace Managers.Tests.Implementations;
public class MidiMapManagerTest
{
- private const DrumMapType SOURCE = DrumMapType.GuitarPro;
- private const DrumMapType TARGET = DrumMapType.StevenSlate;
+ private const DrumMapTypes SOURCE = DrumMapTypes.GuitarPro;
+ private const DrumMapTypes TARGET = DrumMapTypes.StevenSlate;
private const string FILENAME = "test.mid";
[Fact]
@@ -22,8 +22,8 @@ public async Task RemapMidi_CallsServicesWithCorrectParameters()
var variables = new RemapVariables
{
- SourceMapType = SOURCE,
- TargetMapType = TARGET,
+ SourceMapType = SOURCE.ToString(),
+ TargetMapType = TARGET.ToString(),
MidiPath = FILENAME
};
@@ -32,12 +32,19 @@ public async Task RemapMidi_CallsServicesWithCorrectParameters()
var manager = new MidiMapManager(mockMapLoader.Object, mockMidiFileService.Object);
+ // Create a dummy stream for the MIDI file
+ using var midiStream = new System.IO.MemoryStream();
+
// Act
- await manager.RemapMidi(variables);
+ await manager.RemapMidi(
+ variables.SourceMapType,
+ variables.TargetMapType,
+ midiStream
+ );
// Assert
mockMapLoader.Verify(m => m.LoadAsync(SOURCE), Times.Once);
mockMapLoader.Verify(m => m.LoadAsync(TARGET), Times.Once);
- mockMidiFileService.Verify(m => m.RemapAsync(sourceMap, targetMap, "test.mid"), Times.Once);
+ mockMidiFileService.Verify(m => m.RemapAsync(sourceMap, targetMap, midiStream), Times.Once);
}
}
\ No newline at end of file
diff --git a/tests/Managers.Tests/Managers.Tests.csproj b/tests/Managers.Tests/Managers.Tests.csproj
index 53741e2..615694b 100644
--- a/tests/Managers.Tests/Managers.Tests.csproj
+++ b/tests/Managers.Tests/Managers.Tests.csproj
@@ -1,18 +1,24 @@
- net9.0
+ net8.0
enable
enable
false
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
-
-
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
@@ -24,4 +30,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Models.Tests/DrumMapTypeTest.cs b/tests/Models.Tests/DrumMapTypeTest.cs
index d337357..34632dc 100644
--- a/tests/Models.Tests/DrumMapTypeTest.cs
+++ b/tests/Models.Tests/DrumMapTypeTest.cs
@@ -5,30 +5,30 @@ public class DrumMapTypeTest
[Fact]
public void DrumMapType_ContainsExpectedValues()
{
- Assert.Equal(0, (int)DrumMapType.StevenSlate);
- Assert.Equal(1, (int)DrumMapType.GuitarPro);
- Assert.Equal(2, (int)DrumMapType.LogicPro);
- Assert.Equal(3, (int)DrumMapType.ProTools);
+ Assert.Equal(0, (int)DrumMapTypes.StevenSlate);
+ Assert.Equal(1, (int)DrumMapTypes.GuitarPro);
+ Assert.Equal(2, (int)DrumMapTypes.LogicPro);
+ Assert.Equal(3, (int)DrumMapTypes.ProTools);
}
[Theory]
- [InlineData(DrumMapType.StevenSlate)]
- [InlineData(DrumMapType.GuitarPro)]
- [InlineData(DrumMapType.LogicPro)]
- [InlineData(DrumMapType.ProTools)]
- public void DrumMapType_IsDefined(DrumMapType type)
+ [InlineData(DrumMapTypes.StevenSlate)]
+ [InlineData(DrumMapTypes.GuitarPro)]
+ [InlineData(DrumMapTypes.LogicPro)]
+ [InlineData(DrumMapTypes.ProTools)]
+ public void DrumMapType_IsDefined(DrumMapTypes type)
{
Assert.True(Enum.IsDefined(type));
}
[Theory]
- [InlineData("StevenSlate", DrumMapType.StevenSlate)]
- [InlineData("GuitarPro", DrumMapType.GuitarPro)]
- [InlineData("LogicPro", DrumMapType.LogicPro)]
- [InlineData("ProTools", DrumMapType.ProTools)]
- public void DrumMapType_ParseString_ReturnsCorrectEnum(string name, DrumMapType expected)
+ [InlineData("StevenSlate", DrumMapTypes.StevenSlate)]
+ [InlineData("GuitarPro", DrumMapTypes.GuitarPro)]
+ [InlineData("LogicPro", DrumMapTypes.LogicPro)]
+ [InlineData("ProTools", DrumMapTypes.ProTools)]
+ public void DrumMapType_ParseString_ReturnsCorrectEnum(string name, DrumMapTypes expected)
{
- var parsed = (DrumMapType)Enum.Parse(typeof(DrumMapType), name);
+ var parsed = (DrumMapTypes)Enum.Parse(typeof(DrumMapTypes), name);
Assert.Equal(expected, parsed);
}
}
\ No newline at end of file
diff --git a/tests/Models.Tests/Models.Tests.csproj b/tests/Models.Tests/Models.Tests.csproj
index bb1b65b..18d04a5 100644
--- a/tests/Models.Tests/Models.Tests.csproj
+++ b/tests/Models.Tests/Models.Tests.csproj
@@ -1,17 +1,23 @@
- net9.0
+ net8.0
enable
enable
false
-
-
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
@@ -22,4 +28,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Models.Tests/RemapVariablesTest.cs b/tests/Models.Tests/RemapVariablesTest.cs
index 4c1dfea..716a429 100644
--- a/tests/Models.Tests/RemapVariablesTest.cs
+++ b/tests/Models.Tests/RemapVariablesTest.cs
@@ -7,13 +7,13 @@ public void RemapVariables_DefaultConstructor_InitializesProperties()
{
var remapVariables = new RemapVariables
{
- SourceMapType = DrumMapType.GuitarPro,
- TargetMapType = DrumMapType.LogicPro,
+ SourceMapType = DrumMapTypes.GuitarPro.ToString(),
+ TargetMapType = DrumMapTypes.LogicPro.ToString(),
MidiPath = "test.mid"
};
- Assert.Equal(DrumMapType.GuitarPro, remapVariables.SourceMapType);
- Assert.Equal(DrumMapType.LogicPro, remapVariables.TargetMapType);
+ Assert.Equal(DrumMapTypes.GuitarPro.ToString(), remapVariables.SourceMapType);
+ Assert.Equal(DrumMapTypes.LogicPro.ToString(), remapVariables.TargetMapType);
Assert.Equal("test.mid", remapVariables.MidiPath);
}
@@ -22,14 +22,14 @@ public void RemapVariables_CanSetSourceMapType()
{
var remapVariables = new RemapVariables
{
- SourceMapType = DrumMapType.LogicPro,
- TargetMapType = DrumMapType.GuitarPro,
+ SourceMapType = DrumMapTypes.LogicPro.ToString(),
+ TargetMapType = DrumMapTypes.GuitarPro.ToString(),
MidiPath = "file.mid"
};
- remapVariables.SourceMapType = DrumMapType.GuitarPro;
+ remapVariables.SourceMapType = DrumMapTypes.GuitarPro.ToString();
- Assert.Equal(DrumMapType.GuitarPro, remapVariables.SourceMapType);
+ Assert.Equal(DrumMapTypes.GuitarPro.ToString(), remapVariables.SourceMapType);
}
[Fact]
@@ -37,14 +37,14 @@ public void RemapVariables_CanSetTargetMapType()
{
var remapVariables = new RemapVariables
{
- SourceMapType = DrumMapType.GuitarPro,
- TargetMapType = DrumMapType.LogicPro,
+ SourceMapType = DrumMapTypes.GuitarPro.ToString(),
+ TargetMapType = DrumMapTypes.LogicPro.ToString(),
MidiPath = "file.mid"
};
- remapVariables.TargetMapType = DrumMapType.GuitarPro;
+ remapVariables.TargetMapType = DrumMapTypes.GuitarPro.ToString();
- Assert.Equal(DrumMapType.GuitarPro, remapVariables.TargetMapType);
+ Assert.Equal(DrumMapTypes.GuitarPro.ToString(), remapVariables.TargetMapType);
}
[Fact]
@@ -52,8 +52,8 @@ public void RemapVariables_CanSetMidiPath()
{
var remapVariables = new RemapVariables
{
- SourceMapType = DrumMapType.GuitarPro,
- TargetMapType = DrumMapType.LogicPro,
+ SourceMapType = DrumMapTypes.GuitarPro.ToString(),
+ TargetMapType = DrumMapTypes.LogicPro.ToString(),
MidiPath = "old.mid"
};
diff --git a/tests/Services.Tests/Implementation/MapLoaderServiceTest.cs b/tests/Services.Tests/Implementation/MapLoaderServiceTest.cs
index fdf5a51..628daa4 100644
--- a/tests/Services.Tests/Implementation/MapLoaderServiceTest.cs
+++ b/tests/Services.Tests/Implementation/MapLoaderServiceTest.cs
@@ -12,7 +12,7 @@ public class MapLoaderServiceTest
public async Task LoadAsync_ValidType_ReturnsDrumMap()
{
// Arrange
- var type = DrumMapType.ProTools;
+ var type = DrumMapTypes.ProTools;
var resourceName = $"Services.Resources.Maps.{type}.json";
var drumMap = new DrumMap { Name = "TestMap" };
var json = JsonSerializer.Serialize(drumMap);
@@ -35,7 +35,7 @@ public async Task LoadAsync_ValidType_ReturnsDrumMap()
public async Task LoadAsync_ResourceNotFound_ThrowsFileNotFoundException()
{
// Arrange
- var type = DrumMapType.GuitarPro;
+ var type = DrumMapTypes.GuitarPro;
var resourceName = $"Services.Resources.Maps.{type}.json";
var assemblyMock = new Mock();
assemblyMock.Setup(a => a.GetManifestResourceStream(resourceName)).Returns((Stream?)null);
@@ -50,7 +50,7 @@ public async Task LoadAsync_ResourceNotFound_ThrowsFileNotFoundException()
public async Task LoadAsync_InvalidJson_ThrowsException()
{
// Arrange
- var type = DrumMapType.LogicPro;
+ var type = DrumMapTypes.LogicPro;
var resourceName = $"Services.Resources.Maps.{type}.json";
var invalidJson = "{ invalid json }";
var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(invalidJson));
@@ -73,7 +73,7 @@ public MapLoaderServiceTestable(Assembly testAssembly)
_testAssembly = testAssembly;
}
- public new async Task LoadAsync(DrumMapType type)
+ public new async Task LoadAsync(DrumMapTypes type)
{
var resourceName = $"Services.Resources.Maps.{type}.json";
using Stream? stream = _testAssembly.GetManifestResourceStream(resourceName) ??
diff --git a/tests/Services.Tests/Implementation/MidiFileServicesTest.cs b/tests/Services.Tests/Implementation/MidiFileServicesTest.cs
index 6bbea23..510d9b3 100644
--- a/tests/Services.Tests/Implementation/MidiFileServicesTest.cs
+++ b/tests/Services.Tests/Implementation/MidiFileServicesTest.cs
@@ -11,13 +11,13 @@ public class MidiFileServiceTest
[Fact]
public async Task RemapAsync_FileDoesNotExist_ThrowsFileNotFoundException()
{
- var service = new MidiFileService();
+ var service = new MidiFileServiceDryWetMidi();
var sourceMap = new DrumMap();
var targetMap = new DrumMap();
- string fakePath = "nonexistent.mid";
+ using var fakeStream = new MemoryStream();
var ex = await Assert.ThrowsAsync(() =>
- service.RemapAsync(sourceMap, targetMap, fakePath));
+ service.RemapAsync(sourceMap, targetMap, fakeStream));
Assert.Contains("MIDI file not found", ex.Message);
}
@@ -25,7 +25,7 @@ public async Task RemapAsync_FileDoesNotExist_ThrowsFileNotFoundException()
public async Task RemapAsync_ValidFile_RemapsAndSavesFile()
{
// Arrange
- var service = new MidiFileService();
+ var service = new MidiFileServiceDryWetMidi();
var sourceMap = new DrumMap();
var targetMap = new DrumMap();
string tempFile = Path.GetTempFileName();
@@ -38,7 +38,10 @@ public async Task RemapAsync_ValidFile_RemapsAndSavesFile()
try
{
// Act
- await service.RemapAsync(sourceMap, targetMap, midiPath);
+ using (var midiStream = File.OpenRead(midiPath))
+ {
+ await service.RemapAsync(sourceMap, targetMap, midiStream);
+ }
// Assert
string outputPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(midiPath));
diff --git a/tests/Services.Tests/Services.Tests.csproj b/tests/Services.Tests/Services.Tests.csproj
index 120a457..3b43299 100644
--- a/tests/Services.Tests/Services.Tests.csproj
+++ b/tests/Services.Tests/Services.Tests.csproj
@@ -1,18 +1,25 @@
- net9.0
+ net8.0
enable
enable
false
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
-
-
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
@@ -24,4 +31,4 @@
-
+
\ No newline at end of file