new song structure
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
const START_CASE_REGEX = /[_\-\s]+/g;
|
||||
|
||||
const toStartCase = (value) => {
|
||||
const str = String(value || "")
|
||||
.replace(START_CASE_REGEX, " ")
|
||||
.trim();
|
||||
if (!str) return "";
|
||||
return str
|
||||
.split(" ")
|
||||
.filter(Boolean)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
export const STRUCTURE_SEGMENTS = {
|
||||
couplet: {
|
||||
label: "Couplet",
|
||||
promptLabel: "Couplet",
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: false,
|
||||
inputHeight: 225,
|
||||
aliases: ["vers", "verse", "couplet"],
|
||||
},
|
||||
refrain: {
|
||||
label: "Refrain",
|
||||
promptLabel: "Refrain",
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: false,
|
||||
inputHeight: 170,
|
||||
aliases: ["chorus", "refrain"],
|
||||
},
|
||||
short_intro: {
|
||||
label: "Introduction instrumentale courte",
|
||||
promptLabel: "Introduction instrumentale courte",
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "intro",
|
||||
aliases: [
|
||||
"introduction instrumentale courte",
|
||||
"intro courte",
|
||||
"intro instrumentale courte",
|
||||
"short intro",
|
||||
],
|
||||
},
|
||||
long_intro: {
|
||||
label: "Introduction instrumentale longue",
|
||||
promptLabel: "Introduction instrumentale longue",
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "intro",
|
||||
aliases: [
|
||||
"introduction instrumentale longue",
|
||||
"intro longue",
|
||||
"long intro",
|
||||
"intro instrumentale longue",
|
||||
],
|
||||
},
|
||||
pre_chorus: {
|
||||
label: "Pré-refrain",
|
||||
promptLabel: "Pré-refrain",
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
aliases: [
|
||||
"pré-refrain",
|
||||
"pre-refrain",
|
||||
"prechorus",
|
||||
"pre chorus",
|
||||
"pré chorus",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const ALIAS_TO_KEY = Object.entries(STRUCTURE_SEGMENTS).reduce(
|
||||
(acc, [key, meta]) => {
|
||||
const aliases = Array.isArray(meta.aliases) ? meta.aliases : [];
|
||||
aliases.forEach((alias) => {
|
||||
acc[String(alias).toLowerCase()] = key;
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const sanitizeToken = (value) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "");
|
||||
|
||||
export const getSegmentMeta = (type) => {
|
||||
const key = String(type || "").toLowerCase();
|
||||
return STRUCTURE_SEGMENTS[key] || null;
|
||||
};
|
||||
|
||||
export const OPTIONAL_STRUCTURE_SEGMENTS = Object.keys(
|
||||
STRUCTURE_SEGMENTS
|
||||
).filter((key) => STRUCTURE_SEGMENTS[key]?.optional);
|
||||
|
||||
export const normalizeStructureType = (value) => {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const lower = raw.toLowerCase();
|
||||
if (STRUCTURE_SEGMENTS[lower]) return lower;
|
||||
if (ALIAS_TO_KEY[lower]) return ALIAS_TO_KEY[lower];
|
||||
|
||||
const sanitized = sanitizeToken(raw);
|
||||
if (STRUCTURE_SEGMENTS[sanitized]) return sanitized;
|
||||
if (ALIAS_TO_KEY[sanitized]) return ALIAS_TO_KEY[sanitized];
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
export const sanitizeStructureList = (structure = []) => {
|
||||
if (!Array.isArray(structure)) return [];
|
||||
const output = [];
|
||||
|
||||
structure.forEach((segment) => {
|
||||
const type = normalizeStructureType(segment);
|
||||
if (!type) return;
|
||||
const meta = getSegmentMeta(type);
|
||||
|
||||
if (meta) {
|
||||
if (meta.allowMultiple === false) {
|
||||
const alreadyIncluded = output.some((item) => item === type);
|
||||
if (alreadyIncluded) return;
|
||||
}
|
||||
|
||||
if (meta.exclusiveGroup) {
|
||||
const previousIndex = output.findIndex((item) => {
|
||||
const existingMeta = getSegmentMeta(item);
|
||||
return existingMeta?.exclusiveGroup === meta.exclusiveGroup;
|
||||
});
|
||||
if (previousIndex !== -1) {
|
||||
output.splice(previousIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.push(type);
|
||||
});
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
export const formatStructureLabel = (type, occurrence = 1) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
const baseLabel = meta?.label || toStartCase(normalized || type);
|
||||
const showIndex = meta?.showIndex === true;
|
||||
if (showIndex) return `${baseLabel} ${occurrence}`;
|
||||
if (!meta && occurrence > 1) return `${baseLabel} ${occurrence}`;
|
||||
return baseLabel;
|
||||
};
|
||||
|
||||
export const getStructureInputHeight = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
return typeof meta?.inputHeight === "number" ? meta.inputHeight : undefined;
|
||||
};
|
||||
|
||||
export const getPromptLabelForStructure = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
if (meta?.promptLabel) return meta.promptLabel;
|
||||
if (meta?.label) return meta.label;
|
||||
return toStartCase(normalized || type);
|
||||
};
|
||||
|
||||
export const isOptionalStructureType = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
return !!meta?.optional;
|
||||
};
|
||||
|
||||
export const arraysAreSame = (a = [], b = []) => {
|
||||
if (a === b) return true;
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i += 1) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
Reference in New Issue
Block a user