void GenerateClickTrackSamples(CopyContext ctx, QNT_Timestamp currentTick, TempoChange currentTempo, QNT_Duration timeSignatureDuration)
        {
            QNT_Timestamp tempoStart       = currentTempo.time;
            QNT_Timestamp nextBeat         = timeline.GetClosestBeatSnapped(currentTick, currentTempo.timeSignature.Denominator);
            UInt64        currentBeatInBar = ((currentTick.tick - tempoStart.tick) / timeSignatureDuration.tick) % currentTempo.timeSignature.Numerator;

            if (currentBeatInBar != 1)
            {
                TryAddClickEvent(currentTick, nextBeat, hihat_hit);
            }
            else
            {
                TryAddClickEvent(currentTick, nextBeat, hihat_hit3);
            }

            TryAddClickEvent(currentTick, nextBeat + Constants.SixteenthNoteDuration, hihat_hit2);
            TryAddClickEvent(currentTick, nextBeat + Constants.EighthNoteDuration, hihat_open);

            for (int i = clickTrackEvents.Count - 1; i >= 0; i--)
            {
                ClickTrackEvent ev = clickTrackEvents[i];
                ev.clip.currentSample = ev.currentSample;
                ev.clip.CopySampleIntoBuffer(ctx);
                ev.currentSample = ev.clip.currentSample;

                if (ev.clip.scaledCurrentSample > ev.clip.samples.Length)
                {
                    clickTrackEvents.RemoveAt(i);
                }
                else
                {
                    clickTrackEvents[i] = ev;
                }
            }
        }
        public void CopySampleIntoBuffer(CopyContext ctx)
        {
            UInt64 shiftNum = ((UInt64)1 << PrecisionShift);
            UInt64 speed    = (UInt64)frequency * shiftNum / (UInt64)ctx.bufferFreq;

            if (ctx.playbackSpeed != 1.0f)
            {
                speed = (UInt64)(speed * ctx.playbackSpeed);
            }

            float panClamp = Mathf.Clamp(pan, -1.0f, 1.0f);

            int   clipChannel   = 0;
            int   sourceChannel = 0;
            float maxValue      = 0.0f;

            while (sourceChannel < ctx.bufferChannels)
            {
                float panAmount = 1.0f;
                if (sourceChannel == 0)
                {
                    panAmount = Math.Min(1.0f - panClamp, 1.0f);
                }
                else if (sourceChannel == 1)
                {
                    panAmount = Math.Min(1.0f + panClamp, 1.0f);
                }

                float value     = 0.0f;
                long  samplePos = (uint)(currentSample >> PrecisionShift) * channels + clipChannel;
                if (samplePos < samples.Length)
                {
                    value = samples[samplePos];
                }

                float duckValue = Mathf.Clamp(1.0f - duckVolume, 0.0f, 1.0f);
                maxValue = Math.Max(value * ctx.volume * duckValue, maxValue);

                ctx.bufferData[ctx.index * ctx.bufferChannels + sourceChannel] += value * ctx.volume * panAmount * duckValue;

                sourceChannel++;
                clipChannel++;
                if (clipChannel >= channels)
                {
                    clipChannel = 0;
                }
            }

            currentSample  += speed;
            ctx.outputValue = Math.Abs(maxValue);
        }
        void PlayHitsounds(CopyContext ctx, List <HitsoundEvent> events)
        {
            kick.duckVolume       = 0.0f;
            snare.duckVolume      = 0.0f;
            percussion.duckVolume = 0.0f;
            chainStart.duckVolume = 0.0f;
            chainNote.duckVolume  = 0.0f;
            melee.duckVolume      = 0.0f;
            mine.duckVolume       = 0.0f;

            float oldSpeed = ctx.playbackSpeed;

            UInt64 waitSpeed = (UInt64)1 << ClipData.PrecisionShift;

            if (oldSpeed != 1.0f)
            {
                waitSpeed = (UInt64)(waitSpeed * oldSpeed);
            }

            //Always play hitsounds at full speed
            ctx.playbackSpeed = 1.0f;

            for (int i = events.Count - 1; i >= 0; i--)
            {
                HitsoundEvent ev    = events[i];
                bool          valid = true;

                if (ev.waitSamples > 0)
                {
                    if (waitSpeed > ev.waitSamples)
                    {
                        ev.waitSamples = 0;
                    }
                    else
                    {
                        ev.waitSamples -= waitSpeed;
                    }
                }
                else
                {
                    ctx.volume             = hitSoundVolume * ev.volume;
                    ev.sound.currentSample = ev.currentSample;
                    ev.sound.pan           = ev.pan;
                    ev.sound.CopySampleIntoBuffer(ctx);

                    ev.sound.duckVolume = Mathf.Clamp(ev.sound.duckVolume + 0.25f, 0.0f, 1.0f);

                    if (ev.sound.scaledCurrentSample > ev.sound.samples.Length)
                    {
                        events.RemoveAt(i);
                        valid = false;
                    }
                    else
                    {
                        ev.currentSample = ev.sound.currentSample;
                    }
                }

                //Update the pan:

                ev.pan = (ev.xPos - mainCameraX) / 7.15f;



                if (valid)
                {
                    events[i] = ev;
                }
            }

            ctx.playbackSpeed = 1.0f;
        }