@temper.sh/pitch
Version:
Utilities for working with pitch in arbitrary equal temperament spaces
88 lines (78 loc) • 3.5 kB
text/typescript
import { expect, suite, test } from 'vitest'
import { findMostLeftCompactRotation } from './findMostLeftCompactRotation'
suite.concurrent('findMostLeftCompactRotation', () => {
test('returns single candidate unchanged', () => {
const candidates = [[0, 4, 7]]
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 4, 7])
})
test('chooses rotation with smallest first-to-last span', () => {
const candidates = [
[], // span: 7-0 = 7
[], // span: (0+12)-4 = 8
[], // span: (4+12)-7 = 9
]
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 4, 7])
})
test('uses recursive tie-breaking for first-to-last ties', () => {
const candidates = [
[], // first-to-last: 10-0 = 10, first-to-second-last: 8-0 = 8
[], // first-to-last: (0+12)-2 = 10, first-to-second-last: 10-2 = 8
[], // first-to-last: (2+12)-4 = 10, first-to-second-last: (0+12)-4 = 8
]
// All have same first-to-last and first-to-second-last spans, should return first
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 2, 4, 6, 8, 10])
})
test('breaks ties at deeper levels of recursion', () => {
const candidates = [
[], // spans: 6-0=6, 5-0=5, 1-0=1
[], // spans: (0+12)-1=11, 6-1=5, 5-1=4
[], // spans: (1+12)-5=8, (0+12)-5=7, 6-5=1
[], // spans: (5+12)-6=11, 1-6+12=7, (0+12)-6=6
]
// Should choose [0, 1, 5, 6] as it has smallest first-to-last span (6)
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 1, 5, 6])
})
test('handles diminished chord (all intervals equal)', () => {
const candidates = [
[], // all spans: 9-0=9, 6-0=6, 3-0=3
[], // all spans: (0+12)-3=9, 9-3=6, 6-3=3
[], // all spans: (3+12)-6=9, (0+12)-6=6, 9-6=3
[], // all spans: (6+12)-9=9, 3-9+12=6, (0+12)-9=3
]
// All have identical spans at every level, should return first
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 3, 6, 9])
})
test('handles two-element rotations', () => {
const candidates = [
[], // span: 7-0 = 7
[], // span: (0+12)-7 = 5
]
expect(findMostLeftCompactRotation(candidates)).toEqual([7, 0])
})
test('handles three-element rotations with clear winner', () => {
const candidates = [
[], // spans: 8-0=8, 1-0=1
[], // spans: (0+12)-1=11, 8-1=7
[], // spans: (1+12)-8=5, (0+12)-8=4
]
expect(findMostLeftCompactRotation(candidates)).toEqual([8, 0, 1])
})
test('handles chromatic clusters', () => {
const candidates = [
[], // spans: 3-0=3, 2-0=2, 1-0=1
[], // spans: (0+12)-1=11, 3-1=2, 2-1=1
[], // spans: (1+12)-2=11, (0+12)-2=10, 3-2=1
[], // spans: (2+12)-3=11, 1-3+12=10, (0+12)-3=9
]
expect(findMostLeftCompactRotation(candidates)).toEqual([0, 1, 2, 3])
})
test('handles edge case with wrap-around calculations', () => {
const candidates = [
[], // spans: (1+12)-10=3, (0+12)-10=2, 11-10=1
[], // spans: (10+12)-11=11, 1-11+12=2, (0+12)-11=1
[], // spans: 11-0=11, 10-0=10, 1-0=1
[], // spans: (0+12)-1=11, 11-1=10, 10-1=9
]
expect(findMostLeftCompactRotation(candidates)).toEqual([10, 11, 0, 1])
})
})