Pick a pattern's sign in one row, as a song's (#364)
A pattern had two rows for its meter, Beats per bar and Beat, while a song has had one row Sign since #363. Both now pick from the same list. - The list of signs and how a sign becomes a meter moved to time.ts. Song and pattern share it. - A pattern keeps its meter in `sign`. The fields `beats` and `beat` are gone, a sign that is not in the list counts as 4/4. - Create pattern in a song hands the song's sign to the new pattern. - The four patterns of cbbf160bb7e8 were moved to `sign` by hand (three 4/4, one 3/4). The other projects only had 4/4, which stays the default. Tests: - Unit pattern/embedded: 30 green (1 new, 2 rewritten), the counter-test reading `beats` again turns 2 red - e2e 64 + 65 (melektaus-atx): ALL PASSED, 85 checks - Gate (melektaus-atx): CHECK EXIT 0, 272 files, 4215 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d57a2f3c4
commit
8891f027db
9 files changed
+60
-67
No files matched your search
@@ -15,8 +15,9 @@ import { findAsset, findAssetOf, type Project } from '../model/Project';
|
||||
import { assetNamedBy, renewClipIds } from '../model/sound';
|
||||
import { ASSET_FX_KEY, assetFxOf, NO_FX } from '../properties/assetFx';
|
||||
import { nextNumberedName } from '../commands/naming';
|
||||
import { PATTERN_BEAT_KEY, PATTERN_BEATS_KEY, PATTERN_TEMPO_KEY, patternInstrument } from './pattern';
|
||||
import { PATTERN_SIGN_KEY, PATTERN_TEMPO_KEY, patternInstrument } from './pattern';
|
||||
import { type SongItem, songMeter, songTempo, type SongTrack } from './song';
|
||||
import { signOf } from './time';
|
||||
|
||||
export const EMBEDDABLE_TYPES: readonly AssetType[] = ['sound', 'pattern', 'soundfx'];
|
||||
|
||||
@@ -71,12 +72,7 @@ export function assetTitle(project: Project, asset: Asset): string {
|
||||
|
||||
/** The tempo and the meter of a song, as the props a pattern made in it starts with. */
|
||||
export function songClockProps(props: PropertyBag): PropertyBag {
|
||||
const meter = songMeter(props);
|
||||
return {
|
||||
[PATTERN_TEMPO_KEY]: songTempo(props),
|
||||
[PATTERN_BEATS_KEY]: meter.beats,
|
||||
[PATTERN_BEAT_KEY]: String(meter.unit),
|
||||
};
|
||||
return { [PATTERN_TEMPO_KEY]: songTempo(props), [PATTERN_SIGN_KEY]: signOf(songMeter(props)) };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ import { HIGHEST_NOTE, LOWEST_NOTE } from '../instrument/notes';
|
||||
import type { PatternAsset } from '../model/Asset';
|
||||
import { type PropertyBag, textOf } from '../model/Properties';
|
||||
import { heldLoop, type LoopRange, type Sequence } from './sequence';
|
||||
import { heldMeter, heldTempo, type Meter, ticksPerBar } from './time';
|
||||
import { DEFAULT_SIGN, heldTempo, type Meter, signMeter, ticksPerBar } from './time';
|
||||
|
||||
export interface PatternNote {
|
||||
id: string;
|
||||
@@ -24,8 +24,7 @@ export interface PatternNote {
|
||||
export const PATTERN_INSTRUMENT_KEY = 'instrument';
|
||||
export const PATTERN_BARS_KEY = 'bars';
|
||||
export const PATTERN_GRID_KEY = 'grid';
|
||||
export const PATTERN_BEATS_KEY = 'beats';
|
||||
export const PATTERN_BEAT_KEY = 'beat';
|
||||
export const PATTERN_SIGN_KEY = 'sign';
|
||||
export const PATTERN_TEMPO_KEY = 'tempo';
|
||||
|
||||
export const DEFAULT_BARS = 4;
|
||||
@@ -49,7 +48,6 @@ const GRIDS: readonly (readonly [string, number])[] = [
|
||||
];
|
||||
|
||||
export const GRID_OPTIONS = GRIDS.map(([value]) => ({ value, label: value }));
|
||||
export const BEAT_OPTIONS = [1, 2, 4, 8, 16, 32].map((unit) => ({ value: String(unit), label: `1/${unit}` }));
|
||||
|
||||
export function gridTicks(grid: string): number {
|
||||
return (GRIDS.find(([value]) => value === grid) ?? GRIDS[4]!)[1];
|
||||
@@ -66,10 +64,7 @@ function wholeNumber(
|
||||
}
|
||||
|
||||
export function patternMeter(props: PropertyBag): Meter {
|
||||
return heldMeter({
|
||||
beats: wholeNumber(props[PATTERN_BEATS_KEY], 4, 1, 32),
|
||||
unit: Number(textOf(props[PATTERN_BEAT_KEY] ?? '4')),
|
||||
});
|
||||
return signMeter(textOf(props[PATTERN_SIGN_KEY] ?? DEFAULT_SIGN));
|
||||
}
|
||||
|
||||
export function patternTempo(props: PropertyBag): number {
|
||||
|
||||
+2
-25
@@ -14,7 +14,7 @@ import { findSound } from '../model/sound';
|
||||
import { pickedFx } from '../properties/assetFx';
|
||||
import { gridTicks, GRID_OPTIONS, patternInstrument, patternLength } from './pattern';
|
||||
import { heldLoop, type LoopRange, type Sequence, type SequenceClip, type SequenceNote } from './sequence';
|
||||
import { heldMeter, heldTempo, type Meter, secondsOfTicks, ticksPerBar } from './time';
|
||||
import { DEFAULT_SIGN, heldTempo, type Meter, secondsOfTicks, signMeter, ticksPerBar } from './time';
|
||||
|
||||
export interface SongTrack {
|
||||
id: string;
|
||||
@@ -59,26 +59,6 @@ export const SONG_TEMPO_KEY = 'tempo';
|
||||
export const SONG_SIGN_KEY = 'sign';
|
||||
export const SONG_GRID_KEY = 'grid';
|
||||
|
||||
/** The time signatures a song is written in (#363), one row for both numbers. */
|
||||
export const SIGNS: readonly string[] = [
|
||||
'1/2',
|
||||
'2/2',
|
||||
'3/2',
|
||||
'1/4',
|
||||
'2/4',
|
||||
'3/4',
|
||||
'4/4',
|
||||
'5/4',
|
||||
'6/4',
|
||||
'5/8',
|
||||
'6/8',
|
||||
'7/8',
|
||||
'8/8',
|
||||
'9/8',
|
||||
];
|
||||
export const DEFAULT_SIGN = '4/4';
|
||||
export const SIGN_OPTIONS = SIGNS.map((sign) => ({ value: sign, label: sign }));
|
||||
|
||||
export const DEFAULT_SONG_GRID = 'bar';
|
||||
export const SONG_GRID_OPTIONS = [{ value: 'bar', label: 'Bar' }, ...GRID_OPTIONS];
|
||||
/** A new song has this many tracks. */
|
||||
@@ -97,11 +77,8 @@ export function itemMaxDb(kind: SongItemKind): number {
|
||||
return kind === 'sound' ? SOUND_ITEM_MAX_DB : 0;
|
||||
}
|
||||
|
||||
/** The meter of a song's sign; 4/4 where it is not one of `SIGNS`. */
|
||||
export function songMeter(props: PropertyBag): Meter {
|
||||
const sign = textOf(props[SONG_SIGN_KEY] ?? DEFAULT_SIGN);
|
||||
const [beats, unit] = (SIGNS.includes(sign) ? sign : DEFAULT_SIGN).split('/');
|
||||
return heldMeter({ beats: Number(beats), unit: Number(unit) });
|
||||
return signMeter(textOf(props[SONG_SIGN_KEY] ?? DEFAULT_SIGN));
|
||||
}
|
||||
|
||||
export function songTempo(props: PropertyBag): number {
|
||||
|
||||
@@ -28,6 +28,36 @@ export function heldMeter(meter: Meter): Meter {
|
||||
return { beats, unit };
|
||||
}
|
||||
|
||||
/** The time signatures a song or a pattern is written in (#363, #364), one row for both numbers. */
|
||||
export const SIGNS: readonly string[] = [
|
||||
'1/2',
|
||||
'2/2',
|
||||
'3/2',
|
||||
'1/4',
|
||||
'2/4',
|
||||
'3/4',
|
||||
'4/4',
|
||||
'5/4',
|
||||
'6/4',
|
||||
'5/8',
|
||||
'6/8',
|
||||
'7/8',
|
||||
'8/8',
|
||||
'9/8',
|
||||
];
|
||||
export const DEFAULT_SIGN = '4/4';
|
||||
export const SIGN_OPTIONS = SIGNS.map((sign) => ({ value: sign, label: sign }));
|
||||
|
||||
/** The meter of a sign; 4/4 where it is not one of `SIGNS`. */
|
||||
export function signMeter(sign: string): Meter {
|
||||
const [beats, unit] = (SIGNS.includes(sign) ? sign : DEFAULT_SIGN).split('/');
|
||||
return heldMeter({ beats: Number(beats), unit: Number(unit) });
|
||||
}
|
||||
|
||||
export function signOf(meter: Meter): string {
|
||||
return `${meter.beats}/${meter.unit}`;
|
||||
}
|
||||
|
||||
/** A tempo between `MIN_TEMPO` and `MAX_TEMPO`; 0 and anything that is not a number is the default. */
|
||||
export function heldTempo(tempo: number): number {
|
||||
if (!Number.isFinite(tempo) || tempo <= 0) return DEFAULT_TEMPO;
|
||||
|
||||
@@ -105,28 +105,24 @@ import { SHADER_KEY, shaderOf, shaderParamDefs } from './shaders';
|
||||
import { SHADER_RESOLUTION_KEY, SHADER_RESOLUTION_OPTIONS } from './cameraShader';
|
||||
import { LOOP_KEY as SOUND_LOOP_KEY, VOLUME_KEY as SOUND_VOLUME_KEY } from './sounds';
|
||||
import {
|
||||
BEAT_OPTIONS,
|
||||
DEFAULT_BARS,
|
||||
DEFAULT_GRID,
|
||||
GRID_OPTIONS,
|
||||
MAX_BARS,
|
||||
PATTERN_BARS_KEY,
|
||||
PATTERN_BEAT_KEY,
|
||||
PATTERN_BEATS_KEY,
|
||||
PATTERN_GRID_KEY,
|
||||
PATTERN_INSTRUMENT_KEY,
|
||||
PATTERN_SIGN_KEY,
|
||||
PATTERN_TEMPO_KEY,
|
||||
} from '../music/pattern';
|
||||
import {
|
||||
DEFAULT_SIGN,
|
||||
DEFAULT_SONG_GRID,
|
||||
SIGN_OPTIONS,
|
||||
SONG_GRID_KEY,
|
||||
SONG_GRID_OPTIONS,
|
||||
SONG_SIGN_KEY,
|
||||
SONG_TEMPO_KEY,
|
||||
} from '../music/song';
|
||||
import { DEFAULT_TEMPO, MAX_TEMPO, MIN_TEMPO } from '../music/time';
|
||||
import { DEFAULT_SIGN, DEFAULT_TEMPO, MAX_TEMPO, MIN_TEMPO, SIGN_OPTIONS } from '../music/time';
|
||||
import { ASSET_FX_KEY } from './assetFx';
|
||||
import { BUS_FX_KEY, BUS_INTO_KEY, BUS_KEY, BUS_VOLUME_KEY } from './buses';
|
||||
import { Theme } from '../../ui';
|
||||
@@ -737,17 +733,7 @@ const ASSET_SCHEMAS: Record<AssetType, PropertySchema> = {
|
||||
decimals: 0,
|
||||
},
|
||||
{ key: PATTERN_GRID_KEY, label: 'Grid', kind: 'enum', default: DEFAULT_GRID, options: GRID_OPTIONS },
|
||||
{
|
||||
key: PATTERN_BEATS_KEY,
|
||||
label: 'Beats per bar',
|
||||
kind: 'number',
|
||||
default: 4,
|
||||
min: 1,
|
||||
max: 32,
|
||||
step: 1,
|
||||
decimals: 0,
|
||||
},
|
||||
{ key: PATTERN_BEAT_KEY, label: 'Beat', kind: 'enum', default: '4', options: BEAT_OPTIONS },
|
||||
{ key: PATTERN_SIGN_KEY, label: 'Sign', kind: 'enum', default: DEFAULT_SIGN, options: SIGN_OPTIONS },
|
||||
{
|
||||
key: PATTERN_TEMPO_KEY,
|
||||
label: 'Tempo (BPM)',
|
||||
|
||||
Reference in new issue
Block a user