@audiofool/moondog 0.3.0-beta.2 → 0.3.0-beta.4

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">Moondog</h1>
6
6
 
7
- <p align="center">Source candidate 0.3.0-beta.2</p>
7
+ <p align="center">Source candidate 0.3.0-beta.4</p>
8
8
 
9
9
  <p align="center"><strong>A listening room that shows its work.</strong></p>
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audiofool/moondog",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.4",
4
4
  "type": "module",
5
5
  "description": "Local-first personal AI music agent.",
6
6
  "license": "Apache-2.0",
@@ -55,6 +55,7 @@ import { createAppleMusicCatalog } from "../src/integrations/apple-music/catalog
55
55
  import { createTerminalPreviewPlayer } from "../src/integrations/apple-music/terminal-preview-player.mjs";
56
56
  import { createNeteasePlayer } from "../src/integrations/netease/ncm-cli-player.mjs";
57
57
  import { createAtmosphereStream, createLocalGenerator } from "../src/integrations/stable-audio/local-generator.mjs";
58
+ import { createStableAudioSetup } from "../src/integrations/stable-audio/setup.mjs";
58
59
  import { createMusicAppPlayer } from "../src/integrations/apple-music/music-app.mjs";
59
60
  import {
60
61
  createOpenMusicSimilarity,
@@ -922,10 +923,14 @@ async function main() {
922
923
  const neteasePlayer = createNeteasePlayer();
923
924
  const playbackService = await resolvePlaybackService();
924
925
  if (playbackService === "netease") await neteasePlayer.check();
925
- // Moondog makes music on this Mac with the listener's own Stable Audio 3 once they turn it on, in a
926
- // room that can play what it makes. The terminal and the window each keep one generator, so each
927
- // makes one track at a time; a choice saved on one side is checked again on the other.
926
+ // Moondog makes music on this Mac with Stable Audio 3 once the listener turns it on, in a room
927
+ // that can play what it makes: Moondog's own install, downloaded when they ask, or their folder.
928
+ // The terminal and the window each keep one generator, so each makes one track at a time; a
929
+ // choice saved on one side is checked again on the other.
928
930
  const localGenerator = previewPlayer ? createLocalGenerator() : null;
931
+ const generationSetup = localGenerator ? createStableAudioSetup() : null;
932
+ // A model set up or removed, here or on the other side, is checked again.
933
+ const unwatchGenerationSetup = generationSetup?.subscribe(() => { void localGenerator.check().catch(() => {}); }) ?? (() => {});
929
934
  await localGenerator?.check();
930
935
  void localGenerator?.sweep().catch(() => {});
931
936
  const unwatchGeneration = localGenerator?.watch() ?? (() => {});
@@ -1158,7 +1163,12 @@ async function main() {
1158
1163
  status: () => localGenerator.status(),
1159
1164
  check: () => localGenerator.check(),
1160
1165
  save: (settings) => localGenerator.save(settings),
1166
+ // Uses an installed model of Moondog's own install.
1167
+ use: async (model) => (generationSetup?.status()?.models?.[model]?.state === "ready"
1168
+ ? localGenerator.save({ source: "managed", model }) : localGenerator.check()),
1161
1169
  subscribe: (listener) => localGenerator.subscribe(listener),
1170
+ // Download, cancel and remove, shared by the window and the terminal.
1171
+ setup: generationSetup,
1162
1172
  },
1163
1173
  // The room asks whether a Last.fm key is set, and saves one the listener pastes.
1164
1174
  lastfm: {
@@ -1270,6 +1280,10 @@ async function main() {
1270
1280
  } finally {
1271
1281
  unwatchPlayback();
1272
1282
  unwatchGeneration();
1283
+ unwatchGenerationSetup();
1284
+ // A download or uv run this process owns would keep it alive after the room closes; it stops and
1285
+ // resumes next time. One owned by the other window or terminal runs on.
1286
+ await generationSetup?.close?.();
1273
1287
  // A made track still playing is stopped while the generator listens, so its outcome is kept.
1274
1288
  if (localGenerator) {
1275
1289
  previewPlayer?.stop?.();
@@ -1,11 +1,13 @@
1
- // What the listener plays right now, wherever it plays: the Music app on this Mac, Spotify on its
2
- // active device, or NetEase through ncm-cli, with the playlist, album or queue it plays from.
3
- // Two players rarely play at once. When they do, one that plays wins over a paused one, then the
4
- // listener's chosen service, then this order.
5
- export const LISTENING_ORDER = Object.freeze(["apple_music", "spotify", "netease"]);
1
+ // What the listener plays right now, wherever it plays: Moondog's own player (the songs it played or
2
+ // queued, the tracks it made, its stream), the Music app on this Mac, Spotify on its active device,
3
+ // or NetEase through ncm-cli, with the playlist, album or queue it plays from.
4
+ // Two players rarely play at once. When they do, one that plays wins over a paused one, then
5
+ // Moondog's own player, which pauses the others when it starts, then the listener's chosen service,
6
+ // then this order.
7
+ export const LISTENING_ORDER = Object.freeze(["moondog", "apple_music", "spotify", "netease"]);
6
8
 
7
9
  export function activeListening(sources, chosen) {
8
- const order = [...new Set([chosen, ...LISTENING_ORDER])];
10
+ const order = [...new Set(["moondog", chosen, ...LISTENING_ORDER])];
9
11
  const heard = sources.filter((source) => source.song && (source.state === "playing" || source.state === "paused"));
10
12
  return heard.sort((a, b) => (a.state === "playing" ? 0 : 1) - (b.state === "playing" ? 0 : 1) ||
11
13
  order.indexOf(a.service) - order.indexOf(b.service))[0] ?? null;
@@ -75,6 +77,21 @@ async function spotify(application, { signal }) {
75
77
  };
76
78
  }
77
79
 
80
+ // Moondog's own player: Apple Music songs in the window or the terminal, and the tracks Moondog made.
81
+ const madeBy = (track) => ({ title: track.title, artists: track.generated ? ["Moondog"] : track.artist_credit ? [track.artist_credit] : [] });
82
+ function moondogPlayer(application) {
83
+ const now = application.musicPreviewPlayer.nowPlaying();
84
+ if (!now?.song?.title) return { state: "stopped" };
85
+ const kind = !now.song.generated ? "moondog_queue" : application.streamView?.() ? "moondog_stream" : "moondog_made";
86
+ return {
87
+ state: now.state,
88
+ player: "Moondog's own player",
89
+ song: madeBy(now.song),
90
+ from: { kind, name: null, tracks_total: now.count, position: now.position },
91
+ next: now.next.map(madeBy),
92
+ };
93
+ }
94
+
78
95
  async function netease(application) {
79
96
  const now = await application.neteaseNowPlaying();
80
97
  if (now.state === "stopped" || !now.song) return { state: "stopped" };
@@ -91,6 +108,7 @@ async function netease(application) {
91
108
  /** Every player Moondog can read, the one the listener hears first: { active, sources }. */
92
109
  export async function readListeningNow(application, { signal } = {}) {
93
110
  const readers = [
111
+ typeof application.musicPreviewPlayer?.nowPlaying === "function" ? ["moondog", () => moondogPlayer(application)] : null,
94
112
  application.musicApp?.available ? ["apple_music", () => musicApp(application)] : null,
95
113
  application.spotifyReady() ? ["spotify", () => spotify(application, { signal })] : null,
96
114
  application.neteaseReady() ? ["netease", () => netease(application)] : null,
@@ -106,3 +124,54 @@ export async function readListeningNow(application, { signal } = {}) {
106
124
  const active = activeListening(sources, application.playbackService);
107
125
  return { active: active?.service ?? null, sources: active ? [active, ...sources.filter((source) => source !== active)] : sources };
108
126
  }
127
+
128
+ // What Moondog's own player plays: the queue it was sent last, and where in it the player's own
129
+ // events put it. The window and the terminal players keep one each, so the agent can read the songs
130
+ // it played or queued, and the tracks it made, as it reads the Music app or Spotify.
131
+
132
+ const entry = (track) => ({
133
+ title: String(track?.title ?? "").trim(),
134
+ artist_credit: String(track?.artist_credit ?? "").trim(),
135
+ ...(track?.generated === true ? { generated: true } : {}),
136
+ });
137
+
138
+ export function createPlayerNow() {
139
+ // null, or { tracks, index, state } where state is "loading", "playing" or "paused".
140
+ let queue = null;
141
+ return {
142
+ /** A new queue replaces the last one and starts from its first song. */
143
+ queued(tracks) {
144
+ const list = (Array.isArray(tracks) ? tracks : []).map(entry).filter((track) => track.title);
145
+ queue = list.length ? { tracks: list, index: 0, state: "loading" } : null;
146
+ },
147
+ /** A made track queued behind the last one, as Moondog's stream does. */
148
+ appended(track) {
149
+ const added = entry(track);
150
+ if (queue && added.title) queue.tracks.push(added);
151
+ },
152
+ /** One of the player's events: { type, position } moves it along; stopped empties it. */
153
+ observe(event) {
154
+ // The terminal's player announces each new queue with its songs; the window's does not.
155
+ if (event?.type === "queue" && Array.isArray(event.tracks)) { this.queued(event.tracks); return; }
156
+ if (!queue) return;
157
+ if (event?.type === "stopped") { queue = null; return; }
158
+ if (Number.isInteger(event?.position) && event.position >= 1 && event.position <= queue.tracks.length) queue.index = event.position - 1;
159
+ if (event?.type === "playing" || event?.type === "paused" || event?.type === "loading") queue.state = event.type;
160
+ },
161
+ clear() {
162
+ queue = null;
163
+ },
164
+ /** null when nothing is queued, or { state, song, position, count, next } with up to ten songs after it. */
165
+ read() {
166
+ if (!queue) return null;
167
+ return {
168
+ // A song still loading is the one the listener is about to hear.
169
+ state: queue.state === "paused" ? "paused" : "playing",
170
+ song: { ...queue.tracks[queue.index] },
171
+ position: queue.index + 1,
172
+ count: queue.tracks.length,
173
+ next: queue.tracks.slice(queue.index + 1, queue.index + 11).map((track) => ({ ...track })),
174
+ };
175
+ },
176
+ };
177
+ }
@@ -5,7 +5,7 @@ import { compactListenerProfile, readListenerProfile } from "../profile/listener
5
5
  import { mixCounts, songKeys } from "../profile/moondog-suggestions.mjs";
6
6
  import { ARTIST_CREDIT_SEPARATORS } from "./listener-avoids.mjs";
7
7
  import { textNamesSong } from "./track-title.mjs";
8
- import { compileTaste, tasteSources } from "../profile/taste-compiler.mjs";
8
+ import { acceptableRecordTitle, compileTaste, tasteSources } from "../profile/taste-compiler.mjs";
9
9
  import { stableAudioPrompt } from "../integrations/stable-audio/local-generator.mjs";
10
10
 
11
11
  import {
@@ -21,7 +21,24 @@ import { webProviderName } from "../integrations/web/web-result.mjs";
21
21
  import { musicAppSource, readListeningNow } from "./listening-now.mjs";
22
22
  import { normalizeConversationFolder, normalizeConversationLabel, normalizeMemoryContent, shortConversationTitle } from "../memory/local-memory-store.mjs";
23
23
  import { queueListeningIntent, queueCancellationRequested, unheardRequested } from "./spotify-listening-intent.mjs";
24
- import { DEFAULT_LOCALE, normalizeLocale } from "../i18n/index.mjs";
24
+ import { DEFAULT_LOCALE, messageLocale, normalizeLocale } from "../i18n/index.mjs";
25
+
26
+ // How long a record's title in the wrong language waits to be named again before the agent's stays.
27
+ const TITLE_LANGUAGE_TIMEOUT_MS = 10_000;
28
+
29
+ // Whether a record's name is written in a language. A short name gives the word-based guess little to
30
+ // go on, and the guess then falls back to the setting, so the script decides first: Chinese, Japanese
31
+ // and Korean names are in their own script, and the other languages Moondog speaks in none of those.
32
+ const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
33
+ const SCRIPTS = { zh: /\p{Script=Han}/u, "zh-Hant": /\p{Script=Han}/u, ja: /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u, ko: /\p{Script=Hangul}/u };
34
+ export function nameFitsLanguage(name, locale) {
35
+ const text = String(name ?? "");
36
+ if (SCRIPTS[locale] ? !SCRIPTS[locale].test(text) : CJK.test(text)) return false;
37
+ const guess = messageLocale(text, locale);
38
+ // Kanji with no kana, such as 月夜 or 雨音, is Japanese as much as Chinese, so the Japanese setting decides.
39
+ if (locale === "ja" && (guess === "zh" || guess === "zh-Hant") && !/\p{Script=Hangul}/u.test(text)) return true;
40
+ return guess === locale;
41
+ }
25
42
  import { createPlaybackSelection, DEFAULT_PLAYBACK_SERVICE, normalizePlaybackService } from "./playback-preferences.mjs";
26
43
 
27
44
  const minimumNodeVersion = [22, 19, 0];
@@ -586,14 +603,15 @@ export class MoondogApplication {
586
603
  // ones it can trace and none that names an artist or a song. The track and its receipt stay on
587
604
  // this Mac, and its play goes in the ledger as Moondog's own, never as the listener's attention.
588
605
  // moment and request come from the agent's host, not from the model.
589
- async makeTrack({ descriptors, bpm, bpmSource, seconds, title } = {}, { moment = null, request = null, transient = false, signal, onProgress } = {}) {
606
+ async makeTrack({ descriptors, bpm, bpmSource, seconds, title } = {}, { moment = null, request = null, transient = false, signal, onProgress, nameRecord } = {}) {
590
607
  const { compiled, receipt } = await this.#compileForGeneration({ descriptors, bpm, bpmSource, title }, { moment, request, transient });
591
608
  if (!compiled.usable) {
592
609
  return { state: "not_made", reason: compiled.descriptors.length ? "needs_a_sound" : "no_traceable_descriptors", dropped: compiled.dropped };
593
610
  }
594
611
  const prompt = stableAudioPrompt(compiled);
595
612
  const endedStream = await this.#endStream("replaced");
596
- const made = await this.localGenerator.generate({ prompt, seconds, title: compiled.title, receipt }, { signal, onProgress });
613
+ const named = await this.#titleInSettingLanguage(compiled, nameRecord, signal);
614
+ const made = await this.localGenerator.generate({ prompt, seconds, title: named, receipt }, { signal, onProgress });
597
615
  // A reply stopped as the track finished keeps the track, but plays nothing over what is on now.
598
616
  await this.quietOtherPlayers("apple_music", { signal });
599
617
  // The terminal plays the file itself; the window asks for it by id and never sees the path.
@@ -636,18 +654,21 @@ export class MoondogApplication {
636
654
 
637
655
  // Starts Moondog's atmosphere stream from the listener's taste: its first track plays now, and
638
656
  // each next one is made while the last plays. Every track goes in the ledger as Moondog's pick.
639
- async startStream({ descriptors, bpm, bpmSource, seconds, minutes, title } = {}, { moment = null, request = null, transient = false, signal, onProgress } = {}) {
657
+ async startStream({ descriptors, bpm, bpmSource, seconds, minutes, title } = {}, { moment = null, request = null, transient = false, signal, onProgress, nameRecord } = {}) {
640
658
  if (!this.streamReady()) throw Object.assign(new Error("A stream can't run here. Music making must be on, in the terminal room or the window."), { code: "stream_not_ready" });
641
659
  const { compiled, receipt } = await this.#compileForGeneration({ descriptors, bpm, bpmSource, title }, { moment, request, transient });
642
660
  if (!compiled.usable) {
643
661
  return { state: "not_made", reason: compiled.descriptors.length ? "needs_a_sound" : "no_traceable_descriptors", dropped: compiled.dropped };
644
662
  }
645
663
  await this.#endStream("replaced");
664
+ // The stream's name, which its first track carries, in the language Moondog is set to.
665
+ const named = await this.#titleInSettingLanguage(compiled, nameRecord, signal);
646
666
  // Other music pauses only once the first track exists, as for a single track.
647
667
  let started;
648
668
  try {
649
- started = await this.atmosphereStream.start({ compiled, title: compiled.title, seconds, minutes, receipt },
650
- { signal, onProgress, onTrack: (track) => this.#noteGeneratedPlay(track), beforePlay: () => this.quietOtherPlayers("apple_music", { signal }) });
669
+ started = await this.atmosphereStream.start({ compiled, title: named, seconds, minutes, receipt },
670
+ { signal, onProgress, onTrack: (track) => this.#noteGeneratedPlay(track), beforePlay: () => this.quietOtherPlayers("apple_music", { signal }),
671
+ nameTrack: typeof nameRecord === "function" ? this.#streamNamer(nameRecord, compiled) : undefined });
651
672
  } catch (error) {
652
673
  // The listener turned it off while its first track was made: their choice, not a failure to retry.
653
674
  if (error?.code === "generation_stopped" && error.ended === "asked" && !signal?.aborted) return { state: "not_made", reason: "stopped_by_listener" };
@@ -659,6 +680,37 @@ export class MoondogApplication {
659
680
  ...(compiled.bpm ? { bpm: compiled.bpm } : {}), elapsed_ms: made.elapsed_ms };
660
681
  }
661
682
 
683
+ // The agent writes a record's title itself, but can follow the conversation's language instead of
684
+ // the one Moondog is set to. A title in another language is named again in the setting's language,
685
+ // keeping its picture; if that cannot be done in time, the agent's title stays.
686
+ async #titleInSettingLanguage(compiled, nameRecord, signal) {
687
+ if (typeof nameRecord !== "function" || nameFitsLanguage(compiled.title, this.locale)) return compiled.title;
688
+ const terms = compiled.descriptors.map((descriptor) => descriptor.term);
689
+ const timeout = AbortSignal.timeout(TITLE_LANGUAGE_TIMEOUT_MS);
690
+ try {
691
+ const name = await Promise.race([
692
+ this.#streamNamer(nameRecord, compiled)({ draft: compiled.title, featured: terms.slice(0, 3), sound: terms.slice(3),
693
+ ...(compiled.bpm ? { bpm: compiled.bpm } : {}), earlier: [] }, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout }),
694
+ new Promise((resolve) => { timeout.addEventListener("abort", () => resolve(null), { once: true }); }),
695
+ ]);
696
+ return name ?? compiled.title;
697
+ } catch {
698
+ return compiled.title;
699
+ }
700
+ }
701
+
702
+ // Names a stream's later tracks: the agent's namer asks the model, with the names of Moondog's recent
703
+ // tracks so it does not repeat them, and a name passes the same check as the agent's own titles and
704
+ // must be in the language Moondog is set to.
705
+ #streamNamer(nameRecord, compiled) {
706
+ return async (context, options) => {
707
+ const recent = (this.localGenerator?.recentTracks?.(8) ?? []).map((track) => track.title).filter(Boolean);
708
+ const name = acceptableRecordTitle(await nameRecord({ ...context, recent }, options), compiled.names);
709
+ // A name in another language than Moondog's setting is not kept.
710
+ return name && nameFitsLanguage(name, this.locale) ? name : null;
711
+ };
712
+ }
713
+
662
714
  // A single track or a new stream takes over from a running stream, once its next track stops being made.
663
715
  // The stream's track plays on until the new one starts, so nothing goes silent while that is made.
664
716
  // Says whether a stream ended.
@@ -774,9 +826,10 @@ export class MoondogApplication {
774
826
  return this.requireNetease().nowPlaying();
775
827
  }
776
828
 
777
- // Whether any player the listener uses can be read: the Music app, Spotify or NetEase.
829
+ // Whether any player the listener uses can be read: Moondog's own, the Music app, Spotify or NetEase.
778
830
  listeningReadable() {
779
- return this.musicApp?.available === true || this.spotifyReady() || this.neteaseReady();
831
+ return typeof this.musicPreviewPlayer?.nowPlaying === "function" || this.musicApp?.available === true ||
832
+ this.spotifyReady() || this.neteaseReady();
780
833
  }
781
834
 
782
835
  // What the listener plays right now on any of them, and what it plays from (src/core/listening-now.mjs).
@@ -2183,6 +2236,10 @@ export class MoondogApplication {
2183
2236
  const rest = new Set();
2184
2237
  while (album && queue.at(-1)?.album_uri === album && !rest.has(queue.at(-1).uri)) rest.add(queue.pop().uri);
2185
2238
  const seen = new Set([observed.currently_playing?.uri, ...queue.map(track => track.uri)].filter(Boolean));
2239
+ // With nothing playing and nothing queued, Spotify has dropped what Moondog queued (its device
2240
+ // went idle), so those songs were not heard: a new request may queue them again.
2241
+ // A player that could not be read says nothing, so the songs stay held back.
2242
+ if (!seen.size && player && player.is_playing !== true && !player.item) this.recentSimilarQueueUris.clear();
2186
2243
  for (const [uri, acceptedAt] of this.recentSimilarQueueUris) {
2187
2244
  if (Date.now() - acceptedAt > 15 * 60_000) this.recentSimilarQueueUris.delete(uri);
2188
2245
  else seen.add(uri);
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `„${title}“ von ${artist}` : artist; return `\n\nZurückgenommen: deine frühere Wahl zu ${name}.`; },
133
133
  "generate.made": ({ title, length, seed }) => `Auf diesem Mac mit Stable Audio 3 gemacht: „${title}“ (${length}, Seed ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `Der Stream läuft auf diesem Mac weiter, jedes Mal mit einem neuen Stück, bis zu ${minutes} Minuten. Du kannst ihn jederzeit stoppen.`,
135
- "onAir.request": "Geh auf Sendung: Lass die Musik für jetzt weiterlaufen",
135
+ "onAir.request": "Geh auf Sendung: Mach neue Musik für jetzt und lass sie weiterlaufen",
136
136
  "turn.cancelled": "Weitere Schritte abgebrochen.",
137
137
  "turn.interrupted": "Die Antwort wurde unterbrochen. Die erfassten Spotify-Ergebnisse stehen unten; keine Aktion wurde wiederholt.",
138
138
  };
@@ -134,7 +134,7 @@ export default {
134
134
  "generate.made": ({ title, length, seed }) => `Made on this Mac with Stable Audio 3: "${title}" (${length}, seed ${seed}).`,
135
135
  "generate.stream": ({ minutes }) => `The stream keeps going on this Mac, a new track each time, for up to ${minutes} minutes. Stop it any time.`,
136
136
  // What the listener says to go on air, in the language the reply follows (/onair in the terminal).
137
- "onAir.request": "Go on air: keep the music going for right now",
137
+ "onAir.request": "Go on air: make new music for right now and keep it going",
138
138
  "turn.cancelled": "Cancelled further work.",
139
139
  "turn.interrupted": "The response was interrupted. The recorded Spotify outcomes are below; no action was replayed.",
140
140
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nDeshecho: tu elección anterior sobre ${name}.`; },
133
133
  "generate.made": ({ title, length, seed }) => `Hecho en este Mac con Stable Audio 3: «${title}» (${length}, semilla ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `El flujo sigue en este Mac, con una pista nueva cada vez, hasta ${minutes} minutos. Puedes pararlo cuando quieras.`,
135
- "onAir.request": "Sal al aire: que la música siga sonando para este momento",
135
+ "onAir.request": "Sal al aire: crea música nueva para este momento y que siga sonando",
136
136
  "turn.cancelled": "Cancelé el resto.",
137
137
  "turn.interrupted": "La respuesta se interrumpió. Abajo están los resultados registrados en Spotify; no se repitió ninguna acción.",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `« ${title} » de ${artist}` : artist; return `\n\nAnnulé : votre choix précédent sur ${name}.`; },
133
133
  "generate.made": ({ title, length, seed }) => `Fait sur ce Mac avec Stable Audio 3 : « ${title} » (${length}, graine ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `Le flux continue sur ce Mac, avec un nouveau morceau à chaque fois, jusqu'à ${minutes} minutes. Vous pouvez l'arrêter à tout moment.`,
135
- "onAir.request": "Prends l'antenne : que la musique continue pour ce moment",
135
+ "onAir.request": "Prends l'antenne : crée de la musique nouvelle pour ce moment et fais-la durer",
136
136
  "turn.cancelled": "Suite annulée.",
137
137
  "turn.interrupted": "La réponse a été interrompue. Les résultats Spotify enregistrés figurent ci-dessous ; aucune action n'a été relancée.",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" di ${artist}` : artist; return `\n\nAnnullato: la tua scelta precedente su ${name}.`; },
133
133
  "generate.made": ({ title, length, seed }) => `Fatto su questo Mac con Stable Audio 3: «${title}» (${length}, seed ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `Il flusso continua su questo Mac, con un brano nuovo ogni volta, fino a ${minutes} minuti. Puoi fermarlo quando vuoi.`,
135
- "onAir.request": "Vai in onda: tieni la musica accesa per adesso",
135
+ "onAir.request": "Vai in onda: crea musica nuova per adesso e tienila accesa",
136
136
  "turn.cancelled": "Ho annullato il resto.",
137
137
  "turn.interrupted": "La risposta si è interrotta. Gli esiti di Spotify registrati sono qui sotto; nessuna azione è stata ripetuta.",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}の「${title}」` : artist; return `\n\n取り消しました: ${name}についての前の選択。`; },
133
133
  "generate.made": ({ title, length, seed }) => `この Mac の Stable Audio 3 で作りました:「${title}」(${length}、シード ${seed})。`,
134
134
  "generate.stream": ({ minutes }) => `この Mac で一曲ずつ新しい曲を作り続けます(最長 ${minutes} 分)。いつでも止められます。`,
135
- "onAir.request": "オンエアして。今の気分に合わせて音楽をかけ続けて",
135
+ "onAir.request": "オンエアして。今の気分に合わせて新しい音楽を作って、流し続けて",
136
136
  "turn.cancelled": "以降の処理をキャンセルしました。",
137
137
  "turn.interrupted": "返事が中断されました。記録された Spotify の操作結果は下のとおりです。操作は繰り返していません。",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}의 "${title}"` : artist; return `\n\n취소했습니다: ${name}에 대한 이전 선택.`; },
133
133
  "generate.made": ({ title, length, seed }) => `이 Mac의 Stable Audio 3로 만들었습니다: "${title}" (${length}, 시드 ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `이 Mac에서 매번 새 곡을 만들며 최대 ${minutes}분 동안 이어집니다. 언제든 멈출 수 있습니다.`,
135
- "onAir.request": "방송 시작해 줘. 지금 분위기에 맞춰 음악을 계속 틀어 줘",
135
+ "onAir.request": "방송 시작해 줘. 지금 분위기에 맞춰 새 음악을 만들어서 계속 틀어 줘",
136
136
  "turn.cancelled": "남은 작업을 취소했습니다.",
137
137
  "turn.interrupted": "답변이 중단되었습니다. 기록된 Spotify 결과는 아래와 같으며, 어떤 작업도 다시 실행하지 않았습니다.",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nDesfeito: sua escolha anterior sobre ${name}.`; },
133
133
  "generate.made": ({ title, length, seed }) => `Feito neste Mac com o Stable Audio 3: "${title}" (${length}, semente ${seed}).`,
134
134
  "generate.stream": ({ minutes }) => `O fluxo continua neste Mac, com uma faixa nova a cada vez, por até ${minutes} minutos. Pare quando quiser.`,
135
- "onAir.request": "Entre no ar: mantenha a música tocando para agora",
135
+ "onAir.request": "Entre no ar: crie música nova para agora e mantenha tocando",
136
136
  "turn.cancelled": "Cancelei o resto.",
137
137
  "turn.interrupted": "A resposta foi interrompida. Abaixo estão os resultados registrados no Spotify; nenhuma ação foi repetida.",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已撤銷:你之前對${name}的選擇。`; },
133
133
  "generate.made": ({ title, length, seed }) => `用這台 Mac 上的 Stable Audio 3 做的:「${title}」(${length},種子 ${seed})。`,
134
134
  "generate.stream": ({ minutes }) => `音樂流會在這台 Mac 上一首接一首地做下去,最多 ${minutes} 分鐘,隨時可以停。`,
135
- "onAir.request": "開播吧,照著現在的感覺一直放下去",
135
+ "onAir.request": "開播吧:照現在的感覺做新的音樂,一直放下去",
136
136
  "turn.cancelled": "已取消後續操作。",
137
137
  "turn.interrupted": "回覆中斷了。已記錄的 Spotify 結果如下;沒有重新執行任何操作。",
138
138
  };
@@ -132,7 +132,7 @@ export default {
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已撤销:你之前对${name}的选择。`; },
133
133
  "generate.made": ({ title, length, seed }) => `用这台 Mac 上的 Stable Audio 3 做的:「${title}」(${length},种子 ${seed})。`,
134
134
  "generate.stream": ({ minutes }) => `音乐流会在这台 Mac 上一首接一首地做下去,最多 ${minutes} 分钟,随时可以停。`,
135
- "onAir.request": "开播吧,照着现在的感觉一直放下去",
135
+ "onAir.request": "开播吧:按现在的感觉做新的音乐,一直放下去",
136
136
  "turn.cancelled": "已取消后续操作。",
137
137
  "turn.interrupted": "回复中断;已确认的 Spotify 操作结果如下。未重新执行任何操作。",
138
138
  };
@@ -1060,4 +1060,62 @@ export default {
1060
1060
  "The next record is still being cut.": "El siguiente disco aún se está grabando.",
1061
1061
  "Off air.": "Fuera del aire.",
1062
1062
  "Turn on music making first: /generation on.": "Primero activa la creación de música: /generation on.",
1063
+ "Download a Stable Audio 3 model from Stability AI": "Descargar un modelo de Stable Audio 3 de Stability AI",
1064
+ "Make music with a downloaded model": "Hacer música con un modelo descargado",
1065
+ "Delete a downloaded model from this Mac": "Borrar de este Mac un modelo descargado",
1066
+ "Stop the download": "Detener la descarga",
1067
+ "Medium: best sound, about 5.2 GB": "Medium: el mejor sonido, unos 5,2 GB",
1068
+ "Small Music: faster, lighter, about 1.7 GB": "Small Music: más rápido y ligero, unos 1,7 GB",
1069
+ "getting ready": "preparando",
1070
+ "There isn't enough free space on this Mac for the download. Free some up, then use /generation download again.": "No hay espacio libre suficiente en este Mac para la descarga. Libera algo y vuelve a usar /generation download.",
1071
+ "A downloaded file didn't match the one Stability AI publishes, so Moondog deleted it. Use /generation download to try again.": "Un archivo descargado no coincidía con el que publica Stability AI, así que Moondog lo borró. Usa /generation download para intentarlo de nuevo.",
1072
+ "The download couldn't reach its source. Check your connection, then use /generation download to try again.": "La descarga no pudo llegar a su origen. Revisa tu conexión y usa /generation download para intentarlo de nuevo.",
1073
+ "Another Moondog window or terminal is already downloading. /generation shows how far it is.": "Otra ventana o terminal de Moondog ya está descargando. /generation muestra cuánto lleva.",
1074
+ "The download didn't finish: {error}. Use /generation download to try again.": "La descarga no terminó: {error}. Usa /generation download para intentarlo de nuevo.",
1075
+ "unknown error": "error desconocido",
1076
+ "Stable Audio 3 {model} is ready. Ask for a track.": "Stable Audio 3 {model} está listo. Pide una pista.",
1077
+ "Stable Audio 3 {model} is downloaded. Use it with /generation use {choice}.": "Stable Audio 3 {model} está descargado. Úsalo con /generation use {choice}.",
1078
+ "Download stopped. /generation download {choice} picks it up where it left off.": "Descarga detenida. /generation download {choice} sigue donde se quedó.",
1079
+ "download stopped on an error": "descarga detenida por un error",
1080
+ "downloading": "descargando",
1081
+ "downloading, {percent}%": "descargando, {percent}%",
1082
+ "installed, in use": "instalado, en uso",
1083
+ "installed": "instalado",
1084
+ "partly downloaded": "descargado en parte",
1085
+ "not downloaded": "sin descargar",
1086
+ "{size} GB": "{size} GB",
1087
+ "In use: your own install at {folder}.": "En uso: tu propia instalación en {folder}.",
1088
+ "Get a model with /generation download medium or small. /generation use, remove and cancel manage them.": "Consigue un modelo con /generation download medium o small. /generation use, remove y cancel los gestionan.",
1089
+ "Making music on this Mac is off. Get Stable Audio 3 with /generation download medium, or small for a lighter model; music making turns on when it's done.": "Hacer música en este Mac está desactivado. Consigue Stable Audio 3 con /generation download medium, o small para un modelo más ligero; la creación de música se activa al terminar.",
1090
+ "Stable Audio 3 {model} isn't downloaded yet. Get it with /generation download {choice}.": "Stable Audio 3 {model} aún no está descargado. Consíguelo con /generation download {choice}.",
1091
+ "Making music on this Mac is off. Stable Audio 3 {model} is ready; turn it on with /generation on.": "Hacer música en este Mac está desactivado. Stable Audio 3 {model} está listo; actívalo con /generation on.",
1092
+ "Use /generation download, use or remove with medium or small, or /generation cancel.": "Usa /generation download, use o remove con medium o small, o /generation cancel.",
1093
+ "No download is running.": "No hay ninguna descarga en curso.",
1094
+ "Stable Audio 3 {model} is already downloading.": "Stable Audio 3 {model} ya se está descargando.",
1095
+ "Another download is running. /generation cancel stops it.": "Hay otra descarga en curso. /generation cancel la detiene.",
1096
+ "Stable Audio 3 {model} is already on this Mac. Use it with /generation use {choice}.": "Stable Audio 3 {model} ya está en este Mac. Úsalo con /generation use {choice}.",
1097
+ "Already downloaded.": "Ya está descargado.",
1098
+ "Stable Audio 3 comes under the Stability AI Community License ({license}), and its text encoder, T5Gemma, under the Gemma Terms of Use ({gemma}). Moondog downloads it only from Stability AI's official sources and checks every file.\n\nTo agree and download {model} ({size} GB): /generation download {choice} --agree": "Stable Audio 3 se rige por la Stability AI Community License ({license}), y su codificador de texto, T5Gemma, por los Gemma Terms of Use ({gemma}). Moondog lo descarga solo de las fuentes oficiales de Stability AI y comprueba cada archivo.\n\nPara aceptar y descargar {model} ({size} GB): /generation download {choice} --agree",
1099
+ "Waiting for your agreement.": "Esperando tu aceptación.",
1100
+ "The download didn't finish.": "La descarga no terminó.",
1101
+ "Downloading Stable Audio 3 {model} ({size} GB) from Stability AI's official sources. It runs in the background; /generation cancel stops it, and a later download picks up where it left off.": "Descargando Stable Audio 3 {model} ({size} GB) de las fuentes oficiales de Stability AI. Sigue en segundo plano; /generation cancel la detiene, y una descarga posterior sigue donde se quedó.",
1102
+ "Downloading Stable Audio 3 {model}.": "Descargando Stable Audio 3 {model}.",
1103
+ "Wait for the download to finish, or stop it with /generation cancel, before removing a model.": "Espera a que termine la descarga, o detenla con /generation cancel, antes de quitar un modelo.",
1104
+ "A download is running.": "Hay una descarga en curso.",
1105
+ "Stable Audio 3 {model} isn't on this Mac.": "Stable Audio 3 {model} no está en este Mac.",
1106
+ "Removing Stable Audio 3 {model} deletes its {size} GB from this Mac. It's the last model, so the rest of Moondog's Stable Audio 3 setup goes too. To go ahead: /generation remove {choice} --yes": "Quitar Stable Audio 3 {model} borra sus {size} GB de este Mac. Es el último modelo, así que también se va el resto de la instalación de Stable Audio 3 de Moondog. Para seguir: /generation remove {choice} --yes",
1107
+ "Removing Stable Audio 3 {model} deletes its {size} GB from this Mac. To go ahead: /generation remove {choice} --yes": "Quitar Stable Audio 3 {model} borra sus {size} GB de este Mac. Para seguir: /generation remove {choice} --yes",
1108
+ "Nothing removed yet.": "Aún no se ha quitado nada.",
1109
+ "Removed Stable Audio 3 {model} from this Mac.": "Stable Audio 3 {model} quitado de este Mac.",
1110
+ "Removed.": "Quitado.",
1111
+ "Removed. Music making isn't ready.": "Quitado. La creación de música no está lista.",
1112
+ "Use /generation, /generation on [folder] or off, or /generation download, use or remove with medium or small.": "Usa /generation, /generation on [carpeta] u off, o /generation download, use o remove con medium o small.",
1113
+ "best sound": "el mejor sonido",
1114
+ "faster, lighter": "más rápido y ligero",
1115
+ "getting uv": "obteniendo uv",
1116
+ "getting Stable Audio 3's code": "obteniendo el código de Stable Audio 3",
1117
+ "setting up Python": "preparando Python",
1118
+ "downloading the model": "descargando el modelo",
1119
+ "checking the files": "comprobando los archivos",
1120
+ "Stable Audio 3's Python environment couldn't be set up. Check your connection, then use /generation download to try again.": "No se pudo preparar el entorno de Python de Stable Audio 3. Revisa tu conexión y usa /generation download para intentarlo de nuevo.",
1063
1121
  };
@@ -1060,4 +1060,62 @@ export default {
1060
1060
  "The next record is still being cut.": "次のレコードはまだカッティング中です。",
1061
1061
  "Off air.": "オフエアにしました。",
1062
1062
  "Turn on music making first: /generation on.": "先に音楽作りをオンにしてください:/generation on。",
1063
+ "Download a Stable Audio 3 model from Stability AI": "Stability AI から Stable Audio 3 のモデルをダウンロードする",
1064
+ "Make music with a downloaded model": "ダウンロードしたモデルで音楽を作る",
1065
+ "Delete a downloaded model from this Mac": "ダウンロードしたモデルをこの Mac から削除する",
1066
+ "Stop the download": "ダウンロードを止める",
1067
+ "Medium: best sound, about 5.2 GB": "Medium:いちばん良い音、約 5.2 GB",
1068
+ "Small Music: faster, lighter, about 1.7 GB": "Small Music:速くて軽い、約 1.7 GB",
1069
+ "getting ready": "準備中",
1070
+ "There isn't enough free space on this Mac for the download. Free some up, then use /generation download again.": "この Mac にはダウンロードに足りる空き容量がありません。空きを作ってから、もう一度 /generation download を使ってください。",
1071
+ "A downloaded file didn't match the one Stability AI publishes, so Moondog deleted it. Use /generation download to try again.": "ダウンロードしたファイルが Stability AI の公開しているものと一致しなかったため、Moondog が削除しました。/generation download でやり直してください。",
1072
+ "The download couldn't reach its source. Check your connection, then use /generation download to try again.": "ダウンロード元に接続できませんでした。接続を確かめてから、/generation download でやり直してください。",
1073
+ "Another Moondog window or terminal is already downloading. /generation shows how far it is.": "別の Moondog のウィンドウかターミナルがすでにダウンロードしています。/generation で進み具合がわかります。",
1074
+ "The download didn't finish: {error}. Use /generation download to try again.": "ダウンロードが終わりませんでした:{error}。/generation download でやり直してください。",
1075
+ "unknown error": "不明なエラー",
1076
+ "Stable Audio 3 {model} is ready. Ask for a track.": "Stable Audio 3 {model} の準備ができました。曲をリクエストしてください。",
1077
+ "Stable Audio 3 {model} is downloaded. Use it with /generation use {choice}.": "Stable Audio 3 {model} をダウンロードしました。/generation use {choice} で使えます。",
1078
+ "Download stopped. /generation download {choice} picks it up where it left off.": "ダウンロードを止めました。/generation download {choice} で続きから再開します。",
1079
+ "download stopped on an error": "エラーでダウンロードが止まりました",
1080
+ "downloading": "ダウンロード中",
1081
+ "downloading, {percent}%": "ダウンロード中、{percent}%",
1082
+ "installed, in use": "インストール済み、使用中",
1083
+ "installed": "インストール済み",
1084
+ "partly downloaded": "一部ダウンロード済み",
1085
+ "not downloaded": "未ダウンロード",
1086
+ "{size} GB": "{size} GB",
1087
+ "In use: your own install at {folder}.": "使用中:{folder} にある自分のインストール。",
1088
+ "Get a model with /generation download medium or small. /generation use, remove and cancel manage them.": "/generation download medium か small でモデルを入手します。/generation use、remove、cancel で管理します。",
1089
+ "Making music on this Mac is off. Get Stable Audio 3 with /generation download medium, or small for a lighter model; music making turns on when it's done.": "この Mac での音楽作りはオフです。/generation download medium で Stable Audio 3 を入手してください。軽いモデルなら small です。終わると音楽作りがオンになります。",
1090
+ "Stable Audio 3 {model} isn't downloaded yet. Get it with /generation download {choice}.": "Stable Audio 3 {model} はまだダウンロードされていません。/generation download {choice} で入手してください。",
1091
+ "Making music on this Mac is off. Stable Audio 3 {model} is ready; turn it on with /generation on.": "この Mac での音楽作りはオフです。Stable Audio 3 {model} は準備できています。/generation on でオンにしてください。",
1092
+ "Use /generation download, use or remove with medium or small, or /generation cancel.": "/generation download、use、remove に medium か small を付けるか、/generation cancel を使ってください。",
1093
+ "No download is running.": "実行中のダウンロードはありません。",
1094
+ "Stable Audio 3 {model} is already downloading.": "Stable Audio 3 {model} はすでにダウンロード中です。",
1095
+ "Another download is running. /generation cancel stops it.": "別のダウンロードが実行中です。/generation cancel で止まります。",
1096
+ "Stable Audio 3 {model} is already on this Mac. Use it with /generation use {choice}.": "Stable Audio 3 {model} はすでにこの Mac にあります。/generation use {choice} で使えます。",
1097
+ "Already downloaded.": "ダウンロード済みです。",
1098
+ "Stable Audio 3 comes under the Stability AI Community License ({license}), and its text encoder, T5Gemma, under the Gemma Terms of Use ({gemma}). Moondog downloads it only from Stability AI's official sources and checks every file.\n\nTo agree and download {model} ({size} GB): /generation download {choice} --agree": "Stable Audio 3 は Stability AI Community License({license})に、そのテキストエンコーダー T5Gemma は Gemma Terms of Use({gemma})に従います。Moondog は Stability AI の公式の配布元からのみダウンロードし、すべてのファイルを確かめます。\n\n同意して {model}({size} GB)をダウンロードするには:/generation download {choice} --agree",
1099
+ "Waiting for your agreement.": "同意を待っています。",
1100
+ "The download didn't finish.": "ダウンロードが終わりませんでした。",
1101
+ "Downloading Stable Audio 3 {model} ({size} GB) from Stability AI's official sources. It runs in the background; /generation cancel stops it, and a later download picks up where it left off.": "Stability AI の公式の配布元から Stable Audio 3 {model}({size} GB)をダウンロードしています。バックグラウンドで進みます。/generation cancel で止まり、後のダウンロードは続きから再開します。",
1102
+ "Downloading Stable Audio 3 {model}.": "Stable Audio 3 {model} をダウンロードしています。",
1103
+ "Wait for the download to finish, or stop it with /generation cancel, before removing a model.": "モデルを削除する前に、ダウンロードが終わるのを待つか、/generation cancel で止めてください。",
1104
+ "A download is running.": "ダウンロードが実行中です。",
1105
+ "Stable Audio 3 {model} isn't on this Mac.": "Stable Audio 3 {model} はこの Mac にありません。",
1106
+ "Removing Stable Audio 3 {model} deletes its {size} GB from this Mac. It's the last model, so the rest of Moondog's Stable Audio 3 setup goes too. To go ahead: /generation remove {choice} --yes": "Stable Audio 3 {model} を削除すると、この Mac からその {size} GB が消えます。最後のモデルなので、Moondog の Stable Audio 3 のほかの部分も消えます。進めるには:/generation remove {choice} --yes",
1107
+ "Removing Stable Audio 3 {model} deletes its {size} GB from this Mac. To go ahead: /generation remove {choice} --yes": "Stable Audio 3 {model} を削除すると、この Mac からその {size} GB が消えます。進めるには:/generation remove {choice} --yes",
1108
+ "Nothing removed yet.": "まだ何も削除していません。",
1109
+ "Removed Stable Audio 3 {model} from this Mac.": "Stable Audio 3 {model} をこの Mac から削除しました。",
1110
+ "Removed.": "削除しました。",
1111
+ "Removed. Music making isn't ready.": "削除しました。音楽作りの準備ができていません。",
1112
+ "Use /generation, /generation on [folder] or off, or /generation download, use or remove with medium or small.": "/generation、/generation on [フォルダ] か off、または /generation download、use、remove に medium か small を付けて使ってください。",
1113
+ "best sound": "いちばん良い音",
1114
+ "faster, lighter": "速くて軽い",
1115
+ "getting uv": "uv を取得中",
1116
+ "getting Stable Audio 3's code": "Stable Audio 3 のコードを取得中",
1117
+ "setting up Python": "Python を準備中",
1118
+ "downloading the model": "モデルをダウンロード中",
1119
+ "checking the files": "ファイルを確認中",
1120
+ "Stable Audio 3's Python environment couldn't be set up. Check your connection, then use /generation download to try again.": "Stable Audio 3 の Python 環境を準備できませんでした。接続を確かめてから、/generation download でやり直してください。",
1063
1121
  };