@nodaro/prompts 1.0.1
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/LICENSE +105 -0
- package/README.md +27 -0
- package/dist/index.cjs +21240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3599 -0
- package/dist/index.d.ts +3599 -0
- package/dist/index.js +20854 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/action-fx.test.ts +155 -0
- package/src/__tests__/apply-picker-json.test.ts +50 -0
- package/src/__tests__/assemble-image-input.test.ts +248 -0
- package/src/__tests__/assemble-suno-input.test.ts +283 -0
- package/src/__tests__/brand-tokens.test.ts +81 -0
- package/src/__tests__/build-image-prompt-element-injection.test.ts +118 -0
- package/src/__tests__/build-image-prompt-hybrid-format.test.ts +131 -0
- package/src/__tests__/build-image-prompt-mentions.test.ts +809 -0
- package/src/__tests__/build-image-prompt-reference-cap.test.ts +57 -0
- package/src/__tests__/build-image-prompt-reference-numbering.test.ts +237 -0
- package/src/__tests__/build-image-prompt-reference-order.test.ts +313 -0
- package/src/__tests__/camera-motions-from-connections.test.ts +71 -0
- package/src/__tests__/catalog-gapfill.test.ts +53 -0
- package/src/__tests__/character-convergence-image.test.ts +217 -0
- package/src/__tests__/character-default-role-image.test.ts +167 -0
- package/src/__tests__/character-default-role-video.test.ts +168 -0
- package/src/__tests__/character-default-role.test.ts +70 -0
- package/src/__tests__/character-fx.test.ts +200 -0
- package/src/__tests__/entity-prompts-location.test.ts +97 -0
- package/src/__tests__/entity-prompts.test.ts +240 -0
- package/src/__tests__/expand-extra-refs-role.test.ts +50 -0
- package/src/__tests__/factory-presets.test.ts +1045 -0
- package/src/__tests__/factory-snippets.test.ts +68 -0
- package/src/__tests__/framing-multi.test.ts +71 -0
- package/src/__tests__/framing-vantage.test.ts +39 -0
- package/src/__tests__/i18n-entry-completeness.test.ts +269 -0
- package/src/__tests__/identity-lock.test.ts +46 -0
- package/src/__tests__/instrumentation.test.ts +68 -0
- package/src/__tests__/lighting-multi.test.ts +68 -0
- package/src/__tests__/location-convergence-image.test.ts +124 -0
- package/src/__tests__/mention-lock-flag.test.ts +499 -0
- package/src/__tests__/multi-picker-spec.test.ts +71 -0
- package/src/__tests__/music-genre.test.ts +103 -0
- package/src/__tests__/music-mood.test.ts +87 -0
- package/src/__tests__/object-creature-convergence-image.test.ts +105 -0
- package/src/__tests__/parameter-prompt-hint.test.ts +270 -0
- package/src/__tests__/parameter-registry-sync.test.ts +243 -0
- package/src/__tests__/person-age.test.ts +80 -0
- package/src/__tests__/person-analyzer-invariants.test.ts +47 -0
- package/src/__tests__/person-body-axes.test.ts +67 -0
- package/src/__tests__/person-facial-geometry.test.ts +156 -0
- package/src/__tests__/person-regional-aesthetic.test.ts +161 -0
- package/src/__tests__/person-sections.test.ts +18 -0
- package/src/__tests__/picker-analyzer-registry.test.ts +108 -0
- package/src/__tests__/picker-catalogs-project.test.ts +85 -0
- package/src/__tests__/picker-catalogs.test.ts +53 -0
- package/src/__tests__/picker-limits.test.ts +20 -0
- package/src/__tests__/prompt-builder-segments.test.ts +183 -0
- package/src/__tests__/prompt-builder-structured-fields.test.ts +40 -0
- package/src/__tests__/prompt-builder.test.ts +1773 -0
- package/src/__tests__/prompt-wizard-categories.test.ts +31 -0
- package/src/__tests__/provider-prompt-doctrine.test.ts +49 -0
- package/src/__tests__/resolve-prompt-append.test.ts +58 -0
- package/src/__tests__/resolve-prompt.test.ts +44 -0
- package/src/__tests__/role-picker-shared.test.ts +208 -0
- package/src/__tests__/seedance-2-inputs.test.ts +173 -0
- package/src/__tests__/seedance-extend.test.ts +44 -0
- package/src/__tests__/sound-aggregator.test.ts +350 -0
- package/src/__tests__/style-presets.test.ts +34 -0
- package/src/__tests__/temporal-multi.test.ts +74 -0
- package/src/__tests__/transitions.test.ts +213 -0
- package/src/__tests__/video-reference-features.test.ts +41 -0
- package/src/__tests__/video-reference-leading-refs.test.ts +90 -0
- package/src/__tests__/video-reference-resolver.test.ts +233 -0
- package/src/__tests__/video-reference-roles.test.ts +96 -0
- package/src/__tests__/voice-character.test.ts +48 -0
- package/src/__tests__/voice-delivery.test.ts +41 -0
- package/src/__tests__/wardrobe.test.ts +25 -0
- package/src/action-fx.ts +255 -0
- package/src/aesthetic.ts +435 -0
- package/src/assemble-image-input.ts +236 -0
- package/src/assemble-suno-input.ts +147 -0
- package/src/atmosphere.ts +104 -0
- package/src/backdrop.ts +131 -0
- package/src/brand-tokens.ts +154 -0
- package/src/camera-format.ts +76 -0
- package/src/camera-motions.ts +615 -0
- package/src/character-fx.ts +274 -0
- package/src/color-look.ts +103 -0
- package/src/composition-effects.ts +67 -0
- package/src/entity-prompts.ts +231 -0
- package/src/era.ts +292 -0
- package/src/exposure-settings.ts +142 -0
- package/src/factory-presets/generate-image.ts +1644 -0
- package/src/factory-presets/generate-video.ts +1116 -0
- package/src/factory-presets/index.ts +46 -0
- package/src/factory-presets/lottie-overlay.ts +166 -0
- package/src/factory-presets/motion-graphics.ts +350 -0
- package/src/factory-presets/music.ts +734 -0
- package/src/factory-presets/sfx.ts +136 -0
- package/src/factory-presets/shared-image.ts +207 -0
- package/src/factory-presets/switchx.ts +65 -0
- package/src/factory-presets/text.ts +292 -0
- package/src/factory-presets/types.ts +51 -0
- package/src/factory-presets/video-edit.ts +136 -0
- package/src/factory-presets/voice.ts +172 -0
- package/src/factory-presets.ts +2 -0
- package/src/factory-snippets/catalog.ts +105 -0
- package/src/factory-snippets/index.ts +16 -0
- package/src/factory-snippets/types.ts +33 -0
- package/src/framing.ts +634 -0
- package/src/held-prop.ts +187 -0
- package/src/identity-lock.ts +213 -0
- package/src/index.ts +66 -0
- package/src/instrumentation.ts +337 -0
- package/src/lens.ts +59 -0
- package/src/lighting.ts +229 -0
- package/src/loop-subject.ts +276 -0
- package/src/materials.ts +184 -0
- package/src/mood.ts +186 -0
- package/src/music-genre.ts +662 -0
- package/src/music-mood.ts +137 -0
- package/src/object-asset-presets.ts +81 -0
- package/src/parameter-prompt-hint.ts +281 -0
- package/src/person.ts +1368 -0
- package/src/photo-genre.ts +151 -0
- package/src/photographer.ts +612 -0
- package/src/picker-analyzer-registry.ts +374 -0
- package/src/picker-catalogs.ts +858 -0
- package/src/pose.ts +246 -0
- package/src/post-process-effects.ts +94 -0
- package/src/prompt-builder-structured-fields.ts +116 -0
- package/src/prompt-builder.ts +2964 -0
- package/src/prompt-templates.ts +50 -0
- package/src/prompt-wizard-categories.ts +334 -0
- package/src/provider-prompt-doctrine.ts +85 -0
- package/src/render-quality.ts +89 -0
- package/src/resolve-prompt.ts +120 -0
- package/src/seedance-2-inputs.ts +100 -0
- package/src/setting.ts +130 -0
- package/src/sound-aggregator.ts +241 -0
- package/src/style-presets.ts +162 -0
- package/src/style.ts +99 -0
- package/src/styling.ts +585 -0
- package/src/temporal.ts +151 -0
- package/src/transitions.ts +333 -0
- package/src/video-reference-resolver.ts +823 -0
- package/src/voice-character.ts +237 -0
- package/src/voice-delivery.ts +142 -0
- package/src/wardrobe.ts +179 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog of music genres for Suno / MiniMax / Text-to-Audio
|
|
3
|
+
* prompt composition. Shared between frontend (picker UI), backend
|
|
4
|
+
* orchestrator (payload-builder), and frontend DAG executor.
|
|
5
|
+
*
|
|
6
|
+
* Each entry's `promptHint` is the natural-language fragment injected into
|
|
7
|
+
* the consumer's style/prompt field by `composeSoundHintFromConnections`.
|
|
8
|
+
*
|
|
9
|
+
* Top-level genres carry a `category` field used by the picker to render a
|
|
10
|
+
* horizontal tab row (mirrors PersonPicker.ethnicity grouping). Categories
|
|
11
|
+
* are Splice-aligned (https://splice.com/sounds/genres) so the taxonomy
|
|
12
|
+
* matches industry conventions.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { pickIds } from "@nodaro/shared"
|
|
16
|
+
|
|
17
|
+
export type MusicGenreCategory =
|
|
18
|
+
| "hip-hop-rnb"
|
|
19
|
+
| "electronic"
|
|
20
|
+
| "pop"
|
|
21
|
+
| "rock-metal"
|
|
22
|
+
| "acoustic"
|
|
23
|
+
| "global"
|
|
24
|
+
| "cinematic"
|
|
25
|
+
|
|
26
|
+
export const MUSIC_GENRE_CATEGORY_ORDER: ReadonlyArray<MusicGenreCategory> = [
|
|
27
|
+
"hip-hop-rnb",
|
|
28
|
+
"electronic",
|
|
29
|
+
"pop",
|
|
30
|
+
"rock-metal",
|
|
31
|
+
"acoustic",
|
|
32
|
+
"global",
|
|
33
|
+
"cinematic",
|
|
34
|
+
] as const
|
|
35
|
+
|
|
36
|
+
export const MUSIC_GENRE_CATEGORY_LABELS: Readonly<Record<MusicGenreCategory, string>> = {
|
|
37
|
+
"hip-hop-rnb": "Hip Hop / R&B",
|
|
38
|
+
"electronic": "Electronic",
|
|
39
|
+
"pop": "Pop",
|
|
40
|
+
"rock-metal": "Rock / Metal",
|
|
41
|
+
"acoustic": "Acoustic / Roots",
|
|
42
|
+
"global": "Global",
|
|
43
|
+
"cinematic": "Cinematic / Score",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MusicSubgenre {
|
|
47
|
+
readonly id: string
|
|
48
|
+
readonly label: string
|
|
49
|
+
readonly promptHint: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MusicGenre {
|
|
53
|
+
readonly id: string
|
|
54
|
+
readonly label: string
|
|
55
|
+
readonly description: string
|
|
56
|
+
readonly promptHint: string
|
|
57
|
+
readonly category: MusicGenreCategory
|
|
58
|
+
readonly subgenres: ReadonlyArray<MusicSubgenre>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MusicEra {
|
|
62
|
+
readonly id: string
|
|
63
|
+
readonly label: string
|
|
64
|
+
readonly description: string
|
|
65
|
+
readonly promptHint: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sourced from frontend/src/lib/suno-tags.ts SUNO_TAGS Genre + Subgenre buckets.
|
|
69
|
+
export const MUSIC_GENRES: ReadonlyArray<MusicGenre> = [
|
|
70
|
+
// -------- Hip Hop / R&B --------
|
|
71
|
+
{
|
|
72
|
+
id: "hip-hop",
|
|
73
|
+
label: "Hip Hop",
|
|
74
|
+
description: "Rhythmic spoken delivery, sampled or programmed beats",
|
|
75
|
+
promptHint: "hip hop",
|
|
76
|
+
category: "hip-hop-rnb",
|
|
77
|
+
subgenres: [
|
|
78
|
+
{ id: "trap", label: "Trap", promptHint: "trap" },
|
|
79
|
+
{ id: "boom-bap", label: "Boom Bap", promptHint: "boom bap hip hop" },
|
|
80
|
+
{ id: "lo-fi-hip-hop", label: "Lo-fi Hip Hop", promptHint: "lo-fi hip hop" },
|
|
81
|
+
{ id: "drill", label: "Drill", promptHint: "drill" },
|
|
82
|
+
{ id: "uk-drill", label: "UK Drill", promptHint: "UK drill" },
|
|
83
|
+
{ id: "cloud-rap", label: "Cloud Rap", promptHint: "cloud rap" },
|
|
84
|
+
{ id: "conscious-rap", label: "Conscious", promptHint: "conscious hip hop" },
|
|
85
|
+
{ id: "gangsta-rap", label: "Gangsta", promptHint: "gangsta rap" },
|
|
86
|
+
{ id: "jazz-rap", label: "Jazz Rap", promptHint: "jazz rap" },
|
|
87
|
+
{ id: "alt-hip-hop", label: "Alt Hip Hop", promptHint: "alternative hip hop" },
|
|
88
|
+
{ id: "phonk", label: "Phonk", promptHint: "phonk" },
|
|
89
|
+
{ id: "drift-phonk", label: "Drift Phonk", promptHint: "drift phonk" },
|
|
90
|
+
{ id: "uk-hip-hop", label: "UK Hip Hop", promptHint: "UK hip hop" },
|
|
91
|
+
{ id: "grime", label: "Grime", promptHint: "grime" },
|
|
92
|
+
{ id: "pluggnb", label: "PluggnB", promptHint: "pluggnb" },
|
|
93
|
+
{ id: "rage", label: "Rage", promptHint: "rage hip hop" },
|
|
94
|
+
{ id: "jersey-club", label: "Jersey Club", promptHint: "Jersey club" },
|
|
95
|
+
{ id: "brazilian-funk", label: "Brazilian Funk", promptHint: "Brazilian funk" },
|
|
96
|
+
{ id: "moombahton", label: "Moombahton", promptHint: "moombahton" },
|
|
97
|
+
{ id: "glitch-hop", label: "Glitch Hop", promptHint: "glitch hop" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "rnb",
|
|
102
|
+
label: "R&B",
|
|
103
|
+
description: "Smooth vocals, soul-influenced",
|
|
104
|
+
promptHint: "R&B",
|
|
105
|
+
category: "hip-hop-rnb",
|
|
106
|
+
subgenres: [
|
|
107
|
+
{ id: "neo-soul", label: "Neo-Soul", promptHint: "neo-soul" },
|
|
108
|
+
{ id: "contemporary-rnb", label: "Contemporary R&B", promptHint: "contemporary R&B" },
|
|
109
|
+
{ id: "alt-rnb", label: "Alt R&B", promptHint: "alternative R&B" },
|
|
110
|
+
{ id: "neo-rnb", label: "Neo R&B", promptHint: "modern R&B" },
|
|
111
|
+
{ id: "quiet-storm", label: "Quiet Storm", promptHint: "quiet storm" },
|
|
112
|
+
{ id: "new-jack-swing", label: "New Jack Swing", promptHint: "new jack swing" },
|
|
113
|
+
{ id: "future-soul", label: "Future Soul", promptHint: "future soul" },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "soul",
|
|
118
|
+
label: "Soul",
|
|
119
|
+
description: "Emotive vocals, gospel-rooted, vintage R&B",
|
|
120
|
+
promptHint: "soul",
|
|
121
|
+
category: "hip-hop-rnb",
|
|
122
|
+
subgenres: [
|
|
123
|
+
{ id: "motown", label: "Motown", promptHint: "Motown soul" },
|
|
124
|
+
{ id: "northern-soul", label: "Northern Soul", promptHint: "Northern soul" },
|
|
125
|
+
{ id: "southern-soul", label: "Southern Soul", promptHint: "Southern soul" },
|
|
126
|
+
{ id: "psychedelic-soul", label: "Psychedelic Soul", promptHint: "psychedelic soul" },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "funk",
|
|
131
|
+
label: "Funk",
|
|
132
|
+
description: "Syncopated bass, percussive grooves",
|
|
133
|
+
promptHint: "funk",
|
|
134
|
+
category: "hip-hop-rnb",
|
|
135
|
+
subgenres: [
|
|
136
|
+
{ id: "p-funk", label: "P-Funk", promptHint: "P-funk" },
|
|
137
|
+
{ id: "g-funk", label: "G-Funk", promptHint: "G-funk" },
|
|
138
|
+
{ id: "electro-funk", label: "Electro Funk", promptHint: "electro-funk" },
|
|
139
|
+
{ id: "jazz-funk", label: "Jazz Funk", promptHint: "jazz-funk" },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "lofi",
|
|
144
|
+
label: "Lo-Fi",
|
|
145
|
+
description: "Warm tape hiss, mellow beats, nostalgic",
|
|
146
|
+
promptHint: "lo-fi",
|
|
147
|
+
category: "hip-hop-rnb",
|
|
148
|
+
subgenres: [
|
|
149
|
+
{ id: "lofi-beats", label: "Beats", promptHint: "lo-fi beats" },
|
|
150
|
+
{ id: "lofi-jazz", label: "Jazz", promptHint: "lo-fi jazz" },
|
|
151
|
+
{ id: "chillhop", label: "Chillhop", promptHint: "chillhop" },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// -------- Electronic --------
|
|
156
|
+
{
|
|
157
|
+
id: "electronic",
|
|
158
|
+
label: "Electronic",
|
|
159
|
+
description: "Synthesizer / sequencer-driven",
|
|
160
|
+
promptHint: "electronic",
|
|
161
|
+
category: "electronic",
|
|
162
|
+
subgenres: [
|
|
163
|
+
{ id: "house", label: "House", promptHint: "house" },
|
|
164
|
+
{ id: "deep-house", label: "Deep House", promptHint: "deep house" },
|
|
165
|
+
{ id: "tech-house", label: "Tech House", promptHint: "tech house" },
|
|
166
|
+
{ id: "afro-house", label: "Afro House", promptHint: "Afro house" },
|
|
167
|
+
{ id: "amapiano", label: "Amapiano", promptHint: "amapiano" },
|
|
168
|
+
{ id: "techno", label: "Techno", promptHint: "techno" },
|
|
169
|
+
{ id: "minimal-techno", label: "Minimal Techno", promptHint: "minimal techno" },
|
|
170
|
+
{ id: "melodic-techno", label: "Melodic Techno", promptHint: "melodic techno" },
|
|
171
|
+
{ id: "drum-and-bass", label: "Drum & Bass", promptHint: "drum and bass" },
|
|
172
|
+
{ id: "jungle", label: "Jungle", promptHint: "jungle" },
|
|
173
|
+
{ id: "dubstep", label: "Dubstep", promptHint: "dubstep" },
|
|
174
|
+
{ id: "future-bass", label: "Future Bass", promptHint: "future bass" },
|
|
175
|
+
{ id: "trap-edm", label: "Trap (EDM)", promptHint: "trap EDM" },
|
|
176
|
+
{ id: "synthwave", label: "Synthwave", promptHint: "synthwave" },
|
|
177
|
+
{ id: "outrun", label: "Outrun", promptHint: "outrun synthwave" },
|
|
178
|
+
{ id: "vaporwave", label: "Vaporwave", promptHint: "vaporwave" },
|
|
179
|
+
{ id: "trance", label: "Trance", promptHint: "trance" },
|
|
180
|
+
{ id: "psytrance", label: "Psytrance", promptHint: "psytrance" },
|
|
181
|
+
{ id: "ambient", label: "Ambient", promptHint: "ambient electronic" },
|
|
182
|
+
{ id: "idm", label: "IDM", promptHint: "intelligent dance music" },
|
|
183
|
+
{ id: "breakbeat", label: "Breakbeat", promptHint: "breakbeat" },
|
|
184
|
+
{ id: "garage-uk", label: "UK Garage", promptHint: "UK garage" },
|
|
185
|
+
{ id: "hardstyle", label: "Hardstyle", promptHint: "hardstyle" },
|
|
186
|
+
{ id: "footwork", label: "Footwork", promptHint: "footwork" },
|
|
187
|
+
{ id: "indie-electronic", label: "Indie Electronic", promptHint: "indie electronic" },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "industrial",
|
|
192
|
+
label: "Industrial",
|
|
193
|
+
description: "Harsh electronic, mechanical textures, abrasive",
|
|
194
|
+
promptHint: "industrial",
|
|
195
|
+
category: "electronic",
|
|
196
|
+
subgenres: [
|
|
197
|
+
{ id: "ebm", label: "EBM", promptHint: "electronic body music" },
|
|
198
|
+
{ id: "industrial-metal", label: "Industrial Metal", promptHint: "industrial metal" },
|
|
199
|
+
{ id: "noise", label: "Noise", promptHint: "noise industrial" },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "ambient-genre",
|
|
204
|
+
label: "Ambient",
|
|
205
|
+
description: "Atmospheric, slow-evolving, often beatless",
|
|
206
|
+
promptHint: "ambient",
|
|
207
|
+
category: "electronic",
|
|
208
|
+
subgenres: [
|
|
209
|
+
{ id: "dark-ambient", label: "Dark Ambient", promptHint: "dark ambient" },
|
|
210
|
+
{ id: "drone", label: "Drone", promptHint: "drone" },
|
|
211
|
+
{ id: "new-age", label: "New Age", promptHint: "new age ambient" },
|
|
212
|
+
{ id: "space-music", label: "Space", promptHint: "space ambient" },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "experimental",
|
|
217
|
+
label: "Experimental",
|
|
218
|
+
description: "Genre-defying, avant-garde, abstract",
|
|
219
|
+
promptHint: "experimental",
|
|
220
|
+
category: "electronic",
|
|
221
|
+
subgenres: [
|
|
222
|
+
{ id: "musique-concrete", label: "Musique Concrète", promptHint: "musique concrète" },
|
|
223
|
+
{ id: "noise-music", label: "Noise", promptHint: "noise music" },
|
|
224
|
+
{ id: "free-improv", label: "Free Improv", promptHint: "free improvisation" },
|
|
225
|
+
{ id: "sound-art", label: "Sound Art", promptHint: "sound art" },
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "chill",
|
|
230
|
+
label: "Chill",
|
|
231
|
+
description: "Downtempo, relaxed, mellow textures",
|
|
232
|
+
promptHint: "chill",
|
|
233
|
+
category: "electronic",
|
|
234
|
+
subgenres: [
|
|
235
|
+
{ id: "chillout", label: "Chillout", promptHint: "chillout" },
|
|
236
|
+
{ id: "chillwave", label: "Chillwave", promptHint: "chillwave" },
|
|
237
|
+
{ id: "downtempo", label: "Downtempo", promptHint: "downtempo" },
|
|
238
|
+
{ id: "trip-hop", label: "Trip Hop", promptHint: "trip hop" },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "disco",
|
|
243
|
+
label: "Disco",
|
|
244
|
+
description: "Four-on-the-floor, lush strings, dancefloor-era",
|
|
245
|
+
promptHint: "disco",
|
|
246
|
+
category: "electronic",
|
|
247
|
+
subgenres: [
|
|
248
|
+
{ id: "italo-disco", label: "Italo Disco", promptHint: "Italo disco" },
|
|
249
|
+
{ id: "nu-disco", label: "Nu-Disco", promptHint: "nu-disco" },
|
|
250
|
+
{ id: "euro-disco", label: "Euro Disco", promptHint: "Euro disco" },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// -------- Pop --------
|
|
255
|
+
{
|
|
256
|
+
id: "pop",
|
|
257
|
+
label: "Pop",
|
|
258
|
+
description: "Mainstream, hook-driven",
|
|
259
|
+
promptHint: "pop",
|
|
260
|
+
category: "pop",
|
|
261
|
+
subgenres: [
|
|
262
|
+
{ id: "synth-pop", label: "Synth Pop", promptHint: "synth pop" },
|
|
263
|
+
{ id: "dream-pop", label: "Dream Pop", promptHint: "dream pop" },
|
|
264
|
+
{ id: "indie-pop", label: "Indie Pop", promptHint: "indie pop" },
|
|
265
|
+
{ id: "k-pop", label: "K-Pop", promptHint: "k-pop" },
|
|
266
|
+
{ id: "j-pop", label: "J-Pop", promptHint: "j-pop" },
|
|
267
|
+
{ id: "art-pop", label: "Art Pop", promptHint: "art pop" },
|
|
268
|
+
{ id: "bubblegum-pop", label: "Bubblegum", promptHint: "bubblegum pop" },
|
|
269
|
+
{ id: "chamber-pop", label: "Chamber", promptHint: "chamber pop" },
|
|
270
|
+
{ id: "electro-pop", label: "Electro Pop", promptHint: "electro pop" },
|
|
271
|
+
{ id: "hyperpop", label: "Hyperpop", promptHint: "hyperpop" },
|
|
272
|
+
{ id: "power-pop", label: "Power Pop", promptHint: "power pop" },
|
|
273
|
+
{ id: "bedroom-pop", label: "Bedroom Pop", promptHint: "bedroom pop" },
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "anime",
|
|
278
|
+
label: "Anime / J-Rock",
|
|
279
|
+
description: "Japanese animation soundtrack and J-Rock",
|
|
280
|
+
promptHint: "anime",
|
|
281
|
+
category: "pop",
|
|
282
|
+
subgenres: [
|
|
283
|
+
{ id: "anime-opening", label: "Opening", promptHint: "anime opening" },
|
|
284
|
+
{ id: "j-rock", label: "J-Rock", promptHint: "J-rock" },
|
|
285
|
+
{ id: "city-pop", label: "City Pop", promptHint: "city pop" },
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
// -------- Rock / Metal --------
|
|
290
|
+
{
|
|
291
|
+
id: "rock",
|
|
292
|
+
label: "Rock",
|
|
293
|
+
description: "Guitar-driven, prominent backbeat",
|
|
294
|
+
promptHint: "rock",
|
|
295
|
+
category: "rock-metal",
|
|
296
|
+
subgenres: [
|
|
297
|
+
{ id: "classic-rock", label: "Classic Rock", promptHint: "classic rock" },
|
|
298
|
+
{ id: "hard-rock", label: "Hard Rock", promptHint: "hard rock" },
|
|
299
|
+
{ id: "indie-rock", label: "Indie Rock", promptHint: "indie rock" },
|
|
300
|
+
{ id: "punk-rock", label: "Punk Rock", promptHint: "punk rock" },
|
|
301
|
+
{ id: "alt-rock", label: "Alternative", promptHint: "alternative rock" },
|
|
302
|
+
{ id: "psychedelic-rock", label: "Psychedelic", promptHint: "psychedelic rock" },
|
|
303
|
+
{ id: "garage-rock", label: "Garage", promptHint: "garage rock" },
|
|
304
|
+
{ id: "surf-rock", label: "Surf", promptHint: "surf rock" },
|
|
305
|
+
{ id: "prog-rock", label: "Progressive", promptHint: "progressive rock" },
|
|
306
|
+
{ id: "post-rock", label: "Post-Rock", promptHint: "post-rock" },
|
|
307
|
+
{ id: "shoegaze", label: "Shoegaze", promptHint: "shoegaze" },
|
|
308
|
+
{ id: "math-rock", label: "Math Rock", promptHint: "math rock" },
|
|
309
|
+
{ id: "stoner-rock", label: "Stoner", promptHint: "stoner rock" },
|
|
310
|
+
{ id: "emo", label: "Emo", promptHint: "emo" },
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: "punk",
|
|
315
|
+
label: "Punk",
|
|
316
|
+
description: "Fast, aggressive, DIY counter-culture",
|
|
317
|
+
promptHint: "punk",
|
|
318
|
+
category: "rock-metal",
|
|
319
|
+
subgenres: [
|
|
320
|
+
{ id: "hardcore-punk", label: "Hardcore", promptHint: "hardcore punk" },
|
|
321
|
+
{ id: "post-punk", label: "Post-Punk", promptHint: "post-punk" },
|
|
322
|
+
{ id: "pop-punk", label: "Pop Punk", promptHint: "pop punk" },
|
|
323
|
+
{ id: "skate-punk", label: "Skate Punk", promptHint: "skate punk" },
|
|
324
|
+
{ id: "anarcho-punk", label: "Anarcho", promptHint: "anarcho-punk" },
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "metal",
|
|
329
|
+
label: "Metal",
|
|
330
|
+
description: "Heavy distortion, aggressive vocals",
|
|
331
|
+
promptHint: "metal",
|
|
332
|
+
category: "rock-metal",
|
|
333
|
+
subgenres: [
|
|
334
|
+
{ id: "heavy-metal", label: "Heavy Metal", promptHint: "heavy metal" },
|
|
335
|
+
{ id: "death-metal", label: "Death Metal", promptHint: "death metal" },
|
|
336
|
+
{ id: "black-metal", label: "Black Metal", promptHint: "black metal" },
|
|
337
|
+
{ id: "thrash-metal", label: "Thrash", promptHint: "thrash metal" },
|
|
338
|
+
{ id: "doom-metal", label: "Doom", promptHint: "doom metal" },
|
|
339
|
+
{ id: "power-metal", label: "Power", promptHint: "power metal" },
|
|
340
|
+
{ id: "prog-metal", label: "Progressive", promptHint: "progressive metal" },
|
|
341
|
+
{ id: "metalcore", label: "Metalcore", promptHint: "metalcore" },
|
|
342
|
+
{ id: "deathcore", label: "Deathcore", promptHint: "deathcore" },
|
|
343
|
+
{ id: "nu-metal", label: "Nu-Metal", promptHint: "nu-metal" },
|
|
344
|
+
{ id: "sludge-metal", label: "Sludge", promptHint: "sludge metal" },
|
|
345
|
+
{ id: "symphonic-metal", label: "Symphonic", promptHint: "symphonic metal" },
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// -------- Acoustic / Roots --------
|
|
350
|
+
{
|
|
351
|
+
id: "jazz",
|
|
352
|
+
label: "Jazz",
|
|
353
|
+
description: "Improvisation, swing rhythms, complex harmony",
|
|
354
|
+
promptHint: "jazz",
|
|
355
|
+
category: "acoustic",
|
|
356
|
+
subgenres: [
|
|
357
|
+
{ id: "bebop", label: "Bebop", promptHint: "bebop" },
|
|
358
|
+
{ id: "smooth-jazz", label: "Smooth Jazz", promptHint: "smooth jazz" },
|
|
359
|
+
{ id: "fusion", label: "Fusion", promptHint: "jazz fusion" },
|
|
360
|
+
{ id: "cool-jazz", label: "Cool Jazz", promptHint: "cool jazz" },
|
|
361
|
+
{ id: "free-jazz", label: "Free Jazz", promptHint: "free jazz" },
|
|
362
|
+
{ id: "swing", label: "Swing", promptHint: "swing jazz" },
|
|
363
|
+
{ id: "big-band", label: "Big Band", promptHint: "big band jazz" },
|
|
364
|
+
{ id: "dixieland", label: "Dixieland", promptHint: "dixieland jazz" },
|
|
365
|
+
{ id: "latin-jazz", label: "Latin Jazz", promptHint: "latin jazz" },
|
|
366
|
+
{ id: "nu-jazz", label: "Nu-Jazz", promptHint: "nu-jazz" },
|
|
367
|
+
{ id: "acid-jazz", label: "Acid Jazz", promptHint: "acid jazz" },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "blues",
|
|
372
|
+
label: "Blues",
|
|
373
|
+
description: "12-bar form, expressive vocals, bent notes",
|
|
374
|
+
promptHint: "blues",
|
|
375
|
+
category: "acoustic",
|
|
376
|
+
subgenres: [
|
|
377
|
+
{ id: "delta-blues", label: "Delta Blues", promptHint: "Delta blues" },
|
|
378
|
+
{ id: "chicago-blues", label: "Chicago Blues", promptHint: "Chicago blues" },
|
|
379
|
+
{ id: "electric-blues", label: "Electric Blues", promptHint: "electric blues" },
|
|
380
|
+
{ id: "jump-blues", label: "Jump Blues", promptHint: "jump blues" },
|
|
381
|
+
{ id: "blues-rock", label: "Blues Rock", promptHint: "blues rock" },
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: "country",
|
|
386
|
+
label: "Country",
|
|
387
|
+
description: "Acoustic, narrative lyrics",
|
|
388
|
+
promptHint: "country",
|
|
389
|
+
category: "acoustic",
|
|
390
|
+
subgenres: [
|
|
391
|
+
{ id: "country-pop", label: "Country Pop", promptHint: "country pop" },
|
|
392
|
+
{ id: "outlaw-country", label: "Outlaw Country", promptHint: "outlaw country" },
|
|
393
|
+
{ id: "alt-country", label: "Alt Country", promptHint: "alt-country" },
|
|
394
|
+
{ id: "country-rock", label: "Country Rock", promptHint: "country rock" },
|
|
395
|
+
{ id: "americana", label: "Americana", promptHint: "americana" },
|
|
396
|
+
{ id: "honky-tonk", label: "Honky Tonk", promptHint: "honky tonk" },
|
|
397
|
+
{ id: "bluegrass", label: "Bluegrass", promptHint: "bluegrass" },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "folk",
|
|
402
|
+
label: "Folk",
|
|
403
|
+
description: "Acoustic, traditional instruments",
|
|
404
|
+
promptHint: "folk",
|
|
405
|
+
category: "acoustic",
|
|
406
|
+
subgenres: [
|
|
407
|
+
{ id: "indie-folk", label: "Indie Folk", promptHint: "indie folk" },
|
|
408
|
+
{ id: "traditional-folk", label: "Traditional", promptHint: "traditional folk" },
|
|
409
|
+
{ id: "celtic-folk", label: "Celtic", promptHint: "Celtic folk" },
|
|
410
|
+
{ id: "freak-folk", label: "Freak Folk", promptHint: "freak folk" },
|
|
411
|
+
{ id: "folk-rock", label: "Folk Rock", promptHint: "folk rock" },
|
|
412
|
+
{ id: "anti-folk", label: "Anti-Folk", promptHint: "anti-folk" },
|
|
413
|
+
],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "gospel",
|
|
417
|
+
label: "Gospel",
|
|
418
|
+
description: "Christian devotional, choral, often piano-led",
|
|
419
|
+
promptHint: "gospel",
|
|
420
|
+
category: "acoustic",
|
|
421
|
+
subgenres: [
|
|
422
|
+
{ id: "traditional-gospel", label: "Traditional", promptHint: "traditional gospel" },
|
|
423
|
+
{ id: "contemporary-gospel",label: "Contemporary", promptHint: "contemporary gospel" },
|
|
424
|
+
{ id: "urban-gospel", label: "Urban", promptHint: "urban gospel" },
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
// -------- Global --------
|
|
429
|
+
{
|
|
430
|
+
id: "reggae",
|
|
431
|
+
label: "Reggae",
|
|
432
|
+
description: "Off-beat rhythm, Jamaican origin",
|
|
433
|
+
promptHint: "reggae",
|
|
434
|
+
category: "global",
|
|
435
|
+
subgenres: [
|
|
436
|
+
{ id: "roots-reggae", label: "Roots", promptHint: "roots reggae" },
|
|
437
|
+
{ id: "dub", label: "Dub", promptHint: "dub reggae" },
|
|
438
|
+
{ id: "dancehall", label: "Dancehall", promptHint: "dancehall" },
|
|
439
|
+
{ id: "ska", label: "Ska", promptHint: "ska" },
|
|
440
|
+
{ id: "rocksteady", label: "Rocksteady", promptHint: "rocksteady" },
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: "latin",
|
|
445
|
+
label: "Latin",
|
|
446
|
+
description: "Latin American musical traditions",
|
|
447
|
+
promptHint: "latin",
|
|
448
|
+
category: "global",
|
|
449
|
+
subgenres: [
|
|
450
|
+
{ id: "salsa", label: "Salsa", promptHint: "salsa" },
|
|
451
|
+
{ id: "bossa-nova", label: "Bossa Nova", promptHint: "bossa nova" },
|
|
452
|
+
{ id: "reggaeton", label: "Reggaeton", promptHint: "reggaeton" },
|
|
453
|
+
{ id: "cumbia", label: "Cumbia", promptHint: "cumbia" },
|
|
454
|
+
{ id: "merengue", label: "Merengue", promptHint: "merengue" },
|
|
455
|
+
{ id: "bachata", label: "Bachata", promptHint: "bachata" },
|
|
456
|
+
{ id: "tango", label: "Tango", promptHint: "tango" },
|
|
457
|
+
{ id: "mariachi", label: "Mariachi", promptHint: "mariachi" },
|
|
458
|
+
{ id: "samba", label: "Samba", promptHint: "samba" },
|
|
459
|
+
{ id: "flamenco", label: "Flamenco", promptHint: "flamenco" },
|
|
460
|
+
{ id: "latin-pop", label: "Latin Pop", promptHint: "latin pop" },
|
|
461
|
+
{ id: "latin-trap", label: "Latin Trap", promptHint: "latin trap" },
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "world",
|
|
466
|
+
label: "World",
|
|
467
|
+
description: "Non-Western musical traditions",
|
|
468
|
+
promptHint: "world music",
|
|
469
|
+
category: "global",
|
|
470
|
+
subgenres: [
|
|
471
|
+
{ id: "afrobeat", label: "Afrobeat", promptHint: "afrobeat" },
|
|
472
|
+
{ id: "afrobeats", label: "Afrobeats", promptHint: "afrobeats" },
|
|
473
|
+
{ id: "highlife", label: "Highlife", promptHint: "highlife" },
|
|
474
|
+
{ id: "amapiano-w", label: "Amapiano", promptHint: "amapiano" },
|
|
475
|
+
{ id: "bhangra", label: "Bhangra", promptHint: "bhangra" },
|
|
476
|
+
{ id: "bollywood", label: "Bollywood", promptHint: "Bollywood" },
|
|
477
|
+
{ id: "indian-classical", label: "Indian Classical", promptHint: "Indian classical" },
|
|
478
|
+
{ id: "klezmer", label: "Klezmer", promptHint: "klezmer" },
|
|
479
|
+
{ id: "balkan", label: "Balkan", promptHint: "Balkan" },
|
|
480
|
+
{ id: "middle-eastern", label: "Middle Eastern", promptHint: "middle-eastern" },
|
|
481
|
+
{ id: "celtic", label: "Celtic", promptHint: "Celtic" },
|
|
482
|
+
{ id: "gamelan", label: "Gamelan", promptHint: "gamelan" },
|
|
483
|
+
{ id: "throat-singing", label: "Throat Singing", promptHint: "throat singing" },
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
// -------- Cinematic / Score --------
|
|
488
|
+
{
|
|
489
|
+
id: "classical",
|
|
490
|
+
label: "Classical",
|
|
491
|
+
description: "Orchestral, traditional Western art music",
|
|
492
|
+
promptHint: "classical",
|
|
493
|
+
category: "cinematic",
|
|
494
|
+
subgenres: [
|
|
495
|
+
{ id: "baroque", label: "Baroque", promptHint: "baroque classical" },
|
|
496
|
+
{ id: "romantic", label: "Romantic", promptHint: "romantic-era classical" },
|
|
497
|
+
{ id: "modern-classical", label: "Modern Classical", promptHint: "modern classical" },
|
|
498
|
+
{ id: "minimalist-classical",label: "Minimalist", promptHint: "minimalist classical" },
|
|
499
|
+
{ id: "choral", label: "Choral", promptHint: "choral classical" },
|
|
500
|
+
{ id: "chamber-music", label: "Chamber", promptHint: "chamber music" },
|
|
501
|
+
{ id: "opera", label: "Opera", promptHint: "operatic" },
|
|
502
|
+
{ id: "early-music", label: "Early Music", promptHint: "early music" },
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "cinematic",
|
|
507
|
+
label: "Cinematic",
|
|
508
|
+
description: "Score-style, evocative, mood-led",
|
|
509
|
+
promptHint: "cinematic",
|
|
510
|
+
category: "cinematic",
|
|
511
|
+
subgenres: [
|
|
512
|
+
{ id: "epic-orchestral", label: "Epic Orchestral", promptHint: "epic orchestral cinematic" },
|
|
513
|
+
{ id: "minimalist-score", label: "Minimalist Score", promptHint: "minimalist cinematic score" },
|
|
514
|
+
{ id: "trailer-music", label: "Trailer", promptHint: "trailer cinematic music" },
|
|
515
|
+
{ id: "score-action", label: "Action", promptHint: "action film score" },
|
|
516
|
+
{ id: "score-horror", label: "Horror", promptHint: "horror film score" },
|
|
517
|
+
{ id: "score-romance", label: "Romance", promptHint: "romantic film score" },
|
|
518
|
+
{ id: "score-sci-fi", label: "Sci-Fi", promptHint: "sci-fi film score" },
|
|
519
|
+
{ id: "score-fantasy", label: "Fantasy", promptHint: "fantasy film score" },
|
|
520
|
+
{ id: "score-noir", label: "Noir", promptHint: "noir cinematic" },
|
|
521
|
+
{ id: "score-documentary", label: "Documentary", promptHint: "documentary score" },
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
id: "video-game",
|
|
526
|
+
label: "Video Game",
|
|
527
|
+
description: "Game-score-style — chiptune, orchestral, electronic",
|
|
528
|
+
promptHint: "video game music",
|
|
529
|
+
category: "cinematic",
|
|
530
|
+
subgenres: [
|
|
531
|
+
{ id: "chiptune", label: "Chiptune", promptHint: "chiptune" },
|
|
532
|
+
{ id: "8bit", label: "8-bit", promptHint: "8-bit" },
|
|
533
|
+
{ id: "16bit", label: "16-bit", promptHint: "16-bit" },
|
|
534
|
+
{ id: "vgm-orchestral", label: "Orchestral VGM", promptHint: "orchestral video game" },
|
|
535
|
+
{ id: "vgm-synth", label: "Synth VGM", promptHint: "synth video game" },
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
id: "holiday",
|
|
540
|
+
label: "Holiday",
|
|
541
|
+
description: "Seasonal — Christmas, festive, traditional",
|
|
542
|
+
promptHint: "holiday",
|
|
543
|
+
category: "cinematic",
|
|
544
|
+
subgenres: [
|
|
545
|
+
{ id: "christmas", label: "Christmas", promptHint: "Christmas" },
|
|
546
|
+
{ id: "winter", label: "Winter", promptHint: "winter holiday" },
|
|
547
|
+
{ id: "festive", label: "Festive", promptHint: "festive" },
|
|
548
|
+
],
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
id: "children",
|
|
552
|
+
label: "Children's",
|
|
553
|
+
description: "Kids music — sing-alongs, lullabies, educational",
|
|
554
|
+
promptHint: "children's music",
|
|
555
|
+
category: "cinematic",
|
|
556
|
+
subgenres: [
|
|
557
|
+
{ id: "lullaby", label: "Lullaby", promptHint: "lullaby" },
|
|
558
|
+
{ id: "nursery-rhyme", label: "Nursery Rhyme", promptHint: "nursery rhyme" },
|
|
559
|
+
{ id: "kids-pop", label: "Kids Pop", promptHint: "kids pop" },
|
|
560
|
+
],
|
|
561
|
+
},
|
|
562
|
+
] as const
|
|
563
|
+
|
|
564
|
+
export const MUSIC_ERAS: ReadonlyArray<MusicEra> = [
|
|
565
|
+
{ id: "1920s", label: "1920s", description: "Roaring Twenties, jazz age", promptHint: "1920s" },
|
|
566
|
+
{ id: "1930s", label: "1930s", description: "Big band, swing", promptHint: "1930s" },
|
|
567
|
+
{ id: "1940s", label: "1940s", description: "Wartime, bebop emergence", promptHint: "1940s" },
|
|
568
|
+
{ id: "1950s", label: "1950s", description: "Early rock & roll, doo-wop", promptHint: "1950s" },
|
|
569
|
+
{ id: "1960s", label: "1960s", description: "British invasion, Motown", promptHint: "1960s" },
|
|
570
|
+
{ id: "1970s", label: "1970s", description: "Disco, prog rock, punk", promptHint: "1970s" },
|
|
571
|
+
{ id: "1980s", label: "1980s", description: "Synth-pop, new wave", promptHint: "1980s" },
|
|
572
|
+
{ id: "1990s", label: "1990s", description: "Grunge, hip-hop golden age", promptHint: "1990s" },
|
|
573
|
+
{ id: "2000s", label: "2000s", description: "Pop-punk, R&B revival", promptHint: "early 2000s" },
|
|
574
|
+
{ id: "2010s", label: "2010s", description: "EDM mainstream, streaming era", promptHint: "2010s" },
|
|
575
|
+
{ id: "modern", label: "Modern", description: "2020s+ contemporary", promptHint: "modern" },
|
|
576
|
+
{ id: "futurist", label: "Futurist", description: "Sci-fi, otherworldly", promptHint: "futuristic" },
|
|
577
|
+
] as const
|
|
578
|
+
|
|
579
|
+
const GENRE_BY_ID = new Map(MUSIC_GENRES.map((g) => [g.id, g]))
|
|
580
|
+
const ERA_BY_ID = new Map(MUSIC_ERAS.map((e) => [e.id, e]))
|
|
581
|
+
|
|
582
|
+
export function getMusicGenre(id: string | undefined): MusicGenre | undefined {
|
|
583
|
+
if (!id) return undefined
|
|
584
|
+
return GENRE_BY_ID.get(id)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export function getMusicGenreLabel(id: string | undefined): string {
|
|
588
|
+
if (!id) return ""
|
|
589
|
+
return GENRE_BY_ID.get(id)?.label ?? id
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export function getMusicSubgenre(
|
|
593
|
+
genreId: string | undefined,
|
|
594
|
+
subgenreId: string | undefined,
|
|
595
|
+
): MusicSubgenre | undefined {
|
|
596
|
+
if (!genreId || !subgenreId) return undefined
|
|
597
|
+
const genre = GENRE_BY_ID.get(genreId)
|
|
598
|
+
return genre?.subgenres.find((s) => s.id === subgenreId)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function getMusicEra(id: string | undefined): MusicEra | undefined {
|
|
602
|
+
if (!id) return undefined
|
|
603
|
+
return ERA_BY_ID.get(id)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Compose hints from MusicGenreData: optional preText, structured
|
|
608
|
+
* [era] [subgenre|genre] (or " / "-joined genres for multi), optional
|
|
609
|
+
* postText. Returns array — caller joins with ", " (matches buildMoodHints
|
|
610
|
+
* / buildPersonHints pattern).
|
|
611
|
+
*
|
|
612
|
+
* Multi-genre (genre is an array): emit each genre's hint joined with " / "
|
|
613
|
+
* — subgenre is ignored in multi-mode (subgenre is meaningful only against
|
|
614
|
+
* a single chosen genre).
|
|
615
|
+
*/
|
|
616
|
+
export function buildMusicGenreHints(data: {
|
|
617
|
+
readonly preText?: string
|
|
618
|
+
readonly postText?: string
|
|
619
|
+
readonly genre?: string | ReadonlyArray<string>
|
|
620
|
+
readonly subgenre?: string
|
|
621
|
+
readonly era?: string
|
|
622
|
+
}): string {
|
|
623
|
+
const fragments: string[] = []
|
|
624
|
+
|
|
625
|
+
const pre = typeof data.preText === "string" ? data.preText.trim() : ""
|
|
626
|
+
if (pre) fragments.push(pre)
|
|
627
|
+
|
|
628
|
+
const parts: string[] = []
|
|
629
|
+
const era = getMusicEra(data.era)
|
|
630
|
+
if (era) parts.push(era.promptHint)
|
|
631
|
+
|
|
632
|
+
const genreIds = pickIds(data.genre)
|
|
633
|
+
if (genreIds.length > 1) {
|
|
634
|
+
const genreHints = genreIds
|
|
635
|
+
.map((id) => getMusicGenre(id)?.promptHint)
|
|
636
|
+
.filter((h): h is string => !!h)
|
|
637
|
+
if (genreHints.length > 0) parts.push(genreHints.join(" / "))
|
|
638
|
+
} else if (genreIds.length === 1) {
|
|
639
|
+
const sub = getMusicSubgenre(genreIds[0], data.subgenre)
|
|
640
|
+
if (sub) {
|
|
641
|
+
parts.push(sub.promptHint)
|
|
642
|
+
} else {
|
|
643
|
+
const genre = getMusicGenre(genreIds[0])
|
|
644
|
+
if (genre) parts.push(genre.promptHint)
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (parts.length > 0) fragments.push(parts.join(" "))
|
|
648
|
+
|
|
649
|
+
const post = typeof data.postText === "string" ? data.postText.trim() : ""
|
|
650
|
+
if (post) fragments.push(post)
|
|
651
|
+
|
|
652
|
+
return fragments.join(", ")
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** Default data when a music-genre node is dropped on canvas. Empty by design — forces a deliberate pick. */
|
|
656
|
+
export const MUSIC_GENRE_DEFAULT_DATA: {
|
|
657
|
+
preText?: string
|
|
658
|
+
postText?: string
|
|
659
|
+
genre?: string | ReadonlyArray<string>
|
|
660
|
+
subgenre?: string
|
|
661
|
+
era?: string
|
|
662
|
+
} = {}
|