music_generation

This is an old revision of the document!


Music Generation (Generating Melodies)

Overview: Konduktiva provides some example algorithms for music generation, including patterns of notes, octaves, and chords. This page will document those example algorithms and how they can be used.

Things To Note: This tutorial assumes you have already installed Konduktiva successfully using the Konduktiva installation instructions and have read through and understood the first steps tutorial. The tutorial also assumes 2 things. One, Konduktiva has been assigned to the K variable. Two, user created a Musical Environment using the setUpMusicalEnvironment function using K.defaultConfigurationObject as the first argument and 'exampleMidiPlayer' as the third argument then, assigned the output to the e variable.

const K = require('konduktiva')
let e = K.setUpMusicalEnvironment(K.defaultConfigurationObject,4,'exampleMidiPlayer', K.exampleMusicalEnvironmentsExtraConfig)

L-systems, originally devised to model plant development (DuBois, 2003), is a rewriting system (Rewriting, 2023) usually represented by a string of symbols (Manousakis, 2006). These letters are usually part of the alphabet and other common ASCII symbols (DuBois, 2003). To create an L-system, a starting symbol or set of symbols is required (Lindenmayer, 1968)(DuBois, 2003). That part of the L-system is referred to as the axiom (DuBois, 2003). Once the axiom is defined, rules on how to substitute symbols are required. These rules are called productions or Formal Grammars (Formal grammar, 2023), and take the form of a predecessor symbol and a successor string. If a symbol in the string matches a predecessor, it will be replaced by the appropriate successor for that production rule. If a symbol fails to match any of the production predecessors, it will be copied to the appropriate place in the output string. All symbols in the current string are substituted simultaneously. The output (or production) string will then become the next input string. The number of times this process happens, also known as generations, can cause the length of the output to increase if the right set of rules is provided (DuBois, 2003).

String -> {String} -> Number -> String

Use the `K.lsystem` function to generate L-system with more control.

Syntax

K.lsystem (inputString, rules , generations)

Parameters

inputString

The string the L-system should start generating from (axiom). In form of a string

rules

The L-system generation rules in form of an object. The name of the variable is the character that the code will look for the the value is what the code will replace the variable with.

generations

The number of generations of the output L-system.

Examples

let exampleControlledLsystem = K.lsystem('a', {'a': 'ab', 'b': 'bba'}, 5)
console.log(exampleControlledLsystem)
//abbbabbabbaabbbabbaabbbabbaababbbabbabbaabbbabbaababbbabbabbaabbbabbaababbbaabbbabbabbaab
//This is a way code can be used in music. Convert it to numbers and it can be used as value of the noteMap.
//Here the L-system string is converted to numbers by counting how many times a letter appears consecutively:
let letterChanges = K.countLetterChanges(exampleControlledLsystem)
e.noteMaps.lsystemNotes = new K.QuantizedMap(letterChanges.length, letterChanges.map((x, i) => {return i}), letterChanges.map(x => {return [x]}))
//Change exampleMidiPlayer1 to use it:
e.players.exampleMidiPlayer1.noteMap = 'lsystemNotes'
//Start Player:
e.play('exampleMidiPlayer1')

How It Works:

If the generations the lsystem function has to generate is more than 0, it will recursively call itself with one less generation. If the generations it has to generate reaches 0, it will return the string that has been inputted.

[Number] -> [Number] -> Number -> [{Number, Number}]

Generates notes and octaves via L-system logic.

Syntax

K.generateLsystemByAssigningNumberToLetter(mode, octaves, length)

Parameters

mode

The music generated by the L-system will be filtered through the numbers in this array. Normally, one of the modes in western music is placed here.

octaves

An array of numbers which represent the octaves the L-system can use.

length

The lsystem length will not be less than this number.

Examples

let lsystemGeneratedMusic = await K.generateLsystemByAssigningNumberToLetter('blues', [4 ,5, 6], 2)
console.log(lsystemGeneratedMusic)
/*
[
  { note: 5, octave: 6 },  { note: 3, octave: 5 },  { note: 3, octave: 6 },
  { note: 3, octave: 5 },  { note: 10, octave: 5 }, { note: 5, octave: 6 },
  { note: 10, octave: 5 }, { note: 3, octave: 6 },  { note: 6, octave: 5 },
  { note: 6, octave: 5 },  { note: 3, octave: 5 },  { note: 5, octave: 6 },
  { note: 3, octave: 5 },  { note: 10, octave: 6 }, { note: 5, octave: 5 },
  { note: 10, octave: 5 }, { note: 3, octave: 5 },  { note: 6, octave: 6 },
  { note: 6, octave: 5 },  { note: 3, octave: 6 },  { note: 5, octave: 5 },
  { note: 3, octave: 4 },  { note: 10, octave: 4 }, { note: 5, octave: 4 },
  { note: 10, octave: 5 }, { note: 3, octave: 5 },  { note: 6, octave: 6 },
  { note: 6, octave: 5 },  { note: 3, octave: 6 },  { note: 5, octave: 5 },
  { note: 0, octave: 4 },  { note: 3, octave: 4 },  { note: 7, octave: 4 },
  { note: 6, octave: 5 },  { note: 7, octave: 5 },  { note: 3, octave: 6 },
  { note: 10, octave: 5 }, { note: 5, octave: 6 },  { note: 10, octave: 5 },
  { note: 3, octave: 4 },  { note: 6, octave: 4 },  { note: 6, octave: 4 },
  { note: 3, octave: 5 },  { note: 5, octave: 5 },  { note: 3, octave: 6 },
  { note: 10, octave: 5 }, { note: 5, octave: 6 },  { note: 10, octave: 5 },
  { note: 3, octave: 5 },  { note: 6, octave: 5 },  { note: 6, octave: 6 },
  { note: 3, octave: 5 },  { note: 5, octave: 6 },  { note: 3, octave: 5 },
  { note: 10, octave: 4 }, { note: 5, octave: 4 },  { note: 10, octave: 4 },
  { note: 3, octave: 5 },  { note: 6, octave: 5 },  { note: 6, octave: 6 },
  { note: 3, octave: 5 },  { note: 5, octave: 6 },  { note: 0, octave: 5 },
  { note: 5, octave: 4 },  { note: 10, octave: 4 }, { note: 7, octave: 4 },
  { note: 10, octave: 5 }
]
undefined
*/
//Add it to the MusicalEnvironment:
e.octaveMaps.lsystemGeneratedMusic = new K.QuantizedMap(lsystemGeneratedMusic.length, K.A.buildArray(lsystemGeneratedMusic.length, x => {return x}), lsystemGeneratedMusic.map(x => {return x.octave}))
e.noteMaps.lsystemGeneratedMusic = new K.QuantizedMap(lsystemGeneratedMusic.length, K.A.buildArray(lsystemGeneratedMusic.length, x => {return x}), lsystemGeneratedMusic.map(x => {return [x.note]}))
//Make exampleMidiPlayer1 use it:
e.players.exampleMidiPlayer1.octaveMap = 'lsystemGeneratedMusic'
e.players.exampleMidiPlayer1.noteMap = 'lsystemGeneratedMusic'
//Start Player:
e.play('exampleMidiPlayer1')

How It Works:

First, the octaves and the notes are generated. They are generated via the same process. An lsystem is generated in string form. Then converted

A random melody that corresponds to a chord progression can be generated using `generateRandomMelody` function.

String -> String -> Number = 1 -> Number = 12 -> Number = 1 -> Number = 12 -> [Number, Number, String]

Generates a random melody.

Syntax

let randomMelodyExample = K.generateRandomMelody(rootNote, mode, melodyLength, octaveMin, octaveMax, melodyMin, melodyMax)

Parameters

rootNote

A letter from musical letter notation.

mode

Name of a mode from western musical modes. They are listed here:

K.Scale.names()
melodyLength

The length of the melody outputed, Number

octaveMin

Minimum octave amount.

octaveMax

Maximum octave amount.

melodyMin

Minimum melody amount.

melodyMax

Maximum melody amount.

Examples

let randomMelodyExample = K.generateRandomMelody('C', 'blues', 5, 4, 6)
/*Ouput: 
[
  { note: 7, octave: 5, rootNote: 'C' },
  { note: 7, octave: 6, rootNote: 'C' },
  { note: 5, octave: 6, rootNote: 'C' },
  { note: 5, octave: 4, rootNote: 'C' },
  { note: 10, octave: 5, rootNote: 'C' }
]
*/
//You can get a list of all available mode by doing this:
K.Scale.names()
//Assigning the information to the corresponding map:
e.noteMaps.randomMelody = new K.QuantizedMap(randomMelodyExample.length, randomMelodyExample.map((x, i) => {return i}), randomMelodyExample.map(x => {return [x.note]}))
e.octaveMaps.randomMelody = new K.QuantizedMap(randomMelodyExample.length, randomMelodyExample.map((x, i) => {return i}), randomMelodyExample.map(x => {return x.octave}))
e.rootMaps.randomMelody = new K.QuantizedMap(randomMelodyExample.length, randomMelodyExample.map((x, i) => {return i}), randomMelodyExample.map(x => {return x.rootNote}))
//Make exampleMidiPlayer1 use them:
e.players.exampleMidiPlayer1.noteMap = 'randomMelody'
e.players.exampleMidiPlayer1.octaveMap = 'randomMelody'
e.players.exampleMidiPlayer1.rootMap = 'randomMelody'
//Start Player:
e.play('exampleMidiPlayer1')

First, it will find the mode given by user inside Tonal. Then, it will put the mode in a QuantizedMap with a keyspan of 12 and use it as the keys and values. Next the function will return an array filled with objects. Each object will contain variables for note, root note and, octave. The octave is just a random number generated within the range provided by user. The root note is just the root note given by user. The note will be a random number filtered through the nearest lookup method of the QuantizedMap created in step 2. This will find the number closest to the given number inside mode.

Generate circle of fifths chord progressions using the `generateCircleOfFifthsChordProgressions function. The generateCircleOfFifthsChordProgressions function uses the createCircleOfFifths` function.

String -> [String]

Returns circle of fifths chord progression starting from the startingNote.

Syntax

K.createCircleOfFifths(startingNote)

Parameters

startingNote

The note that the circle of fifths algorithm should start from. Also the letter that will be the first item of the array.

Examples

let exampleCircleOfFifths = K.createCircleOfFifths('C')
console.log(exampleCircleOfFifths)
/*
[
  'C',  'G',  'D',  'A',
  'E',  'B',  'F#', 'C#',
  'G#', 'D#', 'A#', 'E#'
]
*/
//Use this in a rootMap:
e.rootMaps.circleOfFifths = new K.QuantizedMap(exampleCircleOfFifths.length, exampleCircleOfFifths.map((x, i) => {return i}), exampleCircleOfFifths)
//Make exampleMidiPlayer1 use it:
e.players.exampleMidiPlayer1.rootMap = 'circleOfFifths'
//Start Player:
e.play('exampleMidiPlayer1')

[Number] -> String -> Number -> [String]

Generates circle of fifths chord progression and outputs it in roman numeral form. The circle of fifths is a way the twelve chromatic notes in modern western music were organized. A chord progression usually resolves smoothly if it follows the circle of fifths, therefore the circle of fifths is chosen to be used in the functions that generate chord progressions (Berklee Online, 2023).

K.generateCircleOfFifthsChordProgressions(chordLengths, key, counterClockwiseChance) 

chordLengths

The length of this array will dictate the length of the array returned.

key

Musical letter notation.

counterClockwiseChance

The chance of the circle being clockwise or counterclockwise.

let circleOfFifthsProgression = K.generateCircleOfFifthsChordProgressions([1,1,1,1], "C", 0.5)
//Add the information to MusicalEnvironment:
//1. Convert information to musical letter notation:
//first argument is key tonic. Second argument is the music in roman numerals.
let circleOfFifthsProgressionAsRoots = K.Progression.fromRomanNumerals('C', circleOfFifthsProgression)

//add it to MusicalEnvironmemnt rootMap:
e.rootMaps.circleOfFifthsProgression = new QuantizedMap(circleOfFifthsProgressionAsRoots.length, circleOfFifthsProgressionAsRoots.map((x, i) => {return i}), circleOfFifthsProgressionAsRoots)

//Start Player:
e.play('exampleMidiPlayer1')

Users of Konduktiva can switch the music being played without stopping the Player and without delay.

To change anything that controls what a player players, you will have to know where to look. Everything is recorded inside the Player object. Access with by doing `e.players.playerName`. For example, if I want to know more about exampleMidiPlayer1 I can do `e.players.exampleMidiPlayer1`

Use ExampleMusicalEnvironment 4 as example.

First identify where the player is looking at to see which notes or chords to play. If I want to find this out for exampleMidiPlayer1 I can log `e.players.exampleMidiPlayer4.noteMap`. . So this means, Player exampleMidiPlayer4 is looking at `e.noteMaps.p4`.

For the default MusicalEnvironment, exampleMidiPlayer4 will be playing circle of fifths chords without changing the timing. For example, I want to make it play random notes I can do this:

//Generate the random notes:
randomMelodyExample = K.generateRandomMelody('C', 'bluesPentatonicScale', 10, 4, 6)
/*output:
[
  { note: 3, octave: 6, rootNote: 'C' },
  { note: 7, octave: 5, rootNote: 'C' },
  { note: 10, octave: 5, rootNote: 'C' },
  { note: 10, octave: 5, rootNote: 'C' },
  { note: 7, octave: 5, rootNote: 'C' },
  { note: 3, octave: 5, rootNote: 'C' },
  { note: 6, octave: 5, rootNote: 'C' },
  { note: 6, octave: 6, rootNote: 'C' },
  { note: 0, octave: 4, rootNote: 'C' },
  { note: 3, octave: 5, rootNote: 'C' }
]
*/

e.rootMaps[e.players.exampleMidiPlayer4.rootMap].values = randomMelodyExample.map(x => {return x.rootNote})
/* output:
[
  'C', 'C', 'C', 'C',
  'C', 'C', 'C', 'C',
  'C', 'C'
]
*/

e.octaveMaps[e.players.exampleMidiPlayer4.octaveMap].values = randomMelodyExample.map(x => {return [x.octave]})
/*output:
[
  [ 6 ], [ 5 ], [ 5 ],
  [ 5 ], [ 5 ], [ 5 ],
  [ 5 ], [ 6 ], [ 4 ],
  [ 5 ]
]
*/

e.noteMaps[e.players.exampleMidiPlayer4.noteMap].values = randomMelodyExample.map(x => {return [x.note]})
/*output:
[
  [ 3 ],  [ 7 ],
  [ 10 ], [ 10 ],
  [ 7 ],  [ 3 ],
  [ 6 ],  [ 6 ],
  [ 0 ],  [ 3 ]
]
*/

Filters that a player can use are stored in the modeFilters variable in the MusicalEnvironment. The modeFilters the player chooses to use is controlled by the modeMaps variable in the MusicalEnvironment.

For example, I want the chords to filter through an aeolian filter then a dorian filter. I will have to create a modeMap that tells the player to do that.

e.modeMaps.aeolianAndDorian = new K.QuantizedMap(20, [0, 10], ['aeolian', 'dorian'])

//configure player to use it:
e.players.exampleMidiPlayer1 = 'aeolianAndDorian'

//make that player play:
e.play('exampleMidiPlayer1')

Click here to learn how to interact better with the musicalEnvironment

Full documentation

Click here to learn how to record your music

DuBois, R. L. (2003). Applications of generative string-substitution systems in computer music. Columbia University. https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=c5b0d630e444201d3ab71fc452942d2065d24690

Manousakis, S. (2006). Musical L-systems. Koninklijk Conservatorium, The Hague (master thesis). https://modularbrains.net/wp-content/uploads/Stelios-Manousakis-Musical-L-systems.pdf

Lindenmayer, Aristid. “Mathematical models for cellular interactions in development” (Two Parts). Journal of Theoretical Biology 18. New York: Elsevier, 1968.

Berklee Online. (2023). Circle of Fifths: The Key to Unlocking Harmonic Understanding – Berklee. Berklee Online Take Note. https://online.berklee.edu/takenote/circle-of-fifths-the-key-to-unlocking-harmonic-understanding/

  • music_generation.1710813040.txt.gz
  • Last modified: 2024/03/18 18:50
  • by steve.wang