Show pageOld revisionsBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== 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 [[https://github.com/renickbell/konduktiva|Konduktiva installation]] instructions and have read through and understood the [[:first_steps|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. <code javascript> const K = require('konduktiva') let e = K.setUpMusicalEnvironment(K.defaultConfigurationObject,4,'exampleMidiPlayer', K.exampleMusicalEnvironmentsExtraConfig) </code> <markdown> ## L-system (Lindenmayer system) 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). ### Generating L-systems With More Control (lsystem) String -> {String} -> Number -> String Use the ```K.lsystem``` function to generate L-system with more control. use the ```K.workerLsystem``` that does the same thing but with worker threads 9smae syntax). #### 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. ### variousLsystems An lsystem is generated each individual character is assigned a number. The code will go through the lsystem and placing the characters in number form into an array. The array is then assigned a name in an object. The object is then returned. #### Syntax ``` variousLsystems(baseName,n,patternLength,rules,generations,replacementValues,inputString, allChars = getAllAlphabets()) ``` #### Parameters ##### baseName This is the base string for all the keys in the returned object. So, the names of the resulting object will be baseName+number. number starts from 0. ##### n The amount of arrays in the returned object. ##### patternLength The total value of all the numbers in each array. ##### rules L-system rules. Object ##### generations L-system generations. Number ##### replacementValues The number each character should be assigned to. ##### inputString The string L-system should start with. The axiom of the L-system. ##### allChars All characters used by the L-system it will default to all the English alphabets if nothing is filled in. #### Examples ``` //Might need to wait a few seconds depending on speed of your device. let varyingLsystem = await K.variousLsystems('testing', 1, 32, {'a': 'b', 'b': 'ab'}, 20, [0.5, 1, 2, 3], 'a') /* > varyingLsystem { testing0: [ 1, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1, 0.5, 1, 0.5, 0.5, 1 ] } */ ``` ### generateLsystemByAssigningNumberToLetter [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 ## Random A random melody that corresponds to a chord progression can be generated using ```generateRandomMelody``` function. ### generateRandomMelody 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') ``` ### Explanation Of How Function Works: 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. ## Circle Of Fifths Generate circle of fifths chord progressions using the ``generateCircleOfFifthsChordProgression`` function. The ``generateCircleOfFifthsChordProgression`` function uses the ``createCircleOfFifths`` function. ### createCircleOfFifths 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') ``` ### generateCircleOfFifthsChordProgression [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). ### Syntax ``` K.generateCircleOfFifthsChordProgression(chordLengths, key, counterClockwiseChance) ``` ### Parameters #### 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. ### Example ``` let circleOfFifthsProgression = K.generateCircleOfFifthsChordProgression([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') ``` ## Switching Between Musics Without stopping Player Users of Konduktiva can switch the music being played without stopping the Player and without delay. ### Identifying Name of Variable 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``` ### Change Notes Or Chords Played + Octave + Root Notes 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', 'blues', 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 ] ] */ ``` ### Pass Melodies through Chord Filters 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.modeMap = 'aeolianAndDorian' //make that player play: e.play('exampleMidiPlayer1') ``` You can also create your own modeFilters like so: ``` //In this example, I will use the controlled L-system generator to generate a modeFilter let lsystemModefilter = K.lsystem('a', {'a': 'ab', 'b': 'bab'}, 5) let lsystemModeFilterChanges = K.countLetterChanges(lsystemModefilter) //Create a modeFilter: e.modeFilters.lsystemModeFilter = new K.QuantizedMap(lsystemModeFilterChanges.length, lsystemModeFilterChanges, lsystemModeFilterChanges) //Create a modeMap to use modeFilter: e.modeMaps.lsystem = new K.QuantizedMap(1, [1], ['lsystemModeFilter']) //Make Player use the created modeMap: e.players.exampleMidiPlayer1.modeMap = 'lsystem' //Play player: e.play('exampleMidiPlayer1') ``` ### More information: [Click here to learn how to interact better with the musicalEnvironment](https://github.com/renickbell/konduktiva/blob/main/konduktiva-documentation.md#interacting-with-the-musical-environment) [Full documentation](https://github.com/renickbell/konduktiva) [Click here to learn how to record your music](http://konduktiva.org/doku.php?id=recording-with-a-macbook) ### Sources: 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/ </markdown> music_generation.txt Last modified: 2024/05/15 02:04by steve.wang