Lay patterns and sounds out on tracks and play them as a song #334
Patterns could only be heard one at a time, in their own tab or from a behavior. A song lays patterns and sounds out on tracks along one time axis and plays them through the scheduler of #332, the third piece of the music tools of #330. - New asset type song under the heading Songs, with four empty tracks, 120 BPM in 4/4 and a grid of a bar. Tempo, meter and grid are rows in the inspector. Tracks, items and the loop are the asset's data, held to their rules on load and in every command. - A track has a name, volume, pan, mute and solo. Each is heard on a bus of its own, so a change reaches what is sounding at once. - A pattern item repeats its pattern for as long as it is, with a transpose and another instrument if wanted. A sound item plays its sound as a clip at its own speed, round and round, through a sound FX if wanted. Both have a volume on top of their track's. - Cut back at the left end or split, an item starts as far into what it plays as the cut is: ticks into a pattern, seconds into a sound. - The tab shows the heads with M, S, volume and pan, the ruler of the Pattern tab and the items, a pattern with its notes, a sound with its waveform, a missing asset red. Patterns and sounds are dragged in from the Assets panel. Body, right and left end move, lengthen and cut back, Alt leaves the grid. Ctrl+C/V/D/A, Ctrl+E splits where playing starts, Delete, arrows. A double-click opens a pattern item in its tab. - What is picked is a new selection of its own, so the inspector shows the item or the track, with a button to delete it. - The scheduler plans clips beside notes. Where the playhead starts or jumps back inside a clip, it is taken up that far into its sound. A clip fades out at the end of its item or of the loop. - The editor plays one piece at a time: a pattern started stops the song and the other way round. - The rules are pure functions in core/music/song, editor/song/ songView and songEdit. Findings: - The suites of4ea752fand2a3b90bwere red at e2e 54: the Pattern pushed Struct below the fold of the + menu. The module now scrolls to it, as e2e 47 does for Vector art. It also left the menu open, which made e2e 55 fail behind it. - The Assets panel only dragged assets a scene can place. Patterns and sounds are dragged now as well, and a scene says why it does not take one instead of calling every asset a scene. Tests: - song.test.ts (new, 14) and song-edit.test.ts (new, 9): model, round trip, sequence of patterns and clips, mute and solo, cuts, clips on the scheduler, moving, sizing, trimming, splitting, duplicating, hits, head parts. sound-library.test.ts +5: clip offset, end fade, pitched clip, track bus, peaks. - e2e 65 (new) on melektaus-atx: ALL PASSED, 18 checks. e2e 54 and 55 with the fix: ALL PASSED, 33 checks. e2e 4 with 30 asset types: ALL PASSED, 62 checks. - Gate on melektaus-atx: CHECK EXIT 0, 250 test files, 3884 tests. - Counter-checks: no taking up of a clip, no pattern offset, no clip end each fail 3, 1 and 1 tests. - Probe on stein2: the tab with four tracks, before and while playing, looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a3b90b017
commit
8f9c250eb6
43 files changed
+3863
-36
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import { findAssetOf } from '../../core/model/Project';
|
||||
import type { HeadPart } from '../song/songView';
|
||||
import { soundLibrary } from '../../viewport/SoundLibrary';
|
||||
import { centreOf, type HookContext } from './hookHelpers';
|
||||
|
||||
/** The Song tab and its tracks (#334). */
|
||||
export function songHooks({ state, editor }: HookContext) {
|
||||
const { songEditor } = editor;
|
||||
const timeline = songEditor.canvas;
|
||||
return {
|
||||
song: {
|
||||
current: () => songEditor.currentAssetId,
|
||||
info: () => songEditor.shownInfo,
|
||||
rect: () => songEditor.canvasRect(),
|
||||
/** The tracks, items and loop as the model holds them. */
|
||||
model: (assetId: string) => {
|
||||
const asset = findAssetOf(state.project, assetId, 'song');
|
||||
return asset
|
||||
? (JSON.parse(
|
||||
JSON.stringify({ tracks: asset.tracks, items: asset.items, loop: asset.loop ?? null }),
|
||||
) as unknown)
|
||||
: null;
|
||||
},
|
||||
picked: () => timeline.pickedIds,
|
||||
pickedTrack: () => timeline.pickedTrack,
|
||||
/** Where a tick of a track's lane, a part of an item, a part of a head and a tick of the ruler are on screen. */
|
||||
laneAt: (tick: number, track: number) => timeline.lanePoint(tick, track),
|
||||
itemAt: (itemId: string, part?: 'start' | 'body' | 'end') => timeline.itemPoint(itemId, part),
|
||||
headAt: (track: number, part: HeadPart, share?: number) => timeline.headPoint(track, part, share),
|
||||
addTrackAt: () => timeline.addTrackPoint(),
|
||||
rulerAt: (tick: number) => timeline.rulerPoint(tick),
|
||||
view: () => timeline.viewInfo(),
|
||||
items: () => timeline.itemsDrawn(),
|
||||
playAt: () => ({ ...centreOf(songEditor.playButton), text: songEditor.playButton.text }),
|
||||
fitAt: () => centreOf(songEditor.fitButton),
|
||||
playing: () => songEditor.isPlaying,
|
||||
/** The notes and clips the scheduler handed to the player since Play, and where it stands. */
|
||||
planned: () => [...songEditor.music.planned],
|
||||
clips: () => [...songEditor.music.clips],
|
||||
position: () => songEditor.music.current?.position() ?? null,
|
||||
clock: () => soundLibrary.clock(),
|
||||
stats: () => soundLibrary.stats(),
|
||||
buses: () => soundLibrary.busLevels(),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user