如何生成无节拍偏移重复的3-11拍1-12音符旋律字符串?
旋律组合生成需求与算法实现
需求概述
需要生成满足以下条件的所有旋律字符串:
- 旋律时长范围:3至11拍
- 可用音符数量范围:1至12个
- 排除仅因节拍偏移导致重复的旋律(例如
1 2 3 4 1 2 3 4和2 3 4 1 2 3 4 1视为重复,只保留其中一种)
旋律示例
[4/4拍]
1 2 3 4 1 2 3 4 ...
[5/4拍]
1 2 1 2 3 1 2 1 2 3 ...
1 2 3 4 5 1 2 3 4 5 ...
2 3 4 5 6 2 3 4 5 6 ...
需要排除的重复类型
1 2 3 4 1 2 3 4 ...
2 3 4 1 2 3 4 1 ...
3 4 1 2 3 4 1 2 ...
目标算法逻辑
期望实现的核心逻辑如下:
function getMelodies(numBeats, numNotes) { // 1. 生成所有符合音符数量要求的音符组合 // 2. 生成每个组合的所有变体 // 3. 过滤掉因节拍偏移产生的重复项 }
现有代码问题
当前尝试的代码仅固定生成4拍相关的有限组合,既未覆盖3-11拍的时长范围,也未处理音符数量的动态变化,更没有实现去重逻辑,无法满足需求:
function getMelodies(beats, notes) { let array = [] let i = 0 while (i < notes) { array.push([i, i, i, i]) array.push([i, i + 1, i + 1, i + 1]); array.push([i, i + 2, i + 2, i + 2]); } }
参考结论
根据数学推导,当节拍数为4、可用音符数为7时,符合要求的旋律共有616种。
改进实现思路与代码
核心步骤拆解
- 生成基础音符序列:根据指定节拍数和可用音符数,生成所有对应长度的音符序列(每个位置取1到可用音符数的整数)
- 去重节拍偏移重复项:对每个序列生成所有循环偏移变体,仅保留每个等价类的唯一代表(如取字典序最小的序列)
- 覆盖多范围参数:遍历3至11的所有节拍数、1至12的所有可用音符数,收集所有符合条件的旋律
实现代码
function getMelodies(minBeats = 3, maxBeats = 11, minNotes = 1, maxNotes = 12) { const allMelodies = new Set(); // 遍历所有符合要求的节拍数与可用音符数组合 for (let numBeats = minBeats; numBeats <= maxBeats; numBeats++) { for (let numNotes = minNotes; numNotes <= maxNotes; numNotes++) { // 递归生成所有长度为numBeats的音符序列 const generateSequences = (length, current = []) => { if (current.length === length) { // 生成当前序列的所有循环偏移变体 const variants = []; for (let offset = 0; offset < length; offset++) { const variant = [...current.slice(offset), ...current.slice(0, offset)]; variants.push(variant.join(' ')); } // 取字典序最小的变体作为唯一标识,避免节拍偏移重复 const uniqueKey = variants.sort()[0]; allMelodies.add(uniqueKey); return; } // 填充每个位置的可能音符 for (let note = 1; note <= numNotes; note++) { generateSequences(length, [...current, note]); } }; generateSequences(numBeats); } } return Array.from(allMelodies); } // 示例:验证4拍7音的旋律数量(应返回616) const fourBeatSevenNotes = getMelodies(4, 4, 7, 7); console.log(fourBeatSevenNotes.length); // 输出616
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

