using NAudio.Midi; using QuikDawEditor.EditingClasses; using QuikDawEditor.MiscClasses; using QuikDawEditor.VST; using System; using System.Linq; using System.Windows; using System.Windows.Threading; using static QuikDawEditor.EDITING.MidiMethods; using static QuikDawEditor.EDITING.MiscMethods; using static QuikDawEditor.EDITING.StaticProperties; namespace QuikDawEditor; public partial class ProjectPlayer { bool IsMidiArmed = false; internal void ArmForMidiRecording(int RecInputNo) { //Debug.WriteLine("midi devices: " + MidiIn.NumberOfDevices.ToString() + "\n" + MidiIn.DeviceInfo(0).ProductName); editingProject.GetRecordingTrack.midiRecordingClip.recordingMidiEvents.Clear(); midiIn = new MidiIn(RecInputNo); midiIn.MessageReceived += MidiIn_MessageReceived; midiIn.Start(); IsMidiArmed = true; } private void MidiIn_MessageReceived(object sender, MidiInMessageEventArgs e) { Track recTrack = editingProject.GetRecordingTrack; double relPlayPosMs = CurrentPlayingPosMS - recTrack.midiRecordingClip.ClipVirtualStartMs; if (e.MidiEvent.GetType() == typeof(NoteOnEvent)) { NoteOnEvent nov = (NoteOnEvent)e.MidiEvent; foreach (ActiveVstPlugin avp in recTrack.TrackInstrumentVsts) avp.midEventsToSend.Add(CreateNoteOnMidiEvent(nov.NoteNumber, nov.Velocity, avp.Channel)); //AddPlayNoteMessage(nov.NoteNumber, nov.Velocity); if (IsProjectRecording && CurrentPlayingPosMS >= RecordStartPosMS) { Application.Current.Dispatcher.Invoke(() => { QDMidiNote playedNote = new QDMidiNote() { Note = nov.NoteNumber, ClipRelNoteOnTimeMs = relPlayPosMs, revZoomX = 1 / editingProject.ViewXZoomFac, Velocity = nov.Velocity, ClipRelNoteOffTimeMs = 0 }; recTrack.midiRecordingClip.recordingMidiEvents.Add(playedNote); }, DispatcherPriority.Render); } } else { // midi note off event or controller if (e.MidiEvent.GetType() == typeof(NoteEvent)) { NoteEvent nevent = (NoteEvent)e.MidiEvent; foreach (ActiveVstPlugin avp in recTrack.TrackInstrumentVsts) avp.midEventsToSend.Add(CreateNoteOffMidiEvent(nevent.NoteNumber, avp.Channel)); if (IsProjectRecording) { QDMidiNote lastPlayedMidiNote = (QDMidiNote)recTrack.midiRecordingClip.recordingMidiEvents.Where(cmn => cmn is QDMidiNote && ((QDMidiNote)cmn).Note == nevent.NoteNumber).LastOrDefault(); if (lastPlayedMidiNote != null) { lastPlayedMidiNote.Released = true; if (relPlayPosMs > lastPlayedMidiNote.ClipRelNoteOnTimeMs) Application.Current.Dispatcher.Invoke(() => { lastPlayedMidiNote.ClipRelNoteOffTimeMs = relPlayPosMs; }, DispatcherPriority.Render); } } return; } if (e.MidiEvent.GetType() == typeof(ControlChangeEvent)) { // sustain pedal & modulation ControlChangeEvent cce = (ControlChangeEvent)e.MidiEvent; //Debug.WriteLine("Val=" + cce.ControllerValue.ToString() + ":::Controller=" + cce.Controller.ToString() + ":::" + ((ushort)cce.Controller).ToString()); switch (cce.Controller) { case MidiController.Sustain: QDSustain newSustain = new QDSustain() { ClipRelTimeMs = relPlayPosMs, SustainValue = cce.ControllerValue }; recTrack.midiRecordingClip.recordingMidiEvents.Add(newSustain); break; case MidiController.Modulation: QDModulation newModulation = new QDModulation() { ClipRelTimeMs = relPlayPosMs, ModValue = cce.ControllerValue }; recTrack.midiRecordingClip.recordingMidiEvents.Add(newModulation); break; } foreach (ActiveVstPlugin avp in recTrack.TrackInstrumentVsts) avp.midEventsToSend.Add(CreateControllerMidiEvent((int)cce.Controller, cce.ControllerValue, avp.Channel)); //AddControllerMessage((int)cce.Controller, cce.ControllerValue); return; } if(e.MidiEvent.GetType() == typeof(PitchWheelChangeEvent)) { //pitch wheel PitchWheelChangeEvent pwce = (PitchWheelChangeEvent)e.MidiEvent; QDPitchChange newPitchChange = new QDPitchChange() { ClipRelTimeMs = relPlayPosMs, PitchValue = pwce.Pitch }; recTrack.midiRecordingClip.recordingMidiEvents.Add(newPitchChange); foreach (ActiveVstPlugin avp in recTrack.TrackInstrumentVsts) avp.midEventsToSend.Add(CreatePitchWheelChangeMidiEvent(pwce.Pitch, avp.Channel)); return; } } } internal void DisarmForMidiRecording() { IsMidiArmed = false; midiIn.MessageReceived -= MidiIn_MessageReceived; midiIn.Stop(); midiIn.Dispose(); } private void InitializeMidiRecordingClip() { RecordStartPosMS = CurrentPlayingPosMS; CurrentPlayingPosMS -= Properties.Settings.Default.LeadInMeasures * editingProject.BeatsPerMeasure * MillisecondsPerBeat; Track recTrack = editingProject.GetRecordingTrack; recTrack.midiRecordingClip.recordingMidiEvents.Clear(); recTrack.midiRecordingClip.ClipWidthMs = 1 * editingProject.ViewXZoomFac; recTrack.midiRecordingClip.ClipVirtualStartMs = RecordStartPosMS; recTrack.midiRecordingClip.IsVisible = true; recTrack.midiRecordingClip.UpdateRecordingClipTransform(); } private void StopRecordingMidiAndMakeClip() { Track recTrack = editingProject.GetRecordingTrack; //Stop recording and convert recorded midi clip to actual midi clip, add to track recTrack.midiRecordingClip.IsVisible = false; double newClipWidthMs = GetNearestSnapMs(recTrack.midiRecordingClip.ClipWidthMs, editingProject.CurrentSnapTo); Clip newMidiClip = new Clip("New recording", ClipType.Midi) { ClipWidthMs = newClipWidthMs, ClipSourceLenMilliseconds = newClipWidthMs, ClipVirtualStartMs = recTrack.midiRecordingClip.ClipVirtualStartMs, }; foreach (QDMidiEvent rmevent in recTrack.midiRecordingClip.recordingMidiEvents) { if (rmevent is QDMidiNote) { QDMidiNote newMidiNote = new QDMidiNote() { ClipRelTimeMs = rmevent.ClipRelTimeMs }; QDMidiNote rmnote = (QDMidiNote)rmevent; newMidiNote.Note = rmnote.Note; newMidiNote.ClipRelNoteOffTimeMs = rmnote.ClipRelNoteOffTimeMs; newMidiNote.Velocity = rmnote.Velocity; newMidiClip.ClipMidiNotes.Add(newMidiNote); } if (rmevent is QDSustain) { QDSustain newSustain = new QDSustain() { ClipRelTimeMs = rmevent.ClipRelTimeMs, SustainValue = (int)rmevent.MainValue }; newMidiClip.ClipSustainEvents.Add(newSustain); } if (rmevent is QDPitchChange) { QDPitchChange newPitchChange = new QDPitchChange() { ClipRelTimeMs = rmevent.ClipRelTimeMs, PitchValue = (int)rmevent.MainValue}; newMidiClip.ClipPitchChangeEvents.Add(newPitchChange); } if (rmevent is QDModulation) { QDModulation newModulation = new QDModulation() { ClipRelTimeMs = rmevent.ClipRelTimeMs, ModValue = (int)rmevent.MainValue }; newMidiClip.ClipModulationEvents.Add(newModulation); } } //Ensure note lengths are not negative foreach (QDMidiNote mnote in newMidiClip.ClipMidiEvents.Where(cme=>cme is QDMidiNote)) mnote.ClipRelNoteOffTimeMs = (mnote.ClipRelNoteOffTimeMs <= mnote.ClipRelNoteOnTimeMs) ? mnote.ClipRelNoteOnTimeMs + 500 : Math.Min(mnote.ClipRelNoteOffTimeMs, newClipWidthMs); newMidiClip.GainPoints.Add(new GainPoint() { sourcePointms = 0, GainValue = 1, IsLeftEdgeGainPoint = true }); newMidiClip.GainPoints.Add(new GainPoint() { sourcePointms = newClipWidthMs, GainValue = 1, IsRightEdgeGainPoint = true }); Clip rightClip = recTrack.Clips.FirstOrDefault(c => c.ClipLeftMs > newMidiClip.ClipVirtualStartMs); int insertIdx = rightClip == null ? recTrack.Clips.Count : recTrack.Clips.IndexOf(rightClip); recTrack.Clips.Insert(insertIdx, newMidiClip); recTrack.SortAndUpdateClipIndexes(); recTrack.midiRecordingClip.recordingMidiEvents.Clear(); } }