Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/ai-music-system / lib/music/generator.js |
| Date | Not specified in source |
| Repository | Janady13/ai-music-system |
| Revision | 9a816470ef81edba7a5d8f3aae62c05eead638a7 |
| Branch | main |
| Path or archive member | lib/music/generator.js |
| Original SHA-256 | 8dfeb341408f7a169457fbd0cffe5492984fb04a0151f808dea95eda16a35085 |
| Git blob SHA | ac8353066323ee9804e8d75b2f8ac28bbf407b00 |
| Reading-copy SHA-256 | 8dfeb341408f7a169457fbd0cffe5492984fb04a0151f808dea95eda16a35085 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/ai-music-system / lib/music/generator.js | main | 9a816470ef81edba7a5d8f3aae62c05eead638a7 | Open original |
| Janady13/ai-music-system / lib/music/generator.js | codex/create-ai-for-music-generation | 017052c0e72c6175378aebb7a7f9dd96e82c3ce2 | Open original |
Recovered source transcript
Download complete source text ↓319 lines · 12.4 KB
const { loadMeta, loadModel, loadCorpus } = require("../training");
const { touch } = require("../state");
function weightedChoice(map, fallback) {
const entries = Object.entries(map || {}).filter(([, weight]) => Number(weight) > 0);
if (!entries.length) return fallback;
const total = entries.reduce((sum, [, weight]) => sum + Number(weight), 0);
let threshold = Math.random() * total;
for (const [value, weight] of entries) {
threshold -= Number(weight);
if (threshold <= 0) return value;
}
return entries[entries.length - 1][0];
}
function pickTop(map, count, fallback = []) {
if (!map || !Object.keys(map).length) return [...fallback];
return Object.entries(map)
.sort((a, b) => Number(b[1]) - Number(a[1]))
.slice(0, count)
.map(([key]) => key);
}
function parseTimeSignature(signature) {
if (typeof signature !== "string") return { beats: 4, note: 4 };
const [beats, note] = signature.split("/").map(Number);
return {
beats: Number.isFinite(beats) && beats > 0 ? beats : 4,
note: Number.isFinite(note) && note > 0 ? note : 4
};
}
function rotateProgression(progression, offset) {
if (!Array.isArray(progression) || !progression.length) return [];
const len = progression.length;
const shift = ((offset % len) + len) % len;
return progression.map((_, idx) => progression[(idx + shift) % len]);
}
function deriveTempo(profile) {
const mean = profile?.tempo?.mean;
const min = profile?.tempo?.min;
const max = profile?.tempo?.max;
if (Number.isFinite(mean) && Number.isFinite(min) && Number.isFinite(max)) {
const variance = Math.max(1, (max - min) / 6);
const jitter = (Math.random() - 0.5) * variance;
return Math.round(Math.max(40, mean + jitter));
}
if (Number.isFinite(mean)) return Math.round(mean);
if (Number.isFinite(min) && Number.isFinite(max)) return Math.round((min + max) / 2);
return 120;
}
function buildArrangement(profile, duration, timeSignature, tempo, progression) {
const beatsPerBar = timeSignature.beats;
const blueprint = weightedChoice(profile.arrangementBlueprints, null);
const sectionNames = blueprint ? blueprint.split(" > ") : ["intro", "theme", "development", "outro"];
const weights = sectionNames.map(name => profile.sections?.[name]?.avgLength || 8);
const totalWeight = weights.reduce((sum, value) => sum + value, 0) || sectionNames.length;
const baseProgression = progression;
let cursor = 0;
return sectionNames.map((name, index) => {
const weight = weights[index] || (totalWeight / sectionNames.length);
const lengthSeconds = duration * (weight / totalWeight);
const beats = tempo * (lengthSeconds / 60);
const bars = Math.max(2, Math.round(beats / beatsPerBar));
const rotated = rotateProgression(baseProgression, index * 2);
const section = {
name,
start: Number(cursor.toFixed(2)),
lengthSeconds: Number(lengthSeconds.toFixed(2)),
bars,
progression: rotated,
focus: {
instruments: pickTop(profile.instruments, 3),
textures: pickTop(profile.textures, 2),
motif: weightedChoice(profile.melodicMotifs, null)
}
};
cursor += lengthSeconds;
return section;
});
}
function buildAutomation(arrangement, textures) {
const automation = [];
for (const section of arrangement) {
const texture = section.focus.textures[0] || textures[0] || "dynamic layer";
automation.push({
at: section.start,
action: `Emphasise ${texture} in the ${section.name} section`,
duration: section.lengthSeconds,
intensity: section.name.includes("build") || section.name.includes("climax") ? "high" : "medium"
});
}
return automation;
}
function selectProfile(model, genre, mood) {
const directKey = `${genre}::${mood}`;
if (model.profiles[directKey]) return { key: directKey, profile: model.profiles[directKey] };
const fallbackEntries = Object.entries(model.profiles)
.filter(([key]) => key.startsWith(`${genre}::`))
.sort((a, b) => (b[1].count || 0) - (a[1].count || 0));
if (fallbackEntries.length) return { key: fallbackEntries[0][0], profile: fallbackEntries[0][1] };
const generic = Object.entries(model.profiles)
.sort((a, b) => (b[1].count || 0) - (a[1].count || 0))[0];
if (generic) return { key: generic[0], profile: generic[1] };
throw new Error("No trained profiles available yet. Please run the trainer.");
}
function deriveConfidence(profile, model) {
const coverage = Object.keys(model.profiles || {}).length || 1;
const density = (profile.count || 0) / (model.datasetSize || 1);
const variety = (
Object.keys(profile.chordProgressions || {}).length +
Object.keys(profile.textures || {}).length +
Object.keys(profile.melodicMotifs || {}).length
) / 12;
const confidence = Math.max(0.25, Math.min(0.98, 0.35 + density * 0.4 + variety * 0.3 + Math.log1p(coverage) * 0.05));
return Number(confidence.toFixed(3));
}
function ensureNumber(value, fallback) {
return Number.isFinite(value) ? value : fallback;
}
function bootstrapProfilesFromCorpus(corpus) {
const profiles = {};
const ensureProfile = (genre, mood) => {
const key = `${genre}::${mood}`;
if (!profiles[key]) {
profiles[key] = {
genre,
mood,
count: 0,
tempo: { min: Infinity, max: 0, mean: 0 },
timeSignatures: {},
keys: {},
chordProgressions: {},
bassStyles: {},
drumPatterns: {},
melodicMotifs: {},
textures: {},
instruments: {},
sections: {},
arrangementBlueprints: {}
};
}
return profiles[key];
};
const increment = (map, value) => {
if (value === undefined || value === null) return;
const key = Array.isArray(value) ? value.join(" | ") : String(value);
if (!key.trim()) return;
map[key] = (map[key] || 0) + 1;
};
for (const sample of corpus) {
if (!sample) continue;
const genre = (sample.genre || "unknown").toLowerCase();
const moods = Array.isArray(sample.moods) && sample.moods.length ? sample.moods : ["default"];
for (const moodRaw of moods) {
const mood = String(moodRaw || "default").toLowerCase();
const profile = ensureProfile(genre, mood);
profile.count += 1;
const tempoInfo = sample.tempo || {};
const min = ensureNumber(tempoInfo.min, ensureNumber(tempoInfo.mean, ensureNumber(tempoInfo, 120)));
const max = ensureNumber(tempoInfo.max, ensureNumber(tempoInfo.mean, min));
const mean = ensureNumber(tempoInfo.mean, (min + max) / 2);
profile.tempo.min = Number.isFinite(profile.tempo.min) ? Math.min(profile.tempo.min, min) : min;
profile.tempo.max = Number.isFinite(profile.tempo.max) ? Math.max(profile.tempo.max, max) : max;
profile.tempo.mean += (mean - profile.tempo.mean) / profile.count;
for (const sig of sample.timeSignatures || []) increment(profile.timeSignatures, sig);
for (const keySig of sample.keys || []) increment(profile.keys, keySig);
for (const progression of sample.chordProgressions || []) increment(profile.chordProgressions, progression);
for (const bass of sample.bassStyles || []) increment(profile.bassStyles, bass);
for (const drum of sample.drumPatterns || []) increment(profile.drumPatterns, drum);
for (const motif of sample.melodicMotifs || []) increment(profile.melodicMotifs, motif);
for (const texture of sample.textures || []) increment(profile.textures, texture);
for (const instrument of sample.instruments || []) increment(profile.instruments, instrument);
if (Array.isArray(sample.arrangement)) {
const names = [];
for (const part of sample.arrangement) {
if (!part) continue;
const name = String(part.section || part.name || part.part || "section").toLowerCase();
const length = Number(part.length || part.bars || part.measures || 8);
names.push(name);
const section = profile.sections[name] || { count: 0, avgLength: 0 };
section.count += 1;
section.avgLength += (length - section.avgLength) / section.count;
profile.sections[name] = section;
}
if (names.length) increment(profile.arrangementBlueprints, names.join(" > "));
}
}
}
return profiles;
}
function ensureModelProfiles(model, corpus) {
if (Object.keys(model.profiles || {}).length) return model;
if (!Array.isArray(corpus) || !corpus.length) {
throw new Error("No model profiles available and dataset is empty. Provide training data first.");
}
const bootstrapped = bootstrapProfilesFromCorpus(corpus);
model = { ...model, profiles: bootstrapped, datasetSize: corpus.length, updatedAt: new Date().toISOString() };
return model;
}
function generateMusicPlan(params = {}) {
const genre = (params.genre || "electronic").toLowerCase();
const mood = (params.mood || "uplifting").toLowerCase();
const duration = Math.max(10, Number(params.duration) || 30);
const meta = loadMeta();
let model = loadModel();
const corpus = loadCorpus();
model = ensureModelProfiles(model, corpus);
const { key, profile } = selectProfile(model, genre, mood);
const tempo = deriveTempo(profile);
const signatureText = weightedChoice(profile.timeSignatures, "4/4");
const timeSignature = parseTimeSignature(signatureText);
const keySignature = weightedChoice(profile.keys, "C Major");
const progressionText = weightedChoice(profile.chordProgressions, "I | V | vi | IV");
const progression = progressionText.split("|").map(token => token.trim()).filter(Boolean);
const arrangement = buildArrangement(profile, duration, timeSignature, tempo, progression);
const instrumentation = pickTop(profile.instruments, 6, ["synth pad", "percussion"]);
const rhythm = pickTop(profile.drumPatterns, 4, ["steady beat"]);
const bass = pickTop(profile.bassStyles, 3, ["root notes"]);
const motifs = pickTop(profile.melodicMotifs, 4);
const textures = pickTop(profile.textures, 4);
const automation = buildAutomation(arrangement, textures);
const confidence = deriveConfidence(profile, model);
const datasetInfo = {
datasetSize: model.datasetSize || corpus.length,
corpusEntries: corpus.length,
profileKey: key,
profileObservations: profile.count || 0
};
touch("last_music_generation");
return {
ok: true,
metadata: {
genre,
mood,
duration,
tempo,
key: keySignature,
timeSignature: signatureText,
engineVersion: "narrow-ai/v1",
generatedAt: new Date().toISOString(),
datasetDigest: model.datasetDigest || meta.datasetDigest || null,
trainingStep: meta.step
},
composition: {
progression,
arrangement,
instrumentation: {
core: instrumentation,
rhythm,
bass,
motifs,
textures
},
performance: {
groove: rhythm[0] || "steady beat",
bassLeadInteraction: bass[0] ? `${bass[0]} supporting ${progression[0] || "root"}` : "root support",
melodicFocus: motifs[0] || progression[0] || "motif driven",
dynamics: arrangement.map(section => ({
section: section.name,
target: section.focus.textures[0] || section.focus.instruments[0] || "ensemble",
contour: section.name.includes("build") || section.name.includes("climax") ? "crescendo" : "balanced"
}))
},
automation,
recommendations: {
transitions: arrangement.slice(1).map(section => ({
at: section.start,
idea: `Use a ${textures[0] || "filter"} sweep into the ${section.name} section.`
})),
mixing: [
`Set master tempo to ${tempo} BPM with ${signatureText} feel.`,
bass[0] ? `Anchor the low end with ${bass[0]}.` : "Ensure low frequencies remain tight.",
textures[0] ? `Highlight ${textures[0]} using slow attack compression.` : "Add depth with subtle reverb layers.",
rhythm[0] ? `Keep ${rhythm[0]} consistent for cohesion.` : "Maintain a steady pulse throughout."
]
}
},
analysis: {
confidence,
dataset: datasetInfo,
reasoning: [
`Selected profile ${key} with ${profile.count || 0} observations from the corpus.`,
`Tempo of ${tempo} BPM derived from mean ${profile.tempo?.mean ?? tempo} BPM in the dataset.`,
`Chord progression emphasises ${progression[0] || "primary"} movement favoured in the dataset.`,
textures[0] ? `Textural focus leans on ${textures[0]} which appears frequently in this mood.` : "Texture palette kept intentionally broad."
]
}
};
}
module.exports = {
generateMusicPlan
};