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); } } }
void AddHitsoundEvents(List <HitsoundEvent> events, QNT_Timestamp start, QNT_Timestamp end) { if (Timeline.orderedNotes.Count == 0) { return; } if (start > end) { QNT_Timestamp temp = start; start = end; end = temp; } float startTime = timeline.TimestampToSeconds(start); foreach (Target t in new NoteEnumerator(start, end)) { TargetData data = t.data; if (data.time < start) { continue; } if (data.time > end) { return; } bool added = false; foreach (HitsoundEvent ev in events) { if (ev.ID == data.ID) { added = true; break; } } if (!added) { HitsoundEvent ev; ev.ID = data.ID; ev.currentSample = 0; ev.waitSamples = (UInt64)((timeline.TimestampToSeconds(data.time) - startTime) * sampleRate) << ClipData.PrecisionShift; ev.sound = kick; ev.time = data.time; ev.pan = (data.x / 7.15f); ev.volume = 1.0f; ev.xPos = data.x; switch (data.velocity) { case TargetVelocity.Standard: ev.sound = kick; break; case TargetVelocity.Percussion: ev.sound = percussion; break; case TargetVelocity.Snare: ev.sound = snare; break; case TargetVelocity.Chain: ev.sound = chainNote; break; case TargetVelocity.ChainStart: ev.sound = chainStart; break; case TargetVelocity.Melee: ev.sound = melee; break; case TargetVelocity.Mine: ev.sound = mine; break; default: continue; } //Only one hitsound at a time per point in time for (int j = events.Count - 1; j >= 0; j--) { if (events[j].sound == ev.sound) { if (events[j].waitSamples == ev.waitSamples) { events.RemoveAt(j); } } } events.Add(ev); } } }
public void PlayClickTrack(QNT_Timestamp endTime) { playClickTrack = true; clickTrackEndTime = endTime; clickTrackNextTick = 0; }
public static QNT_Timestamp GetSnappedValue(QNT_Timestamp time, uint beatSnap) { QNT_Duration snapValue = Constants.DurationFromBeatSnap(beatSnap); return((time / snapValue) * snapValue); }
public static List <float> Detect(ClipData src, Timeline timeline, QNT_Timestamp start, QNT_Timestamp end) { for (int i = 0; i < bpmMatchDatas.Length; i++) { bpmMatchDatas[i].match = 0f; } if (start == end) { return(new List <float>()); } if (end < start) { QNT_Timestamp temp = start; start = end; end = temp; } float startTime = timeline.TimestampToSeconds(start); float endTime = timeline.TimestampToSeconds(end); int channels = src.channels; int frequency = src.frequency; uint numSamples = (uint)(frequency * (endTime - startTime) * channels); int splitFrameSize = BASE_SPLIT_SAMPLE_SIZE; var volumeArr = new float[Mathf.CeilToInt((float)src.samples.Length / (float)splitFrameSize)]; int powerIndex = 0; // Sample data analysis start for (int sampleIndex = 0; sampleIndex < src.samples.Length; sampleIndex += splitFrameSize) { float sum = 0f; for (int frameIndex = sampleIndex; frameIndex < sampleIndex + splitFrameSize; frameIndex++) { if (src.samples.Length <= frameIndex) { break; } // Use the absolute value, because left and right value is -1 to 1 float absValue = Mathf.Abs(src.samples[frameIndex]); if (absValue > 1f) { continue; } // Calculate the amplitude square sum sum += (absValue * absValue); } // Set volume value volumeArr[powerIndex] = Mathf.Sqrt(sum / splitFrameSize); powerIndex++; } // Representing a volume value from 0 to 1 float maxVolume = volumeArr.Max(); for (int i = 0; i < volumeArr.Length; i++) { volumeArr[i] = volumeArr[i] / maxVolume; } // Create volume diff list var diffList = new List <float>(); for (int i = 1; i < volumeArr.Length; i++) { diffList.Add(Mathf.Max(volumeArr[i] - volumeArr[i - 1], 0f)); } // Calculate the degree of coincidence in each BPM int index = 0; float splitFrequency = (float)frequency / (float)splitFrameSize; for (int bpm = MIN_BPM; bpm <= MAX_BPM; bpm++) { float sinMatch = 0f; float cosMatch = 0f; float bps = (float)bpm / 60f; if (diffList.Count > 0) { for (int i = 0; i < diffList.Count; i++) { sinMatch += (diffList[i] * Mathf.Cos(i * 2f * Mathf.PI * bps / splitFrequency)); cosMatch += (diffList[i] * Mathf.Sin(i * 2f * Mathf.PI * bps / splitFrequency)); } sinMatch *= (1f / (float)diffList.Count); cosMatch *= (1f / (float)diffList.Count); } float match = Mathf.Sqrt((sinMatch * sinMatch) + (cosMatch * cosMatch)); bpmMatchDatas[index].bpm = bpm; bpmMatchDatas[index].match = match; index++; } // Returns a high degree of coincidence BPM int matchIndex = Array.FindIndex(bpmMatchDatas, x => x.match == bpmMatchDatas.Max(y => y.match)); var sort = bpmMatchDatas.OrderBy(x => x.match).Reverse().ToList(); List <float> results = new List <float>(); for (int i = 0; i < 10; ++i) { results.Add(sort[i].bpm); } return(results); }
public int CompareTo(QNT_Timestamp other) { return(tick.CompareTo(other.tick)); }