public static byte[] GetTempoEnvelope(int[] groove, int groovePadMode, bool palSource) { // Look in the cache first. var key = new CachedTempoEnvelopeKey() { groove = groove, groovePadMode = groovePadMode, palSource = palSource }; if (cachedTempoEnvelopes.TryGetValue(key, out var env)) { return(env); } // Otherwise build. var dstFactor = palSource ? 6 : 5; var srcFactor = palSource ? 5 : 6; var noteLength = Utils.Min(groove); var grooveNumFrames = Utils.Sum(groove); var grooveRepeatCount = 1; // Repeat the groove until we have something perfectly divisible by 6 (5 on PAL). while ((grooveNumFrames % srcFactor) != 0) { grooveNumFrames += Utils.Sum(groove); grooveRepeatCount++; } // Figure out how many frames that is on the playback machine. var adaptedNumFrames = grooveNumFrames / srcFactor * dstFactor; // Mark some frames as "important", this will typically be the first // and last frame of the note. This will preserve the attack and // 1-frame silence between notes. var importantFrames = new bool[grooveNumFrames]; var frameIndex = 0; for (int i = 0; i < grooveRepeatCount; i++) { for (int j = 0; j < groove.Length; j++) { if (groove[j] == noteLength) { importantFrames[frameIndex] = true; importantFrames[frameIndex + noteLength - 1] = true; } else { if (groovePadMode != GroovePaddingType.Beginning || noteLength == 1) { importantFrames[frameIndex] = true; } else { importantFrames[frameIndex + 1] = true; } if (groovePadMode != GroovePaddingType.End || noteLength == 1) { importantFrames[frameIndex + noteLength] = true; } else { importantFrames[frameIndex + noteLength - 1] = true; } } frameIndex += groove[j]; } } #if FALSE var numSkipFrames = palSource ? adaptedNumFrames - grooveNumFrames : grooveNumFrames - adaptedNumFrames; var bestScore = int.MaxValue; var bestOffset = -1; for (int i = 0; i < srcFactor; i++) { var score = 0; frameIndex = i; for (int j = 0; j < numSkipFrames; j++) { if (importantFrames[frameIndex]) { score++; } frameIndex += srcFactor; } if (score < bestScore) { bestScore = score; bestOffset = i; } } #else // Start by distributing the skip (or double) frames evenly. var numSkipFrames = palSource ? adaptedNumFrames - grooveNumFrames : grooveNumFrames - adaptedNumFrames; var skipFrames = new bool[grooveNumFrames]; frameIndex = srcFactor / 2; for (int i = 0; i < numSkipFrames; i++) { skipFrames[frameIndex] = true; frameIndex += srcFactor; } int GetFrameCost(int idx) { if (!skipFrames[idx]) { return(0); } var cost = 0; // Penalize important frames if (importantFrames[idx]) { cost += srcFactor; } // Look right for another skipped frame. for (int i = 1; i < srcFactor; i++) { var nextIdx = idx + i; if (nextIdx >= skipFrames.Length) { nextIdx -= skipFrames.Length; } if (skipFrames[nextIdx]) { // The closer we are, the higher the cost. cost += (srcFactor - i); break; } } // Look left for another skipped frame. for (int i = 1; i < srcFactor; i++) { var prevIdx = idx - i; if (prevIdx < 0) { prevIdx += skipFrames.Length; } // The closer we are, the higher the cost. if (skipFrames[prevIdx]) { cost += (srcFactor - i); break; } } return(cost); } var frameCosts = new int[grooveNumFrames]; // Optimize. for (int i = 0; i < 100; i++) { // Update costs. var maxCost = -10; var maxCostIndex = -1; var totalCost = 0; for (int j = 0; j < frameCosts.Length; j++) { var cost = GetFrameCost(j); frameCosts[j] = cost; totalCost += cost; if (cost > maxCost) { maxCost = cost; maxCostIndex = j; } } if (maxCost == 0) { break; } var currentFrameCost = GetFrameCost(maxCostIndex); // Try to optimize the most expensive frame by moving it to the left. if (maxCostIndex > 0 && !skipFrames[maxCostIndex - 1] && !importantFrames[maxCostIndex - 1]) { Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex - 1]); if (GetFrameCost(maxCostIndex - 1) < currentFrameCost) { continue; } Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex - 1]); } // Try to optimize the most expensive frame by moving it to the right. if (maxCostIndex < skipFrames.Length - 1 && !skipFrames[maxCostIndex + 1] && !importantFrames[maxCostIndex + 1]) { Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex + 1]); if (GetFrameCost(maxCostIndex + 1) < currentFrameCost) { continue; } Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex + 1]); } break; } #endif // Build the actual envelope. var lastFrameIndex = -1; var firstFrameIndex = -1; var envelope = new List <byte>(); var sum = 0; for (int i = 0; i < skipFrames.Length; i++) { if (skipFrames[i]) { var frameDelta = i - lastFrameIndex; envelope.Add((byte)(frameDelta + (palSource ? 1 : -1))); sum += frameDelta; lastFrameIndex = i; if (firstFrameIndex < 0) { firstFrameIndex = i; } } } if (palSource) { envelope[0]--; } var remainingFrames = skipFrames.Length - sum; if (remainingFrames != 0) { envelope.Add((byte)(remainingFrames + firstFrameIndex + 1 + (palSource ? 1 : -1))); } envelope.Add(0x80); env = envelope.ToArray(); cachedTempoEnvelopes[key] = env; return(env); }