/* restart portamento cycle prior to full restart for tie continuation */ /* only the portamento stuff from FrozenNote is used */ public static void RestartOscBankStatePortamento( OscStateBankRec State, ref FrozenNoteRec FrozenNote) { if (FrozenNote.PortamentoDuration > 0) { State.PortamentoCounter = FrozenNote.PortamentoDuration; State.TotalPortamentoTicks = FrozenNote.PortamentoDuration; State.InitialFrequency = State.CurrentFrequency; /* save current pitch */ State.FinalFrequency = FrozenNote.NominalFrequency; State.PortamentoHertz = ((FrozenNote.OriginalNote.Flags & NoteFlags.ePortamentoUnitsHertzNotHalfsteps) != 0); } else { State.PortamentoCounter = 0; State.CurrentFrequency = FrozenNote.NominalFrequency; } }
/* build a new note object with all parameters determined. *StartAdjustOut */ /* indicates how many ticks before (negative) or after (positive) now that */ /* the key-down should occur. this is added to the scanning gap size and envelope */ /* origins to figure out how to schedule the note */ public static void FixNoteParameters( IncrParamUpdateRec GlobalParamSource, NoteNoteObjectRec Note, out int StartAdjustOut, double EnvelopeTicksPerDurationTick, short PitchIndexAdjust, ref FrozenNoteRec FrozenNote, SynthParamRec SynthParams) { // must assign all fields /* reference to the note that defines this note. */ FrozenNote.OriginalNote = Note; /* frequency determined by pitch index + detuning, in Hertz */ double NominalFrequency; { int i = Note._Pitch + GlobalParamSource.TransposeHalfsteps + PitchIndexAdjust; if (i < 0) { i = 0; } else if (i > Constants.NUMNOTES - 1) { i = Constants.NUMNOTES - 1; } /* compute frequency from index */ #if DEBUG if ((Constants.CENTERNOTE % 12) != 0) { // CENTERNOTE multiple of 12 Debug.Assert(false); throw new ArgumentException(); } #endif double d = GlobalParamSource.FrequencyTable[i % 12].nd.Current; i = (i / 12) - (Constants.CENTERNOTE / 12); d = d * Math.Exp(i * Constants.LOG2) * Constants.MIDDLEC; /* apply detuning */ double e; switch (Note.Flags & NoteFlags.eDetuningModeMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.eDetuningModeDefault: e = (double)Note._Detuning * GlobalParamSource.Detune.nd.Current; if (GlobalParamSource.DetuneHertz) { goto DetuneHertzPoint; } else { goto DetuneHalfStepsPoint; } case NoteFlags.eDetuningModeHalfSteps: e = (double)(Note._Detuning) + GlobalParamSource.Detune.nd.Current; DetuneHalfStepsPoint: NominalFrequency = d * Math.Exp((e / 12) * Constants.LOG2); break; case NoteFlags.eDetuningModeHertz: e = (double)Note._Detuning + GlobalParamSource.Detune.nd.Current; DetuneHertzPoint: NominalFrequency = d + e; break; } } FrozenNote.NominalFrequency = NominalFrequency; /* frequency used for doing multisampling, in Hertz */ if (Note._MultisamplePitchAsIf != -1) { /* compute frequency from index */ int i = Note._MultisamplePitchAsIf; #if DEBUG if ((Constants.CENTERNOTE % 12) != 0) { Debug.Assert(false); throw new ArgumentException(); } #endif double d = GlobalParamSource.FrequencyTable[i % 12].nd.Current; i = (i / 12) - (Constants.CENTERNOTE / 12); d = d * Math.Exp(i * Constants.LOG2) * Constants.MIDDLEC; FrozenNote.MultisampleFrequency = d; } else { FrozenNote.MultisampleFrequency = NominalFrequency; } /* acceleration of envelopes */ FrozenNote.HurryUpFactor = (double)Note._HurryUpFactor * GlobalParamSource.HurryUp.nd.Current; /* duration, in envelope ticks */ int Duration; { int i; switch (Note.Flags & NoteFlags.eDurationMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.e64thNote: i = DURATIONUPDATECLOCKRESOLUTION / 64; break; case NoteFlags.e32ndNote: i = DURATIONUPDATECLOCKRESOLUTION / 32; break; case NoteFlags.e16thNote: i = DURATIONUPDATECLOCKRESOLUTION / 16; break; case NoteFlags.e8thNote: i = DURATIONUPDATECLOCKRESOLUTION / 8; break; case NoteFlags.e4thNote: i = DURATIONUPDATECLOCKRESOLUTION / 4; break; case NoteFlags.e2ndNote: i = DURATIONUPDATECLOCKRESOLUTION / 2; break; case NoteFlags.eWholeNote: i = DURATIONUPDATECLOCKRESOLUTION; break; case NoteFlags.eDoubleNote: i = DURATIONUPDATECLOCKRESOLUTION * 2; break; case NoteFlags.eQuadNote: i = DURATIONUPDATECLOCKRESOLUTION * 4; break; } switch (Note.Flags & NoteFlags.eDivisionMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.eDiv1Modifier: break; case NoteFlags.eDiv3Modifier: i = i / 3; break; case NoteFlags.eDiv5Modifier: i = i / 5; break; case NoteFlags.eDiv7Modifier: i = i / 7; break; } if ((Note.Flags & NoteFlags.eDotModifier) != 0) { i = (i * 3) / 2; } double d = i; switch (Note.Flags & NoteFlags.eDurationAdjustMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.eDurationAdjustDefault: if (GlobalParamSource.DurationAdjustAdditive) { goto DurationAdjustAddPoint; } else { goto DurationAdjustMultPoint; } case NoteFlags.eDurationAdjustAdditive: DurationAdjustAddPoint: d = d + (double)Note._DurationAdjust * (DURATIONUPDATECLOCKRESOLUTION / 4); break; case NoteFlags.eDurationAdjustMultiplicative: DurationAdjustMultPoint: d = d * (double)Note._DurationAdjust; break; } if (GlobalParamSource.DurationAdjustAdditive) { d = d + GlobalParamSource.DurationAdjust.nd.Current * (DURATIONUPDATECLOCKRESOLUTION / 4); } else { d = d * GlobalParamSource.DurationAdjust.nd.Current; } /* this line is what converts from duration update ticks to envelope ticks */ Duration = (int)(d * EnvelopeTicksPerDurationTick); } FrozenNote.Duration = Duration; /* portamento duration, in envelope ticks */ FrozenNote.PortamentoDuration = (int)(((double)Note._PortamentoDuration + GlobalParamSource.Portamento.nd.Current) * (DURATIONUPDATECLOCKRESOLUTION / 4) * EnvelopeTicksPerDurationTick); /* see if portamento occurs before note retrigger */ FrozenNote.PortamentoBeforeNote = ((Note.Flags & NoteFlags.ePortamentoLeadsNote) != 0); /* first release point, in envelope ticks after start of note */ switch (Note.Flags & NoteFlags.eRelease1OriginMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.eRelease1FromStart: FrozenNote.ReleasePoint1 = (int)((double)Note._ReleasePoint1 * Duration); FrozenNote.Release1FromStart = true; break; case NoteFlags.eRelease1FromEnd: FrozenNote.ReleasePoint1 = (int)((1 - (double)Note._ReleasePoint1) * Duration); FrozenNote.Release1FromStart = false; break; case NoteFlags.eRelease1FromDefault: if (GlobalParamSource.ReleasePoint1FromStart) { FrozenNote.ReleasePoint1 = (int)(((double)Note._ReleasePoint1 + GlobalParamSource.ReleasePoint1.nd.Current) * Duration); FrozenNote.Release1FromStart = true; } else { FrozenNote.ReleasePoint1 = (int)((1 - ((double)Note._ReleasePoint1 + GlobalParamSource.ReleasePoint1.nd.Current)) * Duration); FrozenNote.Release1FromStart = false; } break; } /* second release point, in envelope ticks after start of note */ switch (Note.Flags & NoteFlags.eRelease2OriginMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.eRelease2FromStart: FrozenNote.ReleasePoint2 = (int)((double)Note._ReleasePoint2 * Duration); FrozenNote.Release2FromStart = true; break; case NoteFlags.eRelease2FromEnd: FrozenNote.ReleasePoint2 = (int)((1 - (double)Note._ReleasePoint2) * Duration); FrozenNote.Release2FromStart = false; break; case NoteFlags.eRelease2FromDefault: if (GlobalParamSource.ReleasePoint2FromStart) { FrozenNote.ReleasePoint2 = (int)(((double)Note._ReleasePoint2 + GlobalParamSource.ReleasePoint2.nd.Current) * Duration); FrozenNote.Release2FromStart = true; } else { FrozenNote.ReleasePoint2 = (int)((1 - ((double)Note._ReleasePoint2 + GlobalParamSource.ReleasePoint2.nd.Current)) * Duration); FrozenNote.Release2FromStart = false; } break; } /* third release point, in envelope ticks after start of note */ if ((Note.Flags & NoteFlags.eRelease3FromStartNotEnd) != 0) { FrozenNote.ReleasePoint3 = 0; FrozenNote.Release3FromStart = true; } else { FrozenNote.ReleasePoint3 = Duration; FrozenNote.Release3FromStart = false; } /* overall loudness adjustment for envelopes, including global volume scaling */ FrozenNote.LoudnessAdjust = (double)Note._OverallLoudnessAdjustment * GlobalParamSource.Volume.nd.Current; /* stereo positioning for note */ { double d = (double)Note._StereoPositionAdjustment + GlobalParamSource.StereoPosition.nd.Current; if (d < -1) { d = -1; } else if (d > 1) { d = 1; } FrozenNote.StereoPosition = d; } /* accent values for controlling envelopes */ InitializeAccent( ref FrozenNote.Accents, (double)Note._Accent1 + GlobalParamSource.Accent1.nd.Current, (double)Note._Accent2 + GlobalParamSource.Accent2.nd.Current, (double)Note._Accent3 + GlobalParamSource.Accent3.nd.Current, (double)Note._Accent4 + GlobalParamSource.Accent4.nd.Current, (double)Note._Accent5 + GlobalParamSource.Accent5.nd.Current, (double)Note._Accent6 + GlobalParamSource.Accent6.nd.Current, (double)Note._Accent7 + GlobalParamSource.Accent7.nd.Current, (double)Note._Accent8 + GlobalParamSource.Accent8.nd.Current); /* pitch displacement maximum depth, in tonal Hertz */ FrozenNote.PitchDisplacementDepthLimit = (double)Note._PitchDisplacementDepthAdjustment * GlobalParamSource.PitchDisplacementDepthLimit.nd.Current; /* pitch displacement maximum rate, in LFO Hertz */ FrozenNote.PitchDisplacementRateLimit = (double)Note._PitchDisplacementRateAdjustment * GlobalParamSource.PitchDisplacementRateLimit.nd.Current; /* pitch displacement start point, in envelope clocks after start of note */ switch (Note.Flags & NoteFlags.ePitchDisplacementStartOriginMask) { default: Debug.Assert(false); throw new ArgumentException(); case NoteFlags.ePitchDisplacementStartFromStart: FrozenNote.PitchDisplacementStartPoint = (int)(Duration * (double)Note._PitchDisplacementStartPoint); break; case NoteFlags.ePitchDisplacementStartFromEnd: FrozenNote.PitchDisplacementStartPoint = (int)(Duration * (1 - (double)Note._PitchDisplacementStartPoint)); break; case NoteFlags.ePitchDisplacementStartFromDefault: if (GlobalParamSource.PitchDisplacementStartPointFromStart) { FrozenNote.PitchDisplacementStartPoint = (int)(Duration * ((double)Note._PitchDisplacementStartPoint + GlobalParamSource.PitchDisplacementStartPoint.nd.Current)); } else { FrozenNote.PitchDisplacementStartPoint = (int)(Duration * (1 - ((double)Note._PitchDisplacementStartPoint + GlobalParamSource.PitchDisplacementStartPoint.nd.Current))); } break; } StartAdjustOut = (int)(((double)Note._EarlyLateAdjust + GlobalParamSource.EarlyLateAdjust.nd.Current) * Duration); }
/* this is used for resetting a note for a tie */ /* the FrozenNote object is NOT disposed */ public static void ResetOscBankState( OscStateBankRec State, ref FrozenNoteRec FrozenNote, SynthParamRec SynthParams) { bool RetriggerEnvelopes = ((FrozenNote.OriginalNote.Flags & NoteFlags.eRetriggerEnvelopesOnTieFlag) != 0); /* go through the oscillators and retrigger them */ OscStateRec OneState = State.OscillatorList; while (OneState != null) { OneState.StateReference.Restart( ref FrozenNote.Accents, FrozenNote.LoudnessAdjust * State.BankTemplate.InstrOverallLoudness, FrozenNote.HurryUpFactor, RetriggerEnvelopes, FrozenNote.StereoPosition, FrozenNote.NominalFrequency, FrozenNote.PitchDisplacementDepthLimit, FrozenNote.PitchDisplacementRateLimit, SynthParams); OneState = OneState.Next; } LFOGeneratorRetriggerFromOrigin( State.PitchLFO, ref FrozenNote.Accents, FrozenNote.NominalFrequency, FrozenNote.HurryUpFactor, FrozenNote.PitchDisplacementDepthLimit, FrozenNote.PitchDisplacementRateLimit, RetriggerEnvelopes, SynthParams); if (State.CombinedOscEffectGenerator != null) { OscEffectGeneratorRetriggerFromOrigin( State.CombinedOscEffectGenerator, ref FrozenNote.Accents, FrozenNote.NominalFrequency, FrozenNote.HurryUpFactor, RetriggerEnvelopes, SynthParams); } /* if this object ties to a note, then this is the note to tie to. this is */ /* used for finding existing oscillators for tie continuations. */ State.TieToNote = FrozenNote.OriginalNote._Tie; /* portamento control parameters */ if (!FrozenNote.PortamentoBeforeNote) { /* if PortamentoBeforeNote is not set, then we have to restart the portamento */ /* with the current note, otherwise it has already been restarted earlier */ RestartOscBankStatePortamento(State, ref FrozenNote); } /* various counters (in terms of envelope ticks) */ if (State.TieToNote == null) { State.Release1Countdown = FrozenNote.ReleasePoint1; State.Release2Countdown = FrozenNote.ReleasePoint2; State.Release3Countdown = FrozenNote.ReleasePoint3; } else { /* for ties, only honor releases from start */ if (FrozenNote.Release1FromStart) { State.Release1Countdown = FrozenNote.ReleasePoint1; } else { State.Release1Countdown = -1; } if (FrozenNote.Release2FromStart) { State.Release2Countdown = FrozenNote.ReleasePoint2; } else { State.Release2Countdown = -1; } if (FrozenNote.Release3FromStart) { State.Release3Countdown = FrozenNote.ReleasePoint3; } else { State.Release3Countdown = -1; } } /* do not reset PitchLFOStartCountdown since we can't give it a proper value */ /* to do the expected thing, and we'd be interrupting the phase of the LFO */ /* wave generator */ }
/* construct a new oscillator bank state object based on the note. the note is */ /* assumed to start "now" in terms of the parameters in the ParameterUpdator. */ /* the ScanningGapWidth is the number of envelope clock ticks in the current scanning */ /* gap. this is used to determine how far later than "now" in terms of the back */ /* edge of the scanning gap (different from above) the osc bank should start playing. */ /* *WhenToStartPlayingOut returns the number of envelope ticks after the back edge */ /* of the scanning gap that the note should be started. */ /* <already played> | <scanning gap> | <not yet analyzed> */ /* time ---. time ---. time ---. time ---. time ---. time ---. */ /* ^A ^B */ /* point A is the back edge of the scanning gap. as this edge moves forward in time, */ /* oscillator bank state objects are removed from the queue and playback is commenced */ /* for them. */ /* point B is the front edge of the scanning gap. as this edge moves forward in time, */ /* notes are extracted from the track and state bank objects are created for them. */ /* ParameterUpdator always reflects parameters at this point in time. */ public static SynthErrorCodes NewOscBankState( OscBankTemplateRec Template, out int WhenToStartPlayingOut, NoteNoteObjectRec Note, double EnvelopeTicksPerDurationTick, short PitchIndexAdjust, PlayTrackInfoRec TrackInfo, SynthParamRec SynthParams, out OscStateBankRec StateOut) { int ThisPreOriginTime; int StartPointAdjust; WhenToStartPlayingOut = 0; StateOut = null; int MaxOscillatorPreOriginTime = 0; OscStateBankRec State = New(ref SynthParams.freelists.oscStateBankFreeList); // all fields must be assigned: State State.PortamentoHertz = false; State.TotalPortamentoTicks = 0; State.InitialFrequency = 0; State.FinalFrequency = 0; State.BankTemplate = Template; /* freeze the parameters */ FrozenNoteRec FrozenNote = new FrozenNoteRec(); FixNoteParameters( Template.ParamUpdator, Note, out StartPointAdjust, EnvelopeTicksPerDurationTick, PitchIndexAdjust, ref FrozenNote, SynthParams); /* this calculates the differential values for periodic pitch displacements */ State.PitchLFO = NewLFOGenerator( Template.PitchLFOTemplate, out ThisPreOriginTime, ref FrozenNote.Accents, FrozenNote.NominalFrequency, FrozenNote.HurryUpFactor, FrozenNote.PitchDisplacementDepthLimit, FrozenNote.PitchDisplacementRateLimit, FrozenNote.MultisampleFrequency, _PlayTrackParamGetter, TrackInfo, SynthParams); if (ThisPreOriginTime > MaxOscillatorPreOriginTime) { MaxOscillatorPreOriginTime = ThisPreOriginTime; } /* list of oscillators that this oscillator bank is comprised of */ State.OscillatorList = null; for (int i = 0; i < Template.NumOscillatorsInBank; i++) { OscStateRec OneState = New(ref SynthParams.freelists.oscStateFreeList); // all fields must be assigned: OneState /* link it in */ OneState.Next = State.OscillatorList; State.OscillatorList = OneState; /* copy over the function vectors */ OneState.Template = Template.TemplateArray[i]; /* create the oscillator */ SynthErrorCodes Result = OneState.Template.TemplateReference.NewState( FrozenNote.MultisampleFrequency, ref FrozenNote.Accents, FrozenNote.LoudnessAdjust * Template.InstrOverallLoudness, FrozenNote.HurryUpFactor, out ThisPreOriginTime, FrozenNote.StereoPosition, FrozenNote.NominalFrequency, FrozenNote.PitchDisplacementDepthLimit, FrozenNote.PitchDisplacementRateLimit, FrozenNote.PitchDisplacementStartPoint, TrackInfo, SynthParams, out OneState.StateReference); if (Result != SynthErrorCodes.eSynthDone) { return(Result); } if (ThisPreOriginTime > MaxOscillatorPreOriginTime) { MaxOscillatorPreOriginTime = ThisPreOriginTime; } } State.CombinedOscEffectGenerator = null; if ((Template.CombinedOscillatorEffects != null) && (GetEffectSpecListLength(Template.CombinedOscillatorEffects) > 0)) { SynthErrorCodes Result = NewOscEffectGenerator( Template.CombinedOscillatorEffects, ref FrozenNote.Accents, FrozenNote.HurryUpFactor, FrozenNote.NominalFrequency, FrozenNote.MultisampleFrequency, out ThisPreOriginTime, TrackInfo, SynthParams, out State.CombinedOscEffectGenerator); if (Result != SynthErrorCodes.eSynthDone) { return(Result); } if (ThisPreOriginTime > MaxOscillatorPreOriginTime) { MaxOscillatorPreOriginTime = ThisPreOriginTime; } } /* else no combined oscillator effects, State.CombinedOscEffectGenerator is null */ /* if this object ties to a note, then this is the note */ /* to tie to. this is used for finding existing oscillators */ /* for tie continuations. */ State.TieToNote = Note._Tie; /* portamento control parameters */ State.PortamentoCounter = 0; State.CurrentFrequency = FrozenNote.NominalFrequency; /* fix up pre-origin times */ OscStateRec StateScan = State.OscillatorList; while (StateScan != null) { StateScan.StateReference.FixUpPreOrigin( MaxOscillatorPreOriginTime); StateScan = StateScan.Next; } LFOGeneratorFixEnvelopeOrigins( State.PitchLFO, MaxOscillatorPreOriginTime); if (State.CombinedOscEffectGenerator != null) { FixUpOscEffectGeneratorPreOrigin( State.CombinedOscEffectGenerator, MaxOscillatorPreOriginTime); } /* various counters (in terms of envelope ticks) */ if (State.TieToNote == null) { State.Release1Countdown = FrozenNote.ReleasePoint1 + MaxOscillatorPreOriginTime; State.Release2Countdown = FrozenNote.ReleasePoint2 + MaxOscillatorPreOriginTime; State.Release3Countdown = FrozenNote.ReleasePoint3 + MaxOscillatorPreOriginTime; } else { /* for ties, only honor releases from start */ if (FrozenNote.Release1FromStart) { State.Release1Countdown = FrozenNote.ReleasePoint1 + MaxOscillatorPreOriginTime; } else { State.Release1Countdown = -1; } if (FrozenNote.Release2FromStart) { State.Release2Countdown = FrozenNote.ReleasePoint2 + MaxOscillatorPreOriginTime; } else { State.Release2Countdown = -1; } if (FrozenNote.Release3FromStart) { State.Release3Countdown = FrozenNote.ReleasePoint3 + MaxOscillatorPreOriginTime; } else { State.Release3Countdown = -1; } } State.PitchLFOStartCountdown = FrozenNote.PitchDisplacementStartPoint /*+ MaxOscillatorPreOriginTime*/; /* pre origin relationship must be preserved for */ /* pitch LFO trigger */ /* done */ WhenToStartPlayingOut = StartPointAdjust - MaxOscillatorPreOriginTime; StateOut = State; return(SynthErrorCodes.eSynthDone); }