Skip to content

Commit 21cfe90

Browse files
committed
feat: enhance song generation logic in SeedService
- Updated the song generation process to include dynamic tempo settings (60-120 BPM). - Introduced scale selection between blues and phrygian, with a randomized root note and octave. - Enhanced bass and melody layer creation with more varied note generation and rhythmic patterns. - Implemented passing notes in the bass line for added musical interest. - Improved overall structure and readability of the song generation code.
1 parent 109e811 commit 21cfe90

File tree

1 file changed

+134
-22
lines changed

1 file changed

+134
-22
lines changed

apps/backend/src/seed/seed.service.ts

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -184,29 +184,141 @@ export class SeedService {
184184
songTest.meta.name = faker.music.songName();
185185
songTest.meta.originalAuthor = faker.music.artist();
186186

187-
songTest.tempo = faker.helpers.rangeToNumber({ min: 20 * 1, max: 20 * 4 });
188-
const layerCount = faker.helpers.rangeToNumber({ min: 1, max: 5 });
189-
190-
for (let layerIndex = 0; layerIndex < layerCount; layerIndex++) {
191-
const instrument = Instrument.builtIn[layerCount];
192-
const layer = songTest.createLayer();
193-
layer.meta.name = instrument.meta.name;
194-
195-
const notes = Array.from({
196-
length: faker.helpers.rangeToNumber({ min: 20, max: 120 }),
197-
}).map(
198-
() =>
199-
new Note(instrument, {
200-
key: faker.helpers.rangeToNumber({ min: 0, max: 127 }),
201-
velocity: faker.helpers.rangeToNumber({ min: 0, max: 127 }),
202-
panning: faker.helpers.rangeToNumber({ min: -1, max: 1 }),
203-
pitch: faker.helpers.rangeToNumber({ min: -1, max: 1 }),
204-
}),
205-
);
187+
// Tempo: 60-120 BPM (20 * 3 to 20 * 6)
188+
songTest.tempo = faker.helpers.rangeToNumber({ min: 20 * 3, max: 20 * 6 });
189+
190+
// Choose scale type (blues or phrygian)
191+
const useBluesScale = faker.datatype.boolean();
192+
const scaleType = useBluesScale ? 'blues' : 'phrygian';
193+
194+
// Choose root note (C to B, octave 3-5)
195+
const rootOctave = faker.helpers.rangeToNumber({ min: 3, max: 5 });
196+
const rootNote = faker.helpers.rangeToNumber({ min: 0, max: 11 }); // 0=C, 1=C#, etc.
197+
const rootKey = rootNote + rootOctave * 12;
198+
199+
// Define scales (in semitones from root)
200+
const bluesScale = [0, 3, 5, 6, 7, 10]; // 1, b3, 4, b5, 5, b7
201+
const phrygianScale = [0, 1, 3, 5, 7, 8, 10]; // 1, b2, b3, 4, 5, b6, b7
202+
const scale = useBluesScale ? bluesScale : phrygianScale;
203+
204+
// Song length in ticks (4 ticks per beat, 8-16 bars)
205+
const bars = faker.helpers.rangeToNumber({ min: 8, max: 16 });
206+
const beatsPerBar = 4;
207+
const ticksPerBeat = 4;
208+
const totalTicks = bars * beatsPerBar * ticksPerBeat;
209+
210+
// Get instruments for bass and melody
211+
const bassInstrument = Instrument.builtIn[0]; // Lower instrument
212+
const melodyInstrument =
213+
Instrument.builtIn[faker.helpers.rangeToNumber({ min: 1, max: 4 })];
214+
215+
// Create bass layer
216+
const bassLayer = songTest.createLayer();
217+
bassLayer.meta.name = 'Bass';
218+
219+
// Create melody layer
220+
const melodyLayer = songTest.createLayer();
221+
melodyLayer.meta.name = 'Melody';
222+
223+
// Generate bass line (root notes and fifths, simpler rhythm)
224+
const bassNotes: Array<{ tick: number; key: number }> = [];
225+
const bassTickInterval = ticksPerBeat * 2; // Every 2 beats
226+
227+
for (let tick = 0; tick < totalTicks; tick += bassTickInterval) {
228+
// Sometimes skip a beat for variation
229+
if (faker.datatype.boolean({ probability: 0.2 })) continue;
230+
231+
// Choose between root, fifth, or octave
232+
const bassChoice = faker.helpers.arrayElement([0, 7, 12]); // root, fifth, octave
233+
const bassKey = rootKey + bassChoice;
234+
235+
// Keep in valid range (24-60 is a good bass range)
236+
if (bassKey >= 24 && bassKey <= 60) {
237+
bassNotes.push({ tick, key: bassKey });
238+
}
239+
}
240+
241+
// Add some passing notes for more interest
242+
for (let i = 0; i < bassNotes.length - 1; i++) {
243+
if (faker.datatype.boolean({ probability: 0.3 })) {
244+
const currentTick = bassNotes[i].tick;
245+
const nextTick = bassNotes[i + 1].tick;
246+
const midTick = currentTick + (nextTick - currentTick) / 2;
247+
const passingNote = rootKey + faker.helpers.arrayElement([2, 4, 5]); // Minor third, fourth, or tritone
248+
if (passingNote >= 24 && passingNote <= 60) {
249+
bassNotes.push({ tick: midTick, key: passingNote });
250+
}
251+
}
252+
}
253+
254+
// Sort bass notes by tick
255+
bassNotes.sort((a, b) => a.tick - b.tick);
256+
257+
// Generate melody (scale notes, more varied rhythm)
258+
const melodyNotes: Array<{ tick: number; key: number }> = [];
259+
const melodyOctave = rootOctave + 1; // One octave higher than root
260+
const melodyRootKey = rootNote + melodyOctave * 12;
261+
262+
// Create melodic phrases
263+
const phraseLength = beatsPerBar * ticksPerBeat * 2; // 2 bars per phrase
264+
for (
265+
let phraseStart = 0;
266+
phraseStart < totalTicks;
267+
phraseStart += phraseLength
268+
) {
269+
const phraseEnd = Math.min(phraseStart + phraseLength, totalTicks);
270+
271+
// Generate notes in this phrase
272+
let currentTick = phraseStart;
273+
while (currentTick < phraseEnd) {
274+
// Vary note lengths (quarter, eighth, dotted quarter)
275+
const noteLengths = [
276+
ticksPerBeat,
277+
ticksPerBeat / 2,
278+
ticksPerBeat * 1.5,
279+
];
280+
const noteLength = faker.helpers.arrayElement(noteLengths);
281+
282+
if (currentTick + noteLength > phraseEnd) break;
283+
284+
// Choose scale note
285+
const scaleDegree = faker.helpers.arrayElement(scale);
286+
const melodyKey = melodyRootKey + scaleDegree;
287+
288+
// Keep in valid range (48-84 is a good melody range)
289+
if (melodyKey >= 48 && melodyKey <= 84) {
290+
melodyNotes.push({ tick: currentTick, key: melodyKey });
291+
}
292+
293+
// Sometimes add a rest
294+
if (faker.datatype.boolean({ probability: 0.2 })) {
295+
currentTick += noteLength * 0.5;
296+
} else {
297+
currentTick += noteLength;
298+
}
299+
}
300+
}
301+
302+
// Add bass notes to song
303+
for (const { tick, key } of bassNotes) {
304+
const note = new Note(bassInstrument, {
305+
key,
306+
velocity: faker.helpers.rangeToNumber({ min: 80, max: 100 }), // Strong bass
307+
panning: 0,
308+
pitch: 0,
309+
});
310+
songTest.setNote(tick, bassLayer, note);
311+
}
206312

207-
for (let i = 0; i < notes.length; i++)
208-
songTest.setNote(i * 4, layer, notes[i]);
209-
// "i * 4" is placeholder - this is the tick to place on
313+
// Add melody notes to song
314+
for (const { tick, key } of melodyNotes) {
315+
const note = new Note(melodyInstrument, {
316+
key,
317+
velocity: faker.helpers.rangeToNumber({ min: 70, max: 100 }),
318+
panning: faker.helpers.rangeToNumber({ min: -0.3, max: 0.3 }), // Slight panning variation
319+
pitch: 0,
320+
});
321+
songTest.setNote(tick, melodyLayer, note);
210322
}
211323

212324
return songTest;

0 commit comments

Comments
 (0)