using Jacobi.Vst.Core; using NAudio.Midi; using System.Reflection; using System.Windows.Media.Imaging; using static QuikDawEditor.EDITING.VstMethods; namespace QuikDawEditor.EDITING; public partial class MidiMethods { public static VstMidiEvent GetMidiEvent(int mnote, int velocity, int channel) { MemoryStream noteOnStream = new MemoryStream(); BinaryWriter noteOnDatax = new BinaryWriter(noteOnStream); noteOnDatax.Write(MidiMessage.StartNote(mnote, velocity, channel).RawData); return new VstMidiEvent(0, 0, 0, noteOnStream.ToArray(), 0, 0, true); } public static List ConvertMidiEventsToMidiVstEvents(List currmideventsOn, List currmidnotesOff, int channel) { //http://computermusicresource.com/MIDI.Commands.html List currentVstEvents = new List(); foreach (QDMidiEvent mevent in currmideventsOn) { if (mevent is QDMidiNote) { QDMidiNote mnote = (QDMidiNote)mevent; currentVstEvents.Add(CreateNoteOnMidiEvent(mnote.Note, mnote.Velocity, channel)); } if (mevent is QDSustain | mevent is QDModulation) currentVstEvents.Add(CreateControllerMidiEvent(mevent.ControllerCode, (int)mevent.MainValue, channel)); if (mevent is QDPitchChange) currentVstEvents.Add(CreatePitchWheelChangeMidiEvent((int)mevent.MainValue, channel)); } foreach (QDMidiNote mnote in currmidnotesOff) { MemoryStream noteOffStream = new MemoryStream(); noteOffData = new BinaryWriter(noteOffStream); noteOffData.Write(MidiMessage.StopNote(mnote.Note, mnote.Velocity, 1).RawData); VstMidiEvent newVME = new VstMidiEvent(sampleDeltaAt44100, 0, 0, noteOffStream.ToArray(), 0, 0, true); //VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, noteOffStream.ToArray(), 0, 0, true); currentVstEvents.Add(newVME); noteOffData.Close(); noteOffStream.Close(); } return currentVstEvents; } internal static int sampleDeltaAt44100 = (int)(10M * 44100 / 1000); //internal static MemoryStream noteOnStream; //internal static MemoryStream noteOffStream; internal static BinaryWriter noteOffData; internal static BinaryWriter noteOnData; //internal static VstMidiEvent CreateNoteOnMidiEvent(int noteno, int velocity, int channel) //{ // using (MemoryStream noteOnStream = new MemoryStream()) // { // noteOnData = new BinaryWriter(noteOnStream); // noteOnData.Write(MidiMessage.StartNote(noteno, velocity, channel).RawData); // VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, noteOnStream.ToArray(), 0, 0, true); // noteOnData.Close(); // return newVME; // } //} internal static VstMidiEvent CreateNoteOnMidiEvent(int noteno, int velocity, int channel) { MemoryStream noteOnStream = new MemoryStream(); BinaryWriter noteOnDatax = new BinaryWriter(noteOnStream); noteOnDatax.Write(MidiMessage.StartNote(noteno, velocity, channel).RawData); VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, noteOnStream.ToArray(), 0, 0, true); //VstMidiEvent newVME = new VstMidiEvent(sampleDeltaAt44100, 0, 0, noteOnStream.ToArray(), 0, 0, true); noteOnDatax.Close(); noteOnStream.Close(); return newVME; } internal static VstMidiEvent CreateNoteOffMidiEvent(int noteno, int channel) { MemoryStream noteOffStream = new MemoryStream(); noteOffData = new BinaryWriter(noteOffStream); noteOffData.Write(MidiMessage.StopNote(noteno, 0, channel).RawData); //VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, noteOffStream.ToArray(), 0, 0, true); VstMidiEvent newVME = new VstMidiEvent(sampleDeltaAt44100, 0, 0, noteOffStream.ToArray(), 0, 0, true); //VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, noteOffStream.ToArray(), 0, 0, true); //Debug.WriteLine("midinoteOFFcommand= " + newVME.Data[0].ToString() + ":::" + (newVME.Data[0] & 0xF0).ToString()); noteOffStream.Close(); noteOffData.Close(); return newVME; } internal static VstMidiEvent CreateControllerMidiEvent(int controllerCode, int controlValue, int channel) { MemoryStream controllerStream = new MemoryStream(); BinaryWriter controllerData = new BinaryWriter(controllerStream); //Debug.WriteLine("controllerCode=" + controllerCode.ToString() + " ::: val=" + controlValue.ToString()); controllerData.Write(MidiMessage.ChangeControl(controllerCode, controlValue, channel).RawData); VstMidiEvent newVME = new VstMidiEvent(0, 0, 0, controllerStream.ToArray(), 0, 0, true); controllerStream.Close(); controllerData.Close(); return newVME; } internal static VstMidiEvent CreatePitchWheelChangeMidiEvent(int pitchVal, int channel) { byte[] midiData = new byte[] { 0xE0, // Cmd (byte)(pitchVal & 0x7f), // Val 1 (byte)(pitchVal >> 7 & 0x7f), // Val 2 }; MemoryStream controllerStream = new MemoryStream(); BinaryWriter controllerData = new BinaryWriter(controllerStream); VstMidiEvent pitchVME = new VstMidiEvent(0, 0, 0, midiData, 0, 0); controllerStream.Close(); controllerData.Close(); return pitchVME; } internal static void StopAllNotes() { foreach (Track t in editingProject.GetAllTracksInProject.Where(tt => tt.IsMidiTrack)) t.StopMidiNotes(); } public static void AddNewMidiTrackToProject(bool fromFile = false) { Track newTrack = new Track(TrackType.Midi) { TrackName = "New midi track" }; editingProject.Tracks.Add(newTrack); if (!fromFile) undoActions.Add(new TrackAddUndo(newTrack.UndoTrackID)); //Auto add GM instrument vsti as default AddVSTInstrument(newTrack, ScannedVstInstrumentReferences[0]); newTrack.TrackInstrumentVsts[0].IsActive = true; editingProject.NeedsSaving = true; } public static void AddMidiTracks(MidiEventCollection midiEvents, int addToTrackIndex, int insertingClipIdx, double addPositionMs, double maxClipWidth) { double fileBeatsPerMinute = 60; double MicrosecondsPerBeat = 0; double DeltaTicksPerQuarterNote = midiEvents.DeltaTicksPerQuarterNote; if (midiEvents != null && midiEvents.Count() > 0) { TimeSignatureEvent timeSignature = midiEvents[0].OfType().FirstOrDefault(); TempoEvent tev = midiEvents[0].OfType().FirstOrDefault(); //BeatsPerMinute = tev.Tempo; MicrosecondsPerBeat = tev.MicrosecondsPerQuarterNote; Debug.WriteLine(timeSignature.Numerator.ToString() + ":::" + timeSignature.Denominator.ToString() + ":::" + timeSignature.TimeSignature); //Debug.WriteLine("BPM=" + BeatsPerMinute.ToString() + ":::MICROSECPERBEAT=" + MicrosecondsPerBeat.ToString()); //Debug.WriteLine(midiEvents.DeltaTicksPerQuarterNote.ToString()); int addedMidiTracksCount = 0; for (int tno = 0; tno < midiEvents.Tracks; tno++) { if (tno == 0) { //First track is usually null?? //Debug.WriteLine("countmidid[0]=" + midiEvents[0].Count.ToString()); //foreach (MidiEvent midiEvent in midiEvents[tno]) //{ // if (midiEvent.ToString() == "SetTempo") // { // TempoEvent tev = (TempoEvent)midiEvent; fileBeatsPerMinute = tev.Tempo; // MicrosecondsPerBeat = tev.MicrosecondsPerQuarterNote; // } //} } else { AddNewMidiTrackToProject(true); Track newTrack = editingProject.Tracks[editingProject.Tracks.Count - 1]; Clip newMidiClip = new Clip("", ClipType.Midi) { ClipVirtualStartMs = addPositionMs, myTrack = newTrack }; newTrack.Clips.Add(newMidiClip); newTrack.SortAndUpdateClipIndexes(); double currTotalLengthMs = 1000; //default minimum foreach (MidiEvent midiEvent in midiEvents[tno]) { //Debug.WriteLine(midiEvent.CommandCode.ToString()); if (MidiEvent.IsNoteOn(midiEvent) && !MidiEvent.IsNoteOff(midiEvent)) { NoteOnEvent nov = (NoteOnEvent)midiEvent; //*** must also account for timesignature to define measure double noteStartTimeMs = nov.AbsoluteTime / DeltaTicksPerQuarterNote * MicrosecondsPerBeat / 1000; double noteEndTimeMs = nov.OffEvent.AbsoluteTime / DeltaTicksPerQuarterNote * MicrosecondsPerBeat / 1000; double convertFac = fileBeatsPerMinute / BeatsPerMinute; noteStartTimeMs *= convertFac; noteEndTimeMs *= convertFac; newMidiClip.ClipMidiNotes.Add(new QDMidiNote() { Note = nov.NoteNumber, ClipRelNoteOnTimeMs = noteStartTimeMs, ClipRelNoteOffTimeMs = noteEndTimeMs, Velocity = nov.Velocity }); //ADD OTHER MIDI EVENTS HERE TOO... (pitch, sustain, etc.) currTotalLengthMs = noteEndTimeMs; } } double addingClipWidthMs = Math.Min(maxClipWidth, currTotalLengthMs); newMidiClip.ClipWidthMs = addingClipWidthMs; newMidiClip.ClipSourceLenMilliseconds = addingClipWidthMs; newMidiClip.ClipName = "New midi clip"; newMidiClip.GainPoints = new ObservableCollection() { new GainPoint() { sourcePointms = 0, GainValue = 1, IsLeftEdgeGainPoint = true }, new GainPoint() { sourcePointms = newMidiClip.ClipWidthMs, GainValue = 1, IsRightEdgeGainPoint = true } }; TextEvent tevent = midiEvents[tno].OfType().FirstOrDefault(); if (tevent != null) newTrack.TrackName = tevent.Text; PatchChangeEvent pevent = midiEvents[tno].OfType().FirstOrDefault(); if (pevent != null) { int patchNo = pevent.Patch; if (pevent.Channel == 10) patchNo = 253; newTrack.TrackInstrumentVsts[0].SelectedVstProgramIndex = patchNo; newTrack.TrackInstrumentVsts[0].myContext.PluginCommandStub.Commands.SetProgram(patchNo); } addedMidiTracksCount += 1; } } undoActions.Add(new AddMidiFileTracksUndo(editingProject.Tracks.Count - addedMidiTracksCount, addedMidiTracksCount)); } projPlayer.UpdateEndPoint(); editingProject.NeedsSaving = true; } public static Clip AddNewMidiClipToTrack(Clip midiClip, Track addToTrack, int insertingClipIdx, double addPositionMs, double maxClipWidth) { Clip newMidiClip = new Clip(midiClip.ClipName + "(2)", ClipType.Midi); newMidiClip.ClipRelativeLoopStartMs = 0; double addingClipWidthMs = Math.Min(maxClipWidth, midiClip.ClipSourceLenMilliseconds); //copy over all midi events by type (cannot just set new collections because this is after constructor and breaks binding) foreach (QDMidiNote qmn in midiClip.ClipMidiNotes) newMidiClip.ClipMidiNotes.Add(new QDMidiNote(qmn)); foreach (QDSustain qsus in midiClip.ClipSustainEvents) newMidiClip.ClipSustainEvents.Add(new QDSustain(qsus)); foreach (QDPitchChange qpch in midiClip.ClipPitchChangeEvents) newMidiClip.ClipPitchChangeEvents.Add(new QDPitchChange(qpch)); foreach (QDModulation qmod in midiClip.ClipModulationEvents) newMidiClip.ClipModulationEvents.Add(new QDModulation(qmod)); newMidiClip.ClipVirtualStartMs = addPositionMs - midiClip.ClipRelativeLoopStartMs; newMidiClip.ClipRelativeLoopStartMs = midiClip.ClipRelativeLoopStartMs; newMidiClip.ClipSourceLenMilliseconds = addingClipWidthMs; newMidiClip.ClipWidthMs = midiClip.ClipWidthMs; if (midiClip.GainPoints.Count == 0) { newMidiClip.GainPoints = new ObservableCollection(midiClip.GainPoints); newMidiClip.GainPoints.Add(new GainPoint() { sourcePointms = 0, GainValue = 1, IsLeftEdgeGainPoint = true }); newMidiClip.GainPoints.Add(new GainPoint() { sourcePointms = midiClip.ClipSourceLenMilliseconds, GainValue = 1, IsRightEdgeGainPoint = true }); } else { newMidiClip.GainPoints.Clear(); foreach (GainPoint gp in midiClip.GainPoints) { newMidiClip.GainPoints.Add(new GainPoint() { sourcePointms = gp.sourcePointms, IsLeftEdgeGainPoint = gp.IsLeftEdgeGainPoint, IsRightEdgeGainPoint = gp.IsRightEdgeGainPoint, GainValue = gp.GainValue, }); } } if (addToTrack == null) { addToTrack = new Track(TrackType.Midi); editingProject.Tracks.Add(addToTrack); addToTrack.Clips.Add(newMidiClip); addToTrack.SortAndUpdateClipIndexes(); undoActions.Add(new TrackAddUndo(addToTrack.UndoTrackID)); } else { addToTrack.Clips.Insert(insertingClipIdx, newMidiClip); addToTrack.SortAndUpdateClipIndexes(); } editingProject.NeedsSaving = true; return newMidiClip; } public static Pen gridLineDashPen = new Pen(Brushes.White, 0.8) { DashStyle = new DashStyle(new DoubleCollection() { 6, 8 }, 0) }; public static Pen gridLineDashPen2 = new Pen(Brushes.White, 0.6) { DashStyle = new DashStyle(new DoubleCollection() { 4, 6 }, 0) }; public static Pen gridLineSolidPen = new Pen(Brushes.White, 1); public static BitmapSource DrawGridLineGeometry(Rect rect, Geometry geometry) { var visual = new DrawingVisual(); using (var dc = visual.RenderOpen()) { GeometryGroup ggroup = (GeometryGroup)geometry; for (int geomno = 0; geomno < ggroup.Children.Count; geomno++) { LineGeometry lgeom = (LineGeometry)ggroup.Children[geomno]; switch (editingProject.IsTimePlayScale) { case true: if (geomno % 10 == 9) dc.DrawGeometry(null, gridLineSolidPen, lgeom); //dc.DrawGeometry(null, new Pen(Brushes.Red, 3), lgeom); else { if (geomno % 5 == 4) { if (editingProject.ViewXZoomFac > 0.2) dc.DrawGeometry(null, gridLineDashPen, lgeom); } else if (editingProject.ViewXZoomFac > 0.6) dc.DrawGeometry(null, gridLineDashPen2, lgeom); } break; case false: if (geomno % editingProject.BeatsPerMeasure == 0) dc.DrawGeometry(null, gridLineSolidPen, lgeom); else { if (editingProject.ViewXZoomFac > 1) dc.DrawGeometry(null, gridLineDashPen, lgeom); } break; } } } var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static); var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static); var dpiX = (int)dpiXProperty.GetValue(null, null); var dpiY = (int)dpiYProperty.GetValue(null, null); RenderOptions.SetBitmapScalingMode(visual, BitmapScalingMode.HighQuality); RenderTargetBitmap bmp = null; try { bmp = new RenderTargetBitmap((int)(rect.Width * editingProject.ViewXZoomFac * dpiX / 96), (int)(rect.Height * dpiY / 96), dpiX, dpiY, PixelFormats.Pbgra32); //rectwidth + 1 because of added 0 at end bmp.Render(visual); } catch { } return bmp; } public static void QuantizeMidiNotes(Clip quantizeClip, IEnumerable qMidiNotes, decimal snapTo, bool quantizeStart = false, bool quantizeLen = false, bool applyswing = false) { List noteIndexes = new List(qMidiNotes.ToList().ConvertAll(smn => quantizeClip.ClipMidiNotes.IndexOf((QDMidiNote)smn))); List noteOnTimes = new List(qMidiNotes.ToList().ConvertAll(smn => ((QDMidiNote)smn).ClipRelNoteOnTimeMs)); List noteOffTimes = new List(qMidiNotes.ToList().ConvertAll(smn => ((QDMidiNote)smn).ClipRelNoteOffTimeMs)); undoActions.Add(new MidiNotesQuantizeUndo(quantizeClip.UndoClipID, noteIndexes, noteOnTimes, noteOffTimes)); foreach (QDMidiNote mnote in qMidiNotes) { if (quantizeStart) { double oldOnTime = mnote.ClipRelNoteOnTimeMs; mnote.ClipRelNoteOnTimeMs = GetNearestSnapMs(mnote.ClipRelNoteOnTimeMs, snapTo); mnote.ClipRelNoteOffTimeMs += mnote.ClipRelNoteOnTimeMs - oldOnTime; } if (quantizeLen) { mnote.ClipRelNoteOffTimeMs = GetNearestSnapMs(mnote.ClipRelNoteOffTimeMs, snapTo); if (mnote.ClipRelNoteOffTimeMs == mnote.ClipRelNoteOnTimeMs) mnote.ClipRelNoteOffTimeMs = mnote.ClipRelNoteOnTimeMs + Math.Round(GetSnapMsVal(snapTo)); } } if (applyswing) { foreach (QDMidiNote mnote in qMidiNotes) { double beatNo = Math.Round(mnote.ClipRelNoteOnTimeMs % MillisecondsPerBeat / MillisecondsPerBeat, 2); //Debug.WriteLine("beatNO=" + beatNo.ToString()); if (beatNo == 0.5) mnote.ClipRelNoteOnTimeMs += MillisecondsPerBeat / 4 * 0.5; } } editingProject.NeedsSaving = true; } internal static string GetNoteName(int noteNum) { int octv = (int)Math.Truncate(noteNum / 12D); double relnum = noteNum % 12; if (relnum < 5) relnum = relnum / 2; if (relnum >= 5) relnum = (relnum + 1) / 2; string noteString = ((char)(65 + (int)(Math.Truncate(relnum + 2) % 7))).ToString(); if (new List(new double[] { 0.5, 1.5, 3.5, 4.5, 5.5 }).Contains(relnum)) noteString += "#"; return noteString + octv.ToString(); } }