/* create a new quantized MIDI track */ public static QuantizedTrackRec NewQuantizedTrack() { QuantizedTrackRec Track = new QuantizedTrackRec(); Track.EventList = new List <QuantEventRec>(); return(Track); }
/* insert new event sorted into the track */ public static void QuantizedTrackInsertEventSorted( QuantizedTrackRec Track, QuantEventRec Event) { FractionRec OurEventStartTime = GetQuantizedEventTime(Event); int Scan = Track.EventList.Count - 1; while (Scan >= 0) { QuantEventRec OtherEvent = Track.EventList[Scan]; FractionRec OtherEventStartTime = GetQuantizedEventTime(OtherEvent); if (FractionRec.FracGreaterEqual(OurEventStartTime, OtherEventStartTime)) { Track.EventList.Insert(Scan + 1, Event); return; } Scan -= 1; } Track.EventList.Insert(0, Event); }
/* get an indexed event from the track */ public static QuantEventRec GetQuantizedTrackIndexedEvent( QuantizedTrackRec Track, int Index) { return(Track.EventList[Index]); }
/* get the number of events in the track */ public static int GetQuantizedTrackLength(QuantizedTrackRec Track) { return(Track.EventList.Count); }
/* convert quantized track into native track */ public static void ConvertQuantToNote( QuantizedTrackRec QuantizedTrack, TrackObjectRec NoteTrack) { FractionRec CurrentTime; int Index; int Limit; List <QuantEventRec> FrameArray; TieMappingRec TieMapping; int OpcodeDurationTableLength; OpDurRec[] OpcodeDurationTable = new OpDurRec[4 /*divisions*/ * 2 /*dot*/ * 9 /*notetypes*/]; /* initialize variables */ InitializeOpcodeDurationTable(OpcodeDurationTable, out OpcodeDurationTableLength); CurrentTime.Integer = 0; CurrentTime.Fraction = 0; CurrentTime.Denominator = 1; FrameArray = new List <QuantEventRec>(); TieMapping = NewTieMapping(); Limit = GetQuantizedTrackLength(QuantizedTrack); Index = 0; /* iterate over variables */ while (Index < Limit) { FractionRec NextTime; QuantEventRec QuantEvent; bool Continue; int InspectScan; /* reset frame array */ FrameArray.Clear(); /* get the start time of the next available event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); NextTime = GetQuantizedEventTime(QuantEvent); Debug.Assert(FractionIntMultOf64thDiv3(NextTime)); // non-64div3 start time quantization? /* sanity check */ Debug.Assert(!FractionRec.FracGreaterThan(CurrentTime, NextTime)); // next time inconsistency /* get all events starting at this time into FrameArray */ Continue = true; while (Continue && (Index < Limit)) { FractionRec EventTime; /* get the event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); EventTime = GetQuantizedEventTime(QuantEvent); Debug.Assert(FractionIntMultOf64thDiv3(EventTime)); // non-64div3 start time quantization? if (FractionRec.FractionsEqual(EventTime, NextTime)) { /* add event to the list */ FrameArray.Add(QuantEvent); /* go past this event */ Index += 1; } else { /* end hit so stop */ Continue = false; } } /* insert rests to bring current time up to next time */ InsertRests(CurrentTime, NextTime, NoteTrack, OpcodeDurationTable, OpcodeDurationTableLength); /* remove command events from list */ InspectScan = 0; while (InspectScan < FrameArray.Count) { /* get the event */ QuantEvent = FrameArray[InspectScan]; /* determine if event is a command */ if (QuantizedEventGetType(QuantEvent) == QuantEventType.eQuantizedNoteEvent) { /* note events should be skipped */ InspectScan += 1; } else { /* command events should be handled */ switch (QuantizedEventGetType(QuantEvent)) { default: Debug.Assert(false); throw new InvalidOperationException(); case QuantEventType.eQuantizedCommentEvent: { string CommentString; CommandNoteObjectRec Note; FrameObjectRec Frame; FractionRec unusedf; double unusedd; GetQuantizedCommentEventInfo(QuantEvent, out unusedf, out unusedd, out CommentString); Note = new CommandNoteObjectRec(NoteTrack); Note.PutCommandStringArg1(CommentString); Note.PutCommandOpcode(NoteCommands.eCmdMarker); Frame = new FrameObjectRec(); Frame.Add(Note); NoteTrack.FrameArray.Add(Frame); } break; } /* delete event from array */ FrameArray.RemoveAt(InspectScan); /* don't increment InspectScan */ } } /* process remaining notes in FrameArray, computing minimum duration */ /* and updating CurrentTime with minimum duration. */ if (FrameArray.Count != 0) { NoteFlags DurationOpcode; FrameObjectRec Frame; int Scan; int FrameLimit; FractionRec MinimumDuration; FractionRec unusedf; double unusedd; short unuseds; /* initialize minimum duration */ QuantEvent = FrameArray[0]; GetQuantizedNoteEventInfo(QuantEvent, out unusedf, out unusedd, out DurationOpcode, out unusedd, out unuseds, out unuseds, out unuseds); NoteNoteObjectRec.ConvertDurationFrac(DurationOpcode, out MinimumDuration); Debug.Assert(FractionIntMultOf64thDiv3(MinimumDuration)); // non-64div3 duration quantization? /* allocate frame */ Frame = new FrameObjectRec(); /* process notes in frame */ FrameLimit = FrameArray.Count; for (Scan = 0; Scan < FrameLimit; Scan += 1) { FractionRec StartTime; double StartTimeAdjust; NoteFlags Duration; double DurationAdjust; short MIDIPitch; short MIDIAttackVelocity; short MIDIReleaseVelocity; NoteNoteObjectRec Note; FractionRec FracDuration; /* get the note */ QuantEvent = FrameArray[Scan]; Debug.Assert(QuantizedEventGetType(QuantEvent) == QuantEventType.eQuantizedNoteEvent); // non-note in frame array /* get attributes */ GetQuantizedNoteEventInfo(QuantEvent, out StartTime, out StartTimeAdjust, out Duration, out DurationAdjust, out MIDIPitch, out MIDIAttackVelocity, out MIDIReleaseVelocity); Debug.Assert(IntMultOf64thDiv3(Duration)); // non-64div3 duration quantization? Debug.Assert(FractionRec.FractionsEqual(StartTime, CurrentTime)); // start time inconsistency /* create note */ Note = new NoteNoteObjectRec(NoteTrack); Frame.Add(Note); TieMappingAddPair(TieMapping, QuantEvent, Note); /* set note attributes */ Note.PutNoteDuration(Duration & NoteFlags.eDurationMask); Note.PutNoteDurationDivision(Duration & NoteFlags.eDivisionMask); Note.PutNoteDotStatus((Duration & NoteFlags.eDotModifier) != 0); Note.EarlyLateAdjust = StartTimeAdjust; Note.DurationAdjust = DurationAdjust; Note.DurationAdjustMode = NoteFlags.eDurationAdjustMultiplicative; Note.PutNotePitch((short)(MIDIPitch - MIDIC + Constants.CENTERNOTE)); Note.Accent1 = (MIDIAttackVelocity > 0) ? -Math.Log(MIDIAttackVelocity) / Constants.LOG2 + LN127OVERLN2 : 7; Note.Accent2 = (MIDIReleaseVelocity > 0) ? -Math.Log(MIDIReleaseVelocity) / Constants.LOG2 + LN127OVERLN2 : 7; switch ((MIDIPitch - MIDIC + ((MIDIC / 12 + 1) * 12)) % 12) { default: // midi sharp/flat problem Debug.Assert(false); throw new InvalidOperationException(); case 0: /* C */ case 2: /* D */ case 4: /* E */ case 5: /* F */ case 7: /* G */ case 9: /* A */ case 11: /* B */ break; case 1: /* C# */ case 3: /* D# */ case 6: /* F# */ case 8: /* G# */ case 10: /* A# */ Note.PutNoteFlatOrSharpStatus(NoteFlags.eSharpModifier); break; } /* do the minimum duration thing */ NoteNoteObjectRec.ConvertDurationFrac(Duration, out FracDuration); Debug.Assert(FractionIntMultOf64thDiv3(FracDuration)); // non-64div3 duration quantization? if (FractionRec.FracGreaterThan(MinimumDuration, FracDuration)) { MinimumDuration = FracDuration; } } /* add frame to track */ NoteTrack.FrameArray.Add(Frame); /* if minimum duration is greater than time to next event, then */ /* add rests (one to this frame) to fill in the gap */ if (Index < Limit) { FractionRec NextEventTime; FractionRec Difference; /* get the start time of the next available event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); NextEventTime = GetQuantizedEventTime(QuantEvent); Debug.Assert(FractionIntMultOf64thDiv3(NextEventTime)); // non-64div3 start time quantization? FractionRec.SubFractions(NextEventTime, CurrentTime, out Difference); if (FractionRec.FracGreaterThan(MinimumDuration, Difference)) { NoteNoteObjectRec Note; NoteFlags RestOpcode; FractionRec OpcodesDuration; /* insert first rest into frame */ RestOpcode = GetMaxDurationOpcode(Difference, out OpcodesDuration, OpcodeDurationTable, OpcodeDurationTableLength); Debug.Assert(IntMultOf64thDiv3(RestOpcode)); // non-64div3 duration quantization Note = new NoteNoteObjectRec(NoteTrack); Note.PutNoteDuration(RestOpcode & NoteFlags.eDurationMask); Note.PutNoteDurationDivision(RestOpcode & NoteFlags.eDivisionMask); Note.PutNoteDotStatus((RestOpcode & NoteFlags.eDotModifier) != 0); Note.PutNotePitch(Constants.CENTERNOTE); Note.PutNoteIsItARest(true); Frame.Add(Note); /* put new minimum duration in to reflect new rest we added */ NoteNoteObjectRec.ConvertDurationFrac(RestOpcode, out MinimumDuration); } } /* advance thing by minimum duration */ FractionRec.AddFractions(MinimumDuration, CurrentTime, out CurrentTime); Debug.Assert(FractionIntMultOf64thDiv3(CurrentTime)); // non-64div3 start time quantization? } } /* patch up ties */ for (Index = 0; Index < Limit; Index += 1) { QuantEventRec QuantEvent; /* get potential event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); /* see if it ties somewhere */ if ((QuantEventType.eQuantizedNoteEvent == QuantizedEventGetType(QuantEvent)) && (GetQuantizedEventTieTarget(QuantEvent) != null)) { QuantEventRec TieTarget; NoteNoteObjectRec Source; NoteNoteObjectRec Target; /* get tie target */ TieTarget = GetQuantizedEventTieTarget(QuantEvent); /* look up source and target note events */ Source = TieMappingLookup(TieMapping, QuantEvent); Target = TieMappingLookup(TieMapping, TieTarget); /* establish tie */ Source.PutNoteTieTarget(Target); } } /* look for track name comment */ for (Index = 0; Index < Limit; Index += 1) { QuantEventRec QuantEvent; /* get potential event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); /* see if it ties somewhere */ if (QuantEventType.eQuantizedCommentEvent == QuantizedEventGetType(QuantEvent)) { string CommentString; FractionRec unusedf; double unusedd; GetQuantizedCommentEventInfo(QuantEvent, out unusedf, out unusedd, out CommentString); /* check for track name */ if ((CommentString.Length > 11 /*Prefix*/ + Environment.NewLine.Length) && CommentString.StartsWith("Track Name" + Environment.NewLine)) { string NameString = CommentString.Substring(11, CommentString.Length - (11 + 1)); NoteTrack.Name = NameString; goto FinishedSettingTrackName; } } } /* if no track name was found, then use the first comment string */ for (Index = 0; Index < Limit; Index += 1) { QuantEventRec QuantEvent; /* get potential event */ QuantEvent = GetQuantizedTrackIndexedEvent(QuantizedTrack, Index); /* see if it ties somewhere */ if (QuantEventType.eQuantizedCommentEvent == QuantizedEventGetType(QuantEvent)) { string CommentString; FractionRec unusedf; double unusedd; GetQuantizedCommentEventInfo(QuantEvent, out unusedf, out unusedd, out CommentString); /* check for track name */ if ((CommentString.Length > 8 /*Prefix*/ + Environment.NewLine.Length) && CommentString.StartsWith("Comment" + Environment.NewLine)) { string NameString; NameString = CommentString.Substring(8, CommentString.Length - (8 + 1)); NoteTrack.Name = NameString; goto FinishedSettingTrackName; } } } FinishedSettingTrackName: ; }
/* convert interval track into quantized track. */ public static bool ConvertIntervalToQuantized( IntervalTrackRec IntervalTrack, QuantizedTrackRec QuantizedTrack, int MidiQuarterNote) { int Scan; int Limit; TimeMatchRec[] MatchingTable = new TimeMatchRec[4 /*divisions*/ * 2 /*dot*/ * 9 /*notetypes*/]; int MatchingTableLength; /* build duration matching table */ MatchingTableLength = 0; for (Scan = 0; Scan < 4 /*divisions*/ * 2 /*dot*/ * 9 /*notetypes*/; Scan += 1) { double MIDIClocks; NoteFlags Descriptor; FractionRec DurationFraction; /* determine root duration and descriptor */ switch (Scan % 9) { default: Debug.Assert(false); throw new InvalidOperationException(); case 0: Descriptor = NoteFlags.e64thNote; break; case 1: Descriptor = NoteFlags.e32ndNote; break; case 2: Descriptor = NoteFlags.e16thNote; break; case 3: Descriptor = NoteFlags.e8thNote; break; case 4: Descriptor = NoteFlags.e4thNote; break; case 5: Descriptor = NoteFlags.e2ndNote; break; case 6: Descriptor = NoteFlags.eWholeNote; break; case 7: Descriptor = NoteFlags.eDoubleNote; break; case 8: Descriptor = NoteFlags.eQuadNote; break; } /* determine if dot is needed */ if (((Scan / 9) % 2) != 0) { /* dot needed */ Descriptor |= NoteFlags.eDotModifier; } /* determine what division is needed */ switch (((Scan / 9) / 2) % 4) { default: Debug.Assert(false); throw new InvalidOperationException(); case 0: break; case 1: Descriptor |= NoteFlags.eDiv3Modifier; break; case 2: Descriptor |= NoteFlags.eDiv5Modifier; break; case 3: Descriptor |= NoteFlags.eDiv7Modifier; break; } /* how int is this note */ NoteNoteObjectRec.ConvertDurationFrac(Descriptor, out DurationFraction); /* units of whole notes */ MIDIClocks = MidiQuarterNote * (4 * FractionRec.Fraction2Double(DurationFraction)); /* add to table if note can be represented in the timing scheme */ /* AND only if note is an integer multiple of the 64th div3 (see */ /* comment at the top of this file for the rationale) */ if ((MIDIClocks == Math.Floor(MIDIClocks)) && (MIDIClocks >= 1) && IntMultOf64thDiv3(Descriptor)) { MatchingTable[MatchingTableLength].Ticks = (int)MIDIClocks; MatchingTable[MatchingTableLength].Descriptor = Descriptor; MatchingTableLength += 1; } } /* quantize note events */ Limit = GetIntervalTrackLength(IntervalTrack); for (Scan = 0; Scan < Limit; Scan += 1) { IntEventRec Event; Event = GetIntervalTrackIndexedEvent(IntervalTrack, Scan); switch (IntervalEventGetType(Event)) { default: Debug.Assert(false); throw new InvalidOperationException(); case IntEventType.eIntervalNoteEvent: { /* input values */ int StartTime; int Duration; short MIDIPitch; short MIDIAttackVelocity; short MIDIReleaseVelocity; /* output values */ NoteFlags QuantizedDuration; double QuantizedDurationAdjust; int WholeNoteOverflow; FractionRec QuantizedStartTime; double QuantizedStartTimeAdjust; double WholeNoteStartTimeAdjust; /* auxiliary value */ /* stuff */ QuantEventRec QuantEvent; /* get the information */ GetIntervalNoteEventInfo(Event, out StartTime, out Duration, out MIDIPitch, out MIDIAttackVelocity, out MIDIReleaseVelocity); /* quantize duration */ QuantizeDuration(Duration, MidiQuarterNote, MatchingTable, MatchingTableLength, out QuantizedDuration, out QuantizedDurationAdjust, out WholeNoteOverflow); Debug.Assert(IntMultOf64thDiv3(QuantizedDuration)); // non-64div3 duration quantization? /* quantize start time */ QuantizeStartTime(StartTime, MidiQuarterNote, QuantizedDuration, QuantizedDurationAdjust, out QuantizedStartTime, out QuantizedStartTimeAdjust, out WholeNoteStartTimeAdjust); Debug.Assert(FractionIntMultOf64thDiv3(QuantizedStartTime)); // non-64div3 start time quantization? /* bump start time to end of whole note chain */ QuantizedStartTime.Integer += (uint)WholeNoteOverflow; /* create new event & insert into track */ QuantEvent = NewQuantizedNoteEvent(QuantizedStartTime, QuantizedStartTimeAdjust, QuantizedDuration, QuantizedDurationAdjust, MIDIPitch, MIDIAttackVelocity, MIDIReleaseVelocity); QuantizedTrackInsertEventSorted(QuantizedTrack, QuantEvent); /* insert whole notes behind the last note in reverse order */ while (WholeNoteOverflow > 0) { QuantEventRec Predecessor; /* create preceding whole note */ QuantizedStartTime.Integer -= 1; Predecessor = NewQuantizedNoteEvent(QuantizedStartTime, WholeNoteStartTimeAdjust, NoteFlags.eWholeNote, 1, MIDIPitch, MIDIAttackVelocity, MIDIReleaseVelocity); QuantizedTrackInsertEventSorted(QuantizedTrack, Predecessor); /* set tie */ PutQuantizedEventTieTarget(Predecessor, QuantEvent); QuantEvent = Predecessor; /* step */ WholeNoteOverflow -= 1; } } break; case IntEventType.eIntervalCommentEvent: { QuantEventRec QuantEvent; /* input values */ int StartTime; string OriginalString; /* output values */ FractionRec QuantizedStartTime; /* get the information */ GetIntervalCommentEventInfo(Event, out StartTime, out OriginalString); /* compute start time to nearest 64th div3 */ FractionRec.Double2Fraction(StartTime / (double)MidiQuarterNote, 64 * 3, out QuantizedStartTime); Debug.Assert(FractionIntMultOf64thDiv3(QuantizedStartTime)); //non-64div3 start time quantization? /* create new event & insert into track */ QuantEvent = NewQuantizedCommentEvent(QuantizedStartTime, 0, OriginalString); QuantizedTrackInsertEventSorted(QuantizedTrack, QuantEvent); } break; } } return(true); }