Skip to content

Instantly share code, notes, and snippets.

@cyriux
Created October 23, 2016 22:09
Show Gist options
  • Save cyriux/ff67a25132a695658ea26a4428f804c4 to your computer and use it in GitHub Desktop.
Save cyriux/ff67a25132a695658ea26a4428f804c4 to your computer and use it in GitHub Desktop.
Generative-music bluff code from generic patterns manipulations (clone & transform, recombine, group), with a concept of distance (similarity, expected to be not too high & not too low) between the patterns.
package com.martraire.generativemusic;
import static org.apache.commons.lang3.StringUtils.getLevenshteinDistance;
import static org.apache.commons.lang3.StringUtils.join;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* An ordered sequence of values that may represent the notes, beats or velocity
* accents in a melody, timbre changes in a beat, or even the structure of a
* song. A pattern is meant to be cloned, mutated, appended to other patterns to
* form new patterns. A pattern may also describe how to re-combine other
* patterns, and may itself describe a sequence of transformations to be applied
* to a pattern.
*
* A pattern is agnostic to any given musical system. However it is meant to be
* translated into notes, beats etc. within a chosen musical system (scales,
* rhythms grids etc.).
*
* For example, we may have: "1 2 2 3 = D Eb Eb F",
* "1 0 0 1 0 0 1 0 0 0 1 = x..x..x....x", or
* "1 2 1 3 1 2 1 4 1 = kick snare kick clap kick snare kick power-snare", or
* "0 2 1 3 1 1 4 1 1 5 = intro verse chorus verse 2 chorus chorus bridge chorus chorus outro"
*
*
* Generative music may be created in a few passes: 1/ generate a few base
* patterns, plus some transformation-combination-grouping patterns 2/ transform
* each of the patterns according to transformation patterns, resulting into
* more patterns 3/ combine all the resulting patterns according to the
* combination patterns 4/ group some of these patterns into groups into another
* pool, according to the grouping patterns.
*
* Then translate these groups through a given musical system. For examples,
* each group may correspond to a track where the first pattern describes notes
* in a scale, and the second pattern describes the beat on the grid. One
* special group may describe the song arrangement.
*/
public class Pattern implements Iterable<Integer> {
private final List<Integer> values;
public Pattern(List<Integer> values) {
this.values = values;
}
public Pattern(Integer... values) {
this(Arrays.asList(values));
}
public Pattern(Stream<Integer> stream) {
this(stream.collect(Collectors.toList()));
}
@Override
public Iterator<Integer> iterator() {
return values.iterator();
}
public Pattern mutate(MutationType mutationType, int times) {
Pattern mutated = this;
while (times-- > 0) {
mutated = mutated.mutate(mutationType);
}
return mutated;
}
/**
* Transforms the given pattern according to this pattern acting as the
* recipe for the sequence of successive mutations (mutate), where each
* value corresponds to a type of mutation to apply.
*/
public Pattern Transform(Pattern pattern) {
final List<MutationType> mutations = MutationType.fromIntegerSequence(values);
Pattern mutated = pattern;
for (MutationType mutationType : mutations) {
mutated = mutated.mutate(mutationType);
}
return mutated;
}
public Pattern combine(Pattern... patterns) {
return combine(Arrays.asList(patterns));
}
/**
* Combines some of the given patterns together according to this pattern
* acting as the recipe for the successive recombination (append), where
* each value identifies the pattern to append by its index in the given
* list.
*/
public Pattern combine(List<Pattern> patterns) {
Pattern combined = new Pattern();
final int n = patterns.size();
if (n == 0) {
return combined;
}
for (int i : this) {
combined = combined.append(patterns.get(i % n));
}
return combined;
}
/**
* Groups some of of the given patterns together according to this pattern
* acting as the recipe for the grouping (add to the returned list), where
* each value identifies the pattern to include by its index in the given
* list.
*/
public List<Pattern> group(List<Pattern> patterns) {
List<Pattern> group = new ArrayList<>();
final int n = patterns.size();
if (n == 0) {
return group;
}
for (int i : this) {
group.add(patterns.get(i % n));
}
return group;
}
public Pattern mutate(MutationType mutationType) {
switch (mutationType) {
case NONE:
return this;
case REVERSE:
return reverse();
case ROLL:
return roll();
case SHIFT_UP:
return shift(1);
case SHIFT_DOWN:
return shift(-1);
case STRETCH:
return stretch();
case SHORTEN:
return shorten();
case TRUNCATE:
return truncate();
}
return this;
}
private ArrayList<Integer> toNewList() {
return new ArrayList<Integer>(values);
}
public Pattern reverse() {
final ArrayList<Integer> list = toNewList();
Collections.reverse(list);
return new Pattern(list);
}
public Pattern shift(int offset) {
return new Pattern(values.stream().map(i -> i + offset));
}
public Pattern roll() {
if (values.size() < 2) {
return this;
}
final ArrayList<Integer> list = toNewList();
Integer first = list.remove(0);
list.add(first);
return new Pattern(list);
}
public Pattern stretch() {
final ArrayList<Integer> list = new ArrayList<>();
for (Integer i : values) {
list.add(i);
list.add(i);
}
return new Pattern(list);
}
public Pattern shorten() {
final ArrayList<Integer> list = new ArrayList<>();
int index = 0;
for (Integer i : values) {
if (index++ % 2 == 0) {
list.add(i);
}
}
return new Pattern(list);
}
public Pattern truncate() {
if (values.isEmpty()) {
return this;
}
final ArrayList<Integer> list = toNewList();
list.remove(list.size() - 1);
return new Pattern(list);
}
public Pattern append(Pattern t) {
final ArrayList<Integer> list = toNewList();
list.addAll(t.values);
return new Pattern(list);
}
public int distanceTo(Pattern other) {
if (equals(other)) {
return 0;
}
return getLevenshteinDistance(asString(), other.asString());
}
private String asString() {
return join(values, "");
}
@Override
public int hashCode() {
return values.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Pattern)) {
return false;
}
final Pattern other = (Pattern) obj;
return values.equals(other.values);
}
@Override
public String toString() {
return values.toString();
}
public static enum MutationType {
NONE, REVERSE, TRUNCATE, ROLL, SHIFT_UP, SHIFT_DOWN, STRETCH, SHORTEN;
public final static MutationType fromInteger(int index) {
final MutationType[] values = MutationType.values();
return values[index % values.length];
}
public final static List<MutationType> fromIntegerSequence(final List<Integer> indexes) {
return indexes.stream().map(i -> MutationType.fromInteger(i)).collect(Collectors.toList());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment