public void Start(RegionPair region, int channel, int key, int velocity) { this.exclusiveClass = region.ExclusiveClass; this.channel = channel; this.key = key; this.velocity = velocity; if (velocity > 0) { // According to the Polyphone's implementation, the initial attenuation should be reduced to 40%. // I'm not sure why, but this indeed improves the loudness variability. var sampleAttenuation = 0.4F * region.InitialAttenuation; var filterAttenuation = 0.5F * region.InitialFilterQ; var decibels = 2 * SoundFontMath.LinearToDecibels(velocity / 127F) - sampleAttenuation - filterAttenuation; noteGain = SoundFontMath.DecibelsToLinear(decibels); } else { noteGain = 0F; } cutoff = region.InitialFilterCutoffFrequency; resonance = SoundFontMath.DecibelsToLinear(region.InitialFilterQ); vibLfoToPitch = 0.01F * region.VibratoLfoToPitch; modLfoToPitch = 0.01F * region.ModulationLfoToPitch; modEnvToPitch = 0.01F * region.ModulationEnvelopeToPitch; modLfoToCutoff = region.ModulationLfoToFilterCutoffFrequency; modEnvToCutoff = region.ModulationEnvelopeToFilterCutoffFrequency; dynamicCutoff = modLfoToCutoff != 0 || modEnvToCutoff != 0; modLfoToVolume = region.ModulationLfoToVolume; dynamicVolume = modLfoToVolume > 0.05F; instrumentPan = SoundFontMath.Clamp(region.Pan, -50F, 50F); instrumentReverb = 0.01F * region.ReverbEffectsSend; instrumentChorus = 0.01F * region.ChorusEffectsSend; volEnv.Start(region, key, velocity); modEnv.Start(region, key, velocity); vibLfo.StartVibrato(region, key, velocity); modLfo.StartModulation(region, key, velocity); oscillator.Start(synthesizer.SoundFont.WaveDataArray, region); filter.ClearBuffer(); filter.SetLowPassFilter(cutoff, resonance); smoothedCutoff = cutoff; voiceState = VoiceState.Playing; voiceLength = 0; }
public static void Start(this ModulationEnvelope envelope, RegionPair region, int key, int velocity) { // According to the implementation of TinySoundFont, the attack time should be adjusted by the velocity. var delay = region.DelayModulationEnvelope; var attack = region.AttackModulationEnvelope * ((145 - velocity) / 144F); var hold = region.HoldModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeHold, key); var decay = region.DecayModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeDecay, key); var sustain = 1F - region.SustainModulationEnvelope / 100F; var release = region.ReleaseModulationEnvelope; envelope.Start(delay, attack, hold, decay, sustain, release); }
public static void Start(this VolumeEnvelope envelope, RegionPair region, int key, int velocity) { // If the release time is shorter than 10 ms, it will be clamped to 10 ms to avoid pop noise. var delay = region.DelayVolumeEnvelope; var attack = region.AttackVolumeEnvelope; var hold = region.HoldVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeHold, key); var decay = region.DecayVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeDecay, key); var sustain = SoundFontMath.DecibelsToLinear(-region.SustainVolumeEnvelope); var release = Math.Max(region.ReleaseVolumeEnvelope, 0.01F); envelope.Start(delay, attack, hold, decay, sustain, release); }
public void Start(float delay, float attack, float hold, float decay, float sustain, float release) { attackSlope = 1 / attack; decaySlope = -9.226 / decay; releaseSlope = -9.226 / release; attackStartTime = delay; holdStartTime = attackStartTime + attack; decayStartTime = holdStartTime + hold; releaseStartTime = 0; sustainLevel = SoundFontMath.Clamp(sustain, 0F, 1F); releaseLevel = 0; processedSampleCount = 0; stage = Stage.Delay; value = 0; Process(0); }
private bool Process(int sampleCount) { processedSampleCount += sampleCount; var currentTime = (double)processedSampleCount / synthesizer.SampleRate; while (stage <= Stage.Hold) { double endTime; switch (stage) { case Stage.Delay: endTime = attackStartTime; break; case Stage.Attack: endTime = holdStartTime; break; case Stage.Hold: endTime = decayStartTime; break; default: throw new InvalidOperationException("Invalid envelope stage."); } if (currentTime < endTime) { break; } else { stage++; } } switch (stage) { case Stage.Delay: value = 0; priority = 4F + value; return true; case Stage.Attack: value = (float)(attackSlope * (currentTime - attackStartTime)); priority = 3F + value; return true; case Stage.Hold: value = 1; priority = 2F + value; return true; case Stage.Decay: value = Math.Max((float)SoundFontMath.ExpCutoff(decaySlope * (currentTime - decayStartTime)), sustainLevel); priority = 1F + value; return value > SoundFontMath.NonAudible; case Stage.Release: value = (float)(releaseLevel * SoundFontMath.ExpCutoff(releaseSlope * (currentTime - releaseStartTime))); priority = value; return value > SoundFontMath.NonAudible; default: throw new InvalidOperationException("Invalid envelope stage."); } }
public bool Process() { if (noteGain < SoundFontMath.NonAudible) { return(false); } var channelInfo = synthesizer.Channels[channel]; ReleaseIfNecessary(channelInfo); if (!volEnv.Process()) { return(false); } modEnv.Process(); vibLfo.Process(); modLfo.Process(); var vibPitchChange = (0.01F * channelInfo.Modulation + vibLfoToPitch) * vibLfo.Value; var modPitchChange = modLfoToPitch * modLfo.Value + modEnvToPitch * modEnv.Value; var channelPitchChange = channelInfo.Tune + channelInfo.PitchBend; var pitch = key + vibPitchChange + modPitchChange + channelPitchChange; if (!oscillator.Process(block, pitch)) { return(false); } if (dynamicCutoff) { var cents = modLfoToCutoff * modLfo.Value + modEnvToCutoff * modEnv.Value; var factor = SoundFontMath.CentsToMultiplyingFactor(cents); var newCutoff = factor * cutoff; // The cutoff change is limited within x0.5 and x2 to reduce pop noise. var lowerLimit = 0.5F * smoothedCutoff; var upperLimit = 2F * smoothedCutoff; if (newCutoff < lowerLimit) { smoothedCutoff = lowerLimit; } else if (newCutoff > upperLimit) { smoothedCutoff = upperLimit; } else { smoothedCutoff = newCutoff; } filter.SetLowPassFilter(smoothedCutoff, resonance); } filter.Process(block); previousMixGainLeft = currentMixGainLeft; previousMixGainRight = currentMixGainRight; previousReverbSend = currentReverbSend; previousChorusSend = currentChorusSend; // According to the GM spec, the following value should be squared. var ve = channelInfo.Volume * channelInfo.Expression; var channelGain = ve * ve; var mixGain = noteGain * channelGain * volEnv.Value; if (dynamicVolume) { var decibels = modLfoToVolume * modLfo.Value; mixGain *= SoundFontMath.DecibelsToLinear(decibels); } var angle = (MathF.PI / 200F) * (channelInfo.Pan + instrumentPan + 50F); if (angle <= 0F) { currentMixGainLeft = mixGain; currentMixGainRight = 0F; } else if (angle >= SoundFontMath.HalfPi) { currentMixGainLeft = 0F; currentMixGainRight = mixGain; } else { currentMixGainLeft = mixGain * MathF.Cos(angle); currentMixGainRight = mixGain * MathF.Sin(angle); } currentReverbSend = SoundFontMath.Clamp(channelInfo.ReverbSend + instrumentReverb, 0F, 1F); currentChorusSend = SoundFontMath.Clamp(channelInfo.ChorusSend + instrumentChorus, 0F, 1F); if (voiceLength == 0) { previousMixGainLeft = currentMixGainLeft; previousMixGainRight = currentMixGainRight; previousReverbSend = currentReverbSend; previousChorusSend = currentChorusSend; } voiceLength += synthesizer.BlockSize; return(true); }