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);
}
}