mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 05:38:35 +00:00
transpose: support alls combinations of numbers and strings for notes and intervals
This commit is contained in:
parent
558e9e36ff
commit
3d06528393
@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
// import { strict as assert } from 'assert';
|
||||
|
||||
import '../tonal.mjs'; // need to import this to add prototypes
|
||||
import { pure, n, seq } from '@strudel/core';
|
||||
import { pure, n, seq, note } from '@strudel/core';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
|
||||
@ -44,4 +44,36 @@ describe('tonal', () => {
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C3', 'D3', 'E3']);
|
||||
});
|
||||
it('transposes note numbers with interval numbers', () => {
|
||||
expect(
|
||||
note(40, 40, 40)
|
||||
.transpose(0, 1, 2)
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual([40, 41, 42]);
|
||||
expect(seq(40, 40, 40).transpose(0, 1, 2).firstCycleValues).toEqual([40, 41, 42]);
|
||||
});
|
||||
it('transposes note numbers with interval strings', () => {
|
||||
expect(
|
||||
note(40, 40, 40)
|
||||
.transpose('1P', '2M', '3m')
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual([40, 42, 43]);
|
||||
expect(seq(40, 40, 40).transpose('1P', '2M', '3m').firstCycleValues).toEqual([40, 42, 43]);
|
||||
});
|
||||
it('transposes note strings with interval numbers', () => {
|
||||
expect(
|
||||
note('c', 'c', 'c')
|
||||
.transpose(0, 1, 2)
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C', 'Db', 'D']);
|
||||
expect(seq('c', 'c', 'c').transpose(0, 1, 2).firstCycleValues).toEqual(['C', 'Db', 'D']);
|
||||
});
|
||||
it('transposes note strings with interval strings', () => {
|
||||
expect(
|
||||
note('c', 'c', 'c')
|
||||
.transpose('1P', '2M', '3m')
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C', 'D', 'Eb']);
|
||||
expect(seq('c', 'c', 'c').transpose('1P', '2M', '3m').firstCycleValues).toEqual(['C', 'D', 'Eb']);
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,18 +96,37 @@ function scaleOffset(scale, offset, note) {
|
||||
|
||||
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
const interval = !isNaN(Number(intervalOrSemitones))
|
||||
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||
: String(intervalOrSemitones);
|
||||
if (typeof hap.value === 'number') {
|
||||
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
||||
return hap.withValue(() => hap.value + semitones);
|
||||
const note = hap.value.note ?? hap.value;
|
||||
if (typeof note === 'number') {
|
||||
// note is a number, so just add the number semitones of the interval
|
||||
let semitones;
|
||||
if (typeof intervalOrSemitones === 'number') {
|
||||
semitones = intervalOrSemitones;
|
||||
} else if (typeof intervalOrSemitones === 'string') {
|
||||
semitones = Interval.semitones(intervalOrSemitones) || 0;
|
||||
}
|
||||
const targetNote = note + semitones;
|
||||
if (typeof hap.value === 'object') {
|
||||
return hap.withValue(() => ({ ...hap.value, note: targetNote }));
|
||||
}
|
||||
return hap.withValue(() => targetNote);
|
||||
}
|
||||
if (typeof hap.value === 'object')
|
||||
return hap.withValue(() => ({ ...hap.value, note: Note.simplify(Note.transpose(hap.value.note, interval)) }));
|
||||
if (typeof note !== 'string' || !isNote(note)) {
|
||||
logger(`[tonal] transpose: not a note "${note}"`, 'warning');
|
||||
return hap;
|
||||
}
|
||||
// note is a string, so we might be able to preserve harmonics if interval is a string as well
|
||||
const interval = !isNaN(Number(intervalOrSemitones))
|
||||
? Interval.fromSemitones(intervalOrSemitones)
|
||||
: String(intervalOrSemitones);
|
||||
// TODO: move simplify to player to preserve enharmonics
|
||||
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
||||
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
|
||||
// TODO: check if this is still relevant..
|
||||
const targetNote = Note.simplify(Note.transpose(note, interval));
|
||||
if (typeof hap.value === 'object') {
|
||||
return hap.withValue(() => ({ ...hap.value, note: targetNote }));
|
||||
}
|
||||
return hap.withValue(() => targetNote);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user