@hraness/dawg 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/DAWG.md +327 -0
  3. package/LICENSE +21 -0
  4. package/README.md +213 -2
  5. package/core/diff.ts +249 -0
  6. package/core/drums.ts +102 -0
  7. package/core/key.ts +43 -0
  8. package/core/loop.ts +78 -0
  9. package/core/pitch.ts +60 -0
  10. package/core/score.ts +1388 -0
  11. package/core/sdk/eval-child.ts +113 -0
  12. package/core/sdk/eval.ts +257 -0
  13. package/core/sdk/print.ts +393 -0
  14. package/core/sdk/v1.ts +954 -0
  15. package/core/slug.ts +19 -0
  16. package/package.json +45 -4
  17. package/src/agent/agent.ts +853 -0
  18. package/src/agent/brief.ts +160 -0
  19. package/src/agent/gateway.ts +441 -0
  20. package/src/agent/models.ts +633 -0
  21. package/src/agent/ops.ts +157 -0
  22. package/src/agent/planner.ts +259 -0
  23. package/src/agent/provider.ts +454 -0
  24. package/src/agent/sse.ts +114 -0
  25. package/src/agent/tools.ts +1373 -0
  26. package/src/agent/usage.ts +296 -0
  27. package/src/agent/workspace.ts +683 -0
  28. package/src/agent/xcb-agent.ts +262 -0
  29. package/src/agent/xcb.ts +579 -0
  30. package/src/audio/click.ts +125 -0
  31. package/src/audio/clock.ts +68 -0
  32. package/src/audio/engine.ts +841 -0
  33. package/src/audio/live.ts +152 -0
  34. package/src/audio/lock.ts +57 -0
  35. package/src/audio/player.ts +134 -0
  36. package/src/audio/render-worker.ts +68 -0
  37. package/src/audio/renderer.ts +174 -0
  38. package/src/audio/sampler.ts +292 -0
  39. package/src/audio/samples.ts +683 -0
  40. package/src/audio/wav.ts +861 -0
  41. package/src/auth/cli.ts +231 -0
  42. package/src/auth/credentials.ts +411 -0
  43. package/src/auth/discover.ts +481 -0
  44. package/src/auth/login.ts +1191 -0
  45. package/src/auth/openrouter.ts +206 -0
  46. package/src/auth/picker.ts +282 -0
  47. package/src/auth/runner.ts +207 -0
  48. package/src/auth/tui.ts +107 -0
  49. package/src/commands/edit.ts +170 -0
  50. package/src/commands/help.ts +247 -0
  51. package/src/commands/history.ts +69 -0
  52. package/src/commands/music.ts +461 -0
  53. package/src/commands/sample.ts +302 -0
  54. package/src/daemon.ts +31 -0
  55. package/src/main.ts +2209 -0
  56. package/src/media/analyze.ts +364 -0
  57. package/src/media/backend.ts +253 -0
  58. package/src/media/cli.ts +173 -0
  59. package/src/media/download.ts +281 -0
  60. package/src/media/dsp.ts +281 -0
  61. package/src/media/import.ts +130 -0
  62. package/src/media/lyrics.ts +201 -0
  63. package/src/media/notes.ts +363 -0
  64. package/src/media/paths.ts +168 -0
  65. package/src/media/process.ts +226 -0
  66. package/src/media/registry.ts +9 -0
  67. package/src/media/sidecar.ts +72 -0
  68. package/src/media/stemdeck.ts +254 -0
  69. package/src/media/stems.ts +173 -0
  70. package/src/media/tools.ts +292 -0
  71. package/src/media/types.ts +92 -0
  72. package/src/media/vendor/basic-pitch.ts +261 -0
  73. package/src/media/vendor/drums.ts +817 -0
  74. package/src/media/vendor/grid.ts +203 -0
  75. package/src/media/vendor/util.ts +139 -0
  76. package/src/media/vendor/wav.ts +233 -0
  77. package/src/project/check.ts +80 -0
  78. package/src/project/init.ts +253 -0
  79. package/src/project/sync.ts +432 -0
  80. package/src/project/typecheck.ts +149 -0
  81. package/src/render.ts +121 -0
  82. package/src/session/attach.ts +181 -0
  83. package/src/session/client.ts +498 -0
  84. package/src/session/daemon.ts +740 -0
  85. package/src/session/delta.ts +249 -0
  86. package/src/session/list.ts +180 -0
  87. package/src/session/lock.ts +92 -0
  88. package/src/session/meta.ts +253 -0
  89. package/src/session/naming.ts +430 -0
  90. package/src/session/port.ts +481 -0
  91. package/src/session/presence.ts +159 -0
  92. package/src/session/protocol.ts +618 -0
  93. package/src/session/rebase.ts +168 -0
  94. package/src/session/store.ts +581 -0
  95. package/src/tui/menu.ts +1083 -0
  96. package/src/tui/play-mode.ts +442 -0
  97. package/src/tui/play-session.ts +636 -0
  98. package/src/web/fetch.ts +340 -0
  99. package/src/web/http.ts +137 -0
  100. package/src/web/search.ts +681 -0
  101. package/tui/activity.ts +364 -0
  102. package/tui/app.ts +1372 -0
  103. package/tui/drums.ts +65 -0
  104. package/tui/highway.ts +921 -0
  105. package/tui/input.ts +63 -0
  106. package/tui/keys.ts +102 -0
  107. package/tui/layers.ts +80 -0
  108. package/tui/play-strip.ts +143 -0
  109. package/tui/prompt.ts +609 -0
  110. package/tui/render.ts +124 -0
  111. package/tui/screen.ts +247 -0
  112. package/tui/text.ts +72 -0
  113. package/tui/theme.ts +451 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Server-side rebase of agent operation intents.
3
+ *
4
+ * An agent validates its operations against the score at `base`. When other
5
+ * windows committed in the meantime, dawgd replays the operations on the
6
+ * current score instead of rejecting, but only when nothing they touch
7
+ * changed since `base`: the notes they update or remove, the tracks whose
8
+ * settings or contents they rewrite, the tracks they add notes to, and the
9
+ * tempo or length they set. Anything else is a conflict and the caller gets
10
+ * the usual `rebase` reply. The reducer still validates every operation.
11
+ */
12
+ import {
13
+ applyScoreOperation,
14
+ scoreFromJSON,
15
+ type ScoreOperation,
16
+ type TrackScore,
17
+ } from "../../core/score.ts";
18
+ import { rewindComposition, type Rewind } from "./delta.ts";
19
+
20
+ /** How far behind a base may be and still be rebased. */
21
+ export const MAX_REBASE_DISTANCE = 64;
22
+
23
+ export type RebaseResult =
24
+ { ok: true; next: TrackScore } | { ok: false; reason: string };
25
+
26
+ export function rebaseOperations(
27
+ base: TrackScore,
28
+ current: TrackScore,
29
+ operations: readonly ScoreOperation[],
30
+ ): RebaseResult {
31
+ const baseNotes = new Map(base.notes.map((note) => [note.id, note]));
32
+ const currentNotes = new Map(current.notes.map((note) => [note.id, note]));
33
+ const same = (a: unknown, b: unknown) =>
34
+ JSON.stringify(a) === JSON.stringify(b);
35
+ const trackSettings = (score: TrackScore, id: string) =>
36
+ score.tracks.find((track) => track.id === id);
37
+ const trackNotes = (score: TrackScore, id: string) =>
38
+ score.notes.filter((note) => note.trackId === id);
39
+ const settingsUnchanged = (id: string) =>
40
+ same(trackSettings(base, id), trackSettings(current, id));
41
+ const notesUnchanged = (id: string) =>
42
+ same(trackNotes(base, id), trackNotes(current, id));
43
+ // Tracks and notes the intent itself creates are not concurrent edits.
44
+ const added = { tracks: new Set<string>(), notes: new Set<string>() };
45
+ for (const operation of operations) {
46
+ switch (operation.type) {
47
+ case "addTrack": {
48
+ const id = operation.track.id;
49
+ if (typeof id === "string") {
50
+ if (trackSettings(current, id) && !trackSettings(base, id))
51
+ return { ok: false, reason: `track ${id} was added concurrently` };
52
+ added.tracks.add(id);
53
+ }
54
+ break;
55
+ }
56
+ case "addNote": {
57
+ const { id, trackId } = operation.note;
58
+ if (
59
+ typeof id === "string" &&
60
+ currentNotes.has(id) &&
61
+ !baseNotes.has(id)
62
+ )
63
+ return { ok: false, reason: `note ${id} was added concurrently` };
64
+ if (typeof id === "string") added.notes.add(id);
65
+ if (
66
+ typeof trackId === "string" &&
67
+ !added.tracks.has(trackId) &&
68
+ !trackSettings(current, trackId)
69
+ )
70
+ return { ok: false, reason: `track ${trackId} was removed` };
71
+ break;
72
+ }
73
+ case "removeNote":
74
+ case "updateNote": {
75
+ if (added.notes.has(operation.noteId)) break;
76
+ if (
77
+ !same(
78
+ baseNotes.get(operation.noteId),
79
+ currentNotes.get(operation.noteId),
80
+ )
81
+ )
82
+ return { ok: false, reason: `note ${operation.noteId} changed` };
83
+ break;
84
+ }
85
+ case "updateTrack":
86
+ case "setAutomation": {
87
+ if (added.tracks.has(operation.trackId)) break;
88
+ if (!settingsUnchanged(operation.trackId))
89
+ return { ok: false, reason: `track ${operation.trackId} changed` };
90
+ break;
91
+ }
92
+ case "clearTrack": {
93
+ if (added.tracks.has(operation.trackId)) break;
94
+ if (
95
+ !settingsUnchanged(operation.trackId) ||
96
+ !notesUnchanged(operation.trackId)
97
+ )
98
+ return { ok: false, reason: `track ${operation.trackId} changed` };
99
+ break;
100
+ }
101
+ case "setTempo":
102
+ if (base.tempoBpm !== current.tempoBpm)
103
+ return { ok: false, reason: "tempo changed" };
104
+ break;
105
+ case "setBars":
106
+ if (base.bars !== current.bars)
107
+ return { ok: false, reason: "loop length changed" };
108
+ break;
109
+ case "setKey":
110
+ if (base.key !== current.key)
111
+ return { ok: false, reason: "key changed" };
112
+ break;
113
+ case "setMeter":
114
+ if (base.beatsPerBar !== current.beatsPerBar)
115
+ return { ok: false, reason: "meter changed" };
116
+ break;
117
+ case "removeTrack": {
118
+ if (added.tracks.has(operation.trackId)) break;
119
+ if (
120
+ !settingsUnchanged(operation.trackId) ||
121
+ !notesUnchanged(operation.trackId)
122
+ )
123
+ return { ok: false, reason: `track ${operation.trackId} changed` };
124
+ break;
125
+ }
126
+ case "moveTrack": {
127
+ const order = (score: TrackScore) =>
128
+ score.tracks.map((track) => track.id).join("\u0000");
129
+ if (order(base) !== order(current))
130
+ return { ok: false, reason: "track order changed" };
131
+ break;
132
+ }
133
+ default:
134
+ return { ok: false, reason: "unsupported operation" };
135
+ }
136
+ }
137
+ try {
138
+ let next = current;
139
+ for (const operation of operations)
140
+ next = applyScoreOperation(next, operation);
141
+ return { ok: true, next: scoreFromJSON(next.toJSON()) };
142
+ } catch (error) {
143
+ return {
144
+ ok: false,
145
+ reason: error instanceof Error ? error.message : String(error),
146
+ };
147
+ }
148
+ }
149
+
150
+ /**
151
+ * The composition at revision `revision`, recovered by rewinding the current
152
+ * composition through every later event's `rewind`. Undefined when the log
153
+ * cannot vouch for it (a gap in revisions, or a rewind compacted away).
154
+ */
155
+ export function compositionAt(
156
+ record: {
157
+ composition: unknown;
158
+ events: readonly { revision: number; rewind?: Rewind | undefined }[];
159
+ },
160
+ revision: number,
161
+ ): unknown {
162
+ const { events } = record;
163
+ if (!Number.isSafeInteger(revision) || revision < 0) return undefined;
164
+ if (revision >= events.length) return undefined;
165
+ for (let index = revision; index < events.length; index += 1)
166
+ if (events[index]!.revision !== index + 1) return undefined;
167
+ return rewindComposition(record.composition, events, revision);
168
+ }