using Jacobi.Vst.Core; using QuikDawEditor.VST; using System.Text.Json.Serialization; using System.Windows.Media; using System.Windows.Media.Imaging; using static QuikDawEditor.EDITING.MidiMethods; namespace QuikDawEditor.EditingClasses; public partial class Clip { internal void UpdateMidiImageSource() { double drawWidth = MsecToPixelsZoomed(ClipSourceLenMilliseconds); GeometryCollection gcoll = new GeometryCollection(); double noteHeight = 5; double xoffset = 0; //fine horizontal adjustment of note placement if needed //Use scale transform on geometry??? foreach (QDMidiNote mnote in this.ClipMidiNotes) gcoll.Add(new RectangleGeometry(new Rect(MsecToPixelsZoomed(mnote.ClipRelNoteOnTimeMs) + xoffset, (mnote.Note * noteHeight) % UnitHeight, MsecToPixelsZoomed(mnote.NoteLengthMs), noteHeight))); BitmapSource midiBMS = DrawMidiClipGeometry(new Rect(0, 0, drawWidth, UnitHeight + 6), midiNoteBorderPen, MidiNotePaintBrush, new GeometryGroup() { Children = gcoll }); midiBMS.Freeze(); ClipBackgroundSources[0].bSource = midiBMS; RecreateClipUnitBackgrounds(); } //to be used to bind to pianorollzoom for each clip public double pianoRollZoomFacX = 1; internal List playingMidiNotes = new List(); List stopMidiNotes = new List(); internal void StopAllPlayingMidiNotes() { if (this.clipType == ClipType.Midi) foreach (ActiveVstPlugin avp in myTrack.TrackInstrumentVsts.Where(tiv => tiv.IsActive)) avp.SendStopNotes(this.ClipMidiNotes.ToList().ConvertAll(cpmn => cpmn.Note), avp.Channel); } internal void SendClipMidiData(double MasterTimeMs) { double clipRelativeMasterMs = (MasterTimeMs - ClipVirtualStartMs) % ClipSourceLenMilliseconds; List currEventsON = ClipMidiEvents.Where(mn => (mn.ClipRelTimeMs >= clipRelativeMasterMs - projPlayer.ProcessingRateMs) && (mn.ClipRelTimeMs < clipRelativeMasterMs) ).ToList(); if (clipRelativeMasterMs < projPlayer.ProcessingRateMs) //to reset notes/events for looped-extending clips { //Debug.WriteLine("Reached beginning (or new loop start)"); this.StopAllPlayingMidiNotes(); } playingMidiNotes = currEventsON.Where(cmno => cmno is QDMidiNote).ToList().ConvertAll(cmn => ((QDMidiNote)cmn).Note); List currMNsOFF = ClipMidiNotes.Where(mn => (mn.ClipRelNoteOffTimeMs >= clipRelativeMasterMs - projPlayer.ProcessingRateMs) && (mn.ClipRelNoteOffTimeMs <= clipRelativeMasterMs) ).ToList(); //To see if any note messages are doubled (none are) //Debug.WriteLine("\n=====================SEND CLIP MIDI DATA==================\nStopnotes=" + String.Join(":::", stopMidiNotes.ToArray()) + " | playnotes=" + String.Join(":::", playingMidiNotes.ToArray()) + "\n ============================================================"); stopMidiNotes = currMNsOFF.ConvertAll(smn => smn.Note); playingMidiNotes.RemoveAll(mn => stopMidiNotes.Contains(mn)); //Send Midi Events for (int vstno = 0; vstno < myTrack.TrackInstrumentVsts.Count; vstno++) { List vmEvents = ConvertMidiEventsToMidiVstEvents(currEventsON, currMNsOFF, myTrack.TrackInstrumentVsts[vstno].Channel); if (myTrack.TrackInstrumentVsts[vstno].IsActive) if (vmEvents.Count > 0) myTrack.TrackInstrumentVsts[vstno].myContext.PluginCommandStub.Commands.ProcessEvents(vmEvents.ToArray()); } } private void ReverseMidiNotes() { foreach (QDMidiNote mnote in ClipMidiNotes) { double thisnotewidthms = mnote.ClipRelNoteOffTimeMs - mnote.ClipRelNoteOnTimeMs; mnote.ClipRelNoteOnTimeMs = ClipSourceLenMilliseconds - mnote.ClipRelNoteOffTimeMs; mnote.ClipRelNoteOffTimeMs = Math.Min(ClipSourceLenMilliseconds, mnote.ClipRelNoteOnTimeMs + thisnotewidthms); } UpdateMidiImageSource(); } [JsonIgnore(Condition = JsonIgnoreCondition.Always)] public List ClipMidiEvents { get; set; } = new List(); private ObservableCollection _ClipMidiNotes = new ObservableCollection(); public ObservableCollection ClipMidiNotes { get { return _ClipMidiNotes; } set { _ClipMidiNotes = value; _ClipMidiNotes.CollectionChanged += ClipMidiEventsSubCollection_CollectionChanged; foreach (QDMidiNote qdm in value) ClipMidiEvents.Add(qdm); SortClipMidiEvents(); } } private ObservableCollection _ClipSustainEvents = new ObservableCollection(); public ObservableCollection ClipSustainEvents { get { return _ClipSustainEvents; } set { _ClipSustainEvents = value; _ClipSustainEvents.CollectionChanged += ClipMidiEventsSubCollection_CollectionChanged; foreach (QDSustain qsus in value) ClipMidiEvents.Add(qsus); SortClipMidiEvents(); } } private ObservableCollection _ClipPitchChangeEvents = new ObservableCollection(); public ObservableCollection ClipPitchChangeEvents { get { return _ClipPitchChangeEvents; } set { _ClipPitchChangeEvents = value; _ClipPitchChangeEvents.CollectionChanged += ClipMidiEventsSubCollection_CollectionChanged; foreach (QDPitchChange qpc in value) ClipMidiEvents.Add(qpc); SortClipMidiEvents(); } } private ObservableCollection _ClipModulationEvents = new ObservableCollection(); public ObservableCollection ClipModulationEvents { get { return _ClipModulationEvents; } set { _ClipModulationEvents = value; _ClipModulationEvents.CollectionChanged += ClipMidiEventsSubCollection_CollectionChanged; foreach (QDModulation qdm in value) ClipMidiEvents.Add(qdm); SortClipMidiEvents(); } } private void ClipMidiEventsSubCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { foreach (object obj in e.NewItems) ClipMidiEvents.Add((QDMidiEvent)obj); } if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) foreach (object obj in e.OldItems) ClipMidiEvents.Remove((QDMidiEvent)obj); SortClipMidiEvents(); } internal void SortClipMidiEvents() { //this.ClipMidiEvents.RemoveAll(cme => cme == null); this.ClipMidiEvents.Sort((sn1, sn2) => { int timecomp = 0; if (sn1 != null && sn2 != null) { try { timecomp = sn1.ClipRelTimeMs.CompareTo(sn2.ClipRelTimeMs); } catch { } } return timecomp; }); } internal void SortMidiEventSubGroup(Type midiEventType) { switch (midiEventType) { case var type when type == typeof(QDMidiNote): List sortedMNs = new List(this.ClipMidiNotes.OrderBy(mn => mn.ClipRelNoteOnTimeMs)); this.ClipMidiNotes.Clear(); this.ClipMidiEvents.RemoveAll(cme => cme is QDMidiNote); foreach (QDMidiNote mn in sortedMNs) this.ClipMidiNotes.Add(mn); break; case var type when type == typeof(QDSustain): List sortedSSs = new List(this.ClipSustainEvents.OrderBy(ss => ss.ClipRelTimeMs)); this.ClipSustainEvents.Clear(); this.ClipMidiEvents.RemoveAll(cme => cme is QDSustain); foreach (QDSustain ss in sortedSSs) this.ClipSustainEvents.Add(ss); break; case var type when type == typeof(QDPitchChange): List sortedPCs = new List(this.ClipPitchChangeEvents.OrderBy(pch => pch.ClipRelTimeMs)); this.ClipPitchChangeEvents.Clear(); this.ClipMidiEvents.RemoveAll(cme => cme is QDPitchChange); foreach (QDPitchChange pch in sortedPCs) this.ClipPitchChangeEvents.Add(pch); break; case var type when type == typeof(QDModulation): List sortedMDs = new List(this.ClipModulationEvents.OrderBy(md => md.ClipRelTimeMs)); this.ClipModulationEvents.Clear(); this.ClipMidiEvents.RemoveAll(cme => cme is QDModulation); foreach (QDModulation md in sortedMDs) this.ClipModulationEvents.Add(md); break; } } }