Hello everyone! I am menecyco, and recently I resaw a palindrome I made on musescore. It brought me to reseeing what a crab canon is which brought me to...
Make a crab canon generator!
You know, a melody that when played backward follow the leading rules
too AND can be played with its onw
ard version to be in counterpoint 😲?! Well, that's what I am trying to make. It's so complex that making a generator is easier than directly doing one...
Bach was truly a genius to pull off such a feat. Especially since he actually didn't have that much time to do it.
Can you all help me? I actually wrote all the rules of counterpoint I found in tonesavvy, a website with counterpoint rules.
I just need you to help me finish the code or tell me if anything is wrong or should be added!
I would really be thankful to anyone that help me.
This would be truly amazing to see this in fruition!!
Also, it would be great if it exported it as a musecore file at the end.
Enough babbling, here's the file for you to see!
from music21 import *
# Define the rules
consonant_intervals = ["P1", "m3", "M3", "P5", "m6", "M6", "P8"]
tonic_chord_notes = ["C", "E", "G"]
avoid_intervals = ["A2", "d5"]
leap_intervals = ["m3", "M3", "P4", "P5", "m6", "M6"]
max_leap_size = 2
# Generate the counter melody
def generate_crab_canon(melody):
# Create a new empty stream for the counter melody
counter_melody = stream.Part()
# Initialize the first note of the counter melody with the last note of the melody
prev_note = melody[-1]
# Iterate through the notes in the melody backwards
for note in reversed(melody):
# Determine the interval between the melody note and the previous counter melody note
interval = interval.Interval(note, prev_note).name
# Generate a list of possible notes for the counter melody based on the rules
possible_notes = []
for p in range(-7, 8):
n = note.transpose(p)
if n.nameWithOctave not in melody: # ensure note is not already in melody
if interval.Interval(note, n).name in consonant_intervals:
if (interval.direction == interval.Direction.ASCENDING and
n.pitch.name in tonic_chord_notes) or \
(interval.direction == interval.Direction.DESCENDING and
n.pitch.name in [tonic_chord_notes[0], tonic_chord_notes[2]]):
possible_notes.append(n)
# Remove notes that violate the rules
for n in possible_notes.copy():
# Check for parallel fifths or octaves
if interval.Interval(n, prev_note).name in ["P5", "P8"] and \
interval.Interval(n.transpose(interval.Interval(note, melody[0]).semitones), melody[0]).name in ["P5", "P8"]:
possible_notes.remove(n)
# Check for direct octaves or fifths
elif interval.direction == interval.Direction.ASCENDING and \
interval.Interval(prev_note, n).name in ["P5", "P8"] and \
interval.Interval(note, note.transpose(interval.Interval(prev_note, n).semitones)).name in ["P5", "P8"]:
possible_notes.remove(n)
elif interval.direction == interval.Direction.DESCENDING and \
interval.Interval(n, prev_note).name in ["P5", "P8"] and \
interval.Interval(note, note.transpose(interval.Interval(n, prev_note).semitones)).name in ["P5", "P8"]:
possible_notes.remove(n)
# Check for tritones or augmented 2nds
elif interval.Interval(n, note).name in avoid_intervals:
possible_notes.remove(n)
# Select a note from the remaining possible notes, preferring notes that move in steps
selected_note = None
for n in possible_notes:
if interval.Interval(note, n).name in leap_intervals:
if selected_note is None or \
interval.Interval(selected_note, note).name not in leap_intervals or \
interval.Interval(n, note).semitones < interval.Interval(selected_note, note).semitones:
selected_note = n
else:
selected_note = n
break
# If no suitable note was found, select the closest note in the key signature
if selected_note is None:
selected_note = note.transpose(note.pitch.getEnharmonicInScale(scale.MajorScale(
note.pitch.name)).semitones)
# Append the selected note to the counter melody and update the previous note
counter_melody.append(selected_note)
prev_note = selected_note
# Reverse the counter melody and add it to a new empty stream
counter_melody = counter_melody.recurse().reverse()
counter_melody_stream = stream.Stream()
counter_melody_stream.insert(0, counter_melody)
# Resolve the leading tone at the cadence
if counter_melody[-2].
pitch.name == "B":
last_note = counter_melody[-1].transpose(1)
counter_melody[-1] = last_note
# Apply the rule to avoid successive large leaps
for i in range(len(counter_melody) - 2):
if interval.Interval(counter_melody[i], counter_melody[i+1]).name in leap_intervals and \
interval.Interval(counter_melody[i+1], counter_melody[i+2]).name in leap_intervals:
if interval.Interval(counter_melody[i], counter_melody[i+2]).name not in leap_intervals:
leap_size = abs(interval.Interval(counter_melody[i], counter_melody[i+1]).semitones)
if leap_size > max_leap_size:
new_note = counter_melody[i+1].transpose(-1 if interval.Interval(counter_melody[i], counter_melody[i+1]).direction == interval.Direction.ASCENDING else 1)
counter_melody[i+1] = new_note
# Apply the rule to avoid simultaneous leaps in the same direction
for i in range(len(counter_melody) - 1):
if interval.Interval(counter_melody[i], counter_melody[i+1]).direction == \
interval.Interval(melody[i], melody[i+1]).direction:
if abs(interval.Interval(counter_melody[i], counter_melody[i+1]).semitones) > \
abs(interval.Interval(melody[i], melody[i+1]).semitones):
new_note = counter_melody[i+1].transpose(-1 if interval.Interval(counter_melody[i], counter_melody[i+1]).direction == interval.Direction.ASCENDING else 1)
counter_melody[i+1] = new_note
# Apply the rule to allow simultaneous opposite leaps by a third or fourth
for i in range(len(counter_melody) - 1):
if interval.Interval(counter_melody[i], counter_melody[i+1]).semitones in [3, 4]:
if interval.Interval(melody[i], melody[i+1]).semitones == -interval.Interval(counter_melody[i], counter_melody[i+1]).semitones:
continue
elif interval.Interval(counter_melody[i], counter_melody[i+1]).semitones in [-3, -4]:
if interval.Interval(melody[i], melody[i+1]).semitones == -interval.Interval(counter_melody[i], counter_melody[i+1]).semitones:
continue
else:
new_note = counter_melody[i+1].transpose(-1 if interval.Interval(counter_melody[i], counter_melody[i+1]).direction == interval.Direction.ASCENDING else 1)
counter_melody[i+1] = new_note
return counter_melody_stream