@audiofool/moondog 0.2.2 → 0.3.0-beta.2

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.
@@ -5,6 +5,8 @@ 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";
9
+ import { stableAudioPrompt } from "../integrations/stable-audio/local-generator.mjs";
8
10
 
9
11
  import {
10
12
  readAppleMusicSourceStatus,
@@ -221,6 +223,8 @@ export class MoondogApplication {
221
223
  musicSimilarity = null,
222
224
  musicPreviewPlayer = null,
223
225
  neteasePlayer = null,
226
+ localGenerator = null,
227
+ atmosphereStream = null,
224
228
  musicApp = null,
225
229
  agentHostNotes = [],
226
230
  artistIdentityResolver = null,
@@ -266,6 +270,10 @@ export class MoondogApplication {
266
270
  this.musicPreviewPlayer = musicPreviewPlayer;
267
271
  // NetEase Cloud Music through ncm-cli (src/integrations/netease), shared by every conversation.
268
272
  this.neteasePlayer = neteasePlayer;
273
+ // Stable Audio 3 on this Mac (src/integrations/stable-audio), shared by every conversation, so one track is made at a time.
274
+ this.localGenerator = localGenerator;
275
+ // Moondog's atmosphere stream on that generator, one per room (src/integrations/stable-audio).
276
+ this.atmosphereStream = atmosphereStream;
269
277
  // The Music app on this Mac (src/integrations/apple-music/music-app.mjs), read for what the
270
278
  // listener plays there themselves.
271
279
  this.musicApp = musicApp;
@@ -523,6 +531,204 @@ export class MoondogApplication {
523
531
  return this.neteasePlayer;
524
532
  }
525
533
 
534
+ // Moondog makes music on this Mac once the listener turned it on and the install is ready, in a
535
+ // host whose player can play a local track: the terminal room and the window, not one-shot commands.
536
+ localGenerationReady() {
537
+ return this.localGenerator?.ready?.() === true && typeof this.musicPreviewPlayer?.playGenerated === "function";
538
+ }
539
+
540
+ generationStatus() {
541
+ return this.localGenerator?.status?.() ?? null;
542
+ }
543
+
544
+ // Moondog's newest tracks and how each landed, for the agent to calibrate the next one by. They
545
+ // are outcomes of Moondog's own picks, never the listener's taste.
546
+ // Each reply reads the made tracks again, so the other surface's tracks and outcomes show too.
547
+ async refreshMadeTracks() {
548
+ if (this.localGenerationReady()) await this.localGenerator.load?.();
549
+ }
550
+
551
+ madeTracks() {
552
+ if (!this.localGenerationReady()) return null;
553
+ const tracks = this.localGenerator.recentTracks?.(5) ?? [];
554
+ return tracks.length ? tracks : null;
555
+ }
556
+
557
+ // Plays a track Moondog made again. A track the listener named in their own words is their pick:
558
+ // it stays out of the ledger and counts as their replay. Otherwise it is Moondog's pick again.
559
+ async playMadeTrack({ id } = {}, { request = null, signal } = {}) {
560
+ if (!this.localGenerationReady()) throw Object.assign(new Error("Local generation is not ready. The listener can turn it on in Settings or with /generation."), { code: "generation_not_ready" });
561
+ const receipt = await this.localGenerator.receipt(id);
562
+ if (!receipt) throw Object.assign(new Error("There is no track Moondog made with that id on this Mac."), { code: "made_track_not_found" });
563
+ const file = await this.localGenerator.trackFile(id);
564
+ if (!file) throw Object.assign(new Error("That track was part of a stream, which keeps only its newest tracks' audio; its receipt is still here."), { code: "made_track_audio_not_kept" });
565
+ // Playing one of Moondog's tracks again ends its stream, on every surface.
566
+ const endedStream = await this.#endStream("replaced");
567
+ const title = String(receipt.title ?? "");
568
+ // Named means quoted, or the title with Moondog, as on every play path: an open request shares
569
+ // words with the plain titles Moondog gives its records ("something for a rainy day").
570
+ const named = typeof request === "string" && textNamesSong(request, title, ["Moondog"]);
571
+ await this.quietOtherPlayers("apple_music", { signal });
572
+ // A reply stopped before the track starts plays nothing, and no replay is recorded.
573
+ if (signal?.aborted) throw Object.assign(new Error("Nothing was played, because the reply was stopped."), { code: "generation_stopped" });
574
+ // Nothing waits between this check and the play; the replay is written once it plays. The ended
575
+ // stream's track it cuts off, or its queued one that started meanwhile, says nothing about the listener.
576
+ if (endedStream) this.localGenerator.discard?.();
577
+ this.localGenerator.restart?.(id);
578
+ this.musicPreviewPlayer.playGenerated({ id, title, seconds: receipt.audio?.duration_seconds, file });
579
+ if (!named) this.#noteGeneratedPlay({ id, title });
580
+ await this.localGenerator.recordReplay?.(id, named ? "listener" : "moondog");
581
+ return { state: "playing", track: { id, title, seconds: receipt.audio?.duration_seconds ?? null }, chosen_by: named ? "listener" : "moondog" };
582
+ }
583
+
584
+ // Makes one instrumental track from the listener's taste and plays it in Moondog's own player.
585
+ // The agent proposes sound descriptors, each with its source; the taste compiler keeps only the
586
+ // ones it can trace and none that names an artist or a song. The track and its receipt stay on
587
+ // this Mac, and its play goes in the ledger as Moondog's own, never as the listener's attention.
588
+ // 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 } = {}) {
590
+ const { compiled, receipt } = await this.#compileForGeneration({ descriptors, bpm, bpmSource, title }, { moment, request, transient });
591
+ if (!compiled.usable) {
592
+ return { state: "not_made", reason: compiled.descriptors.length ? "needs_a_sound" : "no_traceable_descriptors", dropped: compiled.dropped };
593
+ }
594
+ const prompt = stableAudioPrompt(compiled);
595
+ const endedStream = await this.#endStream("replaced");
596
+ const made = await this.localGenerator.generate({ prompt, seconds, title: compiled.title, receipt }, { signal, onProgress });
597
+ // A reply stopped as the track finished keeps the track, but plays nothing over what is on now.
598
+ await this.quietOtherPlayers("apple_music", { signal });
599
+ // The terminal plays the file itself; the window asks for it by id and never sees the path.
600
+ const file = await this.localGenerator.trackFile(made.id);
601
+ // A reply stopped while the track was made, or while the other players paused, keeps the track but plays nothing.
602
+ if (signal?.aborted) throw Object.assign(new Error("The track was made but not played, because the reply was stopped."), { code: "generation_stopped" });
603
+ // The replaced stream's track it cuts off was stopped by Moondog, not left by the listener.
604
+ if (endedStream) this.localGenerator.discard?.();
605
+ this.musicPreviewPlayer.playGenerated({ id: made.id, title: made.title, seconds: made.duration_seconds, file });
606
+ this.#noteGeneratedPlay(made);
607
+ return { state: "playing", track: { id: made.id, title: made.title, seconds: made.duration_seconds, seed: made.seed },
608
+ prompt, shaped_by: compiled.descriptors, sources: tasteSources(compiled.descriptors), left_out: compiled.dropped,
609
+ ...(compiled.bpm ? { bpm: compiled.bpm } : {}), elapsed_ms: made.elapsed_ms };
610
+ }
611
+
612
+ // The taste compiled for a track or a stream, and what its receipts keep of why.
613
+ async #compileForGeneration({ descriptors, bpm, bpmSource, title }, { moment, request, transient }) {
614
+ if (!this.localGenerationReady()) throw Object.assign(new Error("Local generation is not ready. The listener can turn it on in Settings or with /generation."), { code: "generation_not_ready" });
615
+ this.requireListenerAvoids();
616
+ const context = await this.#tasteContext({ descriptors, moment, request });
617
+ const compiled = compileTaste({ descriptors, bpm, bpm_source: bpmSource, title }, context);
618
+ return { compiled, receipt: {
619
+ shaped_by: compiled.descriptors, left_out: compiled.dropped,
620
+ ...(compiled.bpm ? { bpm: compiled.bpm, bpm_source: compiled.bpm_source } : {}),
621
+ // A conversation that read Spotify stays out of memory, and its words out of the receipt too.
622
+ ...(typeof request === "string" && !transient && !this.transientSpotifyContext ? { request: Array.from(request).slice(0, 300).join("") } : {}),
623
+ avoids_in_force: context.avoids.length,
624
+ } };
625
+ }
626
+
627
+ // A stream needs a player that can queue a made track behind the one playing.
628
+ streamReady() {
629
+ return this.localGenerationReady() && typeof this.atmosphereStream?.start === "function" &&
630
+ typeof this.musicPreviewPlayer?.enqueueGenerated === "function";
631
+ }
632
+
633
+ streamStatus() {
634
+ return this.atmosphereStream?.status?.() ?? null;
635
+ }
636
+
637
+ // Starts Moondog's atmosphere stream from the listener's taste: its first track plays now, and
638
+ // 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 } = {}) {
640
+ 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
+ const { compiled, receipt } = await this.#compileForGeneration({ descriptors, bpm, bpmSource, title }, { moment, request, transient });
642
+ if (!compiled.usable) {
643
+ return { state: "not_made", reason: compiled.descriptors.length ? "needs_a_sound" : "no_traceable_descriptors", dropped: compiled.dropped };
644
+ }
645
+ await this.#endStream("replaced");
646
+ // Other music pauses only once the first track exists, as for a single track.
647
+ let started;
648
+ 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 }) });
651
+ } catch (error) {
652
+ // The listener turned it off while its first track was made: their choice, not a failure to retry.
653
+ if (error?.code === "generation_stopped" && error.ended === "asked" && !signal?.aborted) return { state: "not_made", reason: "stopped_by_listener" };
654
+ throw error;
655
+ }
656
+ const { made, prompt } = started;
657
+ return { state: "streaming", minutes, track: { id: made.id, title: made.title, seconds: made.duration_seconds, seed: made.seed },
658
+ prompt, shaped_by: compiled.descriptors, sources: tasteSources(compiled.descriptors), left_out: compiled.dropped,
659
+ ...(compiled.bpm ? { bpm: compiled.bpm } : {}), elapsed_ms: made.elapsed_ms };
660
+ }
661
+
662
+ // A single track or a new stream takes over from a running stream, once its next track stops being made.
663
+ // The stream's track plays on until the new one starts, so nothing goes silent while that is made.
664
+ // Says whether a stream ended.
665
+ async #endStream(reason) {
666
+ if (!this.atmosphereStream?.active?.()) return false;
667
+ this.atmosphereStream.stop(reason);
668
+ for (let waited = 0; this.localGenerator?.busy?.() && waited < 5_000; waited += 100) await delay(100);
669
+ return true;
670
+ }
671
+
672
+ stopStream() {
673
+ const stopped = this.atmosphereStream?.stop?.("asked") ?? null;
674
+ return stopped ? { state: "stopped", tracks_made: stopped.tracks_made } : { state: "not_running" };
675
+ }
676
+
677
+ // What the window and the terminal show of the stream: { phase, started_at, ends_at, tracks_made }, or null.
678
+ streamView() {
679
+ return this.atmosphereStream?.view?.() ?? null;
680
+ }
681
+
682
+ // Each change of the stream, as { view, ended }; a host without a stream hears nothing.
683
+ subscribeStream(listener) {
684
+ return this.atmosphereStream?.subscribe?.(listener) ?? (() => {});
685
+ }
686
+
687
+ // Whether a Spotify device is this Mac, where its music and Moondog's would play over each other.
688
+ spotifyOnThisMac(device) {
689
+ return spotifyOnThisMac(device);
690
+ }
691
+
692
+ // What the compiler may trace a descriptor to. Each cited finding is read from the saved reading.
693
+ async #tasteContext({ descriptors, moment, request }) {
694
+ const choices = this.domainServices?.listenerChoices?.()?.items ?? [];
695
+ // Every Avoid in force, not only the newest choices, so an old one still keeps its artist out.
696
+ let avoids = null;
697
+ try { avoids = this.domainServices?.activeAvoids?.() ?? null; } catch { /* The newest choices still hold. */ }
698
+ const recent = this.domainServices?.recentAttention?.() ?? null;
699
+ const cited = [...new Set((Array.isArray(descriptors) ? descriptors : [])
700
+ .filter((descriptor) => descriptor?.source === "reading" && typeof descriptor.from === "string").map((descriptor) => descriptor.from.trim()))].slice(0, 12);
701
+ const claims = new Map();
702
+ let revisionId = null;
703
+ for (const claimId of cited) {
704
+ try {
705
+ const reading = await this.getListenerProfile({ claimId });
706
+ if (reading?.claim) { claims.set(claimId, reading.claim); revisionId ??= reading.revision_id ?? null; }
707
+ } catch { /* Not a finding of the saved reading; the compiler drops it. */ }
708
+ }
709
+ let listened = null;
710
+ try { listened = this.domainServices?.listenedNames?.() ?? null; } catch { /* Without a history, only choices are checked. */ }
711
+ return {
712
+ claim: (id) => claims.get(id) ?? null, revisionId,
713
+ likes: choices.filter((choice) => choice.stance === "like"),
714
+ avoids: Array.isArray(avoids) ? avoids : choices.filter((choice) => choice.stance === "avoid"),
715
+ recentArtists: recent?.top_artists ?? [],
716
+ ...(recent?.from_date ? { recentWindow: { from_date: recent.from_date, through_date: recent.through_date } } : {}),
717
+ historyArtists: listened?.artists ?? [], historyTitles: listened?.titles ?? [],
718
+ avoidsName: (name) => !this.avoidAllows({ artist_credit: name, artists: [name] }),
719
+ moment, request,
720
+ };
721
+ }
722
+
723
+ // A track Moondog made and played is its own pick by definition, so no listener-named check applies.
724
+ #noteGeneratedPlay(made) {
725
+ if (typeof this.domainServices?.recordMoondogSuggestions !== "function") return;
726
+ try {
727
+ this.domainServices.recordMoondogSuggestions([{ service: "moondog", action: "generated.play", providerTrackId: `moondog:generated:${made.id}`,
728
+ title: made.title, artistCredit: "", requestedAt: new Date(this.spotifyContextClock()).toISOString() }]);
729
+ } catch { /* The track still plays; only its origin goes unrecorded. The receipt stays. */ }
730
+ }
731
+
526
732
  async searchNetease({ query, limit, intent } = {}) {
527
733
  const songs = await this.requireNetease().search({ query, limit, intent });
528
734
  return Array.isArray(songs) ? this.#withSuggestedBefore(songs, song => ({ service: "netease",
@@ -532,10 +738,13 @@ export class MoondogApplication {
532
738
  async playNetease({ items, mode, intent } = {}) {
533
739
  const player = this.requireNetease();
534
740
  this.requireListenerAvoids();
535
- if (mode !== "queue") await this.quietOtherPlayers("netease");
741
+ const quiet = () => this.quietOtherPlayers("netease");
742
+ if (mode !== "queue") await quiet();
536
743
  // A found song lists its artists; a song asked for by name has only the name it was asked by.
537
- const receipt = await player.play({ items, mode, intent, allow: (song) => this.avoidAllows(Array.isArray(song.artists)
538
- ? { title: song.title, artist_credit: song.artists.join(", "), artists: song.artists } : song) });
744
+ // A queue with nothing loaded starts its first song, and quiets the others just before.
745
+ const receipt = await player.play({ items, mode, intent, ...(mode === "queue" ? { beforePlay: quiet } : {}),
746
+ allow: (song) => this.avoidAllows(Array.isArray(song.artists)
747
+ ? { title: song.title, artist_credit: song.artists.join(", "), artists: song.artists } : song) });
539
748
  if (["playing", "queued"].includes(receipt?.mode)) {
540
749
  const songs = receipt.songs ?? [];
541
750
  const slots = this.#mixSlots(songs.map((song) => ({ title: song.title, artists: song.artists })));
@@ -550,8 +759,13 @@ export class MoondogApplication {
550
759
 
551
760
  async controlNetease({ action, positionSeconds } = {}) {
552
761
  const player = this.requireNetease();
762
+ const quiet = () => this.quietOtherPlayers("netease");
763
+ if (action === "resume") await quiet();
553
764
  if (action === "seek") await player.seek({ positionMs: Math.max(0, Number(positionSeconds) || 0) * 1000 });
554
- else if (["pause", "resume", "next", "previous", "stop"].includes(action)) await player[action]();
765
+ // Next and previous start a song, so the other players go quiet just before; a refusal or a
766
+ // previous that only rewinds starts none.
767
+ else if (action === "next" || action === "previous") await player[action]({ beforePlay: quiet });
768
+ else if (["pause", "resume", "stop"].includes(action)) await player[action]();
555
769
  else throw Object.assign(new Error(`Unknown NetEase action: ${action}`), { code: "netease_failed" });
556
770
  return await player.nowPlaying();
557
771
  }
@@ -645,16 +859,21 @@ export class MoondogApplication {
645
859
  if (starting !== "netease" && this.neteaseReady() && (await this.neteasePlayer.nowPlaying()).state === "playing") await this.neteasePlayer.pause();
646
860
  })(),
647
861
  (async () => {
648
- if (starting !== "apple_music") this.musicPreviewPlayer?.stop?.();
862
+ if (starting === "apple_music") return;
863
+ // Other music ends Moondog's stream too, even while its next track is still being made.
864
+ this.atmosphereStream?.stop?.("other_music");
865
+ this.musicPreviewPlayer?.stop?.();
649
866
  })(),
650
867
  ]);
651
868
  }
652
869
 
653
- // The service just left stops playing, so two services never play at once.
870
+ // The service just left stops playing, so two services never play at once. Moondog's stream is its
871
+ // own channel, not a service: its made tracks play on, and it ends only when a service song plays.
654
872
  async #stopPlaybackService(service) {
655
873
  try {
656
874
  if (service === "apple_music") {
657
- this.musicPreviewPlayer?.stop?.();
875
+ // Apple Music's songs in Moondog's player stop; a player that can't say what it holds stops, as before.
876
+ if (this.musicPreviewPlayer?.holdsCatalogSongs?.() !== false) this.musicPreviewPlayer?.stop?.();
658
877
  // The listener's own Music app too, when it plays.
659
878
  if ((await this.musicApp?.nowPlaying?.())?.state === "playing") await this.musicApp.pause();
660
879
  } else if (service === "netease" && this.neteasePlayer?.ready?.() === true) await this.neteasePlayer.stop();
@@ -706,6 +925,9 @@ export class MoondogApplication {
706
925
  }
707
926
  if (avoided) found.skipped_avoided_count = avoided;
708
927
  if (found.played.length) {
928
+ // Songs Moondog plays end its stream, even one whose first track is still being made; the
929
+ // stream's own tracks also start through quietOtherPlayers("apple_music"), so it can't do this.
930
+ this.atmosphereStream?.stop?.("other_music");
709
931
  await this.quietOtherPlayers("apple_music");
710
932
  await this.musicPreviewPlayer.play(structuredClone(found.played), found.album ? { album: structuredClone(found.album) } : {});
711
933
  }
@@ -1910,8 +2132,6 @@ export class MoondogApplication {
1910
2132
  this.requireSpotifyNonRemovalAction();
1911
2133
  const transaction = this.pendingPlaylistPromptTransaction;
1912
2134
  if (!transaction) throw spotifyResolutionError("spotify_queue_request_required", "Queueing runs inside a listener request.");
1913
- // An idle device starts the first song.
1914
- await this.quietOtherPlayers("spotify", { signal });
1915
2135
  // The model acts on the listener's words, as for a single queue entry. A parsed
1916
2136
  // count only sizes the batch; failing to parse a phrasing never blocks it.
1917
2137
  const intent = transaction.queueIntent ?? null;
@@ -3106,8 +3326,11 @@ export class MoondogApplication {
3106
3326
  const service = this.requireSpotifyService();
3107
3327
  target ??= await service.queueTarget({ ...(deviceId ? { deviceId } : {}) }, { signal });
3108
3328
  const device = target.deviceId ? { deviceId: target.deviceId } : {};
3109
- // Nothing is playing, so a queue entry has nowhere to go: start this song instead.
3329
+ // Nothing is playing, so a queue entry has nowhere to go: start this song instead. That starts
3330
+ // music, so the other players go quiet first; a queue behind a song already there starts none.
3110
3331
  const start = async () => {
3332
+ await this.quietOtherPlayers("spotify", { signal });
3333
+ signal?.throwIfAborted();
3111
3334
  dispatching = true;
3112
3335
  const receipt = await service.resume({ uris: [uri], ...device }, { signal });
3113
3336
  started = label(track);
@@ -3298,15 +3521,18 @@ export class MoondogApplication {
3298
3521
  ...this.webLookupOptions(),
3299
3522
  musicPreviewReady: this.musicPreviewReady(),
3300
3523
  tasteChoicesReady: this.tasteChoicesReady(),
3524
+ localGenerationReady: this.localGenerationReady(),
3301
3525
  }).map(capability => capability.state === "enabled" && this.#playbackServiceExcludes(capability.id)
3302
3526
  ? { ...capability, state: "blocked", blocked_by: capability.id === "listening.now" ? "no_readable_player" : capability.id === "music_app.control" ? "no_music_app" : capability.id === "playback.service" ? "no_playback_service"
3303
- : neteaseCapabilities.has(capability.id) ? "netease_not_ready" : "playback_service_not_spotify" } : capability),
3527
+ : neteaseCapabilities.has(capability.id) ? "netease_not_ready" : capability.id.startsWith("music.generate.stream") ? "stream_not_ready"
3528
+ : "playback_service_not_spotify" } : capability),
3304
3529
  };
3305
3530
  }
3306
3531
 
3307
3532
  // With another music service chosen, Spotify does not play or queue anything; NetEase's
3308
3533
  // tools exist only while it plays. What the listener plays is read wherever it can be.
3309
3534
  #playbackServiceExcludes(capabilityId) {
3535
+ if (capabilityId.startsWith("music.generate.stream")) return !this.streamReady();
3310
3536
  if (capabilityId === "listening.now") return !this.listeningReadable();
3311
3537
  if (capabilityId === "music_app.control") return this.musicApp?.available !== true;
3312
3538
  if (capabilityId === "playback.service") return !this.playbackStatus().services.some((entry) => entry.available);
@@ -3333,6 +3559,7 @@ export class MoondogApplication {
3333
3559
  ...this.webLookupOptions(),
3334
3560
  musicPreviewReady: this.musicPreviewReady(),
3335
3561
  tasteChoicesReady: this.tasteChoicesReady(),
3562
+ localGenerationReady: this.localGenerationReady(),
3336
3563
  }).filter(descriptor => !this.#playbackServiceExcludes(descriptor.capability_id));
3337
3564
  }
3338
3565
 
@@ -3806,6 +4033,12 @@ export class MoondogApplication {
3806
4033
  ok: true,
3807
4034
  state: this.spotifyReady() ? "spotify_control" : "disabled",
3808
4035
  },
4036
+ {
4037
+ id: "music.generate.local",
4038
+ ok: this.localGenerationReady(),
4039
+ state: this.generationStatus()?.state ?? "unavailable",
4040
+ optional_for_local_commands: true,
4041
+ },
3809
4042
  {
3810
4043
  id: "spotify.connection",
3811
4044
  ok: this.spotifyReady(),
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `„${title}“ von ${artist}` : artist; return `\n\nGespeichert: Du magst ${name}. Sag mir jederzeit, wenn du das ändern willst.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `„${title}“ von ${artist}` : artist; return `\n\nGespeichert: Ich lasse ${name} aus allem heraus, was ich spiele und vorschlage. Sag mir jederzeit, wenn du das ändern willst.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `„${title}“ von ${artist}` : artist; return `\n\nZurückgenommen: deine frühere Wahl zu ${name}.`; },
133
+ "generate.made": ({ title, length, seed }) => `Auf diesem Mac mit Stable Audio 3 gemacht: „${title}“ (${length}, Seed ${seed}).`,
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",
133
136
  "turn.cancelled": "Weitere Schritte abgebrochen.",
134
137
  "turn.interrupted": "Die Antwort wurde unterbrochen. Die erfassten Spotify-Ergebnisse stehen unten; keine Aktion wurde wiederholt.",
135
138
  };
@@ -131,6 +131,10 @@ export default {
131
131
  "choice.like": ({ title, artist }) => { const name = title ? `"${title}" by ${artist}` : artist; return `\n\nSaved: you like ${name}. Tell me any time to change it.`; },
132
132
  "choice.avoid": ({ title, artist }) => { const name = title ? `"${title}" by ${artist}` : artist; return `\n\nSaved: I'll keep ${name} out of what I play and suggest. Tell me any time to change it.`; },
133
133
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" by ${artist}` : artist; return `\n\nUndone: your earlier choice about ${name}.`; },
134
+ "generate.made": ({ title, length, seed }) => `Made on this Mac with Stable Audio 3: "${title}" (${length}, seed ${seed}).`,
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
+ // 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",
134
138
  "turn.cancelled": "Cancelled further work.",
135
139
  "turn.interrupted": "The response was interrupted. The recorded Spotify outcomes are below; no action was replayed.",
136
140
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nGuardado: te gusta ${name}. Dímelo cuando quieras cambiarlo.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nGuardado: dejaré ${name} fuera de lo que pongo y te sugiero. Dímelo cuando quieras cambiarlo.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nDeshecho: tu elección anterior sobre ${name}.`; },
133
+ "generate.made": ({ title, length, seed }) => `Hecho en este Mac con Stable Audio 3: «${title}» (${length}, semilla ${seed}).`,
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",
133
136
  "turn.cancelled": "Cancelé el resto.",
134
137
  "turn.interrupted": "La respuesta se interrumpió. Abajo están los resultados registrados en Spotify; no se repitió ninguna acción.",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `« ${title} » de ${artist}` : artist; return `\n\nEnregistré : vous aimez ${name}. Dites-le-moi quand vous voulez pour le changer.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `« ${title} » de ${artist}` : artist; return `\n\nEnregistré : je laisserai ${name} de côté dans ce que je joue et propose. Dites-le-moi quand vous voulez pour le changer.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `« ${title} » de ${artist}` : artist; return `\n\nAnnulé : votre choix précédent sur ${name}.`; },
133
+ "generate.made": ({ title, length, seed }) => `Fait sur ce Mac avec Stable Audio 3 : « ${title} » (${length}, graine ${seed}).`,
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",
133
136
  "turn.cancelled": "Suite annulée.",
134
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.",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `"${title}" di ${artist}` : artist; return `\n\nSalvato: ti piace ${name}. Dimmelo quando vuoi cambiarlo.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `"${title}" di ${artist}` : artist; return `\n\nSalvato: terrò ${name} fuori da ciò che suono e ti propongo. Dimmelo quando vuoi cambiarlo.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" di ${artist}` : artist; return `\n\nAnnullato: la tua scelta precedente su ${name}.`; },
133
+ "generate.made": ({ title, length, seed }) => `Fatto su questo Mac con Stable Audio 3: «${title}» (${length}, seed ${seed}).`,
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",
133
136
  "turn.cancelled": "Ho annullato il resto.",
134
137
  "turn.interrupted": "La risposta si è interrotta. Gli esiti di Spotify registrati sono qui sotto; nessuna azione è stata ripetuta.",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `${artist}の「${title}」` : artist; return `\n\n保存しました: ${name}が好き。変えたいときはいつでも言ってください。`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `${artist}の「${title}」` : artist; return `\n\n保存しました: ${name}は再生にもおすすめにも入れません。変えたいときはいつでも言ってください。`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}の「${title}」` : artist; return `\n\n取り消しました: ${name}についての前の選択。`; },
133
+ "generate.made": ({ title, length, seed }) => `この Mac の Stable Audio 3 で作りました:「${title}」(${length}、シード ${seed})。`,
134
+ "generate.stream": ({ minutes }) => `この Mac で一曲ずつ新しい曲を作り続けます(最長 ${minutes} 分)。いつでも止められます。`,
135
+ "onAir.request": "オンエアして。今の気分に合わせて音楽をかけ続けて",
133
136
  "turn.cancelled": "以降の処理をキャンセルしました。",
134
137
  "turn.interrupted": "返事が中断されました。記録された Spotify の操作結果は下のとおりです。操作は繰り返していません。",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `${artist}의 "${title}"` : artist; return `\n\n저장했습니다: ${name}을(를) 좋아하신다고요. 바꾸고 싶으면 언제든 말씀해 주세요.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `${artist}의 "${title}"` : artist; return `\n\n저장했습니다: ${name}은(는) 재생하거나 추천하지 않겠습니다. 바꾸고 싶으면 언제든 말씀해 주세요.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}의 "${title}"` : artist; return `\n\n취소했습니다: ${name}에 대한 이전 선택.`; },
133
+ "generate.made": ({ title, length, seed }) => `이 Mac의 Stable Audio 3로 만들었습니다: "${title}" (${length}, 시드 ${seed}).`,
134
+ "generate.stream": ({ minutes }) => `이 Mac에서 매번 새 곡을 만들며 최대 ${minutes}분 동안 이어집니다. 언제든 멈출 수 있습니다.`,
135
+ "onAir.request": "방송 시작해 줘. 지금 분위기에 맞춰 음악을 계속 틀어 줘",
133
136
  "turn.cancelled": "남은 작업을 취소했습니다.",
134
137
  "turn.interrupted": "답변이 중단되었습니다. 기록된 Spotify 결과는 아래와 같으며, 어떤 작업도 다시 실행하지 않았습니다.",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nSalvo: você gosta de ${name}. É só me dizer quando quiser mudar.`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nSalvo: vou deixar ${name} fora do que eu toco e sugiro. É só me dizer quando quiser mudar.`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `"${title}" de ${artist}` : artist; return `\n\nDesfeito: sua escolha anterior sobre ${name}.`; },
133
+ "generate.made": ({ title, length, seed }) => `Feito neste Mac com o Stable Audio 3: "${title}" (${length}, semente ${seed}).`,
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",
133
136
  "turn.cancelled": "Cancelei o resto.",
134
137
  "turn.interrupted": "A resposta foi interrompida. Abaixo estão os resultados registrados no Spotify; nenhuma ação foi repetida.",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已儲存:你喜歡${name}。想改的話隨時告訴我。`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已儲存:我播放和推薦時都會避開${name}。想改的話隨時告訴我。`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已撤銷:你之前對${name}的選擇。`; },
133
+ "generate.made": ({ title, length, seed }) => `用這台 Mac 上的 Stable Audio 3 做的:「${title}」(${length},種子 ${seed})。`,
134
+ "generate.stream": ({ minutes }) => `音樂流會在這台 Mac 上一首接一首地做下去,最多 ${minutes} 分鐘,隨時可以停。`,
135
+ "onAir.request": "開播吧,照著現在的感覺一直放下去",
133
136
  "turn.cancelled": "已取消後續操作。",
134
137
  "turn.interrupted": "回覆中斷了。已記錄的 Spotify 結果如下;沒有重新執行任何操作。",
135
138
  };
@@ -130,6 +130,9 @@ export default {
130
130
  "choice.like": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已保存:你喜欢${name}。想改的话随时告诉我。`; },
131
131
  "choice.avoid": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已保存:我播放和推荐时都会避开${name}。想改的话随时告诉我。`; },
132
132
  "choice.retract": ({ title, artist }) => { const name = title ? `${artist}的《${title}》` : artist; return `\n\n已撤销:你之前对${name}的选择。`; },
133
+ "generate.made": ({ title, length, seed }) => `用这台 Mac 上的 Stable Audio 3 做的:「${title}」(${length},种子 ${seed})。`,
134
+ "generate.stream": ({ minutes }) => `音乐流会在这台 Mac 上一首接一首地做下去,最多 ${minutes} 分钟,随时可以停。`,
135
+ "onAir.request": "开播吧,照着现在的感觉一直放下去",
133
136
  "turn.cancelled": "已取消后续操作。",
134
137
  "turn.interrupted": "回复中断;已确认的 Spotify 操作结果如下。未重新执行任何操作。",
135
138
  };
@@ -1027,4 +1027,37 @@ export default {
1027
1027
  "Skipped. /service and /model are here when you want them.": "Omitido. /service y /model siguen aquí cuando los quieras.",
1028
1028
  "Saving your music service...": "Guardando tu servicio de música...",
1029
1029
  "Choose where Moondog plays music": "Elige dónde pone Moondog la música",
1030
+ "Make music on this Mac with Stable Audio 3": "Hacer música en este Mac con Stable Audio 3",
1031
+ "Stop making music here": "Dejar de hacer música aquí",
1032
+ "Making music on this Mac": "Hacer música en este Mac",
1033
+ "Made by Moondog": "Hecho por Moondog",
1034
+ "Stable Audio 3 needs a Mac with Apple silicon, so Moondog can't make music here.": "Stable Audio 3 necesita un Mac con Apple silicon, así que Moondog no puede hacer música aquí.",
1035
+ "Moondog makes music on this Mac with Stable Audio 3, powered by Stability AI. Ask for a track. Stable Audio 3 runs offline, and the track stays on this Mac.": "Moondog hace música en este Mac con Stable Audio 3, powered by Stability AI. Pide una pista. Stable Audio 3 funciona sin conexión y la pista se queda en este Mac.",
1036
+ "Making music on this Mac is off. Stable Audio 3 is ready at {folder}; turn it on with /generation on.": "Hacer música en este Mac está desactivado. Stable Audio 3 está listo en {folder}; actívalo con /generation on.",
1037
+ "Making music on this Mac is off, and Stable Audio 3 isn't at {folder}. Install Stability AI's MLX build from github.com/Stability-AI/stable-audio-3, then use /generation on with its folder.": "Hacer música en este Mac está desactivado, y Stable Audio 3 no está en {folder}. Instala la versión MLX de Stability AI desde github.com/Stability-AI/stable-audio-3 y luego usa /generation on con su carpeta.",
1038
+ "Making music on this Mac is off, and Stable Audio 3 at {folder} isn't fully installed. Run ./install.sh --download medium there, then use /generation on.": "Hacer música en este Mac está desactivado, y Stable Audio 3 en {folder} no está instalado del todo. Ejecuta ./install.sh --download medium ahí y luego usa /generation on.",
1039
+ "Stable Audio 3 at {folder} doesn't have the Medium model yet. Run ./install.sh --download medium there, then use /generation again.": "Stable Audio 3 en {folder} aún no tiene el modelo Medium. Ejecuta ./install.sh --download medium ahí y vuelve a usar /generation.",
1040
+ "Stable Audio 3 at {folder} isn't installed yet. Run ./install.sh --download medium there, then use /generation again.": "Stable Audio 3 en {folder} aún no está instalado. Ejecuta ./install.sh --download medium ahí y vuelve a usar /generation.",
1041
+ "Stable Audio 3 isn't at {folder}. Use /generation on with the folder that holds its sa3 script.": "Stable Audio 3 no está en {folder}. Usa /generation on con la carpeta que contiene su script sa3.",
1042
+ "Use /generation, /generation on [folder], or /generation off.": "Usa /generation, /generation on [carpeta] o /generation off.",
1043
+ "Music making on.": "Creación de música activada.",
1044
+ "Music making off.": "Creación de música desactivada.",
1045
+ "Music making isn't ready.": "La creación de música no está lista.",
1046
+ "warming up": "calentando",
1047
+ "reading your taste": "leyendo tus gustos",
1048
+ "composing": "componiendo",
1049
+ "pressing the record": "prensando el disco",
1050
+ "saving it": "guardándolo",
1051
+ "Let Moondog make music on this Mac": "Deja que Moondog haga música en este Mac",
1052
+ "Make a track": "Hacer una pista",
1053
+ "Play a track Moondog made": "Volver a poner una pista que hizo Moondog",
1054
+ "Start a stream": "Empezar un flujo",
1055
+ "Stop the stream": "Parar el flujo",
1056
+ "Go on air: Moondog keeps making music from your taste": "Sal al aire: Moondog sigue haciendo música a tu gusto",
1057
+ "on air until {time}": "al aire hasta las {time}",
1058
+ "Cutting the next record": "Grabando el siguiente disco",
1059
+ "Off air: the next record couldn't be made.": "Fuera del aire: no se pudo hacer el siguiente disco.",
1060
+ "The next record is still being cut.": "El siguiente disco aún se está grabando.",
1061
+ "Off air.": "Fuera del aire.",
1062
+ "Turn on music making first: /generation on.": "Primero activa la creación de música: /generation on.",
1030
1063
  };
@@ -1027,4 +1027,37 @@ export default {
1027
1027
  "Skipped. /service and /model are here when you want them.": "スキップしました。/service と /model はいつでも使えます。",
1028
1028
  "Saving your music service...": "音楽サービスを保存しています...",
1029
1029
  "Choose where Moondog plays music": "Moondog が音楽を流す場所を選ぶ",
1030
+ "Make music on this Mac with Stable Audio 3": "Stable Audio 3 でこの Mac で音楽を作る",
1031
+ "Stop making music here": "ここでの音楽作りをやめる",
1032
+ "Making music on this Mac": "この Mac での音楽作り",
1033
+ "Made by Moondog": "Moondog 制作",
1034
+ "Stable Audio 3 needs a Mac with Apple silicon, so Moondog can't make music here.": "Stable Audio 3 には Apple シリコン搭載の Mac が必要なので、ここでは Moondog は音楽を作れません。",
1035
+ "Moondog makes music on this Mac with Stable Audio 3, powered by Stability AI. Ask for a track. Stable Audio 3 runs offline, and the track stays on this Mac.": "Moondog はこの Mac の Stable Audio 3 で音楽を作ります。Powered by Stability AI。曲をリクエストしてください。Stable Audio 3 はオフラインで動き、曲はこの Mac に残ります。",
1036
+ "Making music on this Mac is off. Stable Audio 3 is ready at {folder}; turn it on with /generation on.": "この Mac での音楽作りはオフです。Stable Audio 3 は {folder} で準備できています。/generation on でオンにしてください。",
1037
+ "Making music on this Mac is off, and Stable Audio 3 isn't at {folder}. Install Stability AI's MLX build from github.com/Stability-AI/stable-audio-3, then use /generation on with its folder.": "この Mac での音楽作りはオフで、{folder} に Stable Audio 3 がありません。github.com/Stability-AI/stable-audio-3 から Stability AI の MLX 版をインストールしてから、そのフォルダを付けて /generation on を使ってください。",
1038
+ "Making music on this Mac is off, and Stable Audio 3 at {folder} isn't fully installed. Run ./install.sh --download medium there, then use /generation on.": "この Mac での音楽作りはオフで、{folder} の Stable Audio 3 はインストールが終わっていません。そこで ./install.sh --download medium を実行してから、/generation on を使ってください。",
1039
+ "Stable Audio 3 at {folder} doesn't have the Medium model yet. Run ./install.sh --download medium there, then use /generation again.": "{folder} の Stable Audio 3 にはまだ Medium モデルがありません。そこで ./install.sh --download medium を実行してから、もう一度 /generation を使ってください。",
1040
+ "Stable Audio 3 at {folder} isn't installed yet. Run ./install.sh --download medium there, then use /generation again.": "{folder} の Stable Audio 3 はまだインストールされていません。そこで ./install.sh --download medium を実行してから、もう一度 /generation を使ってください。",
1041
+ "Stable Audio 3 isn't at {folder}. Use /generation on with the folder that holds its sa3 script.": "{folder} に Stable Audio 3 がありません。sa3 スクリプトのあるフォルダを付けて /generation on を使ってください。",
1042
+ "Use /generation, /generation on [folder], or /generation off.": "/generation、/generation on [フォルダ]、または /generation off を使ってください。",
1043
+ "Music making on.": "音楽作りをオンにしました。",
1044
+ "Music making off.": "音楽作りをオフにしました。",
1045
+ "Music making isn't ready.": "音楽作りはまだ準備できていません。",
1046
+ "warming up": "ウォームアップ中",
1047
+ "reading your taste": "好みを読んでいます",
1048
+ "composing": "作曲中",
1049
+ "pressing the record": "レコードをプレス中",
1050
+ "saving it": "保存中",
1051
+ "Let Moondog make music on this Mac": "この Mac で Moondog に音楽を作らせる",
1052
+ "Make a track": "曲を作る",
1053
+ "Play a track Moondog made": "Moondog が作った曲をもう一度かける",
1054
+ "Start a stream": "音楽の流れを始める",
1055
+ "Stop the stream": "音楽の流れを止める",
1056
+ "Go on air: Moondog keeps making music from your taste": "オンエア:あなたの好みから Moondog が音楽を作り続ける",
1057
+ "on air until {time}": "{time} までオンエア",
1058
+ "Cutting the next record": "次のレコードをカッティング中",
1059
+ "Off air: the next record couldn't be made.": "オフエア:次のレコードを作れませんでした。",
1060
+ "The next record is still being cut.": "次のレコードはまだカッティング中です。",
1061
+ "Off air.": "オフエアにしました。",
1062
+ "Turn on music making first: /generation on.": "先に音楽作りをオンにしてください:/generation on。",
1030
1063
  };
@@ -1027,4 +1027,37 @@ export default {
1027
1027
  "Skipped. /service and /model are here when you want them.": "Pulado. /service e /model continuam aqui quando você quiser.",
1028
1028
  "Saving your music service...": "Salvando seu serviço de música...",
1029
1029
  "Choose where Moondog plays music": "Escolha onde o Moondog toca música",
1030
+ "Make music on this Mac with Stable Audio 3": "Fazer música neste Mac com o Stable Audio 3",
1031
+ "Stop making music here": "Parar de fazer música aqui",
1032
+ "Making music on this Mac": "Fazer música neste Mac",
1033
+ "Made by Moondog": "Feito pelo Moondog",
1034
+ "Stable Audio 3 needs a Mac with Apple silicon, so Moondog can't make music here.": "O Stable Audio 3 precisa de um Mac com Apple silicon, então o Moondog não pode fazer música aqui.",
1035
+ "Moondog makes music on this Mac with Stable Audio 3, powered by Stability AI. Ask for a track. Stable Audio 3 runs offline, and the track stays on this Mac.": "O Moondog faz música neste Mac com o Stable Audio 3, powered by Stability AI. Peça uma faixa. O Stable Audio 3 funciona offline, e a faixa fica neste Mac.",
1036
+ "Making music on this Mac is off. Stable Audio 3 is ready at {folder}; turn it on with /generation on.": "Fazer música neste Mac está desativado. O Stable Audio 3 está pronto em {folder}; ative com /generation on.",
1037
+ "Making music on this Mac is off, and Stable Audio 3 isn't at {folder}. Install Stability AI's MLX build from github.com/Stability-AI/stable-audio-3, then use /generation on with its folder.": "Fazer música neste Mac está desativado, e o Stable Audio 3 não está em {folder}. Instale a versão MLX da Stability AI a partir de github.com/Stability-AI/stable-audio-3 e depois use /generation on com a pasta dela.",
1038
+ "Making music on this Mac is off, and Stable Audio 3 at {folder} isn't fully installed. Run ./install.sh --download medium there, then use /generation on.": "Fazer música neste Mac está desativado, e o Stable Audio 3 em {folder} não está totalmente instalado. Execute ./install.sh --download medium lá e depois use /generation on.",
1039
+ "Stable Audio 3 at {folder} doesn't have the Medium model yet. Run ./install.sh --download medium there, then use /generation again.": "O Stable Audio 3 em {folder} ainda não tem o modelo Medium. Execute ./install.sh --download medium lá e use /generation de novo.",
1040
+ "Stable Audio 3 at {folder} isn't installed yet. Run ./install.sh --download medium there, then use /generation again.": "O Stable Audio 3 em {folder} ainda não está instalado. Execute ./install.sh --download medium lá e use /generation de novo.",
1041
+ "Stable Audio 3 isn't at {folder}. Use /generation on with the folder that holds its sa3 script.": "O Stable Audio 3 não está em {folder}. Use /generation on com a pasta que contém o script sa3.",
1042
+ "Use /generation, /generation on [folder], or /generation off.": "Use /generation, /generation on [pasta] ou /generation off.",
1043
+ "Music making on.": "Criação de música ativada.",
1044
+ "Music making off.": "Criação de música desativada.",
1045
+ "Music making isn't ready.": "A criação de música não está pronta.",
1046
+ "warming up": "aquecendo",
1047
+ "reading your taste": "lendo seu gosto",
1048
+ "composing": "compondo",
1049
+ "pressing the record": "prensando o disco",
1050
+ "saving it": "salvando",
1051
+ "Let Moondog make music on this Mac": "Deixe o Moondog fazer música neste Mac",
1052
+ "Make a track": "Fazer uma faixa",
1053
+ "Play a track Moondog made": "Tocar de novo uma faixa que o Moondog fez",
1054
+ "Start a stream": "Começar um fluxo",
1055
+ "Stop the stream": "Parar o fluxo",
1056
+ "Go on air: Moondog keeps making music from your taste": "Entre no ar: o Moondog segue fazendo música a partir do seu gosto",
1057
+ "on air until {time}": "no ar até {time}",
1058
+ "Cutting the next record": "Gravando o próximo disco",
1059
+ "Off air: the next record couldn't be made.": "Fora do ar: não deu para fazer o próximo disco.",
1060
+ "The next record is still being cut.": "O próximo disco ainda está sendo gravado.",
1061
+ "Off air.": "Fora do ar.",
1062
+ "Turn on music making first: /generation on.": "Ative a criação de música primeiro: /generation on.",
1030
1063
  };