Example #1
0
        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;
        }
Example #2
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);
        }
Example #3
0
        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.");
            }
        }
Example #6
0
        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);
        }