Closed akira0245 closed 3 years ago
Hi,
Hmm, it's not possible right now. I'll think how to provide such info during playback and come back to you.
Thanks!
@akira0245 Can you please say what kind of logic do you want to implement? Something like "if the number of track chunk is N, skip an event"?
@akira0245 Can you please say what kind of logic do you want to implement? Something like "if the number of track chunk is N, skip an event"?
If a NoteEvent has a Track property, my Device should look like this. This Device is very basic and now only handles NoteOn and NoteOff. I just need to do an action before a NoteOnEvent is played for each different track. Thank you!
class BardPlayDevice : IOutputDevice
{
public void PrepareForEventsSending()
{
}
public void SendEvent(MidiEvent midiEvent)
{
var keyboard = Plugin.pluginInterface.Framework.Gui.GetAddonByName("PerformanceModeWide", 1);
if (keyboard == null) return;
if (midiEvent is NoteOnEvent noteOnEvent)
{
if (Plugin.config.OverrideGuitarTones)
{
var tone = Plugin.config.TracksTone[midiEvent.Track];
playlib.SwitchTone(PerformanceToneChange.Address, tone);
}
var noteNum = noteOnEvent.NoteNumber;
playlib.PressKey(keyboard.Address, noteNum);
}
else if (midiEvent is NoteOffEvent noteOffEvent)
{
var noteNum = noteOffEvent.NoteNumber;
playlib.ReleaseKey(keyboard.Address, noteNum);
}
}
public event EventHandler<MidiEventSentEventArgs> EventSent;
}
@akira0245 Can you please say what kind of logic do you want to implement? Something like "if the number of track chunk is N, skip an event"?
Yes, it would also be handy to be able to skip events so that I don't have to regenerate a PlayBack when I enable or disable a track.
Thanks for clarification. I'll think about how to design the new API. I suppose signature of the SendEvent
will be changed to provide an event metadata.
It seems it's quite complicated. There is a big pitfall with assigning track chunk number to event sent to a device by playback. Some events can be not presented in any track chunk and can be generated by playback on the fly. Cases where this occurs:
MoveToTime
method, for example) and new time falls in middle of some note. If TrackNotes
property set to true
, new NoteOnEvent
will be generated, but it doesn't actually exist in a track chunk.TrackProgram
, TrackPitchValue
and TrackControlValue
properties.We also can't add property like TrackChunkIndex
to MidiEvent
because there are a lot of situations where this property is not relevant.
I have one idea, need to check it. But anyway your task will require some additional code from you, since we can't support track chunk index in MIDI events directly due to reasons mentioned above.
Thanks for your work! For my purposes, any extra code is fine as long as there is a way to get enough midievent metadata. I approve it's a good idea to pass these information through the SendEvent signature.
@akira0245 solution is ready, I'll provide it tomorrow.
Well, the changes I've made:
sealed
modifier from TimedEvent
, Note
and Chord
so you can subclass from them;sealed
modifier from Playback
and added TryPlayEvent
virtual method to the class which by default sends event to a device;IMetadata
interface which is respected by Playback
if implemented by input timed objects.Please install 5.2.1-prerelease7 version of the package from NuGet to get new changes available (or build the library from sources of the develop branch).
For your task you need:
create your own implementation of TimedEvent
implementing IMetadata
:
private sealed class TimedEventWithTrackChunkIndex : TimedEvent, IMetadata
{
public TimedEventWithTrackChunkIndex(MidiEvent midiEvent, long time, int trackChunkIndex)
: base(midiEvent, time)
{
Metadata = trackChunkIndex;
}
public object Metadata { get; set; }
}
so Metadata
property will hold the index of a track chunk.
create your own implementation of Playback
:
private sealed class MyPlayback : Playback
{
public MyPlayback(IEnumerable<ITimedObject> timedObjects, TempoMap tempoMap)
: base(timedObjects, tempoMap)
{
}
protected override bool TryPlayEvent(MidiEvent midiEvent, object metadata)
{
// Place your logic here
// Return true if event played (sent to plug-in); false otherwise
}
}
so in fact you even don't need a custom output device. But if you want, you can do something like that:
private sealed class MyOutputDevice : IOutputDevice
{
public event EventHandler<MidiEventSentEventArgs> EventSent;
public void PrepareForEventsSending()
{
}
public void SendEvent(MidiEvent midiEvent)
{
}
public bool SendEventWithMetadata(MidiEvent midiEvent, object metadata)
{
// Place your logic here
// Return true if event sent to plug-in; false otherwise
}
}
and in TryPlayEvent
of Playback
:
protected override bool TryPlayEvent(MidiEvent midiEvent, object metadata)
{
return ((MyOutputDevice)OutputDevice).SendEventWithMetadata(midiEvent, metadata);
}
create playback from instances of TimedEventWithTrackChunkIndex
:
var timedEvents = midiFile
.GetTrackChunks()
.SelectMany((c, i) => c.GetTimedEvents().Select(e => new TimedEventWithTrackChunkIndex(e.Event, e.Time, i)))
.OrderBy(e => e.Time);
var tempoMap = midiFile.GetTempoMap();
var playback = new MyPlayback(timedEvents, tempoMap);
Modify constructor of MyPlayback
if you want to pass output device. But as I said before, it seems you don't need custom output device.
And... that's all. No breaking changes in API and your task solved :)
@melanchall Thank you for the quick update and also for writing such detailed instructions, it solved my problem perfectly. I really appreciate DryWetMIDI and your work, thank you again!
:rocket: 5.2.1 version is released now!
Prerelease NuGet packages will be unlisted soon, so please update the package references to the new version.
Thanks for using the library!
Hi, I'm using a custom IOutputDevice to play a PlayBack, I want this device process NoteEvent differently between different Tracks. but a NoteEvent doesn't have a track property. How should I solve this? Thank you.