UNPKG

tuneflow

Version:

Programmable, extensible music composition & arrangement

747 lines (719 loc) 21.8 kB
import { AutomationTarget, AutomationTargetType, AutomationValue, Song, TrackType, TuneflowPlugin, } from '../src'; describe('Automation-related Tests', () => { class TestUtilsPlugin extends TuneflowPlugin {} const testUtilsPlugin = new TestUtilsPlugin(); let song = new Song(); beforeEach(() => { song = new Song(); // @ts-ignore song.setPluginContextInternal(testUtilsPlugin); song.setResolution(480); song.createTempoChange({ ticks: 0, bpm: 120, }); song.createTrack({ type: TrackType.MIDI_TRACK }); }); describe('AutomationTarget', () => { it('Gets and sets properties correctly', async () => { let target = new AutomationTarget(AutomationTargetType.VOLUME); expect(target.getType()).toBe(AutomationTargetType.VOLUME); target = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'someplugininstanceid', 'someparamid', ); expect(target.getType()).toBe(AutomationTargetType.AUDIO_PLUGIN); expect(target.getPluginInstanceId()).toBe('someplugininstanceid'); expect(target.getParamId()).toBe('someparamid'); target.setPluginInstanceId('anotherplugininstanceid'); expect(target.getPluginInstanceId()).toBe('anotherplugininstanceid'); target.setParamId('anotherparamid'); expect(target.getParamId()).toBe('anotherparamid'); target.setType(AutomationTargetType.PAN); expect(target.getType()).toBe(AutomationTargetType.PAN); }); it('Encodes target correctly', async () => { const target1 = new AutomationTarget(AutomationTargetType.VOLUME); expect(target1.toTfAutomationTargetId()).toBe('1'); const target2 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); expect(target2.toTfAutomationTargetId()).toBe(`3^^pluginId1^^paramId1`); }); it('Checks equality correctly', async () => { const target1 = new AutomationTarget(AutomationTargetType.PAN); const target2 = new AutomationTarget(AutomationTargetType.PAN); const target3 = new AutomationTarget(AutomationTargetType.VOLUME); const target4 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); const target5 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId2', 'paramId2', ); const target6 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId2', 'paramId2', ); expect(target1.equals(target2)).toBe(true); expect(target1.equals(target3)).toBe(false); expect(target1.equals(target4)).toBe(false); expect(target4.equals(target5)).toBe(false); expect(target5.equals(target6)).toBe(true); }); it('Clones target correctly', async () => { const target1 = new AutomationTarget(AutomationTargetType.VOLUME); const target2 = target1.clone(); expect(target1 === target2).toBe(false); expect(target1.equals(target2)).toBe(true); }); }); describe('AutomationValue', () => { it('Sets and gets disabled correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getDisabled()).toBe(false); automationValue.setDisabled(true); expect(automationValue.getDisabled()).toBe(true); }); it('Adds and gets points correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, ]); automationValue.addPoint(/* tick= */ 2, /* value= */ 1); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 1, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, ]); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.1, /* overwrite= */ true); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.1, }, { id: expect.any(Number), tick: 3, value: 0.5, }, ]); }); it('gets points within range correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.65); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.55); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPointsInRange(2, 3)).toEqual([ { id: expect.any(Number), tick: 2, value: 0.55, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.65, }, { id: expect.any(Number), tick: 3, value: 0.5, }, ]); expect(automationValue.getPointsInRange(4, 3)).toEqual([]); expect(automationValue.getPointsInRange(5, 6)).toEqual([]); expect(automationValue.getPointsInRange(4, 5)).toEqual([ { id: expect.any(Number), tick: 4, value: 0.15, }, ]); expect(automationValue.getPointsInRange(0, 1)).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, ]); }); it('Removes points correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); const points = automationValue.getPoints(); const point2Id = points[2].id; automationValue.removePoints([points[0].id, points[1].id, points[3].id]); expect(automationValue.getPoints()).toEqual([ { id: point2Id, tick: 3, value: 0.5, }, ]); }); it('Removes points in range correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.65); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.55); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.55, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.65, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); automationValue.removePointsInRange(2, 3); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); }); it('Moves multiple points to left overwrite correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.65); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.95); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.85); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.35); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.95, }, { id: expect.any(Number), tick: 1, value: 0.65, }, { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.35, }, { id: expect.any(Number), tick: 2, value: 0.85, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); const points = automationValue.getPoints(); automationValue.movePoints([points[1].id, points[2].id, points[3].id, points[4].id], -1, 0.3); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 0, value: expect.closeTo(0.95), }, { id: expect.any(Number), tick: 0, value: expect.closeTo(0.55), }, { id: expect.any(Number), tick: 1, value: expect.closeTo(0.65), }, { id: expect.any(Number), tick: 1, value: 1, }, { id: expect.any(Number), tick: 2, value: expect.closeTo(0.75), }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.15), }, ]); }); it('Moves multiple points to right overwrite correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.65); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.95); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.85); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.35); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.95, }, { id: expect.any(Number), tick: 1, value: 0.65, }, { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.35, }, { id: expect.any(Number), tick: 2, value: 0.85, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); const points = automationValue.getPoints(); automationValue.movePoints([points[1].id, points[2].id, points[3].id, points[4].id], 2, -0.3); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: expect.closeTo(0.95), }, { id: expect.any(Number), tick: 3, value: expect.closeTo(0.35), }, { id: expect.any(Number), tick: 3, value: 0, }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.05), }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.55), }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.15), }, ]); }); it('Moves single point no overwrite correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); const points = automationValue.getPoints(); automationValue.movePoints([points[2].id], -2, 0.3, /* overwriteValuesInDragArea= */ false); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: expect.closeTo(0.25), }, { id: expect.any(Number), tick: 1, value: expect.closeTo(0.8), }, { id: expect.any(Number), tick: 2, value: expect.closeTo(0.75), }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.15), }, ]); }); it('Moves single point overwrite correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.65); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.85); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.85, }, { id: expect.any(Number), tick: 1, value: 0.65, }, { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); const points = automationValue.getPoints(); automationValue.movePoints([points[1].id], 3, -0.3); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: expect.closeTo(0.85), }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.35), }, { id: expect.any(Number), tick: 4, value: expect.closeTo(0.15), }, ]); }); it('Moves points in range no overwrite correctly', async () => { const automationValue = new AutomationValue(); expect(automationValue.getPoints()).toEqual([]); automationValue.addPoint(/* tick= */ 3, /* value= */ 0.5); automationValue.addPoint(/* tick= */ 1, /* value= */ 0.25); automationValue.addPoint(/* tick= */ 2, /* value= */ 0.75); automationValue.addPoint(/* tick= */ 4, /* value= */ 0.15); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 2, value: 0.75, }, { id: expect.any(Number), tick: 3, value: 0.5, }, { id: expect.any(Number), tick: 4, value: 0.15, }, ]); automationValue.movePointsInRange(2, 3, 4, 0, /* overwriteValuesInDragArea= */ false); expect(automationValue.getPoints()).toEqual([ { id: expect.any(Number), tick: 1, value: 0.25, }, { id: expect.any(Number), tick: 4, value: 0.15, }, { id: expect.any(Number), tick: 6, value: 0.75, }, { id: expect.any(Number), tick: 7, value: 0.5, }, ]); }); }); describe('AutomationData', () => { it('Adds automation correctly', async () => { const track = song.getTracks()[0]; const target = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); expect(track.getAutomation().getAutomationTargets()).toEqual([]); expect( track.getAutomation().getAutomationValueById(target.toTfAutomationTargetId()), ).toBeUndefined(); track.getAutomation().addAutomation(target); expect(track.getAutomation().getAutomationTargets().length).toBe(1); expect(track.getAutomation().getAutomationValueByTarget(target)).toBeTruthy(); expect(track.getAutomation().getAutomationValueById(target.toTfAutomationTargetId())).toBe( track.getAutomation().getAutomationValueByTarget(target), ); }); it('Removes automation correctly', async () => { const track = song.getTracks()[0]; const target = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); track.getAutomation().addAutomation(target); track.getAutomation().addAutomation(target); expect(track.getAutomation().getAutomationTargets().length).toBe(2); expect(track.getAutomation().getAutomationValueByTarget(target)).toBeTruthy(); track.getAutomation().removeAutomation(target); expect(track.getAutomation().getAutomationTargets().length).toBe(0); expect(track.getAutomation().getAutomationValueByTarget(target)).toBeFalsy(); }); it('Multiple automation targets update the same value', async () => { const track = song.getTracks()[0]; const target1 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); const target2 = new AutomationTarget( AutomationTargetType.AUDIO_PLUGIN, 'pluginId1', 'paramId1', ); track.getAutomation().addAutomation(target1); track.getAutomation().addAutomation(target2); expect(track.getAutomation().getAutomationTargets().length).toBe(2); expect( ( track.getAutomation().getAutomationValueByTarget(target1) as AutomationValue ).getDisabled(), ).toBe(false); expect( ( track.getAutomation().getAutomationValueByTarget(target2) as AutomationValue ).getDisabled(), ).toBe(false); (track.getAutomation().getAutomationValueByTarget(target1) as AutomationValue).setDisabled( true, ); expect( ( track.getAutomation().getAutomationValueByTarget(target2) as AutomationValue ).getDisabled(), ).toBe(true); }); }); });