tuneflow
Version:
Programmable, extensible music composition & arrangement
1,105 lines (1,058 loc) • 33.9 kB
text/typescript
import {
AutomationTarget,
AutomationTargetType,
Clip,
Song,
TrackType,
TuneflowPlugin,
} from '../src';
import type { Note, AutomationValue } from '../src';
import { assertClipRange, assertNotesAreEqual, createTestNotes } from '../src/test_utils';
describe('MIDI Clip-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,
});
const track = song.createTrack({ type: TrackType.MIDI_TRACK });
const clip1 = track.createMIDIClip({
clipStartTick: 0,
});
clip1.createNote({
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
});
clip1.createNote({
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
});
clip1.createNote({
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
});
clip1.adjustClipLeft(0);
clip1.adjustClipRight(15);
const clip2 = track.createMIDIClip({
clipStartTick: 21,
});
clip2.createNote({
pitch: 66,
velocity: 80,
startTick: 25,
endTick: 30,
});
clip2.createNote({
pitch: 64,
velocity: 80,
startTick: 18,
endTick: 30,
});
clip2.createNote({
pitch: 68,
velocity: 80,
startTick: 24,
endTick: 35,
});
clip2.adjustClipLeft(21);
clip2.adjustClipRight(30);
const clip3 = track.createMIDIClip({
clipStartTick: 40,
});
clip3.createNote({
pitch: 71,
velocity: 80,
startTick: 55,
endTick: 65,
});
clip3.createNote({
pitch: 69,
velocity: 80,
startTick: 45,
endTick: 50,
});
clip3.createNote({
pitch: 67,
velocity: 80,
startTick: 40,
endTick: 45,
});
clip3.adjustClipLeft(40);
clip3.adjustClipRight(65);
});
describe('isNoteInClip', () => {
it('Checks isNoteInClip correctly', () => {
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 4,
/* noteEndTick= */ 5,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeFalsy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 5,
/* noteEndTick= */ 10,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeFalsy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 6,
/* noteEndTick= */ 10,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeTruthy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 6,
/* noteEndTick= */ 12,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeTruthy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 7,
/* noteEndTick= */ 12,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeTruthy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 7,
/* noteEndTick= */ 13,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeTruthy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 12,
/* noteEndTick= */ 13,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeFalsy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 13,
/* noteEndTick= */ 15,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeFalsy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ 5,
/* noteEndTick= */ 13,
/* clipStartTick= */ 6,
/* clipEndTick= */ 12,
),
).toBeFalsy();
expect(
Clip.isNoteInClip(
/* noteStartTick= */ -13,
/* noteEndTick= */ 5,
/* clipStartTick= */ 0,
/* clipEndTick= */ 12,
),
).toBeTruthy();
});
});
describe('Basic operations', () => {
it('Gets clip ranges correctly.', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
expect(clip1.getClipStartTick()).toBe(0);
expect(clip1.getClipEndTick()).toBe(15);
});
it("Gets notes within the clip's range correctly.", async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
const clip2 = track.getClips()[1];
const clip3 = track.getClips()[2];
// Notes that are at the edge are included.
assertNotesAreEqual(
clip1.getNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
],
clip1,
),
);
// Note that start before the clip start are excluded.
assertNotesAreEqual(
clip2.getNotes(),
createTestNotes(
[
{
pitch: 68,
velocity: 80,
startTick: 24,
endTick: 35,
},
{
pitch: 66,
velocity: 80,
startTick: 25,
endTick: 30,
},
],
clip2,
),
);
// Note are sorted and all notes that are within clip are returned.
assertNotesAreEqual(
clip3.getNotes(),
createTestNotes(
[
{
pitch: 67,
velocity: 80,
startTick: 40,
endTick: 45,
},
{
pitch: 69,
velocity: 80,
startTick: 45,
endTick: 50,
},
{
pitch: 71,
velocity: 80,
startTick: 55,
endTick: 65,
},
],
clip3,
),
);
});
it('Clears notes correctly', async () => {
const clip1 = song.getTracks()[0].getClips()[0];
expect(clip1.getRawNotes().length).toBe(3);
clip1.clearNotes();
expect(clip1.getRawNotes().length).toBe(0);
});
});
describe('Create notes', () => {
it('Rejects if creating note with invalid pitch', async () => {
const clip1 = song.getTracks()[0].getClips()[0];
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
// Create notes with invalid pitch.
const createdNote1 = clip1.createNote({
pitch: 128,
velocity: 80,
startTick: 2,
endTick: 8,
});
expect(createdNote1).toBeNull();
const createdNote2 = clip1.createNote({
pitch: -1,
velocity: 80,
startTick: 2,
endTick: 8,
});
expect(createdNote2).toBeNull();
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
});
it('Rejects if creating note with invalid velocity', async () => {
const clip1 = song.getTracks()[0].getClips()[0];
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
// Create notes with invalid pitch.
const createdNote1 = clip1.createNote({
pitch: 58,
velocity: 128,
startTick: 2,
endTick: 8,
});
expect(createdNote1).toBeNull();
const createdNote2 = clip1.createNote({
pitch: 56,
velocity: -1,
startTick: 2,
endTick: 8,
});
expect(createdNote2).toBeNull();
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
});
it('Rejects if creating note with invalid range', async () => {
const clip1 = song.getTracks()[0].getClips()[0];
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
// Create notes with invalid pitch.
const createdNote1 = clip1.createNote({
pitch: 58,
velocity: 80,
startTick: -20,
endTick: -1,
});
expect(createdNote1).toBeNull();
const createdNote2 = clip1.createNote({
pitch: 56,
velocity: 80,
startTick: 9,
endTick: 8,
});
expect(createdNote2).toBeNull();
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
});
it('Creates a note correctly if creating note with valid params', async () => {
const clip1 = song.getTracks()[0].getClips()[0];
expect(clip1.getRawNotes().length).toBe(3);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
// Create notes with invalid pitch.
const createdNote1 = clip1.createNote({
pitch: 58,
velocity: 80,
startTick: 12,
endTick: 14,
});
assertNotesAreEqual(
[createdNote1 as Note],
createTestNotes(
[
{
pitch: 58,
velocity: 80,
startTick: 12,
endTick: 14,
},
],
clip1,
),
);
expect(clip1.getRawNotes().length).toBe(4);
assertNotesAreEqual(
clip1.getRawNotes(),
createTestNotes(
[
{
pitch: 64,
velocity: 80,
startTick: 0,
endTick: 10,
},
{
pitch: 58,
velocity: 80,
startTick: 12,
endTick: 14,
},
{
pitch: 68,
velocity: 80,
startTick: 14,
endTick: 20,
},
{
pitch: 66,
velocity: 80,
startTick: 15,
endTick: 20,
},
],
clip1,
),
);
});
});
describe('Move clips', () => {
it('Moves clip - no overlapping', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClipTo(70, /* moveAssociatedTrackAutomationPoints= */ false);
assertClipRange(clip1, 70, 85);
assertClipRange(clip2, 21, 30);
assertClipRange(clip3, 40, 65);
});
it('Moves clip - overlaps on the left side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClipTo(10, /* moveAssociatedTrackAutomationPoints= */ false);
assertClipRange(clip1, 10, 25);
assertClipRange(clip2, 26, 30);
assertClipRange(clip3, 40, 65);
});
it('Moves clip - overlaps on the right side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClipTo(60, /* moveAssociatedTrackAutomationPoints= */ false);
assertClipRange(clip1, 60, 75);
assertClipRange(clip2, 21, 30);
assertClipRange(clip3, 40, 59);
});
it('Moves clip - overlaps on the left and the right side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClipTo(28, /* moveAssociatedTrackAutomationPoints= */ false);
assertClipRange(clip1, 28, 43);
assertClipRange(clip2, 21, 27);
assertClipRange(clip3, 44, 65);
});
it('Moves clip - overlaps in the middle', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
let clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.moveClipTo(42, /* moveAssociatedTrackAutomationPoints= */ false);
expect(track.getClips().length).toBe(4);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
clip2 = track.getClips()[1];
assertClipRange(clip2, 40, 41);
clip3 = track.getClips()[2];
assertClipRange(clip3, 42, 51);
const clip4 = track.getClips()[3];
assertClipRange(clip4, 52, 65);
});
it('Moves clip - delete out of range clip', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClip(-99999, /* moveAssociatedTrackAutomationPoints= */ false);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
assertClipRange(clip1, 21, 30);
clip2 = track.getClips()[1];
assertClipRange(clip2, 40, 65);
});
it('Moves clip - move associated track automation points.', async () => {
const track = song.getTracks()[0];
const automationTarget = new AutomationTarget(AutomationTargetType.PAN);
track.getAutomation().addAutomation(automationTarget);
track
.getAutomation()
.getOrCreateAutomationValueById(automationTarget.toTfAutomationTargetId())
.addPoint(7, 0.7);
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.moveClipTo(70, /* moveAssociatedTrackAutomationPoints= */ true);
assertClipRange(clip1, 70, 85);
assertClipRange(clip2, 21, 30);
assertClipRange(clip3, 40, 65);
expect(
(
track.getAutomation().getAutomationValueByTarget(automationTarget) as AutomationValue
).getPoints(),
).toEqual([
{
id: expect.any(Number),
tick: 77,
value: 0.7,
},
]);
});
});
describe('Adjust clip ranges', () => {
it('Adjusts clip left - no overlapping', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.adjustClipLeft(35);
assertClipRange(clip1, 0, 15);
assertClipRange(clip2, 21, 30);
assertClipRange(clip3, 35, 65);
});
it('Adjusts clip left - overlaps on the right side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.adjustClipLeft(25);
assertClipRange(clip1, 0, 15);
assertClipRange(clip2, 21, 24);
assertClipRange(clip3, 25, 65);
});
it('Adjusts clip left - overlaps on the whole clip and on the right', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.adjustClipLeft(10);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
clip2 = track.getClips()[1];
assertClipRange(clip1, 0, 9);
assertClipRange(clip2, 10, 65);
});
it('Adjusts clip left - overlaps two whole clips', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.adjustClipLeft(-10);
expect(track.getClips().length).toBe(1);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 65);
});
it('Adjusts clip right - no overlapping', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.adjustClipRight(18);
assertClipRange(clip1, 0, 18);
assertClipRange(clip2, 21, 30);
assertClipRange(clip3, 40, 65);
});
it('Adjusts clip right - overlaps on the left side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.adjustClipRight(25);
assertClipRange(clip1, 0, 25);
assertClipRange(clip2, 26, 30);
assertClipRange(clip3, 40, 65);
});
it('Adjusts clip right - overlaps on the whole clip and on the left', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.adjustClipRight(50);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
clip2 = track.getClips()[1];
assertClipRange(clip1, 0, 50);
assertClipRange(clip2, 51, 65);
});
it('Adjusts clip right - overlaps two whole clips', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.adjustClipRight(1000);
expect(track.getClips().length).toBe(1);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 1000);
});
it('Adjusts clip range - no overlapping', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(18);
clip2.adjustClipRight(35);
assertClipRange(clip1, 0, 15);
assertClipRange(clip2, 18, 35);
assertClipRange(clip3, 40, 65);
});
it('Adjusts clip range - overlaps on the left side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(10);
clip2.adjustClipRight(35);
assertClipRange(clip1, 0, 9);
assertClipRange(clip2, 10, 35);
assertClipRange(clip3, 40, 65);
});
it('Adjusts clip range - overlaps on the whole clip to the left', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(0);
clip2.adjustClipRight(35);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
clip2 = track.getClips()[1];
assertClipRange(clip1, 0, 35);
assertClipRange(clip2, 40, 65);
});
it('Adjusts clip range - overlaps on the right side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(21);
clip2.adjustClipRight(45);
assertClipRange(clip1, 0, 15);
assertClipRange(clip2, 21, 45);
assertClipRange(clip3, 46, 65);
});
it('Adjusts clip range - overlaps on the whole clip to the right', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(21);
clip2.adjustClipRight(1000);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
clip2 = track.getClips()[1];
assertClipRange(clip1, 0, 15);
assertClipRange(clip2, 21, 1000);
});
it('Adjusts clip range - overlaps on the left and the right side', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(10);
clip2.adjustClipRight(45);
assertClipRange(clip1, 0, 9);
assertClipRange(clip2, 10, 45);
assertClipRange(clip3, 46, 65);
});
it('Adjusts clip range - overlaps two whole clips', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.adjustClipLeft(0);
clip2.adjustClipRight(1000);
expect(track.getClips().length).toBe(1);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 1000);
});
});
describe('Insert clip', () => {
it('Insert clip - no overlapping', async () => {
const track = song.getTracks()[0];
track.createMIDIClip({
clipStartTick: 32,
clipEndTick: 38,
});
expect(track.getClips().length).toBe(4);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 32, 38);
const clip4 = track.getClips()[3];
assertClipRange(clip4, 40, 65);
});
it('Insert clip - overlaps with left and right', async () => {
const track = song.getTracks()[0];
track.createMIDIClip({
clipStartTick: 25,
clipEndTick: 45,
});
expect(track.getClips().length).toBe(4);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 24);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 25, 45);
const clip4 = track.getClips()[3];
assertClipRange(clip4, 46, 65);
});
it('Insert clip - overlaps with whole clips', async () => {
const track = song.getTracks()[0];
track.createMIDIClip({
clipStartTick: 21,
clipEndTick: 65,
});
expect(track.getClips().length).toBe(2);
const clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
const clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 65);
});
});
describe('Delete clip', () => {
it('Delete first clip', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip1.deleteFromParent(/* deleteAssociatedTrackAutomation= */ false);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
assertClipRange(clip1, 21, 30);
clip2 = track.getClips()[1];
assertClipRange(clip2, 40, 65);
});
it('Delete middle clip', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip2.deleteFromParent(/* deleteAssociatedTrackAutomation= */ false);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
clip2 = track.getClips()[1];
assertClipRange(clip2, 40, 65);
});
it('Delete last clip', async () => {
const track = song.getTracks()[0];
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.deleteFromParent(/* deleteAssociatedTrackAutomation= */ false);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
});
it('Delete last clip - delete associated track automation', async () => {
const track = song.getTracks()[0];
const automationTarget = new AutomationTarget(AutomationTargetType.PAN);
track.getAutomation().addAutomation(automationTarget);
track
.getAutomation()
.getOrCreateAutomationValueById(automationTarget.toTfAutomationTargetId())
.addPoint(47, 0.7);
expect(
(
track.getAutomation().getAutomationValueByTarget(automationTarget) as AutomationValue
).getPoints(),
).toEqual([
{
id: expect.any(Number),
tick: 47,
value: 0.7,
},
]);
expect(track.getClips().length).toBe(3);
let clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
let clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
const clip3 = track.getClips()[2];
assertClipRange(clip3, 40, 65);
clip3.deleteFromParent(/* deleteAssociatedTrackAutomation= */ true);
expect(track.getClips().length).toBe(2);
clip1 = track.getClips()[0];
assertClipRange(clip1, 0, 15);
clip2 = track.getClips()[1];
assertClipRange(clip2, 21, 30);
expect(
(
track.getAutomation().getAutomationValueByTarget(automationTarget) as AutomationValue
).getPoints(),
).toEqual([]);
});
});
});