├── examples ├── python │ ├── geneseq │ │ ├── __init__.py │ │ ├── individual.py │ │ └── population.py │ └── mutate_melody.py ├── README.md └── max │ ├── overview.maxpat │ └── genesequencer.maxpat ├── .gitignore ├── geneseq.xcodeproj ├── xcuserdata │ └── stephenmeyer.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── LICENSE.txt ├── README.md └── geneseq.c /examples/python/geneseq/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.xcworkspace 4 | /**/__pycache__ 5 | -------------------------------------------------------------------------------- /geneseq.xcodeproj/xcuserdata/stephenmeyer.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | max-external.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/python/mutate_melody.py: -------------------------------------------------------------------------------- 1 | import random 2 | from geneseq.individual import Individual 3 | from geneseq.population import Population 4 | 5 | 6 | # Number of individuals in each generation 7 | POPULATION_SIZE = 64 8 | 9 | # Valid gene nucleotides 10 | GENES = '012345678' 11 | 12 | # Target string to be generated 13 | TARGET = "12030050" 14 | 15 | 16 | def main(): 17 | population = Population(GENES, TARGET, POPULATION_SIZE) 18 | population.seed() 19 | 20 | while not population.has_converged(): 21 | population.print_evolution_cycle() 22 | population.evolve() 23 | 24 | population.print_evolution_cycle() 25 | 26 | 27 | if __name__ == '__main__': 28 | main() 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Steve Meyer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # geneseq Examples 2 | 3 | ## Max 4 | 5 | After building the geneseq object from source, you can open the Max patch examples: 6 | 7 | * **overview.maxpat** a simple overview of inputs/outputs 8 | * **genesequencer.maxpat** generate MIDI notes from a geneseq object for two alternating sequences 9 | 10 | ## Python 11 | 12 | The python examples are included as a simple object-oriented example of the geneseq logic. Prior to writing the C code for the object, the python code was adapted to work out the logic without concern for C pointers and lower level data structures. 13 | 14 | Run the python code: 15 | 16 | ```bash 17 | $ cd examples/python 18 | $ python mutate_melody.py 19 | Generation: 1 (2) Best Chromosome: 16664205 Fitness: 7 20 | Generation: 2 (5) Best Chromosome: 16664205 Fitness: 7 21 | Generation: 3 (11) Best Chromosome: 12865705 Fitness: 6 22 | Generation: 4 (24) Best Chromosome: 12765705 Fitness: 6 23 | Generation: 5 (51) Best Chromosome: 12255755 Fitness: 5 24 | Generation: 6 (108) Best Chromosome: 12271055 Fitness: 4 25 | Generation: 7 (64) Best Chromosome: 12650005 Fitness: 4 26 | Generation: 8 (64) Best Chromosome: 12835450 Fitness: 3 27 | Generation: 9 (64) Best Chromosome: 12840000 Fitness: 3 28 | Generation: 10 (64) Best Chromosome: 12260050 Fitness: 2 29 | Generation: 11 (64) Best Chromosome: 12830750 Fitness: 2 30 | Generation: 12 (64) Best Chromosome: 12530750 Fitness: 2 31 | Generation: 13 (64) Best Chromosome: 12060050 Fitness: 1 32 | Generation: 14 (64) Best Chromosome: 12230050 Fitness: 1 33 | Generation: 15 (64) Best Chromosome: 12230050 Fitness: 1 34 | Generation: 16 (64) Best Chromosome: 12030050 Fitness: 0 35 | ``` 36 | -------------------------------------------------------------------------------- /examples/python/geneseq/individual.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Individual(object): 5 | ''' 6 | Class representing individual in population. A single chromosome individual. 7 | ''' 8 | def __init__(self, population, chromosome): 9 | self.population = population 10 | self.chromosome = chromosome 11 | self.fitness = self.cal_fitness() 12 | 13 | def __str__(self): 14 | return "".join(self.chromosome) 15 | 16 | def mate(self, par2): 17 | ''' 18 | Perform mating and produce new offspring 19 | ''' 20 | 21 | # chromosome for offspring 22 | child_chromosome = [] 23 | for gp1, gp2 in zip(self.chromosome, par2.chromosome): 24 | 25 | # random probability 26 | prob = random.random() 27 | 28 | # if prob is less than 0.45, insert gene 29 | # from parent 1 30 | if prob < 0.45: 31 | child_chromosome.append(gp1) 32 | 33 | # if prob is between 0.45 and 0.90, insert 34 | # gene from parent 2 35 | elif prob < 0.90: 36 | child_chromosome.append(gp2) 37 | 38 | # otherwise insert random gene(mutate), 39 | # for maintaining diversity 40 | else: 41 | child_chromosome.append(random.choice(self.population.nucleotides)) 42 | 43 | # create new Individual(offspring) using 44 | # generated chromosome for offspring 45 | return Individual(self.population, child_chromosome) 46 | 47 | def cal_fitness(self): 48 | ''' 49 | Calculate fittness score, it is the number of 50 | characters in string which differ from target 51 | string. 52 | ''' 53 | fitness = 0 54 | for gs, gt in zip(self.chromosome, self.population.target): 55 | if gs != gt: fitness+= 1 56 | return fitness 57 | -------------------------------------------------------------------------------- /examples/python/geneseq/population.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | import copy 4 | 5 | from collections import defaultdict 6 | from .individual import Individual 7 | 8 | 9 | class Population(object): 10 | ''' 11 | Class representing a collection of individuals 12 | ''' 13 | def __init__(self, nucleotides='012345678', target='10300050', size=128): 14 | self.nucleotides = nucleotides 15 | self.target = target 16 | self.size = size 17 | self.individuals = defaultdict(list) 18 | self.count = 0 19 | self.generation = 1 20 | 21 | def evolve(self): 22 | ''' 23 | Move the population ahead one generation by evolving it towards the target sequence 24 | ''' 25 | new_generation = defaultdict(list) 26 | population_count = 0 27 | 28 | # 10% of fittest population automatically goes to the next generation 29 | s = int( math.ceil((10*self.count)/100) ) 30 | scores = list(self.candidate_scores()) 31 | top_candidates = [] 32 | while s > 0: 33 | if len(top_candidates) == 0: 34 | next_top_score = scores.pop(0) 35 | top_candidates = copy.copy(self.individuals_with_score(next_top_score)) 36 | new_gen_indiv = top_candidates.pop(random.randrange(len(top_candidates))) 37 | new_generation[new_gen_indiv.fitness].append(new_gen_indiv) 38 | s -= 1 39 | population_count += 1 40 | 41 | # The fittest 50% of the population will mate to produce offspring 42 | indivs = self.sorted_individiduals() 43 | if int((90*len(indivs))/100) + population_count < self.size: 44 | s = len(indivs) * 2 45 | elif int((90*len(indivs))/100) + population_count > self.size: 46 | s = self.size - population_count 47 | else: 48 | s = int( (90*self.size) / 100 ) 49 | 50 | for _ in range(s): 51 | # Ensure no individual mates with itself 52 | end_index = 2 if len(indivs) < 4 else int(len(indivs)/2) 53 | parent1, parent2 = random.sample(indivs[:end_index], 2) 54 | 55 | child = parent1.mate(parent2) 56 | new_generation[child.fitness].append(child) 57 | population_count += 1 58 | 59 | self.individuals = new_generation 60 | self.count = population_count 61 | self.generation += 1 62 | 63 | def has_converged(self): 64 | ''' 65 | The population has converged when at least one of its individuals matches the target 66 | ''' 67 | return self.best_candidate().fitness == 0 68 | 69 | def seed(self, initial_population=2): 70 | ''' 71 | When the population is empty, seed it based on the population params passed when initialized 72 | ''' 73 | for _ in range(initial_population): 74 | chromosome = self.create_chromosome() 75 | self.add(Individual(self, chromosome)) 76 | self.count += initial_population 77 | 78 | def create_chromosome(self): 79 | ''' 80 | Create chromosome string out of the population's nucleotides 81 | ''' 82 | return [random.choice(self.nucleotides) for _ in range(len(self.target))] 83 | 84 | def sorted_individiduals(self): 85 | flattened_indivs = [indiv for score_group in self.individuals.values() for indiv in score_group] 86 | flattened_indivs.sort(key=lambda x: x.fitness) 87 | return flattened_indivs 88 | 89 | def add(self, individual): 90 | self.individuals[individual.fitness].append(individual) 91 | 92 | def best_candidate(self): 93 | return random.choice(self.best_candidates()) 94 | 95 | def best_candidates(self): 96 | # Get a list of all candidates with the lowest score 97 | return self.individuals[self.best_candidate_score()] 98 | 99 | def best_candidate_score(self): 100 | return self.candidate_scores()[0] 101 | 102 | def candidate_scores(self): 103 | return sorted(self.individuals.keys()) 104 | 105 | def individuals_with_score(self, score): 106 | return self.individuals[score] 107 | 108 | def print_evolution_cycle(self): 109 | best_candidate = self.best_candidate() 110 | format_args = (self.generation, self.count, best_candidate, best_candidate.fitness) 111 | print("Generation: %i (%i)\tBest Chromosome: %s\tFitness: %i" % format_args) 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geneseq 2 | 3 | A genetic algorithm melody generator/sequencer for Max/MSP. 4 | 5 | ## Building & Installing 6 | 7 | You can build this object if you are comfortable building a Max external on your local system. See the [Max SDK from Cycling 74's website](https://cycling74.com/downloads/sdk). 8 | 9 | ## Known Issues 10 | 11 | This Max external is a work in progress and is currently buggy. It has been known to crash Max and M4L/Ableton Live when copying an instance of the object. 12 | 13 | ## About 14 | 15 | This work is an attempt to build a melody sequencer based on a genetic algorithm. It was inspired by the [Generative sequencers thread from the lines community](https://llllllll.co/t/generative-sequencers/19155). 16 | 17 | ### Overview 18 | 19 | A genetic algorithm "evolves" towards a target state, in this case a target melody specified as scale degrees. Evolution is based on a population of individuals. Think of a population of robots "improvising" by each generating different random melodies. The robots are trying to resolve to a target melody. The robot with a melody most similar to the target is chosen for each evolution/generation for the population as the current sequence. When at least one robot produces the target melody, the genetic algorithm has converged and ceases to evolve. Evolution is a process that happens by sending the Max object an "evolve" message. 20 | 21 | At present, the following fixed properties are in place: 22 | 23 | * Population size: 64 individuals 24 | * Sequence length: 8 steps 25 | * Genes (i.e., the nucleotides): integers 0-8 26 | * Parents per bred individual: 2 27 | * Default target melody: 1 2 0 3 0 0 5 0 28 | 29 | ### Individuals 30 | 31 | An individual is a member of the population. It is a melody generated from the list of valid genes. 32 | 33 | The initial population of individuals is seeded from random combinations of genes. 34 | 35 | ### Genes 36 | 37 | The valid genes that can make up an individual are the integers 0-8 inclusive. 0 is intended to represent a skipped sequence step, or silence. Numbers 1-8 are scale degrees root/tonic (1) up to the note one octave above (8). For example, a gene value of 5 is intended to be the 5th scale degree. 38 | 39 | ### Scores 40 | 41 | Individuals are scored so that the best melody can be chosen for a given generation. This individual sequence will be sent out the first/left outlet of the object, along with its fitness score sent out the second outlet. 42 | 43 | Individuals are "scored" in two ways. First, they have an integer fitness score based on how many steps they have in common with the current target sequence. A lower score is more fit and closer to the target. Second, they have a random score as a float generated between 0 and 1. The purpose of the random score is to add some variation to the sequencer output when choosing a best candidate individual at a particular point in time. For example, given the following two individuals: 44 | 45 | ``` 46 | 1 8 0 3 0 0 5 0 47 | 1 2 0 8 0 0 5 0 48 | ``` 49 | 50 | both would have a fitness score of 1 using the default target melody above since each is off by only one step (the 8 in each sequence). The random score is used so that for some generations, the first is chose and in other generations the second is chosen if they are the two lowest by fitness score alone. 51 | 52 | ### Evolution of Generations 53 | 54 | Generation 0 is a random seeded population when the Max object is created. Subsequent generations evolve from the current generation when the geneseq object receives the message "evolve" in its left inlet. 55 | 56 | When evolution happens, the 10% most fit individuals of the current generation are automatically added to the next generation. The remaining members of the population are "bred" from the 50% most fit members of the population. In this promiscuous community, the parents are selected at random, but no parent breeds with itself. Bred individuals get genes randomly according to the following (rough) percentages. For each step in the new individual's sequence: 57 | 58 | * 45% chance of receiving parent 1's scale degree at the current step 59 | * 45% chance of receiving parent 2's scale degree at the current step 60 | * 10% chance of a random mutation occurring: random gene is chosen 61 | 62 | Individuals are always scored when they are created and once all individuals have been created for a given evolutionary cycle, the entire population of individuals is sorted. An individual is sorted by the sum of its fitness score and random scores. 63 | 64 | The top scored individual becomes the next sequence set out the left outlet. It is possible that for successive generations the same sequence is output. This is common as an evolutionary cycle approaches convergence. 65 | 66 | ### Convergence 67 | 68 | The sequencer evolves its population when it receives an "evolve" message. When at least one melody matches the current target sequence, it converges. A bang is sent out the rightmost outlet on convergence. This happens the first time an "evolve" message results in convergence and on all subsequent times an "evolve" message is received until a new target is set. 69 | 70 | ## Examples 71 | 72 | See the [examples](examples) directory for Max patches that illustrate how the geneseq object works and object-oriented Python code that may be a little bit easier to read than the C. 73 | -------------------------------------------------------------------------------- /geneseq.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 22CF11AE0EE9A8840054F513 /* geneseq.c in Sources */ = {isa = PBXBuildFile; fileRef = 22CF11AD0EE9A8840054F513 /* geneseq.c */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 22CF10220EE984600054F513 /* maxmspsdk.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = maxmspsdk.xcconfig; path = ../../maxmspsdk.xcconfig; sourceTree = SOURCE_ROOT; }; 15 | 22CF11AD0EE9A8840054F513 /* geneseq.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = geneseq.c; sourceTree = ""; }; 16 | 2FBBEAE508F335360078DB84 /* geneseq.mxo */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = geneseq.mxo; sourceTree = BUILT_PRODUCTS_DIR; }; 17 | /* End PBXFileReference section */ 18 | 19 | /* Begin PBXFrameworksBuildPhase section */ 20 | 2FBBEADC08F335360078DB84 /* Frameworks */ = { 21 | isa = PBXFrameworksBuildPhase; 22 | buildActionMask = 2147483647; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXFrameworksBuildPhase section */ 28 | 29 | /* Begin PBXGroup section */ 30 | 089C166AFE841209C02AAC07 /* iterator */ = { 31 | isa = PBXGroup; 32 | children = ( 33 | 22CF10220EE984600054F513 /* maxmspsdk.xcconfig */, 34 | 22CF11AD0EE9A8840054F513 /* geneseq.c */, 35 | 19C28FB4FE9D528D11CA2CBB /* Products */, 36 | ); 37 | name = iterator; 38 | sourceTree = ""; 39 | }; 40 | 19C28FB4FE9D528D11CA2CBB /* Products */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | 2FBBEAE508F335360078DB84 /* geneseq.mxo */, 44 | ); 45 | name = Products; 46 | sourceTree = ""; 47 | }; 48 | /* End PBXGroup section */ 49 | 50 | /* Begin PBXHeadersBuildPhase section */ 51 | 2FBBEAD708F335360078DB84 /* Headers */ = { 52 | isa = PBXHeadersBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXHeadersBuildPhase section */ 59 | 60 | /* Begin PBXNativeTarget section */ 61 | 2FBBEAD608F335360078DB84 /* max-external */ = { 62 | isa = PBXNativeTarget; 63 | buildConfigurationList = 2FBBEAE008F335360078DB84 /* Build configuration list for PBXNativeTarget "max-external" */; 64 | buildPhases = ( 65 | 2FBBEAD708F335360078DB84 /* Headers */, 66 | 2FBBEAD808F335360078DB84 /* Resources */, 67 | 2FBBEADA08F335360078DB84 /* Sources */, 68 | 2FBBEADC08F335360078DB84 /* Frameworks */, 69 | 2FBBEADF08F335360078DB84 /* Rez */, 70 | ); 71 | buildRules = ( 72 | ); 73 | dependencies = ( 74 | ); 75 | name = "max-external"; 76 | productName = iterator; 77 | productReference = 2FBBEAE508F335360078DB84 /* geneseq.mxo */; 78 | productType = "com.apple.product-type.bundle"; 79 | }; 80 | /* End PBXNativeTarget section */ 81 | 82 | /* Begin PBXProject section */ 83 | 089C1669FE841209C02AAC07 /* Project object */ = { 84 | isa = PBXProject; 85 | attributes = { 86 | LastUpgradeCheck = 0610; 87 | }; 88 | buildConfigurationList = 2FBBEACF08F335010078DB84 /* Build configuration list for PBXProject "geneseq" */; 89 | compatibilityVersion = "Xcode 3.2"; 90 | developmentRegion = English; 91 | hasScannedForEncodings = 1; 92 | knownRegions = ( 93 | English, 94 | en, 95 | ); 96 | mainGroup = 089C166AFE841209C02AAC07 /* iterator */; 97 | projectDirPath = ""; 98 | projectRoot = ""; 99 | targets = ( 100 | 2FBBEAD608F335360078DB84 /* max-external */, 101 | ); 102 | }; 103 | /* End PBXProject section */ 104 | 105 | /* Begin PBXResourcesBuildPhase section */ 106 | 2FBBEAD808F335360078DB84 /* Resources */ = { 107 | isa = PBXResourcesBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXResourcesBuildPhase section */ 114 | 115 | /* Begin PBXRezBuildPhase section */ 116 | 2FBBEADF08F335360078DB84 /* Rez */ = { 117 | isa = PBXRezBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXRezBuildPhase section */ 124 | 125 | /* Begin PBXSourcesBuildPhase section */ 126 | 2FBBEADA08F335360078DB84 /* Sources */ = { 127 | isa = PBXSourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 22CF11AE0EE9A8840054F513 /* geneseq.c in Sources */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXSourcesBuildPhase section */ 135 | 136 | /* Begin XCBuildConfiguration section */ 137 | 2FBBEAD008F335010078DB84 /* Development */ = { 138 | isa = XCBuildConfiguration; 139 | buildSettings = { 140 | }; 141 | name = Development; 142 | }; 143 | 2FBBEAD108F335010078DB84 /* Deployment */ = { 144 | isa = XCBuildConfiguration; 145 | buildSettings = { 146 | }; 147 | name = Deployment; 148 | }; 149 | 2FBBEAE108F335360078DB84 /* Development */ = { 150 | isa = XCBuildConfiguration; 151 | baseConfigurationReference = 22CF10220EE984600054F513 /* maxmspsdk.xcconfig */; 152 | buildSettings = { 153 | COPY_PHASE_STRIP = NO; 154 | GCC_OPTIMIZATION_LEVEL = 0; 155 | OTHER_LDFLAGS = "$(C74_SYM_LINKER_FLAGS)"; 156 | PRODUCT_NAME = geneseq; 157 | }; 158 | name = Development; 159 | }; 160 | 2FBBEAE208F335360078DB84 /* Deployment */ = { 161 | isa = XCBuildConfiguration; 162 | baseConfigurationReference = 22CF10220EE984600054F513 /* maxmspsdk.xcconfig */; 163 | buildSettings = { 164 | COPY_PHASE_STRIP = YES; 165 | OTHER_LDFLAGS = "$(C74_SYM_LINKER_FLAGS)"; 166 | PRODUCT_NAME = geneseq; 167 | }; 168 | name = Deployment; 169 | }; 170 | /* End XCBuildConfiguration section */ 171 | 172 | /* Begin XCConfigurationList section */ 173 | 2FBBEACF08F335010078DB84 /* Build configuration list for PBXProject "geneseq" */ = { 174 | isa = XCConfigurationList; 175 | buildConfigurations = ( 176 | 2FBBEAD008F335010078DB84 /* Development */, 177 | 2FBBEAD108F335010078DB84 /* Deployment */, 178 | ); 179 | defaultConfigurationIsVisible = 0; 180 | defaultConfigurationName = Development; 181 | }; 182 | 2FBBEAE008F335360078DB84 /* Build configuration list for PBXNativeTarget "max-external" */ = { 183 | isa = XCConfigurationList; 184 | buildConfigurations = ( 185 | 2FBBEAE108F335360078DB84 /* Development */, 186 | 2FBBEAE208F335360078DB84 /* Deployment */, 187 | ); 188 | defaultConfigurationIsVisible = 0; 189 | defaultConfigurationName = Development; 190 | }; 191 | /* End XCConfigurationList section */ 192 | }; 193 | rootObject = 089C1669FE841209C02AAC07 /* Project object */; 194 | } 195 | -------------------------------------------------------------------------------- /examples/max/overview.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 1, 7 | "revision" : 3, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 239.0, 222.0, 640.0, 480.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-10", 43 | "linecount" : 3, 44 | "maxclass" : "comment", 45 | "numinlets" : 1, 46 | "numoutlets" : 0, 47 | "patching_rect" : [ 373.0, 210.0, 243.0, 47.0 ], 48 | "text" : "4) Click on one of the new sequence message boxes to set a new target. Evolve these sequences until convergence." 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-9", 55 | "linecount" : 4, 56 | "maxclass" : "comment", 57 | "numinlets" : 1, 58 | "numoutlets" : 0, 59 | "patching_rect" : [ 373.0, 129.0, 243.0, 60.0 ], 60 | "text" : "3) Click on the 'evolve' message box to move the population generation and output new values. Repeat this step until the last outlet bangs to indicate convergence." 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-7", 67 | "linecount" : 2, 68 | "maxclass" : "comment", 69 | "numinlets" : 1, 70 | "numoutlets" : 0, 71 | "patching_rect" : [ 373.0, 73.0, 243.0, 33.0 ], 72 | "text" : "2) Click on the [button] to see the top scored individual." 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "id" : "obj-5", 79 | "linecount" : 2, 80 | "maxclass" : "comment", 81 | "numinlets" : 1, 82 | "numoutlets" : 0, 83 | "patching_rect" : [ 373.0, 15.0, 243.0, 33.0 ], 84 | "text" : "1) Click on the 'gettarget' message box to see the default sequence." 85 | } 86 | 87 | } 88 | , { 89 | "box" : { 90 | "id" : "obj-24", 91 | "maxclass" : "newobj", 92 | "numinlets" : 1, 93 | "numoutlets" : 2, 94 | "outlettype" : [ "bang", "" ], 95 | "patching_rect" : [ 138.0, 60.0, 78.875, 22.0 ], 96 | "text" : "t b l" 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "id" : "obj-23", 103 | "maxclass" : "message", 104 | "numinlets" : 2, 105 | "numoutlets" : 1, 106 | "outlettype" : [ "" ], 107 | "patching_rect" : [ 259.0, 17.0, 85.0, 22.0 ], 108 | "text" : "1 0 3 0 5 0 7 8" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "id" : "obj-22", 115 | "maxclass" : "message", 116 | "numinlets" : 2, 117 | "numoutlets" : 1, 118 | "outlettype" : [ "" ], 119 | "patching_rect" : [ 91.0, 110.0, 43.0, 22.0 ], 120 | "text" : "evolve" 121 | } 122 | 123 | } 124 | , { 125 | "box" : { 126 | "id" : "obj-20", 127 | "maxclass" : "message", 128 | "numinlets" : 2, 129 | "numoutlets" : 1, 130 | "outlettype" : [ "" ], 131 | "patching_rect" : [ 11.0, 198.0, 99.0, 22.0 ] 132 | } 133 | 134 | } 135 | , { 136 | "box" : { 137 | "id" : "obj-18", 138 | "maxclass" : "button", 139 | "numinlets" : 1, 140 | "numoutlets" : 1, 141 | "outlettype" : [ "bang" ], 142 | "parameter_enable" : 0, 143 | "patching_rect" : [ 41.0, 110.0, 24.0, 24.0 ] 144 | } 145 | 146 | } 147 | , { 148 | "box" : { 149 | "id" : "obj-16", 150 | "maxclass" : "number", 151 | "numinlets" : 1, 152 | "numoutlets" : 2, 153 | "outlettype" : [ "", "bang" ], 154 | "parameter_enable" : 0, 155 | "patching_rect" : [ 150.25, 198.0, 50.0, 22.0 ] 156 | } 157 | 158 | } 159 | , { 160 | "box" : { 161 | "id" : "obj-15", 162 | "maxclass" : "number", 163 | "numinlets" : 1, 164 | "numoutlets" : 2, 165 | "outlettype" : [ "", "bang" ], 166 | "parameter_enable" : 0, 167 | "patching_rect" : [ 209.5, 198.0, 50.0, 22.0 ] 168 | } 169 | 170 | } 171 | , { 172 | "box" : { 173 | "id" : "obj-12", 174 | "maxclass" : "newobj", 175 | "numinlets" : 1, 176 | "numoutlets" : 1, 177 | "outlettype" : [ "" ], 178 | "patching_rect" : [ 197.875, 110.0, 87.0, 22.0 ], 179 | "text" : "prepend target" 180 | } 181 | 182 | } 183 | , { 184 | "box" : { 185 | "id" : "obj-11", 186 | "maxclass" : "message", 187 | "numinlets" : 2, 188 | "numoutlets" : 1, 189 | "outlettype" : [ "" ], 190 | "patching_rect" : [ 138.0, 17.0, 85.0, 22.0 ], 191 | "text" : "1 1 1 0 5 5 5 0" 192 | } 193 | 194 | } 195 | , { 196 | "box" : { 197 | "id" : "obj-8", 198 | "maxclass" : "message", 199 | "numinlets" : 2, 200 | "numoutlets" : 1, 201 | "outlettype" : [ "" ], 202 | "patching_rect" : [ 138.0, 110.0, 56.0, 22.0 ], 203 | "text" : "gettarget" 204 | } 205 | 206 | } 207 | , { 208 | "box" : { 209 | "id" : "obj-6", 210 | "maxclass" : "message", 211 | "numinlets" : 2, 212 | "numoutlets" : 1, 213 | "outlettype" : [ "" ], 214 | "patching_rect" : [ 195.0, 232.0, 92.75, 22.0 ] 215 | } 216 | 217 | } 218 | , { 219 | "box" : { 220 | "id" : "obj-4", 221 | "maxclass" : "button", 222 | "numinlets" : 1, 223 | "numoutlets" : 1, 224 | "outlettype" : [ "bang" ], 225 | "parameter_enable" : 0, 226 | "patching_rect" : [ 328.0, 198.0, 24.0, 24.0 ] 227 | } 228 | 229 | } 230 | , { 231 | "box" : { 232 | "id" : "obj-1", 233 | "maxclass" : "newobj", 234 | "numinlets" : 1, 235 | "numoutlets" : 5, 236 | "outlettype" : [ "", "int", "int", "", "bang" ], 237 | "patching_rect" : [ 91.0, 162.0, 256.0, 22.0 ], 238 | "text" : "geneseq" 239 | } 240 | 241 | } 242 | ], 243 | "lines" : [ { 244 | "patchline" : { 245 | "destination" : [ "obj-15", 0 ], 246 | "source" : [ "obj-1", 2 ] 247 | } 248 | 249 | } 250 | , { 251 | "patchline" : { 252 | "destination" : [ "obj-16", 0 ], 253 | "source" : [ "obj-1", 1 ] 254 | } 255 | 256 | } 257 | , { 258 | "patchline" : { 259 | "destination" : [ "obj-20", 1 ], 260 | "source" : [ "obj-1", 0 ] 261 | } 262 | 263 | } 264 | , { 265 | "patchline" : { 266 | "destination" : [ "obj-4", 0 ], 267 | "source" : [ "obj-1", 4 ] 268 | } 269 | 270 | } 271 | , { 272 | "patchline" : { 273 | "destination" : [ "obj-6", 1 ], 274 | "source" : [ "obj-1", 3 ] 275 | } 276 | 277 | } 278 | , { 279 | "patchline" : { 280 | "destination" : [ "obj-24", 0 ], 281 | "source" : [ "obj-11", 0 ] 282 | } 283 | 284 | } 285 | , { 286 | "patchline" : { 287 | "destination" : [ "obj-1", 0 ], 288 | "source" : [ "obj-12", 0 ] 289 | } 290 | 291 | } 292 | , { 293 | "patchline" : { 294 | "destination" : [ "obj-1", 0 ], 295 | "source" : [ "obj-18", 0 ] 296 | } 297 | 298 | } 299 | , { 300 | "patchline" : { 301 | "destination" : [ "obj-1", 0 ], 302 | "source" : [ "obj-22", 0 ] 303 | } 304 | 305 | } 306 | , { 307 | "patchline" : { 308 | "destination" : [ "obj-24", 0 ], 309 | "source" : [ "obj-23", 0 ] 310 | } 311 | 312 | } 313 | , { 314 | "patchline" : { 315 | "destination" : [ "obj-12", 0 ], 316 | "source" : [ "obj-24", 1 ] 317 | } 318 | 319 | } 320 | , { 321 | "patchline" : { 322 | "destination" : [ "obj-18", 0 ], 323 | "order" : 1, 324 | "source" : [ "obj-24", 0 ] 325 | } 326 | 327 | } 328 | , { 329 | "patchline" : { 330 | "destination" : [ "obj-8", 0 ], 331 | "order" : 0, 332 | "source" : [ "obj-24", 0 ] 333 | } 334 | 335 | } 336 | , { 337 | "patchline" : { 338 | "destination" : [ "obj-1", 0 ], 339 | "source" : [ "obj-8", 0 ] 340 | } 341 | 342 | } 343 | ], 344 | "dependency_cache" : [ { 345 | "name" : "geneseq.mxo", 346 | "type" : "iLaX" 347 | } 348 | ], 349 | "autosave" : 0 350 | } 351 | 352 | } 353 | -------------------------------------------------------------------------------- /geneseq.c: -------------------------------------------------------------------------------- 1 | /** 2 | geneseq - a genetic algorithm melody generator/sequencer for Max/MSP 3 | steve meyer 4 | */ 5 | 6 | #include "ext.h" // standard Max include, always required 7 | #include "ext_obex.h" // required for new style Max object 8 | 9 | 10 | #define POPULATION_SIZE 64 // number of individuals that will improvise/evolve toward the target melody 11 | #define SEQ_STEPS 8 // sequence size TODO: make this configurable 12 | #define GENES 8 // sequences will be based on numbers 0-8. 0 is silence, 13 | // 1-8 are scale steps tonic through octave (i.e., 5 = 5th) 14 | #define PARENTS 2 // number of individuals that provide genes from to new children 15 | #define INIT_POP 4 // starter population TODO: not fully implemented 16 | 17 | 18 | /* 19 | An individual improviser. 20 | 21 | An individual is a member of the geneseq population. The first generation's patterns 22 | will be generated randomly from the valid genes. Successive generations are bred 23 | from previous generations. 24 | 25 | All improvisors are scored. The fitness score is an indication of how well the improviser 26 | has adapted/resolved to the target melody after each evolution/breeding. The random score is 27 | "in the right place at the right time." Did the improviser luck out and get the right gig? 28 | */ 29 | struct individual { 30 | t_atom pattern[SEQ_STEPS]; 31 | int fitness_score; 32 | double random_score; 33 | }; 34 | int default_target[] = {1,2,0,3,0,0,5,0}; 35 | int parent_indices[] = {0, 1}; 36 | 37 | 38 | ////////////////////////// object struct 39 | typedef struct _geneseq 40 | { 41 | t_object ob; // the object itself (must be first) 42 | void *m_outlet5; // bang on convergence 43 | void *m_outlet4; // the target pattern 44 | void *m_outlet3; // the current generation 45 | void *m_outlet2; // the best candidate score 46 | void *m_outlet1; // the best candidate pattern 47 | t_atom target[SEQ_STEPS]; 48 | struct individual population[POPULATION_SIZE]; 49 | int generation; 50 | int current_population; 51 | } t_geneseq; 52 | 53 | ///////////////////////// function prototypes 54 | // Max standard functions 55 | void *geneseq_new(t_symbol *s, long argc, t_atom *argv); 56 | void geneseq_free(t_geneseq *x); 57 | void geneseq_assist(t_geneseq *x, void *b, long m, long a, char *s); 58 | 59 | // Max object functions 60 | void geneseq_bang(t_geneseq *x); 61 | void geneseq_target(t_geneseq *x, t_symbol *s, long argc, t_atom *argv); 62 | void geneseq_gettarget(t_geneseq *x, t_symbol *s, long argc, t_atom *argv); 63 | void geneseq_evolve(t_geneseq *x, t_symbol *s, long argc, t_atom *argv); 64 | void seed(t_geneseq *x); 65 | void breed(t_geneseq *x, int breeding_pop, int child_idx); 66 | 67 | // Internal utilities 68 | void unique_sample(int numbers[], int max, int k); 69 | int compare_individuals(const void *a, const void *b); 70 | 71 | //////////////////////// global class pointer variable 72 | void *geneseq_class; 73 | 74 | 75 | void ext_main(void *r) 76 | { 77 | t_class *c; 78 | 79 | c = class_new("geneseq", (method)geneseq_new, (method)geneseq_free, (long)sizeof(t_geneseq), 80 | 0L /* leave NULL!! */, A_GIMME, 0); 81 | 82 | class_addmethod(c, (method)geneseq_assist, "assist", A_CANT, 0); 83 | class_addmethod(c, (method)geneseq_bang, "bang", 0); 84 | class_addmethod(c, (method)geneseq_target, "target", A_GIMME, 0); 85 | class_addmethod(c, (method)geneseq_gettarget, "gettarget", A_GIMME, 0); 86 | class_addmethod(c, (method)geneseq_evolve, "evolve", A_GIMME, 0); 87 | 88 | class_register(CLASS_BOX, c); /* CLASS_NOBOX */ 89 | geneseq_class = c; 90 | 91 | post("I am the geneseq object"); 92 | } 93 | 94 | 95 | void geneseq_bang(t_geneseq *x) 96 | { 97 | outlet_int(x->m_outlet3, x->generation); 98 | outlet_int(x->m_outlet2, x->population[0].fitness_score); 99 | outlet_list(x->m_outlet1, NULL, SEQ_STEPS, x->population[0].pattern); 100 | } 101 | 102 | 103 | /* 104 | Evolve the population by one generation towards convergence with the target melody. 105 | 106 | The population is always in a sorted state. Identify the fittest 10% of the population 107 | to move to the next generation. Then use the fittest 50% of the population to breed 108 | new children to refill the remaining population. Finally resort the population and 109 | generate outputs: bang if converged and send out best candidate, etc. 110 | */ 111 | void geneseq_evolve(t_geneseq *x, t_symbol *s, long argc, t_atom *argv) 112 | { 113 | if (x->population[0].fitness_score != 0) 114 | { 115 | x->generation += 1; 116 | int num_fittest = ceil((x->current_population * 10) / 100.0); 117 | for (int i = 0; i < num_fittest; i++) 118 | x->population[i].random_score = random() / (double) RAND_MAX; 119 | 120 | int breeding_pop = x->current_population / 2; 121 | int num_children = x->current_population * 2; 122 | int max = num_children; 123 | if (num_children + num_fittest > POPULATION_SIZE) 124 | { 125 | num_children = POPULATION_SIZE - num_fittest; 126 | max = POPULATION_SIZE; 127 | } 128 | 129 | x->current_population = num_fittest; 130 | for (int i = num_fittest; i < max; i++) 131 | { 132 | breed(x, breeding_pop, i); 133 | x->current_population += 1; 134 | } 135 | qsort(x->population, POPULATION_SIZE, sizeof(struct individual), compare_individuals); 136 | } 137 | 138 | if (x->population[0].fitness_score == 0) outlet_bang(x->m_outlet5); 139 | geneseq_bang(x); 140 | } 141 | 142 | 143 | /* 144 | Given a set of parents breed a new individual for the given child index. 145 | 146 | Breeding population is a count of the number of eligible parents to randomly choose from. 147 | Once two parent indices are chosen the child's new pattern is based on a random selection 148 | of parent 1's or parent 2's genes. 10% of the time the genes are the result of a 149 | mutation. 150 | */ 151 | void breed(t_geneseq *x, int breeding_pop, int child_idx) 152 | { 153 | unique_sample(parent_indices, breeding_pop, PARENTS); 154 | struct individual parent1 = x->population[parent_indices[0]]; 155 | struct individual parent2 = x->population[parent_indices[1]]; 156 | struct individual *child = &x->population[child_idx]; 157 | 158 | int score = 0; 159 | int step_val; 160 | for (int step = 0; step < SEQ_STEPS; step++) 161 | { 162 | // Randomly sample from the parents 163 | if (rand() % 2 == 0) 164 | step_val = atom_getlong(parent1.pattern+step); 165 | else 166 | step_val = atom_getlong(parent2.pattern+step); 167 | 168 | // About 10% of the time, mutations occur 169 | if (rand() % 100 > 90) 170 | step_val = random() % (GENES + 1); 171 | 172 | atom_setlong(child->pattern+step, step_val); 173 | if (step_val != atom_getlong(x->target+step)) 174 | score += 1; 175 | } 176 | child->fitness_score = score; 177 | child->random_score = rand() / (double) RAND_MAX; 178 | } 179 | 180 | 181 | /* 182 | Update the target sequence to be used for the population. Will also regenerate/reseed the population. 183 | */ 184 | void geneseq_target(t_geneseq *x, t_symbol *s, long argc, t_atom *argv) 185 | { 186 | if (argc != SEQ_STEPS) 187 | { 188 | error("sequence not %i steps", SEQ_STEPS); 189 | } 190 | else 191 | { 192 | for (int step = 0; step < SEQ_STEPS; step++) 193 | atom_setlong(x->target+step, atom_getlong(argv+step)); 194 | 195 | seed(x); 196 | } 197 | } 198 | 199 | 200 | /* 201 | Generate an entirely new population of individuals. 202 | */ 203 | void seed(t_geneseq *x) 204 | { 205 | int score, step_val; 206 | for (int i = 0; i < POPULATION_SIZE; i++) 207 | { 208 | score = 0; 209 | for (int step = 0; step < SEQ_STEPS; step++) 210 | { 211 | step_val = random() % (GENES + 1); 212 | atom_setlong(x->population[i].pattern+step, step_val); 213 | if (step_val != atom_getlong(x->target+step)) 214 | score += 1; 215 | } 216 | x->population[i].fitness_score = score; 217 | x->population[i].random_score = random() / (double) RAND_MAX; 218 | } 219 | qsort(x->population, POPULATION_SIZE, sizeof(struct individual), compare_individuals); 220 | } 221 | 222 | 223 | void geneseq_gettarget(t_geneseq *x, t_symbol *s, long argc, t_atom *argv) 224 | { 225 | outlet_list(x->m_outlet4, NULL, SEQ_STEPS, x->target); 226 | } 227 | 228 | 229 | void geneseq_assist(t_geneseq *x, void *b, long io, long index, char *s) 230 | { 231 | switch(io) 232 | { 233 | case ASSIST_INLET: 234 | sprintf(s, "bang outputs best pattern, score, generation; 'evolve' breeds, then outputs pattern, score, generation; 'target' + int list sets the target sequence; 'gettarget' sends pattern's current target"); 235 | break; 236 | case ASSIST_OUTLET: 237 | switch(index) 238 | { 239 | case 0: 240 | sprintf(s, "outlet 1: best sequence pattern"); 241 | break; 242 | case 1: 243 | sprintf(s, "outlet 2: best sequence fitness"); 244 | break; 245 | case 2: 246 | sprintf(s, "outlet 3: current generation"); 247 | break; 248 | case 3: 249 | sprintf(s, "outlet 4: list for target sequence when gettarget message received"); 250 | break; 251 | case 4: 252 | sprintf(s, "outlet 5: bang on convergence"); 253 | break; 254 | } 255 | } 256 | } 257 | 258 | 259 | void geneseq_free(t_geneseq *x) 260 | { 261 | sysmem_freeptr(x->population); 262 | } 263 | 264 | 265 | void *geneseq_new(t_symbol *s, long argc, t_atom *argv) 266 | { 267 | t_geneseq *x = NULL; 268 | x = (t_geneseq *)object_alloc(geneseq_class); 269 | atom_setlong(x->generation, 0); 270 | x->current_population = INIT_POP; 271 | 272 | x->m_outlet5 = bangout((t_object *)x); 273 | x->m_outlet4 = outlet_new((t_object *)x, NULL); 274 | x->m_outlet3 = intout((t_object *)x); 275 | x->m_outlet2 = intout((t_object *)x); 276 | x->m_outlet1 = outlet_new((t_object *)x, NULL); 277 | 278 | for (int i = 0; i < SEQ_STEPS; i++) 279 | atom_setlong(x->target+i, default_target[i]); 280 | 281 | seed(x); 282 | 283 | return (x); 284 | } 285 | 286 | 287 | // qsort struct comparision function 288 | int compare_individuals(const void *a, const void *b) 289 | { 290 | struct individual *ia = (struct individual *)a; 291 | struct individual *ib = (struct individual *)b; 292 | double a_sort = ia->fitness_score + ia->random_score; 293 | double b_sort = ib->fitness_score + ib->random_score; 294 | return (int)(1000.f * a_sort - 1000.f * b_sort); 295 | } 296 | 297 | 298 | /* 299 | Insert k ints into numbers[] between 0 and max. 300 | Do not allow duplicates. 301 | */ 302 | void unique_sample(int numbers[], int max, int k) 303 | { 304 | if (k > max) k = max; 305 | 306 | int sample_range[max]; 307 | for (int i = 0; i < max; i++) 308 | sample_range[i] = i; 309 | 310 | int rand_index, tmp; 311 | for (int i = 0; i < k; i++) 312 | { 313 | rand_index = rand() % (max - i); 314 | numbers[i] = sample_range[rand_index]; 315 | 316 | tmp = sample_range[max - 1]; 317 | sample_range[max - 1] = numbers[i]; 318 | sample_range[rand_index] = tmp; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /examples/max/genesequencer.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 1, 7 | "revision" : 3, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 641.0, 225.0, 944.0, 715.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-42", 43 | "maxclass" : "comment", 44 | "numinlets" : 1, 45 | "numoutlets" : 0, 46 | "patching_rect" : [ 409.0, 328.0, 89.75, 20.0 ], 47 | "text" : "Current target" 48 | } 49 | 50 | } 51 | , { 52 | "box" : { 53 | "id" : "obj-41", 54 | "maxclass" : "comment", 55 | "numinlets" : 1, 56 | "numoutlets" : 0, 57 | "patching_rect" : [ 378.0, 273.0, 103.0, 20.0 ], 58 | "text" : "Most fit individual" 59 | } 60 | 61 | } 62 | , { 63 | "box" : { 64 | "id" : "obj-30", 65 | "maxclass" : "comment", 66 | "numinlets" : 1, 67 | "numoutlets" : 0, 68 | "patching_rect" : [ 9.5, 301.0, 242.0, 20.0 ], 69 | "presentation_linecount" : 2, 70 | "text" : "Current Seq Step Value (Scale Degree) =>", 71 | "textjustification" : 2 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "id" : "obj-8", 78 | "maxclass" : "comment", 79 | "numinlets" : 1, 80 | "numoutlets" : 0, 81 | "patching_rect" : [ 401.75, 525.5, 97.0, 20.0 ], 82 | "text" : "<= Scale Root" 83 | } 84 | 85 | } 86 | , { 87 | "box" : { 88 | "id" : "obj-25", 89 | "linecount" : 2, 90 | "maxclass" : "comment", 91 | "numinlets" : 1, 92 | "numoutlets" : 0, 93 | "patching_rect" : [ 207.5, 620.5, 229.0, 33.0 ], 94 | "text" : "DO MORE INTERESTING STUFF WITH YOUR SEQUENCES NOTES HERE..." 95 | } 96 | 97 | } 98 | , { 99 | "box" : { 100 | "id" : "obj-21", 101 | "maxclass" : "comment", 102 | "numinlets" : 1, 103 | "numoutlets" : 0, 104 | "patching_rect" : [ 57.5, 334.0, 194.0, 20.0 ], 105 | "text" : "Drop zeroes cuz they're rests =>", 106 | "textjustification" : 2 107 | } 108 | 109 | } 110 | , { 111 | "box" : { 112 | "id" : "obj-15", 113 | "maxclass" : "newobj", 114 | "numinlets" : 2, 115 | "numoutlets" : 2, 116 | "outlettype" : [ "bang", "" ], 117 | "patching_rect" : [ 257.5, 334.0, 50.0, 22.0 ], 118 | "text" : "select 0" 119 | } 120 | 121 | } 122 | , { 123 | "box" : { 124 | "id" : "obj-29", 125 | "linecount" : 2, 126 | "maxclass" : "comment", 127 | "numinlets" : 1, 128 | "numoutlets" : 0, 129 | "patching_rect" : [ 85.5, 383.5, 201.0, 33.0 ], 130 | "text" : "TRANSLATE SCALE DEGREES INTO MIDI NOTES AT THIS POINT" 131 | } 132 | 133 | } 134 | , { 135 | "box" : { 136 | "id" : "obj-24", 137 | "maxclass" : "comment", 138 | "numinlets" : 1, 139 | "numoutlets" : 0, 140 | "patching_rect" : [ 60.5, 231.0, 191.0, 20.0 ], 141 | "text" : "Current Sequence Step =>", 142 | "textjustification" : 2 143 | } 144 | 145 | } 146 | , { 147 | "box" : { 148 | "id" : "obj-22", 149 | "linecount" : 4, 150 | "maxclass" : "comment", 151 | "numinlets" : 1, 152 | "numoutlets" : 0, 153 | "patching_rect" : [ 725.0, 525.5, 175.0, 60.0 ], 154 | "text" : "<= Target sequences\n Set the sequence, evolve\n towards it, then flip to the\n next one after convergence" 155 | } 156 | 157 | } 158 | , { 159 | "box" : { 160 | "id" : "obj-20", 161 | "linecount" : 3, 162 | "maxclass" : "comment", 163 | "numinlets" : 1, 164 | "numoutlets" : 0, 165 | "patching_rect" : [ 620.0, 394.5, 165.0, 47.0 ], 166 | "text" : "<= Let the target sequence\n play a few times before \n moving to next one." 167 | } 168 | 169 | } 170 | , { 171 | "box" : { 172 | "id" : "obj-13", 173 | "maxclass" : "comment", 174 | "numinlets" : 1, 175 | "numoutlets" : 0, 176 | "patching_rect" : [ 69.5, 588.5, 217.0, 20.0 ], 177 | "text" : "Current Sequence Step MIDI Note =>", 178 | "textjustification" : 2 179 | } 180 | 181 | } 182 | , { 183 | "box" : { 184 | "id" : "obj-11", 185 | "maxclass" : "comment", 186 | "numinlets" : 1, 187 | "numoutlets" : 0, 188 | "patching_rect" : [ 157.5, 430.5, 129.0, 20.0 ], 189 | "text" : "Minor Scale Steps =>", 190 | "textjustification" : 2 191 | } 192 | 193 | } 194 | , { 195 | "box" : { 196 | "id" : "obj-4", 197 | "maxclass" : "number", 198 | "numinlets" : 1, 199 | "numoutlets" : 2, 200 | "outlettype" : [ "", "bang" ], 201 | "parameter_enable" : 0, 202 | "patching_rect" : [ 288.65625, 588.5, 50.0, 22.0 ] 203 | } 204 | 205 | } 206 | , { 207 | "box" : { 208 | "id" : "obj-128", 209 | "maxclass" : "newobj", 210 | "numinlets" : 2, 211 | "numoutlets" : 1, 212 | "outlettype" : [ "int" ], 213 | "patching_rect" : [ 288.65625, 556.5, 78.0, 22.0 ], 214 | "text" : "+ 36" 215 | } 216 | 217 | } 218 | , { 219 | "box" : { 220 | "id" : "obj-129", 221 | "maxclass" : "newobj", 222 | "numinlets" : 1, 223 | "numoutlets" : 1, 224 | "outlettype" : [ "" ], 225 | "patching_rect" : [ 347.65625, 492.5, 77.0, 22.0 ], 226 | "text" : "loadmess 48" 227 | } 228 | 229 | } 230 | , { 231 | "box" : { 232 | "format" : 4, 233 | "id" : "obj-130", 234 | "maxclass" : "number", 235 | "numinlets" : 1, 236 | "numoutlets" : 2, 237 | "outlettype" : [ "", "bang" ], 238 | "parameter_enable" : 0, 239 | "patching_rect" : [ 347.65625, 524.5, 50.0, 22.0 ] 240 | } 241 | 242 | } 243 | , { 244 | "box" : { 245 | "id" : "obj-152", 246 | "maxclass" : "number", 247 | "numinlets" : 1, 248 | "numoutlets" : 2, 249 | "outlettype" : [ "", "bang" ], 250 | "parameter_enable" : 0, 251 | "patching_rect" : [ 288.5, 524.5, 50.0, 22.0 ] 252 | } 253 | 254 | } 255 | , { 256 | "box" : { 257 | "id" : "obj-153", 258 | "maxclass" : "newobj", 259 | "numinlets" : 2, 260 | "numoutlets" : 2, 261 | "outlettype" : [ "", "" ], 262 | "patching_rect" : [ 288.5, 492.5, 43.0, 22.0 ], 263 | "text" : "zl.sum" 264 | } 265 | 266 | } 267 | , { 268 | "box" : { 269 | "id" : "obj-155", 270 | "maxclass" : "newobj", 271 | "numinlets" : 2, 272 | "numoutlets" : 2, 273 | "outlettype" : [ "", "" ], 274 | "patching_rect" : [ 288.5, 462.5, 110.5, 22.0 ], 275 | "text" : "zl.slice" 276 | } 277 | 278 | } 279 | , { 280 | "box" : { 281 | "id" : "obj-156", 282 | "maxclass" : "newobj", 283 | "numinlets" : 1, 284 | "numoutlets" : 2, 285 | "outlettype" : [ "bang", "int" ], 286 | "patching_rect" : [ 288.5, 389.0, 110.5, 22.0 ], 287 | "text" : "t b i" 288 | } 289 | 290 | } 291 | , { 292 | "box" : { 293 | "id" : "obj-157", 294 | "maxclass" : "message", 295 | "numinlets" : 2, 296 | "numoutlets" : 1, 297 | "outlettype" : [ "" ], 298 | "patching_rect" : [ 288.5, 430.5, 85.0, 22.0 ], 299 | "text" : "0 2 1 2 2 1 2 2" 300 | } 301 | 302 | } 303 | , { 304 | "box" : { 305 | "id" : "obj-6", 306 | "maxclass" : "button", 307 | "numinlets" : 1, 308 | "numoutlets" : 1, 309 | "outlettype" : [ "bang" ], 310 | "parameter_enable" : 0, 311 | "patching_rect" : [ 415.0, 172.0, 24.0, 24.0 ] 312 | } 313 | 314 | } 315 | , { 316 | "box" : { 317 | "id" : "obj-40", 318 | "maxclass" : "newobj", 319 | "numinlets" : 1, 320 | "numoutlets" : 1, 321 | "outlettype" : [ "bang" ], 322 | "patching_rect" : [ 477.75, 423.5, 58.0, 22.0 ], 323 | "text" : "loadbang" 324 | } 325 | 326 | } 327 | , { 328 | "box" : { 329 | "id" : "obj-39", 330 | "maxclass" : "newobj", 331 | "numinlets" : 2, 332 | "numoutlets" : 2, 333 | "outlettype" : [ "bang", "" ], 334 | "patching_rect" : [ 545.0, 423.5, 50.0, 22.0 ], 335 | "text" : "select 4" 336 | } 337 | 338 | } 339 | , { 340 | "box" : { 341 | "id" : "obj-38", 342 | "maxclass" : "newobj", 343 | "numinlets" : 5, 344 | "numoutlets" : 4, 345 | "outlettype" : [ "int", "", "", "int" ], 346 | "patching_rect" : [ 545.0, 394.5, 69.0, 22.0 ], 347 | "text" : "counter 1 4" 348 | } 349 | 350 | } 351 | , { 352 | "box" : { 353 | "id" : "obj-37", 354 | "maxclass" : "newobj", 355 | "numinlets" : 1, 356 | "numoutlets" : 3, 357 | "outlettype" : [ "bang", "bang", "" ], 358 | "patching_rect" : [ 415.0, 141.0, 149.0, 22.0 ], 359 | "text" : "t b b l" 360 | } 361 | 362 | } 363 | , { 364 | "box" : { 365 | "id" : "obj-36", 366 | "maxclass" : "newobj", 367 | "numinlets" : 1, 368 | "numoutlets" : 1, 369 | "outlettype" : [ "" ], 370 | "patching_rect" : [ 545.0, 172.0, 87.0, 22.0 ], 371 | "text" : "prepend target" 372 | } 373 | 374 | } 375 | , { 376 | "box" : { 377 | "id" : "obj-35", 378 | "maxclass" : "newobj", 379 | "numinlets" : 0, 380 | "numoutlets" : 1, 381 | "outlettype" : [ "" ], 382 | "patching_rect" : [ 415.0, 109.0, 90.0, 22.0 ], 383 | "text" : "r update_target" 384 | } 385 | 386 | } 387 | , { 388 | "box" : { 389 | "id" : "obj-34", 390 | "maxclass" : "newobj", 391 | "numinlets" : 1, 392 | "numoutlets" : 0, 393 | "patching_rect" : [ 545.0, 564.5, 92.0, 22.0 ], 394 | "text" : "s update_target" 395 | } 396 | 397 | } 398 | , { 399 | "box" : { 400 | "id" : "obj-33", 401 | "maxclass" : "message", 402 | "numinlets" : 2, 403 | "numoutlets" : 1, 404 | "outlettype" : [ "" ], 405 | "patching_rect" : [ 634.0, 525.5, 85.0, 22.0 ], 406 | "text" : "1 0 0 0 8 0 5 0" 407 | } 408 | 409 | } 410 | , { 411 | "box" : { 412 | "id" : "obj-31", 413 | "maxclass" : "message", 414 | "numinlets" : 2, 415 | "numoutlets" : 1, 416 | "outlettype" : [ "" ], 417 | "patching_rect" : [ 545.0, 525.5, 85.0, 22.0 ], 418 | "text" : "1 1 0 3 5 5 0 7" 419 | } 420 | 421 | } 422 | , { 423 | "box" : { 424 | "id" : "obj-28", 425 | "maxclass" : "newobj", 426 | "numinlets" : 3, 427 | "numoutlets" : 3, 428 | "outlettype" : [ "bang", "bang", "" ], 429 | "patching_rect" : [ 545.0, 492.5, 197.0, 22.0 ], 430 | "text" : "select 1 2" 431 | } 432 | 433 | } 434 | , { 435 | "box" : { 436 | "id" : "obj-27", 437 | "maxclass" : "newobj", 438 | "numinlets" : 5, 439 | "numoutlets" : 4, 440 | "outlettype" : [ "int", "", "", "int" ], 441 | "patching_rect" : [ 545.0, 462.5, 69.0, 22.0 ], 442 | "text" : "counter 1 2" 443 | } 444 | 445 | } 446 | , { 447 | "box" : { 448 | "id" : "obj-26", 449 | "maxclass" : "message", 450 | "numinlets" : 2, 451 | "numoutlets" : 1, 452 | "outlettype" : [ "" ], 453 | "patching_rect" : [ 480.0, 172.0, 56.0, 22.0 ], 454 | "text" : "gettarget" 455 | } 456 | 457 | } 458 | , { 459 | "box" : { 460 | "id" : "obj-23", 461 | "maxclass" : "message", 462 | "numinlets" : 2, 463 | "numoutlets" : 1, 464 | "outlettype" : [ "" ], 465 | "patching_rect" : [ 409.0, 352.0, 106.5, 22.0 ], 466 | "text" : "1 0 0 0 8 0 5 0" 467 | } 468 | 469 | } 470 | , { 471 | "box" : { 472 | "id" : "obj-19", 473 | "maxclass" : "button", 474 | "numinlets" : 1, 475 | "numoutlets" : 1, 476 | "outlettype" : [ "bang" ], 477 | "parameter_enable" : 0, 478 | "patching_rect" : [ 545.0, 273.0, 24.0, 24.0 ] 479 | } 480 | 481 | } 482 | , { 483 | "box" : { 484 | "id" : "obj-18", 485 | "maxclass" : "newobj", 486 | "numinlets" : 2, 487 | "numoutlets" : 2, 488 | "outlettype" : [ "bang", "" ], 489 | "patching_rect" : [ 351.0, 141.0, 50.0, 22.0 ], 490 | "text" : "select 7" 491 | } 492 | 493 | } 494 | , { 495 | "box" : { 496 | "id" : "obj-17", 497 | "maxclass" : "newobj", 498 | "numinlets" : 1, 499 | "numoutlets" : 2, 500 | "outlettype" : [ "int", "int" ], 501 | "patching_rect" : [ 247.0, 105.0, 29.5, 22.0 ], 502 | "text" : "t i i" 503 | } 504 | 505 | } 506 | , { 507 | "box" : { 508 | "id" : "obj-16", 509 | "maxclass" : "message", 510 | "numinlets" : 2, 511 | "numoutlets" : 1, 512 | "outlettype" : [ "" ], 513 | "patching_rect" : [ 351.0, 173.0, 43.0, 22.0 ], 514 | "text" : "evolve" 515 | } 516 | 517 | } 518 | , { 519 | "box" : { 520 | "id" : "obj-14", 521 | "maxclass" : "number", 522 | "numinlets" : 1, 523 | "numoutlets" : 2, 524 | "outlettype" : [ "", "bang" ], 525 | "parameter_enable" : 0, 526 | "patching_rect" : [ 257.5, 301.0, 50.0, 22.0 ] 527 | } 528 | 529 | } 530 | , { 531 | "box" : { 532 | "id" : "obj-12", 533 | "maxclass" : "toggle", 534 | "numinlets" : 1, 535 | "numoutlets" : 1, 536 | "outlettype" : [ "int" ], 537 | "parameter_enable" : 0, 538 | "patching_rect" : [ 247.0, 10.0, 24.0, 24.0 ] 539 | } 540 | 541 | } 542 | , { 543 | "box" : { 544 | "id" : "obj-10", 545 | "maxclass" : "newobj", 546 | "numinlets" : 2, 547 | "numoutlets" : 1, 548 | "outlettype" : [ "bang" ], 549 | "patching_rect" : [ 247.0, 43.0, 63.0, 22.0 ], 550 | "text" : "metro 250" 551 | } 552 | 553 | } 554 | , { 555 | "box" : { 556 | "id" : "obj-9", 557 | "maxclass" : "number", 558 | "numinlets" : 1, 559 | "numoutlets" : 2, 560 | "outlettype" : [ "", "bang" ], 561 | "parameter_enable" : 0, 562 | "patching_rect" : [ 257.5, 231.0, 50.0, 22.0 ] 563 | } 564 | 565 | } 566 | , { 567 | "box" : { 568 | "id" : "obj-7", 569 | "maxclass" : "message", 570 | "numinlets" : 2, 571 | "numoutlets" : 1, 572 | "outlettype" : [ "" ], 573 | "patching_rect" : [ 378.0, 301.0, 120.75, 22.0 ], 574 | "text" : "1 0 0 0 8 0 0 0" 575 | } 576 | 577 | } 578 | , { 579 | "box" : { 580 | "id" : "obj-5", 581 | "maxclass" : "newobj", 582 | "numinlets" : 5, 583 | "numoutlets" : 4, 584 | "outlettype" : [ "int", "", "", "int" ], 585 | "patching_rect" : [ 247.0, 73.0, 69.0, 22.0 ], 586 | "text" : "counter 0 7" 587 | } 588 | 589 | } 590 | , { 591 | "box" : { 592 | "id" : "obj-2", 593 | "maxclass" : "newobj", 594 | "numinlets" : 2, 595 | "numoutlets" : 2, 596 | "outlettype" : [ "", "" ], 597 | "patching_rect" : [ 257.5, 273.0, 112.5, 22.0 ], 598 | "text" : "zl.lookup" 599 | } 600 | 601 | } 602 | , { 603 | "box" : { 604 | "id" : "obj-1", 605 | "maxclass" : "newobj", 606 | "numinlets" : 1, 607 | "numoutlets" : 5, 608 | "outlettype" : [ "", "int", "int", "", "bang" ], 609 | "patching_rect" : [ 351.0, 231.0, 213.0, 22.0 ], 610 | "text" : "geneseq" 611 | } 612 | 613 | } 614 | ], 615 | "lines" : [ { 616 | "patchline" : { 617 | "destination" : [ "obj-19", 0 ], 618 | "source" : [ "obj-1", 4 ] 619 | } 620 | 621 | } 622 | , { 623 | "patchline" : { 624 | "destination" : [ "obj-2", 1 ], 625 | "order" : 1, 626 | "source" : [ "obj-1", 0 ] 627 | } 628 | 629 | } 630 | , { 631 | "patchline" : { 632 | "destination" : [ "obj-23", 1 ], 633 | "source" : [ "obj-1", 3 ] 634 | } 635 | 636 | } 637 | , { 638 | "patchline" : { 639 | "destination" : [ "obj-7", 1 ], 640 | "midpoints" : [ 360.5, 262.5, 489.25, 262.5 ], 641 | "order" : 0, 642 | "source" : [ "obj-1", 0 ] 643 | } 644 | 645 | } 646 | , { 647 | "patchline" : { 648 | "destination" : [ "obj-5", 0 ], 649 | "source" : [ "obj-10", 0 ] 650 | } 651 | 652 | } 653 | , { 654 | "patchline" : { 655 | "destination" : [ "obj-10", 0 ], 656 | "source" : [ "obj-12", 0 ] 657 | } 658 | 659 | } 660 | , { 661 | "patchline" : { 662 | "destination" : [ "obj-4", 0 ], 663 | "source" : [ "obj-128", 0 ] 664 | } 665 | 666 | } 667 | , { 668 | "patchline" : { 669 | "destination" : [ "obj-130", 0 ], 670 | "source" : [ "obj-129", 0 ] 671 | } 672 | 673 | } 674 | , { 675 | "patchline" : { 676 | "destination" : [ "obj-128", 1 ], 677 | "source" : [ "obj-130", 0 ] 678 | } 679 | 680 | } 681 | , { 682 | "patchline" : { 683 | "destination" : [ "obj-15", 0 ], 684 | "source" : [ "obj-14", 0 ] 685 | } 686 | 687 | } 688 | , { 689 | "patchline" : { 690 | "destination" : [ "obj-156", 0 ], 691 | "source" : [ "obj-15", 1 ] 692 | } 693 | 694 | } 695 | , { 696 | "patchline" : { 697 | "destination" : [ "obj-128", 0 ], 698 | "source" : [ "obj-152", 0 ] 699 | } 700 | 701 | } 702 | , { 703 | "patchline" : { 704 | "destination" : [ "obj-152", 0 ], 705 | "source" : [ "obj-153", 0 ] 706 | } 707 | 708 | } 709 | , { 710 | "patchline" : { 711 | "destination" : [ "obj-153", 0 ], 712 | "source" : [ "obj-155", 0 ] 713 | } 714 | 715 | } 716 | , { 717 | "patchline" : { 718 | "destination" : [ "obj-155", 1 ], 719 | "source" : [ "obj-156", 1 ] 720 | } 721 | 722 | } 723 | , { 724 | "patchline" : { 725 | "destination" : [ "obj-157", 0 ], 726 | "source" : [ "obj-156", 0 ] 727 | } 728 | 729 | } 730 | , { 731 | "patchline" : { 732 | "destination" : [ "obj-155", 0 ], 733 | "source" : [ "obj-157", 0 ] 734 | } 735 | 736 | } 737 | , { 738 | "patchline" : { 739 | "destination" : [ "obj-1", 0 ], 740 | "source" : [ "obj-16", 0 ] 741 | } 742 | 743 | } 744 | , { 745 | "patchline" : { 746 | "destination" : [ "obj-18", 0 ], 747 | "midpoints" : [ 256.5, 133.5, 360.5, 133.5 ], 748 | "source" : [ "obj-17", 0 ] 749 | } 750 | 751 | } 752 | , { 753 | "patchline" : { 754 | "destination" : [ "obj-9", 0 ], 755 | "source" : [ "obj-17", 1 ] 756 | } 757 | 758 | } 759 | , { 760 | "patchline" : { 761 | "destination" : [ "obj-16", 0 ], 762 | "source" : [ "obj-18", 0 ] 763 | } 764 | 765 | } 766 | , { 767 | "patchline" : { 768 | "destination" : [ "obj-38", 0 ], 769 | "source" : [ "obj-19", 0 ] 770 | } 771 | 772 | } 773 | , { 774 | "patchline" : { 775 | "destination" : [ "obj-14", 0 ], 776 | "source" : [ "obj-2", 0 ] 777 | } 778 | 779 | } 780 | , { 781 | "patchline" : { 782 | "destination" : [ "obj-1", 0 ], 783 | "midpoints" : [ 489.5, 217.0, 360.5, 217.0 ], 784 | "source" : [ "obj-26", 0 ] 785 | } 786 | 787 | } 788 | , { 789 | "patchline" : { 790 | "destination" : [ "obj-28", 0 ], 791 | "source" : [ "obj-27", 0 ] 792 | } 793 | 794 | } 795 | , { 796 | "patchline" : { 797 | "destination" : [ "obj-31", 0 ], 798 | "source" : [ "obj-28", 0 ] 799 | } 800 | 801 | } 802 | , { 803 | "patchline" : { 804 | "destination" : [ "obj-33", 0 ], 805 | "source" : [ "obj-28", 1 ] 806 | } 807 | 808 | } 809 | , { 810 | "patchline" : { 811 | "destination" : [ "obj-34", 0 ], 812 | "source" : [ "obj-31", 0 ] 813 | } 814 | 815 | } 816 | , { 817 | "patchline" : { 818 | "destination" : [ "obj-34", 0 ], 819 | "source" : [ "obj-33", 0 ] 820 | } 821 | 822 | } 823 | , { 824 | "patchline" : { 825 | "destination" : [ "obj-37", 0 ], 826 | "source" : [ "obj-35", 0 ] 827 | } 828 | 829 | } 830 | , { 831 | "patchline" : { 832 | "destination" : [ "obj-1", 0 ], 833 | "midpoints" : [ 554.5, 225.0, 360.5, 225.0 ], 834 | "source" : [ "obj-36", 0 ] 835 | } 836 | 837 | } 838 | , { 839 | "patchline" : { 840 | "destination" : [ "obj-26", 0 ], 841 | "source" : [ "obj-37", 1 ] 842 | } 843 | 844 | } 845 | , { 846 | "patchline" : { 847 | "destination" : [ "obj-36", 0 ], 848 | "source" : [ "obj-37", 2 ] 849 | } 850 | 851 | } 852 | , { 853 | "patchline" : { 854 | "destination" : [ "obj-6", 0 ], 855 | "source" : [ "obj-37", 0 ] 856 | } 857 | 858 | } 859 | , { 860 | "patchline" : { 861 | "destination" : [ "obj-39", 0 ], 862 | "source" : [ "obj-38", 0 ] 863 | } 864 | 865 | } 866 | , { 867 | "patchline" : { 868 | "destination" : [ "obj-27", 0 ], 869 | "source" : [ "obj-39", 0 ] 870 | } 871 | 872 | } 873 | , { 874 | "patchline" : { 875 | "destination" : [ "obj-27", 0 ], 876 | "midpoints" : [ 487.25, 452.5, 554.5, 452.5 ], 877 | "source" : [ "obj-40", 0 ] 878 | } 879 | 880 | } 881 | , { 882 | "patchline" : { 883 | "destination" : [ "obj-17", 0 ], 884 | "source" : [ "obj-5", 0 ] 885 | } 886 | 887 | } 888 | , { 889 | "patchline" : { 890 | "destination" : [ "obj-1", 0 ], 891 | "midpoints" : [ 424.5, 210.0, 360.5, 210.0 ], 892 | "source" : [ "obj-6", 0 ] 893 | } 894 | 895 | } 896 | , { 897 | "patchline" : { 898 | "destination" : [ "obj-2", 0 ], 899 | "source" : [ "obj-9", 0 ] 900 | } 901 | 902 | } 903 | ], 904 | "dependency_cache" : [ { 905 | "name" : "geneseq.mxo", 906 | "type" : "iLaX" 907 | } 908 | ], 909 | "autosave" : 0 910 | } 911 | 912 | } 913 | --------------------------------------------------------------------------------