@@ -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