Skip to content

Commit ee58309

Browse files
committed
a
1 parent 2f767b3 commit ee58309

26 files changed

Lines changed: 301 additions & 204 deletions

docs/CompositionState.md

Lines changed: 147 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ export interface CompositionState {
142142
flipBinT3: number[];
143143
flipBinF3: number[];
144144
stutterPanCHs: number[];
145+
// Backwards-compatible divisions alias
146+
divisions: number;
147+
148+
// Sections config for testing
149+
SECTIONS?: { min: number; max: number };
145150

146151
// Logging
147152
LOG: string;
@@ -268,7 +273,10 @@ export class CompositionStateService implements CompositionState {
268273
velocity = 99;
269274
flipBinT3: number[] = [];
270275
flipBinF3: number[] = [];
271-
stutterPanCHs: number[] = [];
276+
stutterPanCHs: number[] = []; // Backwards-compatible divisions alias
277+
divisions = 4;
278+
// Sections config for testing (allows tests to seed section ranges into state)
279+
SECTIONS = { min: 1, max: 1 };
272280

273281
// Logging
274282
LOG = 'none';
@@ -277,58 +285,81 @@ export class CompositionStateService implements CompositionState {
277285
* Sync state with globalThis
278286
*/
279287
syncToGlobal() {
280-
const g = globalThis as any;
281-
g.sectionIndex = this.sectionIndex;
282-
g.totalSections = this.totalSections;
283-
g.sectionStart = this.sectionStart;
284-
g.phraseIndex = this.phraseIndex;
285-
g.phrasesPerSection = this.phrasesPerSection;
286-
g.phraseStart = this.phraseStart;
287-
g.measureIndex = this.measureIndex;
288-
g.measureStart = this.measureStart;
289-
g.beatIndex = this.beatIndex;
290-
g.numerator = this.numerator;
291-
g.denominator = this.denominator;
292-
g.beatCount = this.beatCount;
293-
g.beatStart = this.beatStart;
294-
g.divsPerBeat = this.divsPerBeat;
295-
g.divIndex = this.divIndex;
296-
g.divStart = this.divStart;
297-
g.subdivsPerDiv = this.subdivsPerDiv;
298-
g.subdivIndex = this.subdivIndex;
299-
g.subdivStart = this.subdivStart;
300-
g.subsubdivIndex = this.subsubdivIndex;
301-
g.composer = this.composer;
302-
g.activeMotif = this.activeMotif;
303-
g.BPM = this.BPM;
304-
g.beatRhythm = this.beatRhythm;
305-
g.divRhythm = this.divRhythm;
306-
g.subdivRhythm = this.subdivRhythm;
307-
g.LOG = this.LOG;
288+
// Sync to PolychronContext.state and PolychronContext.test for legacy compatibility without globals
289+
const poly = getPolychronContext();
290+
poly.state = poly.state || {} as any;
291+
poly.test = poly.test || {} as any;
292+
293+
// State vars
294+
poly.state.sectionIndex = this.sectionIndex;
295+
poly.state.totalSections = this.totalSections;
296+
poly.state.sectionStart = this.sectionStart;
297+
poly.state.phraseIndex = this.phraseIndex;
298+
poly.state.phrasesPerSection = this.phrasesPerSection;
299+
poly.state.phraseStart = this.phraseStart;
300+
poly.state.measureIndex = this.measureIndex;
301+
poly.state.measureStart = this.measureStart;
302+
poly.state.beatIndex = this.beatIndex;
303+
poly.state.numerator = this.numerator;
304+
poly.state.denominator = this.denominator;
305+
poly.state.beatCount = this.beatCount;
306+
poly.state.beatStart = this.beatStart;
307+
poly.state.divsPerBeat = this.divsPerBeat;
308+
poly.state.divisions = this.divisions;
309+
poly.state.divIndex = this.divIndex;
310+
poly.state.divStart = this.divStart;
311+
poly.state.subdivsPerDiv = this.subdivsPerDiv;
312+
poly.state.subdivIndex = this.subdivIndex;
313+
poly.state.subdivStart = this.subdivStart;
314+
poly.state.subsubdivIndex = this.subsubdivIndex;
315+
poly.state.composer = this.composer;
316+
poly.state.activeMotif = this.activeMotif;
317+
poly.state.BPM = this.BPM;
318+
poly.state.beatRhythm = this.beatRhythm;
319+
poly.state.divRhythm = this.divRhythm;
320+
poly.state.subdivRhythm = this.subdivRhythm;
321+
poly.state.measuresPerPhrase = this.measuresPerPhrase;
322+
poly.state.SECTIONS = this.SECTIONS;
323+
324+
// Test namespace for logging/legacy read
325+
poly.test.LOG = this.LOG;
326+
327+
// Do NOT write to globalThis; only keep DI-friendly namespaces in sync
328+
poly.state = poly.state || {} as any; // ensure state exists
308329
}
309330

310331
/**
311332
* Sync state from globalThis (for test setup)
312333
*/
313334
syncFromGlobal() {
314-
const g = globalThis as any;
315-
if (g.sectionIndex !== undefined) this.sectionIndex = g.sectionIndex;
316-
if (g.totalSections !== undefined) this.totalSections = g.totalSections;
317-
if (g.phraseIndex !== undefined) this.phraseIndex = g.phraseIndex;
318-
if (g.measureIndex !== undefined) this.measureIndex = g.measureIndex;
319-
if (g.beatIndex !== undefined) this.beatIndex = g.beatIndex;
320-
if (g.numerator !== undefined) this.numerator = g.numerator;
321-
if (g.denominator !== undefined) this.denominator = g.denominator;
322-
if (g.beatCount !== undefined) this.beatCount = g.beatCount;
323-
if (g.divIndex !== undefined) this.divIndex = g.divIndex;
324-
if (g.subdivIndex !== undefined) this.subdivIndex = g.subdivIndex;
325-
if (g.subsubdivIndex !== undefined) this.subsubdivIndex = g.subsubdivIndex;
326-
if (g.composer !== undefined) this.composer = g.composer;
327-
if (g.activeMotif !== undefined) this.activeMotif = g.activeMotif;
328-
if (g.BPM !== undefined) this.BPM = g.BPM;
329-
if (g.flipBinT3 !== undefined) this.flipBinT3 = g.flipBinT3;
330-
if (g.flipBinF3 !== undefined) this.flipBinF3 = g.flipBinF3;
331-
if (g.LOG !== undefined) this.LOG = g.LOG;
335+
const poly = getPolychronContext();
336+
const gState = poly.state || {} as any;
337+
const gTest = poly.test || {} as any;
338+
339+
// First read from the DI-friendly namespaces
340+
if (gState.sectionIndex !== undefined) this.sectionIndex = gState.sectionIndex;
341+
if (gState.totalSections !== undefined) this.totalSections = gState.totalSections;
342+
if (gState.phraseIndex !== undefined) this.phraseIndex = gState.phraseIndex;
343+
if (gState.measureIndex !== undefined) this.measureIndex = gState.measureIndex;
344+
if (gState.beatIndex !== undefined) this.beatIndex = gState.beatIndex;
345+
if (gState.numerator !== undefined) this.numerator = gState.numerator;
346+
if (gState.denominator !== undefined) this.denominator = gState.denominator;
347+
if (gState.beatCount !== undefined) this.beatCount = gState.beatCount;
348+
if (gState.divIndex !== undefined) this.divIndex = gState.divIndex;
349+
if (gState.divisions !== undefined) this.divisions = gState.divisions;
350+
if (gState.subdivIndex !== undefined) this.subdivIndex = gState.subdivIndex;
351+
if (gState.subsubdivIndex !== undefined) this.subsubdivIndex = gState.subsubdivIndex;
352+
if (gState.composer !== undefined) this.composer = gState.composer;
353+
if (gState.activeMotif !== undefined) this.activeMotif = gState.activeMotif;
354+
if (gState.BPM !== undefined) this.BPM = gState.BPM;
355+
if (gState.measuresPerPhrase !== undefined) this.measuresPerPhrase = gState.measuresPerPhrase;
356+
if (gState.SECTIONS !== undefined) this.SECTIONS = gState.SECTIONS;
357+
if (gState.flipBinT3 !== undefined) this.flipBinT3 = gState.flipBinT3;
358+
if (gState.flipBinF3 !== undefined) this.flipBinF3 = gState.flipBinF3;
359+
if (gTest.LOG !== undefined) this.LOG = gTest.LOG;
360+
361+
// Do not read from globalThis; DI namespaces are authoritative (poly.state & poly.test)
362+
// (no-op - already read from poly.state and poly.test above)
332363
}
333364

334365
/**
@@ -362,34 +393,47 @@ Mirror state to/from `globalThis` for certain initialization flows.
362393

363394
```typescript
364395
syncToGlobal() {
365-
const g = globalThis as any;
366-
g.sectionIndex = this.sectionIndex;
367-
g.totalSections = this.totalSections;
368-
g.sectionStart = this.sectionStart;
369-
g.phraseIndex = this.phraseIndex;
370-
g.phrasesPerSection = this.phrasesPerSection;
371-
g.phraseStart = this.phraseStart;
372-
g.measureIndex = this.measureIndex;
373-
g.measureStart = this.measureStart;
374-
g.beatIndex = this.beatIndex;
375-
g.numerator = this.numerator;
376-
g.denominator = this.denominator;
377-
g.beatCount = this.beatCount;
378-
g.beatStart = this.beatStart;
379-
g.divsPerBeat = this.divsPerBeat;
380-
g.divIndex = this.divIndex;
381-
g.divStart = this.divStart;
382-
g.subdivsPerDiv = this.subdivsPerDiv;
383-
g.subdivIndex = this.subdivIndex;
384-
g.subdivStart = this.subdivStart;
385-
g.subsubdivIndex = this.subsubdivIndex;
386-
g.composer = this.composer;
387-
g.activeMotif = this.activeMotif;
388-
g.BPM = this.BPM;
389-
g.beatRhythm = this.beatRhythm;
390-
g.divRhythm = this.divRhythm;
391-
g.subdivRhythm = this.subdivRhythm;
392-
g.LOG = this.LOG;
396+
// Sync to PolychronContext.state and PolychronContext.test for legacy compatibility without globals
397+
const poly = getPolychronContext();
398+
poly.state = poly.state || {} as any;
399+
poly.test = poly.test || {} as any;
400+
401+
// State vars
402+
poly.state.sectionIndex = this.sectionIndex;
403+
poly.state.totalSections = this.totalSections;
404+
poly.state.sectionStart = this.sectionStart;
405+
poly.state.phraseIndex = this.phraseIndex;
406+
poly.state.phrasesPerSection = this.phrasesPerSection;
407+
poly.state.phraseStart = this.phraseStart;
408+
poly.state.measureIndex = this.measureIndex;
409+
poly.state.measureStart = this.measureStart;
410+
poly.state.beatIndex = this.beatIndex;
411+
poly.state.numerator = this.numerator;
412+
poly.state.denominator = this.denominator;
413+
poly.state.beatCount = this.beatCount;
414+
poly.state.beatStart = this.beatStart;
415+
poly.state.divsPerBeat = this.divsPerBeat;
416+
poly.state.divisions = this.divisions;
417+
poly.state.divIndex = this.divIndex;
418+
poly.state.divStart = this.divStart;
419+
poly.state.subdivsPerDiv = this.subdivsPerDiv;
420+
poly.state.subdivIndex = this.subdivIndex;
421+
poly.state.subdivStart = this.subdivStart;
422+
poly.state.subsubdivIndex = this.subsubdivIndex;
423+
poly.state.composer = this.composer;
424+
poly.state.activeMotif = this.activeMotif;
425+
poly.state.BPM = this.BPM;
426+
poly.state.beatRhythm = this.beatRhythm;
427+
poly.state.divRhythm = this.divRhythm;
428+
poly.state.subdivRhythm = this.subdivRhythm;
429+
poly.state.measuresPerPhrase = this.measuresPerPhrase;
430+
poly.state.SECTIONS = this.SECTIONS;
431+
432+
// Test namespace for logging/legacy read
433+
poly.test.LOG = this.LOG;
434+
435+
// Do NOT write to globalThis; only keep DI-friendly namespaces in sync
436+
poly.state = poly.state || {} as any; // ensure state exists
393437
}
394438
```
395439

@@ -399,24 +443,34 @@ syncToGlobal() {
399443

400444
```typescript
401445
syncFromGlobal() {
402-
const g = globalThis as any;
403-
if (g.sectionIndex !== undefined) this.sectionIndex = g.sectionIndex;
404-
if (g.totalSections !== undefined) this.totalSections = g.totalSections;
405-
if (g.phraseIndex !== undefined) this.phraseIndex = g.phraseIndex;
406-
if (g.measureIndex !== undefined) this.measureIndex = g.measureIndex;
407-
if (g.beatIndex !== undefined) this.beatIndex = g.beatIndex;
408-
if (g.numerator !== undefined) this.numerator = g.numerator;
409-
if (g.denominator !== undefined) this.denominator = g.denominator;
410-
if (g.beatCount !== undefined) this.beatCount = g.beatCount;
411-
if (g.divIndex !== undefined) this.divIndex = g.divIndex;
412-
if (g.subdivIndex !== undefined) this.subdivIndex = g.subdivIndex;
413-
if (g.subsubdivIndex !== undefined) this.subsubdivIndex = g.subsubdivIndex;
414-
if (g.composer !== undefined) this.composer = g.composer;
415-
if (g.activeMotif !== undefined) this.activeMotif = g.activeMotif;
416-
if (g.BPM !== undefined) this.BPM = g.BPM;
417-
if (g.flipBinT3 !== undefined) this.flipBinT3 = g.flipBinT3;
418-
if (g.flipBinF3 !== undefined) this.flipBinF3 = g.flipBinF3;
419-
if (g.LOG !== undefined) this.LOG = g.LOG;
446+
const poly = getPolychronContext();
447+
const gState = poly.state || {} as any;
448+
const gTest = poly.test || {} as any;
449+
450+
// First read from the DI-friendly namespaces
451+
if (gState.sectionIndex !== undefined) this.sectionIndex = gState.sectionIndex;
452+
if (gState.totalSections !== undefined) this.totalSections = gState.totalSections;
453+
if (gState.phraseIndex !== undefined) this.phraseIndex = gState.phraseIndex;
454+
if (gState.measureIndex !== undefined) this.measureIndex = gState.measureIndex;
455+
if (gState.beatIndex !== undefined) this.beatIndex = gState.beatIndex;
456+
if (gState.numerator !== undefined) this.numerator = gState.numerator;
457+
if (gState.denominator !== undefined) this.denominator = gState.denominator;
458+
if (gState.beatCount !== undefined) this.beatCount = gState.beatCount;
459+
if (gState.divIndex !== undefined) this.divIndex = gState.divIndex;
460+
if (gState.divisions !== undefined) this.divisions = gState.divisions;
461+
if (gState.subdivIndex !== undefined) this.subdivIndex = gState.subdivIndex;
462+
if (gState.subsubdivIndex !== undefined) this.subsubdivIndex = gState.subsubdivIndex;
463+
if (gState.composer !== undefined) this.composer = gState.composer;
464+
if (gState.activeMotif !== undefined) this.activeMotif = gState.activeMotif;
465+
if (gState.BPM !== undefined) this.BPM = gState.BPM;
466+
if (gState.measuresPerPhrase !== undefined) this.measuresPerPhrase = gState.measuresPerPhrase;
467+
if (gState.SECTIONS !== undefined) this.SECTIONS = gState.SECTIONS;
468+
if (gState.flipBinT3 !== undefined) this.flipBinT3 = gState.flipBinT3;
469+
if (gState.flipBinF3 !== undefined) this.flipBinF3 = gState.flipBinF3;
470+
if (gTest.LOG !== undefined) this.LOG = gTest.LOG;
471+
472+
// Do not read from globalThis; DI namespaces are authoritative (poly.state & poly.test)
473+
// (no-op - already read from poly.state and poly.test above)
420474
}
421475
```
422476

docs/composers/MeasureComposer.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,18 @@ class MeasureComposer {
7979
}
8080

8181
getBpmRatio(): number {
82-
return (globalThis as any).bpmRatio ?? 1;
82+
try {
83+
// Avoid importing getPolychronContext at module initialization to prevent circular imports.
84+
// Prefer a runtime lookup if available on globalThis (set by PolychronInit during initialization).
85+
const maybeGet = (globalThis as any).getPolychronContext;
86+
if (typeof maybeGet === 'function') {
87+
return maybeGet().state?.bpmRatio ?? 1;
88+
}
89+
const poly = (globalThis as any).PolychronContext || {};
90+
return poly.state?.bpmRatio ?? 1;
91+
} catch (e) {
92+
return 1;
93+
}
8394
}
8495

8596
getOctaveRange(): number[] {

docs/composers/ModeComposer.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ class RandomModeComposer extends RandomGenericComposer<any> {
8888
}
8989

9090
randomizeItem() {
91-
// Prefer DI/imported arrays, but fall back to legacy globals for tests
92-
const modes = (Array.isArray(allModes) && allModes.length) ? allModes : (g.allModes || []);
93-
const notes = (Array.isArray(allNotes) && allNotes.length) ? allNotes : (g.allNotes || []);
91+
// Prefer DI/imported arrays, but fall back to PolychronContext.test for legacy test data
92+
const modes = (Array.isArray(allModes) && allModes.length) ? allModes : (getPolychronContext().test?.allModes || []);
93+
const notes = (Array.isArray(allNotes) && allNotes.length) ? allNotes : (getPolychronContext().test?.allNotes || []);
9494
const modeIdx = Math.max(0, ri(modes.length - 1));
9595
const rootIdx = Math.max(0, ri(notes.length - 1));
9696
let randomMode = modes[modeIdx] || 'ionian';
@@ -127,9 +127,9 @@ Chooses random mode/root then sets notes with mode-or-scale fallback.
127127

128128
```typescript
129129
randomizeItem() {
130-
// Prefer DI/imported arrays, but fall back to legacy globals for tests
131-
const modes = (Array.isArray(allModes) && allModes.length) ? allModes : (g.allModes || []);
132-
const notes = (Array.isArray(allNotes) && allNotes.length) ? allNotes : (g.allNotes || []);
130+
// Prefer DI/imported arrays, but fall back to PolychronContext.test for legacy test data
131+
const modes = (Array.isArray(allModes) && allModes.length) ? allModes : (getPolychronContext().test?.allModes || []);
132+
const notes = (Array.isArray(allNotes) && allNotes.length) ? allNotes : (getPolychronContext().test?.allNotes || []);
133133
const modeIdx = Math.max(0, ri(modes.length - 1));
134134
const rootIdx = Math.max(0, ri(notes.length - 1));
135135
let randomMode = modes[modeIdx] || 'ionian';

docs/fxManager.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ class FxManager {
100100
*/
101101
stutterPan(channels: number[], ctx: any, numStutters?: number, duration?: number): void {
102102
const state = ctx?.state ?? {} as any;
103-
const riFn = ctx?.utils?.ri ?? (globalThis as any).ri ?? ri;
104-
const rfFn = ctx?.utils?.rf ?? (globalThis as any).rf ?? rf;
105-
const raFn = ctx?.utils?.ra ?? (globalThis as any).ra ?? ra;
106-
const modClampFn = ctx?.utils?.modClamp ?? (globalThis as any).modClamp ?? modClamp;
103+
const riFn = ctx?.utils?.ri ?? getPolychronContext().utils.ri ?? ri;
104+
const rfFn = ctx?.utils?.rf ?? getPolychronContext().utils.rf ?? rf;
105+
const raFn = ctx?.utils?.ra ?? getPolychronContext().utils.ra ?? ra;
106+
const modClampFn = ctx?.utils?.modClamp ?? getPolychronContext().utils.modClamp ?? modClamp;
107107

108108
numStutters = numStutters || riFn(30, 90);
109109
const tpSec = state.tpSec ?? 1;
@@ -160,9 +160,9 @@ class FxManager {
160160
*/
161161
stutterFX(channels: number[], ctx: any, numStutters?: number, duration?: number): void {
162162
const state = ctx?.state ?? {} as any;
163-
const riFn = ctx?.utils?.ri ?? (globalThis as any).ri ?? ri;
164-
const rfFn = ctx?.utils?.rf ?? (globalThis as any).rf ?? rf;
165-
const raFn = ctx?.utils?.ra ?? (globalThis as any).ra ?? ra;
163+
const riFn = ctx?.utils?.ri ?? getPolychronContext().utils.ri ?? ri;
164+
const rfFn = ctx?.utils?.rf ?? getPolychronContext().utils.rf ?? rf;
165+
const raFn = ctx?.utils?.ra ?? getPolychronContext().utils.ra ?? ra;
166166

167167
numStutters = numStutters || riFn(30, 100);
168168
const tpSec = state.tpSec ?? 1;
@@ -288,10 +288,10 @@ Pan stutter with edge margins and center zones.
288288
```typescript
289289
stutterPan(channels: number[], ctx: any, numStutters?: number, duration?: number): void {
290290
const state = ctx?.state ?? {} as any;
291-
const riFn = ctx?.utils?.ri ?? (globalThis as any).ri ?? ri;
292-
const rfFn = ctx?.utils?.rf ?? (globalThis as any).rf ?? rf;
293-
const raFn = ctx?.utils?.ra ?? (globalThis as any).ra ?? ra;
294-
const modClampFn = ctx?.utils?.modClamp ?? (globalThis as any).modClamp ?? modClamp;
291+
const riFn = ctx?.utils?.ri ?? getPolychronContext().utils.ri ?? ri;
292+
const rfFn = ctx?.utils?.rf ?? getPolychronContext().utils.rf ?? rf;
293+
const raFn = ctx?.utils?.ra ?? getPolychronContext().utils.ra ?? ra;
294+
const modClampFn = ctx?.utils?.modClamp ?? getPolychronContext().utils.modClamp ?? modClamp;
295295

296296
numStutters = numStutters || riFn(30, 90);
297297
const tpSec = state.tpSec ?? 1;
@@ -355,9 +355,9 @@ FX parameter stutter using CC targets (91/92/93/71/74).
355355
```typescript
356356
stutterFX(channels: number[], ctx: any, numStutters?: number, duration?: number): void {
357357
const state = ctx?.state ?? {} as any;
358-
const riFn = ctx?.utils?.ri ?? (globalThis as any).ri ?? ri;
359-
const rfFn = ctx?.utils?.rf ?? (globalThis as any).rf ?? rf;
360-
const raFn = ctx?.utils?.ra ?? (globalThis as any).ra ?? ra;
358+
const riFn = ctx?.utils?.ri ?? getPolychronContext().utils.ri ?? ri;
359+
const rfFn = ctx?.utils?.rf ?? getPolychronContext().utils.rf ?? rf;
360+
const raFn = ctx?.utils?.ra ?? getPolychronContext().utils.ra ?? ra;
361361

362362
numStutters = numStutters || riFn(30, 100);
363363
const tpSec = state.tpSec ?? 1;

0 commit comments

Comments
 (0)