new song structure
This commit is contained in:
@@ -16,6 +16,20 @@ const STRUCTURE_PROMPT_LABELS = {
|
|||||||
short_intro: "introduction instrumentale courte",
|
short_intro: "introduction instrumentale courte",
|
||||||
long_intro: "introduction instrumentale longue",
|
long_intro: "introduction instrumentale longue",
|
||||||
pre_chorus: "pré-refrain",
|
pre_chorus: "pré-refrain",
|
||||||
|
pre_refrain_instrumental: "pré-refrain instrumental",
|
||||||
|
pont: "pont",
|
||||||
|
solo_de_guitare: "solo de guitare",
|
||||||
|
solo_de_guitare_electrique: "solo de guitare électrique",
|
||||||
|
solo_de_batterie: "solo de batterie",
|
||||||
|
solo_de_saxophone: "solo de saxophone",
|
||||||
|
solo_de_violon: "solo de violon",
|
||||||
|
break: "break",
|
||||||
|
interlude: "interlude",
|
||||||
|
interlude_melodique: "interlude mélodique",
|
||||||
|
final_apogee: "final apogée",
|
||||||
|
arret_net: "arrêt net",
|
||||||
|
fade_out: "fade out",
|
||||||
|
transition_douce: "transition douce vers le silence",
|
||||||
};
|
};
|
||||||
|
|
||||||
const STRUCTURE_ALIASES = {
|
const STRUCTURE_ALIASES = {
|
||||||
@@ -30,6 +44,20 @@ const STRUCTURE_ALIASES = {
|
|||||||
"pre chorus": "pre_chorus",
|
"pre chorus": "pre_chorus",
|
||||||
"pre-chorus": "pre_chorus",
|
"pre-chorus": "pre_chorus",
|
||||||
prechorus: "pre_chorus",
|
prechorus: "pre_chorus",
|
||||||
|
"pré-refrain instrumental": "pre_refrain_instrumental",
|
||||||
|
"pre-refrain instrumental": "pre_refrain_instrumental",
|
||||||
|
"instrumental pre-chorus": "pre_refrain_instrumental",
|
||||||
|
"instrumental pre chorus": "pre_refrain_instrumental",
|
||||||
|
bridge: "pont",
|
||||||
|
guitar_solo: "solo_de_guitare",
|
||||||
|
electric_guitar_solo: "solo_de_guitare_electrique",
|
||||||
|
drum_solo: "solo_de_batterie",
|
||||||
|
sax_solo: "solo_de_saxophone",
|
||||||
|
violin_solo: "solo_de_violon",
|
||||||
|
melodic_interlude: "interlude_melodique",
|
||||||
|
grand_finale: "final_apogee",
|
||||||
|
sudden_stop: "arret_net",
|
||||||
|
soft_transition: "transition_douce",
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeStructureValue = (value) => {
|
const normalizeStructureValue = (value) => {
|
||||||
@@ -61,6 +89,27 @@ const sanitizeStructureEntries = (structure = []) => {
|
|||||||
if (!output.includes("pre_chorus")) output.push("pre_chorus");
|
if (!output.includes("pre_chorus")) output.push("pre_chorus");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (normalized === "pre_refrain_instrumental") {
|
||||||
|
if (!output.includes("pre_refrain_instrumental")) {
|
||||||
|
output.push("pre_refrain_instrumental");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
normalized === "final_apogee" ||
|
||||||
|
normalized === "arret_net" ||
|
||||||
|
normalized === "fade_out" ||
|
||||||
|
normalized === "transition_douce"
|
||||||
|
) {
|
||||||
|
const outroIndex = output.findIndex((item) =>
|
||||||
|
["final_apogee", "arret_net", "fade_out", "transition_douce"].includes(
|
||||||
|
item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (outroIndex !== -1) {
|
||||||
|
output.splice(outroIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
output.push(normalized);
|
output.push(normalized);
|
||||||
});
|
});
|
||||||
return output;
|
return output;
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
let type = "section";
|
let type = "section";
|
||||||
if (lower.includes("refrain") || lower.includes("chorus")) type = "refrain";
|
if (lower.includes("refrain") || lower.includes("chorus")) type = "refrain";
|
||||||
else if (lower.includes("couplet") || lower.includes("verse")) type = "couplet";
|
else if (lower.includes("couplet") || lower.includes("verse")) type = "couplet";
|
||||||
else if (lower.includes("bridge")) type = "bridge";
|
else if (lower.includes("pont") || lower.includes("bridge")) type = "pont";
|
||||||
else if (lower.includes("intro")) type = "intro";
|
else if (lower.includes("intro")) type = "intro";
|
||||||
const indexMatch = label.match(/(\d+)/);
|
const indexMatch = label.match(/(\d+)/);
|
||||||
const index = indexMatch ? Number(indexMatch[1]) : undefined;
|
const index = indexMatch ? Number(indexMatch[1]) : undefined;
|
||||||
@@ -373,7 +373,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
if (fallback) return fallback;
|
if (fallback) return fallback;
|
||||||
if (type === "refrain") return index > 1 ? `Refrain ${index}` : "Refrain";
|
if (type === "refrain") return index > 1 ? `Refrain ${index}` : "Refrain";
|
||||||
if (type === "couplet") return index > 1 ? `Couplet ${index}` : "Couplet";
|
if (type === "couplet") return index > 1 ? `Couplet ${index}` : "Couplet";
|
||||||
if (type === "bridge") return index > 1 ? `Pont ${index}` : "Pont";
|
if (type === "pont") return index > 1 ? `Pont ${index}` : "Pont";
|
||||||
if (type === "intro") return "Intro";
|
if (type === "intro") return "Intro";
|
||||||
return index > 1 ? `Section ${index}` : "Section";
|
return index > 1 ? `Section ${index}` : "Section";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
lower.includes("pre-refrain")
|
lower.includes("pre-refrain")
|
||||||
)
|
)
|
||||||
type = "pre_chorus";
|
type = "pre_chorus";
|
||||||
else if (lower.includes("bridge")) type = "bridge";
|
else if (lower.includes("pont") || lower.includes("bridge")) type = "pont";
|
||||||
else if (lower.includes("intro")) {
|
else if (lower.includes("intro")) {
|
||||||
if (lower.includes("long")) type = "long_intro";
|
if (lower.includes("long")) type = "long_intro";
|
||||||
else type = "short_intro";
|
else type = "short_intro";
|
||||||
@@ -468,7 +468,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
return index > 1 ? `Refrain ${index}` : "Refrain";
|
return index > 1 ? `Refrain ${index}` : "Refrain";
|
||||||
if (normalized === "couplet")
|
if (normalized === "couplet")
|
||||||
return index > 1 ? `Couplet ${index}` : "Couplet";
|
return index > 1 ? `Couplet ${index}` : "Couplet";
|
||||||
if (type === "bridge") return index > 1 ? `Pont ${index}` : "Pont";
|
if (type === "pont") return index > 1 ? `Pont ${index}` : "Pont";
|
||||||
if (type === "intro") return index > 1 ? `Intro ${index}` : "Intro";
|
if (type === "intro") return index > 1 ? `Intro ${index}` : "Intro";
|
||||||
return index > 1 ? `Section ${index}` : "Section";
|
return index > 1 ? `Section ${index}` : "Section";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
|||||||
const hasShownErrorAlert = useRef(false);
|
const hasShownErrorAlert = useRef(false);
|
||||||
const { setIsLoading } = useMinuit();
|
const { setIsLoading } = useMinuit();
|
||||||
const { updateProjectData } = useUser();
|
const { updateProjectData } = useUser();
|
||||||
|
const progressValue = Math.max(0, Math.min(100, Math.round(progress)));
|
||||||
|
|
||||||
// Reset when becomes active
|
// Reset when becomes active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -301,7 +302,7 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
|||||||
: "Ton texte est\nen cours de création"}
|
: "Ton texte est\nen cours de création"}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={{ alignItems: "center", gap: 16 }}>
|
<View style={{ alignItems: "center", gap: 16 }}>
|
||||||
<ProgressBar gradient progress={progress} />
|
<ProgressBar gradient progress={progressValue} />
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -309,7 +310,7 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
|||||||
fontFamily: FONT_FAMILY.InterRegular,
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{progress}%
|
{progressValue}%
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{!saved && result && !error && (
|
{!saved && result && !error && (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
Image,
|
Image,
|
||||||
@@ -15,6 +15,7 @@ import { icons } from "../../assets";
|
|||||||
import { Palette } from "../../styles";
|
import { Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import {
|
import {
|
||||||
|
arraysAreSame,
|
||||||
formatStructureLabel,
|
formatStructureLabel,
|
||||||
getSegmentMeta,
|
getSegmentMeta,
|
||||||
OPTIONAL_STRUCTURE_SEGMENTS,
|
OPTIONAL_STRUCTURE_SEGMENTS,
|
||||||
@@ -40,20 +41,28 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
() => sanitizeStructureList(baseStructure),
|
() => sanitizeStructureList(baseStructure),
|
||||||
[baseStructure]
|
[baseStructure]
|
||||||
);
|
);
|
||||||
|
const extractValues = useCallback(
|
||||||
|
(items) =>
|
||||||
|
(items || []).map((item) => item?.type || item?.value).filter(Boolean),
|
||||||
|
[]
|
||||||
|
);
|
||||||
const [sortableItems, setSortableItems] = useState(() =>
|
const [sortableItems, setSortableItems] = useState(() =>
|
||||||
buildSortableItems(sanitizedBase)
|
buildSortableItems(sanitizedBase)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSortableItems(buildSortableItems(sanitizedBase));
|
setSortableItems((prev) => {
|
||||||
}, [sanitizedBase]);
|
const currentValues = extractValues(prev);
|
||||||
|
if (arraysAreSame(currentValues, sanitizedBase)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return buildSortableItems(sanitizedBase);
|
||||||
|
});
|
||||||
|
}, [sanitizedBase, extractValues]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const values = sortableItems
|
const values = extractValues(sortableItems);
|
||||||
.map((item) => item?.type || item?.value)
|
|
||||||
.filter(Boolean);
|
|
||||||
onChange?.(values);
|
onChange?.(values);
|
||||||
}, [sortableItems, onChange]);
|
}, [sortableItems, onChange, extractValues]);
|
||||||
|
|
||||||
const labeledItems = useMemo(() => {
|
const labeledItems = useMemo(() => {
|
||||||
const counts = {};
|
const counts = {};
|
||||||
@@ -80,40 +89,63 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedTypes = useMemo(() => {
|
const segmentCounts = useMemo(() => {
|
||||||
const set = new Set();
|
const counts = {};
|
||||||
sortableItems.forEach((item) => {
|
extractValues(sortableItems).forEach((type) => {
|
||||||
if (item?.type) set.add(item.type);
|
counts[type] = (counts[type] || 0) + 1;
|
||||||
else if (item?.value) set.add(item.value);
|
|
||||||
});
|
});
|
||||||
return set;
|
return counts;
|
||||||
}, [sortableItems]);
|
}, [sortableItems, extractValues]);
|
||||||
|
|
||||||
const toggleOptionalSegment = (type) => {
|
const handleRemoveItem = useCallback(
|
||||||
|
(itemId) => {
|
||||||
setSortableItems((prev) => {
|
setSortableItems((prev) => {
|
||||||
const currentValues = prev.map((item) => item.type || item.value);
|
const remaining = prev.filter((item) => item.id !== itemId);
|
||||||
if (currentValues.includes(type)) {
|
const sanitized = sanitizeStructureList(extractValues(remaining));
|
||||||
const filtered = currentValues.filter((value) => value !== type);
|
return buildSortableItems(sanitized);
|
||||||
return buildSortableItems(filtered);
|
});
|
||||||
}
|
},
|
||||||
|
[extractValues]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOptionalSegmentPress = useCallback(
|
||||||
|
(type) => {
|
||||||
const meta = getSegmentMeta(type);
|
const meta = getSegmentMeta(type);
|
||||||
|
setSortableItems((prev) => {
|
||||||
|
const currentValues = extractValues(prev);
|
||||||
let nextValues = [...currentValues];
|
let nextValues = [...currentValues];
|
||||||
|
|
||||||
if (meta?.exclusiveGroup) {
|
if (meta?.exclusiveGroup) {
|
||||||
nextValues = nextValues.filter((value) => {
|
nextValues = nextValues.filter((value) => {
|
||||||
const itemMeta = getSegmentMeta(value);
|
const itemMeta = getSegmentMeta(value);
|
||||||
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowsMultiple = meta?.allowMultiple !== false;
|
||||||
|
if (!allowsMultiple && nextValues.includes(type)) {
|
||||||
|
const filtered = nextValues.filter((value) => value !== type);
|
||||||
|
const sanitized = sanitizeStructureList(filtered);
|
||||||
|
return buildSortableItems(sanitized);
|
||||||
|
}
|
||||||
|
|
||||||
nextValues.push(type);
|
nextValues.push(type);
|
||||||
return buildSortableItems(nextValues);
|
const sanitized = sanitizeStructureList(nextValues);
|
||||||
|
return buildSortableItems(sanitized);
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
[extractValues]
|
||||||
|
);
|
||||||
|
|
||||||
const Row = ({ item: rowData, onPress }) => {
|
const Row = ({ item: rowData, onPress }) => {
|
||||||
const isButton = typeof onPress === "function";
|
const isButton = typeof onPress === "function";
|
||||||
const Wrapper = isButton ? TouchableOpacity : View;
|
const Wrapper = isButton ? TouchableOpacity : View;
|
||||||
const selected = rowData?.selected;
|
const selected = rowData?.selected;
|
||||||
const icon = rowData?.icon;
|
const icon = rowData?.icon;
|
||||||
|
const baseType = rowData?.type || rowData?.value;
|
||||||
|
const meta = getSegmentMeta(baseType);
|
||||||
|
const canRemove = rowData?.sortable && meta?.optional === true;
|
||||||
|
const count = rowData?.count ?? 0;
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -139,14 +171,29 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
|
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
|
||||||
</View>
|
</View>
|
||||||
{rowData?.sortable ? (
|
{rowData?.sortable ? (
|
||||||
|
<View style={styles.rowActions}>
|
||||||
<Sortable.Handle>
|
<Sortable.Handle>
|
||||||
<Image source={icons.dragDots} style={styles.handleIcon} />
|
<Image source={icons.dragDots} style={styles.handleIcon} />
|
||||||
</Sortable.Handle>
|
</Sortable.Handle>
|
||||||
|
{canRemove && (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => handleRemoveItem(rowData.id)}
|
||||||
|
style={styles.removeButton}
|
||||||
|
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
||||||
|
>
|
||||||
|
<Image source={icons.close} style={styles.removeIcon} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<View
|
||||||
source={icon || (selected ? icons.check : icons.add)}
|
style={[
|
||||||
style={[styles.optionIcon]}
|
styles.countBadge,
|
||||||
/>
|
count > 0 ? styles.countBadgeActive : null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={styles.countBadgeText}>{count}</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</BlurView>
|
</BlurView>
|
||||||
</View>
|
</View>
|
||||||
@@ -166,7 +213,9 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
contentContainerStyle={{ paddingVertical: 4, paddingBottom: 24 }}
|
contentContainerStyle={{ paddingVertical: 4, paddingBottom: 24 }}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
<>
|
<View style={styles.sectionTag}>
|
||||||
|
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
||||||
|
</View>
|
||||||
<Sortable.Grid
|
<Sortable.Grid
|
||||||
columns={1}
|
columns={1}
|
||||||
rowGap={12}
|
rowGap={12}
|
||||||
@@ -175,18 +224,20 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
customHandle
|
customHandle
|
||||||
scrollableRef={scrollRef}
|
scrollableRef={scrollRef}
|
||||||
onDragEnd={({ data: newData }) => {
|
onDragEnd={({ data: newData }) => {
|
||||||
const values = (newData || [])
|
const values = extractValues(newData);
|
||||||
.map((it) => it?.type || it?.value)
|
|
||||||
.filter(Boolean);
|
|
||||||
const sanitized = sanitizeStructureList(values);
|
const sanitized = sanitizeStructureList(values);
|
||||||
setSortableItems(buildSortableItems(sanitized));
|
setSortableItems(buildSortableItems(sanitized));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
||||||
|
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
||||||
|
</View>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={optionalSegments}
|
data={optionalSegments}
|
||||||
gap={12}
|
gap={12}
|
||||||
renderItem={({ item }) => {
|
renderItem={({ item }) => {
|
||||||
const selected = selectedTypes.has(item.type);
|
const count = segmentCounts[item.type] || 0;
|
||||||
|
const selected = count > 0;
|
||||||
return (
|
return (
|
||||||
<View style={{ marginTop: 12 }}>
|
<View style={{ marginTop: 12 }}>
|
||||||
<Row
|
<Row
|
||||||
@@ -194,8 +245,9 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
...item,
|
...item,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
selected,
|
selected,
|
||||||
|
count,
|
||||||
}}
|
}}
|
||||||
onPress={() => toggleOptionalSegment(item.type)}
|
onPress={() => handleOptionalSegmentPress(item.type)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -205,7 +257,6 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
nestedScrollEnabled={false}
|
nestedScrollEnabled={false}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
</Animated.ScrollView>
|
</Animated.ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -267,10 +318,57 @@ const styles = StyleSheet.create({
|
|||||||
width: 22,
|
width: 22,
|
||||||
height: 22,
|
height: 22,
|
||||||
tintColor: Palette.white,
|
tintColor: Palette.white,
|
||||||
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
optionIcon: {
|
optionIcon: {
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
tintColor: Palette.white,
|
tintColor: Palette.white,
|
||||||
},
|
},
|
||||||
|
countBadge: {
|
||||||
|
minWidth: 28,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: "rgba(255,255,255,0.08)",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
countBadgeActive: {
|
||||||
|
backgroundColor: Palette.transparentWhite,
|
||||||
|
},
|
||||||
|
countBadgeText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: Palette.white,
|
||||||
|
fontFamily: FONT_FAMILY.InterMedium,
|
||||||
|
},
|
||||||
|
rowActions: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
removeButton: {
|
||||||
|
padding: 6,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: "rgba(255,255,255,0.08)",
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
tintColor: Palette.white,
|
||||||
|
},
|
||||||
|
sectionTag: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: "rgba(255,255,255,0.12)",
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
sectionTagText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: Palette.white,
|
||||||
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||||
|
letterSpacing: 1,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
"intro instrumentale longue",
|
"intro instrumentale longue",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// pre_chorus: {
|
||||||
|
// label: "Pré-refrain",
|
||||||
|
// promptLabel: "Pré-refrain",
|
||||||
|
// showIndex: false,
|
||||||
pre_chorus: {
|
pre_chorus: {
|
||||||
label: "Pré-refrain",
|
label: "Pré-refrain",
|
||||||
promptLabel: "Pré-refrain",
|
promptLabel: "Pré-refrain",
|
||||||
@@ -81,6 +85,151 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
"pré chorus",
|
"pré chorus",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
pre_refrain_instrumental: {
|
||||||
|
label: "Pré-refrain instrumental",
|
||||||
|
promptLabel: "Pré-refrain instrumental",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: false,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
exclusiveGroup: "pre_chorus",
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["instrumental_pre_chorus"],
|
||||||
|
},
|
||||||
|
pont: {
|
||||||
|
label: "Pont",
|
||||||
|
promptLabel: "Pont",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 225,
|
||||||
|
requiresLyrics: true,
|
||||||
|
aliases: ["bridge"],
|
||||||
|
},
|
||||||
|
solo_de_guitare: {
|
||||||
|
label: "Solo de guitare",
|
||||||
|
promptLabel: "Solo de guitare",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["guitar_solo", "solo_guitare"],
|
||||||
|
},
|
||||||
|
solo_de_guitare_electrique: {
|
||||||
|
label: "Solo de guitare électrique",
|
||||||
|
promptLabel: "Solo de guitare électrique",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["electric_guitar_solo", "solo_guitare_electrique"],
|
||||||
|
},
|
||||||
|
solo_de_batterie: {
|
||||||
|
label: "Solo de batterie",
|
||||||
|
promptLabel: "Solo de batterie",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["drum_solo", "solo_batterie"],
|
||||||
|
},
|
||||||
|
solo_de_saxophone: {
|
||||||
|
label: "Solo de saxophone",
|
||||||
|
promptLabel: "Solo de saxophone",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["sax_solo", "solo_saxo"],
|
||||||
|
},
|
||||||
|
solo_de_violon: {
|
||||||
|
label: "Solo de violon",
|
||||||
|
promptLabel: "Solo de violon",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["violin_solo", "solo_violon"],
|
||||||
|
},
|
||||||
|
break: {
|
||||||
|
label: "Break",
|
||||||
|
promptLabel: "Break",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
interlude: {
|
||||||
|
label: "Interlude",
|
||||||
|
promptLabel: "Interlude",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
interlude_melodique: {
|
||||||
|
label: "Interlude mélodique",
|
||||||
|
promptLabel: "Interlude mélodique",
|
||||||
|
showIndex: true,
|
||||||
|
allowMultiple: true,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["melodic_interlude"],
|
||||||
|
},
|
||||||
|
final_apogee: {
|
||||||
|
label: "Final apogée",
|
||||||
|
promptLabel: "Final apogée",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: false,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 170,
|
||||||
|
exclusiveGroup: "outro",
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["grand_finale"],
|
||||||
|
},
|
||||||
|
arret_net: {
|
||||||
|
label: "Arrêt net",
|
||||||
|
promptLabel: "Arrêt net",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: false,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
exclusiveGroup: "outro",
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["sudden_stop"],
|
||||||
|
},
|
||||||
|
fade_out: {
|
||||||
|
label: "Fade out",
|
||||||
|
promptLabel: "Fade out",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: false,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
exclusiveGroup: "outro",
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
transition_douce: {
|
||||||
|
label: "Transition douce vers le silence",
|
||||||
|
promptLabel: "Transition douce",
|
||||||
|
showIndex: false,
|
||||||
|
allowMultiple: false,
|
||||||
|
optional: true,
|
||||||
|
inputHeight: 150,
|
||||||
|
exclusiveGroup: "outro",
|
||||||
|
requiresLyrics: false,
|
||||||
|
aliases: ["soft_transition"],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALIAS_TO_KEY = Object.entries(STRUCTURE_SEGMENTS).reduce(
|
const ALIAS_TO_KEY = Object.entries(STRUCTURE_SEGMENTS).reduce(
|
||||||
@@ -133,6 +282,7 @@ export const sanitizeStructureList = (structure = []) => {
|
|||||||
if (!Array.isArray(structure)) return [];
|
if (!Array.isArray(structure)) return [];
|
||||||
const output = [];
|
const output = [];
|
||||||
let shouldInjectPreChorus = false;
|
let shouldInjectPreChorus = false;
|
||||||
|
let shouldInjectInstrumentalPreChorus = false;
|
||||||
|
|
||||||
structure.forEach((segment) => {
|
structure.forEach((segment) => {
|
||||||
const type = normalizeStructureType(segment);
|
const type = normalizeStructureType(segment);
|
||||||
@@ -160,6 +310,10 @@ export const sanitizeStructureList = (structure = []) => {
|
|||||||
shouldInjectPreChorus = true;
|
shouldInjectPreChorus = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (type === "pre_refrain_instrumental") {
|
||||||
|
shouldInjectInstrumentalPreChorus = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
output.push(type);
|
output.push(type);
|
||||||
});
|
});
|
||||||
@@ -188,6 +342,38 @@ export const sanitizeStructureList = (structure = []) => {
|
|||||||
result = expanded;
|
result = expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldInjectInstrumentalPreChorus) {
|
||||||
|
const baseWithoutInstrumental = result.filter(
|
||||||
|
(item) => item !== "pre_refrain_instrumental"
|
||||||
|
);
|
||||||
|
const expanded = [];
|
||||||
|
|
||||||
|
baseWithoutInstrumental.forEach((item) => {
|
||||||
|
if (item === "pre_chorus") {
|
||||||
|
const previous = expanded[expanded.length - 1];
|
||||||
|
if (previous !== "pre_refrain_instrumental") {
|
||||||
|
expanded.push("pre_refrain_instrumental");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item === "refrain") {
|
||||||
|
const previous = expanded[expanded.length - 1];
|
||||||
|
if (
|
||||||
|
previous !== "pre_refrain_instrumental" &&
|
||||||
|
previous !== "pre_chorus"
|
||||||
|
) {
|
||||||
|
expanded.push("pre_refrain_instrumental");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expanded.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!expanded.includes("pre_refrain_instrumental")) {
|
||||||
|
expanded.push("pre_refrain_instrumental");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = expanded;
|
||||||
|
}
|
||||||
|
|
||||||
const introIndex = result.findIndex(
|
const introIndex = result.findIndex(
|
||||||
(item) => item === "short_intro" || item === "long_intro"
|
(item) => item === "short_intro" || item === "long_intro"
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user