instrumental introduction
This commit is contained in:
@@ -39,8 +39,10 @@ import { FONT_FAMILY } from "../../styles/Fonts";
|
|||||||
import Style, { gutters, size } from "../../styles/Style";
|
import Style, { gutters, size } from "../../styles/Style";
|
||||||
import {
|
import {
|
||||||
formatStructureLabel,
|
formatStructureLabel,
|
||||||
|
getPromptLabelForStructure,
|
||||||
getSegmentMeta,
|
getSegmentMeta,
|
||||||
normalizeStructureType,
|
normalizeStructureType,
|
||||||
|
segmentRequiresLyrics,
|
||||||
} from "../../utils/songStructure";
|
} from "../../utils/songStructure";
|
||||||
|
|
||||||
// 20 secondes
|
// 20 secondes
|
||||||
@@ -333,12 +335,20 @@ const MusicDetails = ({ route }) => {
|
|||||||
const counts = {};
|
const counts = {};
|
||||||
return project.lyrics
|
return project.lyrics
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
const body = (s?.lyrics || "").trim();
|
|
||||||
if (!body) return null;
|
|
||||||
const type = normalizeStructureType(s?.type);
|
const type = normalizeStructureType(s?.type);
|
||||||
|
const requiresLyrics = segmentRequiresLyrics(type);
|
||||||
|
const body = (s?.lyrics || "").trim();
|
||||||
|
if (requiresLyrics && !body) return null;
|
||||||
counts[type] = (counts[type] || 0) + 1;
|
counts[type] = (counts[type] || 0) + 1;
|
||||||
const label = formatStructureLabel(type, counts[type]);
|
const label = formatStructureLabel(type, counts[type]);
|
||||||
return `[${label}]\n${body}`;
|
const displayLabel =
|
||||||
|
typeof label === "string" && label.length > 0
|
||||||
|
? label
|
||||||
|
: getPromptLabelForStructure(type);
|
||||||
|
if (!requiresLyrics) {
|
||||||
|
return `[${displayLabel}] — section instrumentale`;
|
||||||
|
}
|
||||||
|
return `[${displayLabel}]\n${body}`;
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
@@ -404,8 +414,11 @@ const MusicDetails = ({ route }) => {
|
|||||||
const normalized = normalizeStructureType(type);
|
const normalized = normalizeStructureType(type);
|
||||||
const meta = getSegmentMeta(normalized);
|
const meta = getSegmentMeta(normalized);
|
||||||
if (meta) {
|
if (meta) {
|
||||||
return formatStructureLabel(normalized, index);
|
const label = formatStructureLabel(normalized, index);
|
||||||
|
if (label) return label;
|
||||||
}
|
}
|
||||||
|
const prompt = getPromptLabelForStructure(normalized);
|
||||||
|
if (prompt) return prompt;
|
||||||
if (normalized === "refrain")
|
if (normalized === "refrain")
|
||||||
return index > 1 ? `Refrain ${index}` : "Refrain";
|
return index > 1 ? `Refrain ${index}` : "Refrain";
|
||||||
if (normalized === "couplet")
|
if (normalized === "couplet")
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import { navigate } from "../../navigation/NavigationService";
|
|||||||
import { useUser } from "../../providers/UserDataProvider";
|
import { useUser } from "../../providers/UserDataProvider";
|
||||||
import { Palette, Style } from "../../styles";
|
import { Palette, Style } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import { sanitizeStructureList } from "../../utils/songStructure";
|
import {
|
||||||
|
normalizeStructureType,
|
||||||
|
sanitizeStructureList,
|
||||||
|
segmentRequiresLyrics,
|
||||||
|
} from "../../utils/songStructure";
|
||||||
|
|
||||||
const CreatingLyrics = ({ active, config, selections }) => {
|
const CreatingLyrics = ({ active, config, selections }) => {
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
@@ -74,21 +78,40 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
|||||||
});
|
});
|
||||||
const rawLyrics = Array.isArray(data?.lyrics) ? data.lyrics : [];
|
const rawLyrics = Array.isArray(data?.lyrics) ? data.lyrics : [];
|
||||||
const normalizedLyrics = rawLyrics.map((section) => {
|
const normalizedLyrics = rawLyrics.map((section) => {
|
||||||
const sanitizedType =
|
const sanitizedType = normalizeStructureType(section?.type);
|
||||||
sanitizeStructureList([section?.type])[0] || section?.type || "";
|
|
||||||
return {
|
return {
|
||||||
...section,
|
...section,
|
||||||
type: sanitizedType,
|
type: sanitizedType,
|
||||||
|
lyrics: section?.lyrics || "",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const alignedLyrics =
|
|
||||||
sanitizedStructure.length > 0 &&
|
const remaining = [...normalizedLyrics];
|
||||||
sanitizedStructure.length === normalizedLyrics.length
|
const takeForType = (type, { fallback = true } = {}) => {
|
||||||
? normalizedLyrics.map((section, index) => ({
|
const index = remaining.findIndex((item) => item.type === type);
|
||||||
...section,
|
if (index !== -1) {
|
||||||
type: sanitizedStructure[index],
|
return remaining.splice(index, 1)[0];
|
||||||
}))
|
}
|
||||||
: normalizedLyrics;
|
if (!fallback) return null;
|
||||||
|
return remaining.shift() || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const structuredLyrics = sanitizedStructure.length
|
||||||
|
? sanitizedStructure.map((rawType) => {
|
||||||
|
const type = normalizeStructureType(rawType);
|
||||||
|
if (!segmentRequiresLyrics(type)) {
|
||||||
|
const match = takeForType(type, { fallback: false });
|
||||||
|
return { type, lyrics: match?.lyrics || "" };
|
||||||
|
}
|
||||||
|
const match = takeForType(type, { fallback: true });
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
lyrics: match?.lyrics || "",
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: normalizedLyrics;
|
||||||
|
|
||||||
|
const alignedLyrics = structuredLyrics.concat(remaining);
|
||||||
const processedResult = {
|
const processedResult = {
|
||||||
...data,
|
...data,
|
||||||
lyrics: alignedLyrics,
|
lyrics: alignedLyrics,
|
||||||
|
|||||||
@@ -23,15 +23,14 @@ import {
|
|||||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||||
|
|
||||||
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
||||||
const createItemId = (type) => `${type}-${Date.now()}-${randomSuffix()}`;
|
|
||||||
const createSortableItem = (type, index = 0) => ({
|
const createSortableItem = (type, index = 0) => ({
|
||||||
id: `${type}-${index}-${randomSuffix()}`,
|
id: `${type}-${index}-${randomSuffix()}`,
|
||||||
type,
|
type,
|
||||||
value: type,
|
value: type,
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildInitialItems = (structure = []) =>
|
const buildSortableItems = (values = []) =>
|
||||||
sanitizeStructureList(structure).map((type, index) =>
|
sanitizeStructureList(values).map((type, index) =>
|
||||||
createSortableItem(type, index)
|
createSortableItem(type, index)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -42,24 +41,17 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
[baseStructure]
|
[baseStructure]
|
||||||
);
|
);
|
||||||
const [sortableItems, setSortableItems] = useState(() =>
|
const [sortableItems, setSortableItems] = useState(() =>
|
||||||
buildInitialItems(sanitizedBase)
|
buildSortableItems(sanitizedBase)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSortableItems((prev) => {
|
setSortableItems(buildSortableItems(sanitizedBase));
|
||||||
const prevTypes = prev.map((item) => item.type);
|
|
||||||
if (
|
|
||||||
prevTypes.length === sanitizedBase.length &&
|
|
||||||
prevTypes.every((type, index) => type === sanitizedBase[index])
|
|
||||||
) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return buildInitialItems(sanitizedBase);
|
|
||||||
});
|
|
||||||
}, [sanitizedBase]);
|
}, [sanitizedBase]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const values = sortableItems.map((item) => item.type).filter(Boolean);
|
const values = sortableItems
|
||||||
|
.map((item) => item?.type || item?.value)
|
||||||
|
.filter(Boolean);
|
||||||
onChange?.(values);
|
onChange?.(values);
|
||||||
}, [sortableItems, onChange]);
|
}, [sortableItems, onChange]);
|
||||||
|
|
||||||
@@ -99,20 +91,21 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
|
|
||||||
const toggleOptionalSegment = (type) => {
|
const toggleOptionalSegment = (type) => {
|
||||||
setSortableItems((prev) => {
|
setSortableItems((prev) => {
|
||||||
const existsIndex = prev.findIndex((item) => item.type === type);
|
const currentValues = prev.map((item) => item.type || item.value);
|
||||||
if (existsIndex !== -1) {
|
if (currentValues.includes(type)) {
|
||||||
return prev.filter((item) => item.type !== type);
|
const filtered = currentValues.filter((value) => value !== type);
|
||||||
|
return buildSortableItems(filtered);
|
||||||
}
|
}
|
||||||
const meta = getSegmentMeta(type);
|
const meta = getSegmentMeta(type);
|
||||||
let next = [...prev];
|
let nextValues = [...currentValues];
|
||||||
if (meta?.exclusiveGroup) {
|
if (meta?.exclusiveGroup) {
|
||||||
next = next.filter((item) => {
|
nextValues = nextValues.filter((value) => {
|
||||||
const itemMeta = getSegmentMeta(item.type || item.value);
|
const itemMeta = getSegmentMeta(value);
|
||||||
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
next.push(createSortableItem(type, next.length));
|
nextValues.push(type);
|
||||||
return next;
|
return buildSortableItems(nextValues);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -182,15 +175,11 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
customHandle
|
customHandle
|
||||||
scrollableRef={scrollRef}
|
scrollableRef={scrollRef}
|
||||||
onDragEnd={({ data: newData }) => {
|
onDragEnd={({ data: newData }) => {
|
||||||
const next = (newData || []).map((it, index) => {
|
const values = (newData || [])
|
||||||
const nextType = it?.type || it?.value;
|
.map((it) => it?.type || it?.value)
|
||||||
return {
|
.filter(Boolean);
|
||||||
id: it?.id || createItemId(nextType || `section-${index}`),
|
const sanitized = sanitizeStructureList(values);
|
||||||
type: nextType,
|
setSortableItems(buildSortableItems(sanitized));
|
||||||
value: nextType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setSortableItems(next.filter((item) => item.type));
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ const getScrollY = () => (typeof window !== "undefined" ? window.scrollY : 0);
|
|||||||
|
|
||||||
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
||||||
|
|
||||||
const buildItems = (structure = []) => {
|
const buildSortableItems = (values = []) => {
|
||||||
const counters = {};
|
const counters = {};
|
||||||
return structure.map((segment, index) => {
|
return sanitizeStructureList(values).map((segment, index) => {
|
||||||
counters[segment] = (counters[segment] || 0) + 1;
|
counters[segment] = (counters[segment] || 0) + 1;
|
||||||
return {
|
return {
|
||||||
id: `${segment}-${index}-${randomSuffix()}`,
|
id: `${segment}-${index}-${randomSuffix()}`,
|
||||||
@@ -44,7 +44,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
() => sanitizeStructureList(baseStructure),
|
() => sanitizeStructureList(baseStructure),
|
||||||
[baseStructure]
|
[baseStructure]
|
||||||
);
|
);
|
||||||
const [items, setItems] = React.useState(() => buildItems(sanitized));
|
const [items, setItems] = React.useState(() => buildSortableItems(sanitized));
|
||||||
const [activeItemId, setActiveItemId] = React.useState(null);
|
const [activeItemId, setActiveItemId] = React.useState(null);
|
||||||
const containerRef = React.useRef(null);
|
const containerRef = React.useRef(null);
|
||||||
const dragStateRef = React.useRef({
|
const dragStateRef = React.useRef({
|
||||||
@@ -54,38 +54,40 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
console.debug("[CustomizeSongStructure.web] sanitized", sanitized);
|
console.debug("[CustomizeSongStructure.web] sanitized", sanitized);
|
||||||
setItems((prev) => {
|
setItems(buildSortableItems(sanitized));
|
||||||
const prevTypes = prev.map((item) => item.type);
|
|
||||||
if (prevTypes.length === sanitized.length) {
|
|
||||||
const same = prevTypes.every((type, idx) => type === sanitized[idx]);
|
|
||||||
if (same) return prev;
|
|
||||||
}
|
|
||||||
return buildItems(sanitized);
|
|
||||||
});
|
|
||||||
dragStateRef.current = { itemId: null, containerTop: 0 };
|
dragStateRef.current = { itemId: null, containerTop: 0 };
|
||||||
setActiveItemId(null);
|
setActiveItemId(null);
|
||||||
}, [sanitized]);
|
}, [sanitized]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
console.debug("[CustomizeSongStructure.web] items", items);
|
console.debug("[CustomizeSongStructure.web] items", items);
|
||||||
onChange?.(items.map((item) => item.type));
|
onChange?.(
|
||||||
|
items.map((item) => (item?.type || item?.value)).filter(Boolean)
|
||||||
|
);
|
||||||
}, [items, onChange]);
|
}, [items, onChange]);
|
||||||
|
|
||||||
const labeledItems = React.useMemo(() => {
|
const labeledItems = React.useMemo(() => {
|
||||||
const counts = {};
|
const counts = {};
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
counts[item.type] = (counts[item.type] || 0) + 1;
|
const type = item?.type || item?.value;
|
||||||
|
counts[type] = (counts[type] || 0) + 1;
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
label: formatStructureLabel(item.type, counts[item.type]),
|
type,
|
||||||
|
value: type,
|
||||||
|
label: formatStructureLabel(type, counts[type]),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
const selectedTypes = React.useMemo(
|
const selectedTypes = React.useMemo(() => {
|
||||||
() => new Set(items.map((item) => item.type)),
|
const set = new Set();
|
||||||
[items]
|
items.forEach((item) => {
|
||||||
);
|
if (item?.type) set.add(item.type);
|
||||||
|
else if (item?.value) set.add(item.value);
|
||||||
|
});
|
||||||
|
return set;
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
const optionalSegments = React.useMemo(
|
const optionalSegments = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -98,24 +100,21 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
|
|
||||||
const toggleOptionalSegment = React.useCallback((type) => {
|
const toggleOptionalSegment = React.useCallback((type) => {
|
||||||
setItems((prev) => {
|
setItems((prev) => {
|
||||||
const exists = prev.some((item) => item.type === type);
|
const currentValues = prev.map((item) => item.type || item.value);
|
||||||
if (exists) {
|
if (currentValues.includes(type)) {
|
||||||
return prev.filter((item) => item.type !== type);
|
const filtered = currentValues.filter((value) => value !== type);
|
||||||
|
return buildSortableItems(filtered);
|
||||||
}
|
}
|
||||||
const meta = getSegmentMeta(type);
|
const meta = getSegmentMeta(type);
|
||||||
let next = [...prev];
|
let nextValues = [...currentValues];
|
||||||
if (meta?.exclusiveGroup) {
|
if (meta?.exclusiveGroup) {
|
||||||
next = next.filter((item) => {
|
nextValues = nextValues.filter((value) => {
|
||||||
const itemMeta = getSegmentMeta(item.type);
|
const itemMeta = getSegmentMeta(value);
|
||||||
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
next.push({
|
nextValues.push(type);
|
||||||
id: `${type}-${Date.now()}-${randomSuffix()}`,
|
return buildSortableItems(nextValues);
|
||||||
type,
|
|
||||||
value: type,
|
|
||||||
});
|
|
||||||
return next;
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -169,6 +168,9 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRelease = () => {
|
const handleRelease = () => {
|
||||||
|
setItems((prev) =>
|
||||||
|
buildSortableItems(prev.map((item) => item.type || item.value))
|
||||||
|
);
|
||||||
dragStateRef.current = {
|
dragStateRef.current = {
|
||||||
itemId: null,
|
itemId: null,
|
||||||
containerTop: 0,
|
containerTop: 0,
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ import { Palette, gutters } from "../../styles";
|
|||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import {
|
import {
|
||||||
formatStructureLabel,
|
formatStructureLabel,
|
||||||
|
getPromptLabelForStructure,
|
||||||
getSegmentMeta,
|
getSegmentMeta,
|
||||||
normalizeStructureType,
|
normalizeStructureType,
|
||||||
sanitizeStructureList,
|
sanitizeStructureList,
|
||||||
|
segmentRequiresLyrics,
|
||||||
} from "../../utils/songStructure";
|
} from "../../utils/songStructure";
|
||||||
import CustomInput from "./components/CustomInput";
|
import CustomInput from "./components/CustomInput";
|
||||||
|
|
||||||
@@ -52,21 +54,42 @@ const Lyrics = ({ navigation }) => {
|
|||||||
? sanitizeStructureList(projectConfig.structure)
|
? sanitizeStructureList(projectConfig.structure)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (normalizedSections.length) {
|
const structureToUse =
|
||||||
return {
|
targetStructure && targetStructure.length
|
||||||
title,
|
? targetStructure
|
||||||
sections: normalizedSections,
|
: normalizedSections.map((s) => s.type);
|
||||||
};
|
|
||||||
|
if (!structureToUse.length && normalizedSections.length) {
|
||||||
|
return { title, sections: normalizedSections };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetStructure && targetStructure.length) {
|
const remaining = [...normalizedSections];
|
||||||
return {
|
const takeMatching = (type) => {
|
||||||
title,
|
const index = remaining.findIndex((s) => s.type === type);
|
||||||
sections: targetStructure.map((t) => ({ type: t, lyrics: "" })),
|
if (index !== -1) {
|
||||||
};
|
return remaining.splice(index, 1)[0];
|
||||||
}
|
}
|
||||||
|
return remaining.shift() || null;
|
||||||
|
};
|
||||||
|
|
||||||
return { title, sections: [] };
|
const sections = structureToUse.map((segmentType) => {
|
||||||
|
const normalizedType = normalizeStructureType(segmentType);
|
||||||
|
if (!segmentRequiresLyrics(normalizedType)) {
|
||||||
|
return { type: normalizedType, lyrics: "" };
|
||||||
|
}
|
||||||
|
const matched = takeMatching(normalizedType);
|
||||||
|
return {
|
||||||
|
type: normalizedType,
|
||||||
|
lyrics: matched?.lyrics || "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const remainingTextual = remaining.filter((item) =>
|
||||||
|
segmentRequiresLyrics(item.type)
|
||||||
|
);
|
||||||
|
sections.push(...remainingTextual);
|
||||||
|
|
||||||
|
return { title, sections };
|
||||||
}, [projectTitle, projectLyrics, projectConfig]);
|
}, [projectTitle, projectLyrics, projectConfig]);
|
||||||
|
|
||||||
const [titleValue, setTitleValue] = useState(initial.title || "");
|
const [titleValue, setTitleValue] = useState(initial.title || "");
|
||||||
@@ -131,7 +154,11 @@ const Lyrics = ({ navigation }) => {
|
|||||||
alertMessage("Titre manquant", "Veuillez renseigner un titre.");
|
alertMessage("Titre manquant", "Veuillez renseigner un titre.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const invalid = (sections || []).some((s) => !(s?.lyrics || "").trim());
|
const invalid = (sections || []).some((s) => {
|
||||||
|
const type = normalizeStructureType(s?.type);
|
||||||
|
if (!segmentRequiresLyrics(type)) return false;
|
||||||
|
return !(s?.lyrics || "").trim();
|
||||||
|
});
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
alertMessage(
|
alertMessage(
|
||||||
"Champs incomplets",
|
"Champs incomplets",
|
||||||
@@ -141,7 +168,9 @@ const Lyrics = ({ navigation }) => {
|
|||||||
}
|
}
|
||||||
const normalizedNewLyrics = (sections || []).map((s) => ({
|
const normalizedNewLyrics = (sections || []).map((s) => ({
|
||||||
type: normalizeStructureType(s?.type),
|
type: normalizeStructureType(s?.type),
|
||||||
lyrics: (s?.lyrics || "").trim(),
|
lyrics: segmentRequiresLyrics(normalizeStructureType(s?.type))
|
||||||
|
? (s?.lyrics || "").trim()
|
||||||
|
: "",
|
||||||
}));
|
}));
|
||||||
const normalizedOldLyrics = Array.isArray(projectLyrics)
|
const normalizedOldLyrics = Array.isArray(projectLyrics)
|
||||||
? projectLyrics.map((s) => ({
|
? projectLyrics.map((s) => ({
|
||||||
@@ -349,6 +378,18 @@ const Lyrics = ({ navigation }) => {
|
|||||||
? 170
|
? 170
|
||||||
: 225;
|
: 225;
|
||||||
const placeholder = meta?.label || label;
|
const placeholder = meta?.label || label;
|
||||||
|
const requiresLyrics = segmentRequiresLyrics(key);
|
||||||
|
if (!requiresLyrics) {
|
||||||
|
return (
|
||||||
|
<View key={idx} style={styles.instrumentalBlock}>
|
||||||
|
<Text style={styles.instrumentalTitle}>{label}</Text>
|
||||||
|
<Text style={styles.instrumentalText}>
|
||||||
|
{getPromptLabelForStructure(key)} — section instrumentale
|
||||||
|
sans paroles.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<CustomInput
|
<CustomInput
|
||||||
key={idx}
|
key={idx}
|
||||||
@@ -417,4 +458,21 @@ const styles = StyleSheet.create({
|
|||||||
instructionsHighlight: {
|
instructionsHighlight: {
|
||||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||||
},
|
},
|
||||||
|
instrumentalBlock: {
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: Palette.ultraLightWhite,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
instrumentalTitle: {
|
||||||
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: Palette.white,
|
||||||
|
},
|
||||||
|
instrumentalText: {
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
fontSize: 14,
|
||||||
|
color: Palette.white,
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
optional: false,
|
optional: false,
|
||||||
inputHeight: 225,
|
inputHeight: 225,
|
||||||
|
requiresLyrics: true,
|
||||||
aliases: ["vers", "verse", "couplet"],
|
aliases: ["vers", "verse", "couplet"],
|
||||||
},
|
},
|
||||||
refrain: {
|
refrain: {
|
||||||
@@ -29,6 +30,7 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
optional: false,
|
optional: false,
|
||||||
inputHeight: 170,
|
inputHeight: 170,
|
||||||
|
requiresLyrics: true,
|
||||||
aliases: ["chorus", "refrain"],
|
aliases: ["chorus", "refrain"],
|
||||||
},
|
},
|
||||||
short_intro: {
|
short_intro: {
|
||||||
@@ -39,6 +41,7 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
inputHeight: 150,
|
inputHeight: 150,
|
||||||
exclusiveGroup: "intro",
|
exclusiveGroup: "intro",
|
||||||
|
requiresLyrics: false,
|
||||||
aliases: [
|
aliases: [
|
||||||
"introduction instrumentale courte",
|
"introduction instrumentale courte",
|
||||||
"intro courte",
|
"intro courte",
|
||||||
@@ -54,6 +57,7 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
inputHeight: 150,
|
inputHeight: 150,
|
||||||
exclusiveGroup: "intro",
|
exclusiveGroup: "intro",
|
||||||
|
requiresLyrics: false,
|
||||||
aliases: [
|
aliases: [
|
||||||
"introduction instrumentale longue",
|
"introduction instrumentale longue",
|
||||||
"intro longue",
|
"intro longue",
|
||||||
@@ -68,6 +72,7 @@ export const STRUCTURE_SEGMENTS = {
|
|||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
inputHeight: 150,
|
inputHeight: 150,
|
||||||
|
requiresLyrics: true,
|
||||||
aliases: [
|
aliases: [
|
||||||
"pré-refrain",
|
"pré-refrain",
|
||||||
"pre-refrain",
|
"pre-refrain",
|
||||||
@@ -101,6 +106,12 @@ export const getSegmentMeta = (type) => {
|
|||||||
return STRUCTURE_SEGMENTS[key] || null;
|
return STRUCTURE_SEGMENTS[key] || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const segmentRequiresLyrics = (type) => {
|
||||||
|
const meta = getSegmentMeta(type);
|
||||||
|
if (!meta) return true;
|
||||||
|
return meta.requiresLyrics !== false;
|
||||||
|
};
|
||||||
|
|
||||||
export const OPTIONAL_STRUCTURE_SEGMENTS = Object.keys(
|
export const OPTIONAL_STRUCTURE_SEGMENTS = Object.keys(
|
||||||
STRUCTURE_SEGMENTS
|
STRUCTURE_SEGMENTS
|
||||||
).filter((key) => STRUCTURE_SEGMENTS[key]?.optional);
|
).filter((key) => STRUCTURE_SEGMENTS[key]?.optional);
|
||||||
@@ -121,6 +132,7 @@ export const normalizeStructureType = (value) => {
|
|||||||
export const sanitizeStructureList = (structure = []) => {
|
export const sanitizeStructureList = (structure = []) => {
|
||||||
if (!Array.isArray(structure)) return [];
|
if (!Array.isArray(structure)) return [];
|
||||||
const output = [];
|
const output = [];
|
||||||
|
let shouldInjectPreChorus = false;
|
||||||
|
|
||||||
structure.forEach((segment) => {
|
structure.forEach((segment) => {
|
||||||
const type = normalizeStructureType(segment);
|
const type = normalizeStructureType(segment);
|
||||||
@@ -144,10 +156,47 @@ export const sanitizeStructureList = (structure = []) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "pre_chorus") {
|
||||||
|
shouldInjectPreChorus = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
output.push(type);
|
output.push(type);
|
||||||
});
|
});
|
||||||
|
|
||||||
return output;
|
let result = output;
|
||||||
|
|
||||||
|
if (shouldInjectPreChorus) {
|
||||||
|
const baseWithoutPre = result.filter((item) => item !== "pre_chorus");
|
||||||
|
const hasRefrain = baseWithoutPre.some((item) => item === "refrain");
|
||||||
|
const expanded = [];
|
||||||
|
|
||||||
|
baseWithoutPre.forEach((item, idx) => {
|
||||||
|
if (item === "refrain") {
|
||||||
|
const previous = expanded[expanded.length - 1];
|
||||||
|
if (previous !== "pre_chorus") {
|
||||||
|
expanded.push("pre_chorus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expanded.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasRefrain) {
|
||||||
|
expanded.push("pre_chorus");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
const introIndex = result.findIndex(
|
||||||
|
(item) => item === "short_intro" || item === "long_intro"
|
||||||
|
);
|
||||||
|
if (introIndex > 0) {
|
||||||
|
const [intro] = result.splice(introIndex, 1);
|
||||||
|
result.unshift(intro);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatStructureLabel = (type, occurrence = 1) => {
|
export const formatStructureLabel = (type, occurrence = 1) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user