Exemplo n.º 1
0
        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;
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Adds TempoChange Events to the given Chart from the given Dictionary of
        /// position to tempo values parsed from the Chart or Song.
        /// </summary>
        /// <param name="tempos">
        /// Dictionary of time to value of tempos parsed from the Song or Chart.
        /// </param>
        /// <param name="chart">Chart to add TempoChange Events to.</param>
        public static void AddTempos(Dictionary <double, double> tempos, Chart chart)
        {
            // Insert tempo change events.
            foreach (var tempo in tempos)
            {
                var tempoChangeEvent = new TempoChange(tempo.Value)
                {
                    Position = new MetricPosition(
                        (int)tempo.Key / NumBeatsPerMeasure,
                        (int)tempo.Key % NumBeatsPerMeasure,
                        FindClosestSMSubDivision(tempo.Key - (int)tempo.Key))
                };

                // Record the actual doubles.
                tempoChangeEvent.Extras.AddSourceExtra(TagFumenDoublePosition, tempo.Key);
                chart.Layers[0].Events.Add(tempoChangeEvent);
            }
        }
Exemplo n.º 3
0
    public void Activate()
    {
        if (!timeline.paused)
        {
            timeline.TogglePlayback();
        }

        gameObject.GetComponent <CanvasGroup>().DOFade(1.0f, 0.3f);
        gameObject.SetActive(true);

        TempoChange tempo = timeline.GetTempoForTime(Timeline.time);

        dynamicBpmInput.GetComponent <TMP_InputField>().text          = Constants.DisplayBPMFromMicrosecondsPerQuaterNote(tempo.microsecondsPerQuarterNote);
        timeSignatureNumerator.GetComponent <TMP_InputField>().text   = tempo.timeSignature.Numerator.ToString();
        timeSignatureDenomerator.GetComponent <TMP_InputField>().text = tempo.timeSignature.Denominator.ToString();

        dynamicBpmInput.ActivateInputField();
    }
Exemplo n.º 4
0
        void ClickTrackPCMReaderCallback(float[] data)
        {
            CopyContext ctx;

            ctx.bufferData     = data;
            ctx.bufferChannels = 2;
            ctx.bufferFreq     = sampleRate;
            ctx.playbackSpeed  = 1.0f;
            ctx.outputValue    = 0.0f;
            ctx.volume         = volume;

            for (int dataIndex = 0; dataIndex < data.Length / 2; dataIndex++)
            {
                ctx.index = dataIndex;

                QNT_Timestamp currentTick           = timeline.ShiftTick(new QNT_Timestamp(0), (float)((dataIndex) / (float)sampleRate));
                TempoChange   currentTempo          = timeline.GetTempoForTime(currentTick);
                QNT_Duration  timeSignatureDuration = new QNT_Duration(Constants.PulsesPerWholeNote / currentTempo.timeSignature.Denominator);
                GenerateClickTrackSamples(ctx, currentTick, currentTempo, timeSignatureDuration);
            }
        }
Exemplo n.º 5
0
        void OnAudioFilterRead(float[] bufferData, int bufferChannels)
        {
            CopyContext ctx;

            ctx.bufferData     = bufferData;
            ctx.bufferChannels = bufferChannels;
            ctx.bufferFreq     = sampleRate;
            ctx.playbackSpeed  = speed;
            ctx.outputValue    = 0.0f;

            if (clearHitsounds)
            {
                hitsoundEvents.Clear();
                hitSoundEnd    = new QNT_Timestamp(0);
                clearHitsounds = false;
            }

            if (clearClicksounds)
            {
                clickTrackEvents.Clear();
                clearClicksounds = false;
            }

            if (newPreviewHitsoundEvents != null)
            {
                List <HitsoundEvent> newPreview = newPreviewHitsoundEvents;
                newPreviewHitsoundEvents = null;

                previewHitsoundEvents.Clear();
                previewHitsoundEvents = newPreview;
            }

            if (playPreview)
            {
                int dataIndex = 0;

                while (dataIndex < bufferData.Length / bufferChannels && (preview.currentSample < currentPreviewSongSampleEnd) && (preview.scaledCurrentSample) < song.samples.Length)
                {
                    ctx.index         = dataIndex;
                    ctx.volume        = volume;
                    ctx.playbackSpeed = speed;
                    preview.CopySampleIntoBuffer(ctx);

                    PlayHitsounds(ctx, previewHitsoundEvents);

                    ++dataIndex;
                }

                if (preview.currentSample >= currentPreviewSongSampleEnd || (preview.scaledCurrentSample) >= song.samples.Length)
                {
                    playPreview = false;
                }
            }

            if (paused || (song.scaledCurrentSample) > song.samples.Length)
            {
                hitsoundEvents.Clear();

                //Continue to flush the hitsounds
                if (!playPreview && previewHitsoundEvents.Count > 0)
                {
                    int dataIndex = 0;

                    while (dataIndex < bufferData.Length / bufferChannels)
                    {
                        ctx.index  = dataIndex;
                        ctx.volume = hitSoundVolume;
                        PlayHitsounds(ctx, previewHitsoundEvents);
                        ++dataIndex;
                    }
                }

                return;
            }

            if (song != null)
            {
                QNT_Timestamp timeStart = timeline.ShiftTick(new QNT_Timestamp(0), (float)song.currentTime);
                QNT_Timestamp timeEnd   = timeline.ShiftTick(new QNT_Timestamp(0), (float)(song.currentTime + (bufferData.Length / bufferChannels) / (float)song.frequency * (song.frequency / (float)sampleRate)));

                if (timeEnd > hitSoundEnd)
                {
                    hitSoundEnd = timeStart + Constants.QuarterNoteDuration;
                    AddHitsoundEvents(hitsoundEvents, timeStart, hitSoundEnd);
                }
            }

            for (int dataIndex = 0; dataIndex < bufferData.Length / bufferChannels; dataIndex++)
            {
                ctx.volume        = volume;
                ctx.index         = dataIndex;
                ctx.playbackSpeed = speed;
                song.CopySampleIntoBuffer(ctx);

                if (songExtra != null)
                {
                    songExtra.CopySampleIntoBuffer(ctx);
                }

                //Play hitsounds (reverse iterate so we can remove)
                ctx.volume = hitSoundVolume;
                PlayHitsounds(ctx, hitsoundEvents);

                ctx.playbackSpeed = speed;
                if (leftSustain != null)
                {
                    ctx.volume = leftSustainVolume;
                    leftSustain.CopySampleIntoBuffer(ctx);
                }

                if (rightSustain != null)
                {
                    ctx.volume = rightSustainVolume;
                    rightSustain.CopySampleIntoBuffer(ctx);
                }

                QNT_Timestamp currentTick           = timeline.ShiftTick(new QNT_Timestamp(0), (float)GetTimeFromCurrentSample());
                TempoChange   currentTempo          = timeline.GetTempoForTime(currentTick);
                QNT_Duration  timeSignatureDuration = new QNT_Duration(Constants.PulsesPerWholeNote / currentTempo.timeSignature.Denominator);

                if (playMetronome)
                {
                    if (GetTimeFromCurrentSample() > nextMetronomeTick)
                    {
                        metronomeSamplesLeft = (int)(sampleRate * metronomeTickLength);
                        nextMetronomeTick    = 0;
                    }

                    if (nextMetronomeTick == 0)
                    {
                        QNT_Timestamp nextBeat = timeline.GetClosestBeatSnapped(timeline.ShiftTick(new QNT_Timestamp(0), (float)GetTimeFromCurrentSample()) + timeSignatureDuration, currentTempo.timeSignature.Denominator);
                        nextMetronomeTick = timeline.TimestampToSeconds(nextBeat);
                    }
                }

                //Metronome
                if (metronomeSamplesLeft > 0)
                {
                    const uint MetronomeTickFreq    = 817;
                    const uint BigMetronomeTickFreq = 1024;

                    QNT_Timestamp tempoStart = currentTempo.time;

                    uint tickFreq = 0;

                    UInt64 currentBeatInBar = ((currentTick.tick - tempoStart.tick) / timeSignatureDuration.tick) % currentTempo.timeSignature.Numerator;

                    if (currentBeatInBar == 0)
                    {
                        tickFreq = BigMetronomeTickFreq;
                    }
                    else
                    {
                        tickFreq = MetronomeTickFreq;
                    }

                    for (int c = 0; c < bufferChannels; ++c)
                    {
                        int   totalMetroSamples = (int)(sampleRate * metronomeTickLength);
                        float metro             = Mathf.Sin(Mathf.PI * 2 * tickFreq * ((totalMetroSamples - metronomeSamplesLeft) / (float)totalMetroSamples) * metronomeTickLength) * 0.33f;
                        bufferData[dataIndex * bufferChannels + c] = Mathf.Clamp(bufferData[dataIndex * bufferChannels + c] + metro, -1.0f, 1.0f);
                    }
                    metronomeSamplesLeft -= 1;
                }


                if (playClickTrack && currentTick < clickTrackEndTime)
                {
                    ctx.volume        = volume;
                    ctx.index         = dataIndex;
                    ctx.playbackSpeed = 1.0f;
                    GenerateClickTrackSamples(ctx, currentTick, currentTempo, timeSignatureDuration);
                }
            }
        }
Exemplo n.º 6
0
        //======Generate other data from known information===
        private void AnalyzeMIDI()
        {
            long tempo = 500000; //Default tempo in case none is given

            //Create lists and arrays
            //-to put PlayedNotes in per track
            //-to store notes that have been pressed but not released
            //-To track what event we're checking, per track
            List<List<PlayedNote>> lists = new List<List<PlayedNote>>();
            List<List<PlayedNote>> unfinishednotes = new List<List<PlayedNote>>();
            for (int i = 0; i < tracks.Length; i++)
            {
                lists.Add(new List<PlayedNote>());
                unfinishednotes.Add(new List<PlayedNote>());
            }

            int[] currentevent = new int[lists.Count]; //Tracks the current event # per list
            long[] currenttime = new long[lists.Count];
            for (int i = 0; i < lists.Count; i++)
            {
                currentevent[i] = 0;
                currenttime[i] = 0;
            }

            List<TempoChange> tempoChanges = new List<TempoChange>();
            this.tempoChanges = new TempoChange[0];

            //Start reading all the lists at the same time, in MIDI timing order
            bool reading = true;

            while (reading)
            {
                //Check which next MIDI event of all tracks has the lowest delta time
                long lowesttime = long.MaxValue;
                int lowestlist = -1;
                for (int i = 0; i < lists.Count; i++)
                {
                    if (currentevent[i] < tracks[i].events.Length) //Ignore if at end of track
                    {
                        if (tracks[i].events[currentevent[i]].time + currenttime[i] < lowesttime)
                        {
                            lowestlist = i;
                            lowesttime = tracks[i].events[currentevent[i]].time + currenttime[i];
                        }
                    }
                }
                if (lowestlist == -1) { reading = false; } //All tracks at end of track
                else //You got the current event, parse it
                {
                    long newtempo = tempo;
                    Event ev = tracks[lowestlist].events[currentevent[lowestlist]];
                    if (ev.type == EventType.Midi)
                    {
                        ReanalyzeType: //Jumps here if case NoteOn realizes it's actually a NoteOff
                        switch(ev.midiEvent.midiType)
                        {
                            case MidiType.NoteOn: //Note on event
                                {
                                    if (ev.midiEvent.note.velocity == 0)
                                    {
                                        ev.midiEvent.midiType = MidiType.NoteOff;
                                        goto ReanalyzeType; //Return to start of switch to fall to NoteOff instead
                                    }
                                    PlayedNote pn = new PlayedNote();
                                    pn.note = ev.midiEvent.note;
                                    pn.time = currenttime[lowestlist] + TicksToMicroSeconds(ev.time, tempo, currenttime[lowestlist]);
                                    unfinishednotes[lowestlist].Add(pn);
                                    break;
                                }

                            case MidiType.NoteOff: //Note off event
                                {
                                    PlayedNote pn = new PlayedNote();
                                    for (int i = 0; i < unfinishednotes[lowestlist].Count; i++)
                                    {
                                        if (unfinishednotes[lowestlist][i].note.number == ev.midiEvent.note.number)
                                        {
                                            pn = unfinishednotes[lowestlist][i];
                                        }
                                    }
                                    unfinishednotes[lowestlist].Remove(pn); //TODO: Improve, causes 33% of CPU usage of entire LoadMIDI(), has to look for IndexOf() which causes slowdown
                                    pn.length = (currenttime[lowestlist] + TicksToMicroSeconds(ev.time, tempo, currenttime[lowestlist])) - pn.time;
                                    float division = pn.length / tempo;
                                    Msg("Division: " + division);
                                    if (division > 7.9 && division < 8.1) pn.lengthtype = LengthType.DoubleWhole;
                                    else if (division > 3.9 && division < 4.1) pn.lengthtype = LengthType.Whole;
                                    else if (division > 1.9 && division < 2.1) pn.lengthtype = LengthType.Half;
                                    else if (division > 0.9 && division < 1.1) pn.lengthtype = LengthType.Quarter;
                                    else if (division > 0.45 && division < 0.55) pn.lengthtype = LengthType.Eighth;
                                    else if (division > 0.20 && division < 0.30) pn.lengthtype = LengthType.Sixteenth;
                                    else if (division > 0.1125 && division < 0.1375) pn.lengthtype = LengthType.ThirtySecond;
                                    else if (division > 0.0620 && division < 0.0630) pn.lengthtype = LengthType.SixtyFourth;
                                    //Why hello there Beethoven!
                                    else if (division > 0.03120 && division < 0.03130) pn.lengthtype = LengthType.HundredTwentyEight;
                                    else pn.lengthtype = LengthType.Unknown;

                                    lists[lowestlist].Add(pn);
                                    break;
                                }
                        }
                    }
                    else if (ev.type == EventType.Meta)
                    {
                        switch (ev.metaType)
                        {
                            case MetaType.Tempo:
                                TempoChange tc = new TempoChange();
                                tc.time = currenttime[lowestlist] + TicksToMicroSeconds(ev.time, tempo, currenttime[lowestlist]);
                                tc.oldTempo = tempo;
                                newtempo = ConvertThreeByteInt(ev.data);
                                if (currenttime[lowestlist] == 0) this.tempo = newtempo;
                                tc.newTempo = newtempo;

                                tempoChanges.Add(tc);
                                this.tempoChanges = tempoChanges.ToArray();
                                break;
                        }
                    }

                    currenttime[lowestlist] += TicksToMicroSeconds(ev.time, tempo, currenttime[lowestlist]);
                    tempo = newtempo;
                    currentevent[lowestlist]++;
                }
            }

            for (int i = 0; i < tracks.Length; i++)
            {
                tracks[i].notes = lists[i].ToArray();
            }
            this.tempoChanges = tempoChanges.ToArray();
        }
Exemplo n.º 7
0
        //Converts delta-time ticks (used for MIDI Event timing) to microseconds
        //Microseconds per tick = tempo / ticksPerQuarterNote
        public long TicksToMicroSeconds(long deltatime, long tempo, long trackLastEventTime)
        {
            long returnvalue = 0;
            long remainingDeltaTime = deltatime;

            if (tempoChanges.Length > 0)
            {
                TempoChange[] haxTempoChanges = new TempoChange[tempoChanges.Length + 1];
                for (int i = 0; i < tempoChanges.Length; i++) { haxTempoChanges[i] = tempoChanges[i]; }
                haxTempoChanges[haxTempoChanges.Length - 1] = new TempoChange()
                {
                    oldTempo = haxTempoChanges[haxTempoChanges.Length - 2].newTempo,
                    newTempo = haxTempoChanges[haxTempoChanges.Length - 2].newTempo,
                    time = long.MaxValue
                };

                long currentTime = trackLastEventTime;

                for (int i = 0; i < tempoChanges.Length - 1; i++)
                {
                    if (tempoChanges[i + 1].time <= currentTime)
                    {
                        //Skip this change; the next one starts before this is relevant
                    }
                    else
                    {
                        long endValue = tempoChanges[i + 1].time;

                        long timeToGrab = remainingDeltaTime * (tempoChanges[i].newTempo / header.ticksPerQuarterNote);
                        long deltaToRemove = remainingDeltaTime; //Take all by default
                        if (currentTime + timeToGrab > endValue)
                        {
                            //Whoa, you got too much, take a bit less
                            float percentageYouShouldGetInstead = (float)((float)endValue - (float)currentTime) / (float)timeToGrab;
                            deltaToRemove = Convert.ToInt64((float)deltaToRemove * (float)percentageYouShouldGetInstead);
                            timeToGrab = deltaToRemove * (tempoChanges[i].newTempo / header.ticksPerQuarterNote);
                        }

                        returnvalue += timeToGrab;
                        remainingDeltaTime -= deltaToRemove;
                    }
                }
            }

            returnvalue += remainingDeltaTime * (tempo / header.ticksPerQuarterNote);

            return returnvalue;
        }
Exemplo n.º 8
0
 public void SetInfoFromData(TempoChange tempoData)
 {
     this.tempoData = tempoData;
     bpm.text       = $"{Constants.DisplayBPMFromMicrosecondsPerQuaterNote(tempoData.microsecondsPerQuarterNote)} bpm";
     timestamp.text = GetTimestampFromSeconds(tempoData.secondsFromStart);
 }