End-to-End Example: Build, Load, Preview, Apply

1. Project file

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\RetroWaveLab.Core.Abstractions\RetroWaveLab.Core.Abstractions.csproj" />
  </ItemGroup>
</Project>

2. Plugin entry class

using RetroWaveLab.Core.Abstractions;

namespace RetroWaveLab.Plugins.Example;

public sealed class ExamplePlugin : IPlugin, IProvidesMenuContributions
{
    private IHostContext? _context;

    public string Id => "example-plugin";
    public string DisplayName => "Example Plugin";
    public Version Version => new(1, 0, 0);

    public void OnLoad(IHostContext context)
    {
        _context = context;
        foreach (var item in GetMenuContributions())
        {
            _context.WindowManager.AddMenuContribution(item.Sort, item.MenuPath, item.Execute, item.IsEnabled);
        }
    }

    public void OnUnload()
    {
        StopPreview();
        _context = null;
    }

    public IEnumerable<MenuContribution> GetMenuContributions()
    {
        yield return new MenuContribution(
            MenuPath: "Effects.100.Example.Open",
            Execute: _ => OpenDialog(),
            Sort: 100,
            IsEnabled: () => _context?.DocumentService.ActiveDocument is not null);
    }

3. Effect logic with preview/apply/cancel

    private const string PreviewOwnerId = "example-plugin.preview";

    private void OpenDialog()
    {
        if (_context is null) return;

        var state = _context.SampleControllerBus.State;
        var start = Math.Min(state.SelectionStart, state.SelectionEnd);
        var endExclusive = Math.Max(state.SelectionStart, state.SelectionEnd);
        if (start == endExclusive)
        {
            start = state.ViewStart;
            endExclusive = state.ViewStart + state.ViewLength;
        }

        var source = _context.DocumentService.CopyEffectiveRangeToBuffer(start, endExclusive);
        using var preview = _context.DocumentService.BeginPreview(start, endExclusive);
        using var dlg = new ExampleEffectDialog();

        void RefreshPreview()
        {
            var processed = ProcessGain(source, dlg.GainDb);
            preview.ApplyPreview(processed);

            if (dlg.PreviewEnabled)
            {
                _context.AudioSystemBus.RequestPlayback(new PlaybackCommand(
                    PlaybackCommandType.PlayBuffer,
                    Buffer: processed,
                    StartFrame: 0,
                    EndFrameExclusive: processed.InterleavedSamples.Length / processed.Channels,
                    Loop: true,
                    PreviewOwnerId: PreviewOwnerId));
            }
            else
            {
                StopPreview();
            }
        }

        dlg.SettingsChanged += (_, _) => RefreshPreview();
        RefreshPreview();

        var result = dlg.ShowDialog();
        StopPreview();

        if (result == DialogResult.OK)
        {
            preview.Commit();
        }
        else
        {
            preview.Cancel();
        }
    }

    private void StopPreview()
    {
        _context?.AudioSystemBus.RequestPlayback(new PlaybackCommand(
            PlaybackCommandType.Stop,
            PreviewOwnerId: PreviewOwnerId));
    }

4. DSP function

    private static SampleBuffer ProcessGain(SampleBuffer source, float gainDb)
    {
        var gain = (float)Math.Pow(10.0, gainDb / 20.0);
        var data = (float[])source.InterleavedSamples.Clone();

        for (var i = 0; i < data.Length; i++)
        {
            data[i] = Math.Clamp(data[i] * gain, -1f, 1f);
        }

        return new SampleBuffer(data, source.Channels, source.SampleRate, source.BitDepth);
    }
}

5. Dialogue skeleton

public partial class ExampleEffectDialog : Form
{
    public event EventHandler? SettingsChanged;

    public bool PreviewEnabled => _previewCheck.Checked;
    public float GainDb => (float)_gainValue.Value;

    public ExampleEffectDialog()
    {
        InitializeComponent();
        _previewCheck.CheckedChanged += (_, _) => SettingsChanged?.Invoke(this, EventArgs.Empty);
        _gainValue.ValueChanged += (_, _) => SettingsChanged?.Invoke(this, EventArgs.Empty);
    }
}

6. Plugin manifest

{
  "id": "example-plugin",
  "displayName": "Example Plugin",
  "version": "1.0.0",
  "assembly": "RetroWaveLab.Plugins.Example.dll",
  "entryType": "RetroWaveLab.Plugins.Example.ExamplePlugin"
}

7. Deployment layout

Plugins/Example/
  example.plugin.json
  RetroWaveLab.Plugins.Example.dll
  RetroWaveLab.Plugins.Example.deps.json (if produced)
  any plugin-local dependencies

Back to top