///|
pub(all) enum ConsonanceClass {
Perfect
Imperfect
Dissonant
} derive(Debug, Eq, ToJson)
///|
pub(all) struct VerticalEvent {
index : Int
lower : Int
upper : Int
semitones : Int
interval_class : Int
class : ConsonanceClass
inversion : Bool
} derive(Debug, Eq, ToJson)
///|
pub(all) struct HarmonySummary {
events : Int
perfect : Int
imperfect : Int
dissonant : Int
crossings : Int
highest : Int
lowest : Int
} derive(Debug, Eq, ToJson)
///|
pub fn consonance_class(semitones : Int) -> ConsonanceClass {
if is_perfect_consonance(semitones) {
Perfect
} else if is_imperfect_consonance(semitones) {
Imperfect
} else {
Dissonant
}
}
///|
pub fn consonance_class_name(value : ConsonanceClass) -> String {
match value {
Perfect => "perfect"
Imperfect => "imperfect"
Dissonant => "dissonant"
}
}
///|
pub fn vertical_event(lower : Note, upper : Note, index : Int) -> VerticalEvent {
let semitones = upper.midi - lower.midi
{
index,
lower: lower.midi,
upper: upper.midi,
semitones,
interval_class: interval_class(semitones),
class: consonance_class(semitones),
inversion: upper.midi < lower.midi,
}
}
///|
pub fn vertical_events(
lower : Array[Note],
upper : Array[Note],
) -> Array[VerticalEvent] {
let result : Array[VerticalEvent] = []
let n = if lower.length() < upper.length() {
lower.length()
} else {
upper.length()
}
for i in 0.. HarmonySummary {
let events = vertical_events(lower, upper)
let mut perfect = 0
let mut imperfect = 0
let mut dissonant = 0
let mut crossings = 0
let mut highest = 0
let mut lowest = 127
for event in events {
match event.class {
Perfect => perfect += 1
Imperfect => imperfect += 1
Dissonant => dissonant += 1
}
if event.inversion {
crossings += 1
}
if event.upper > highest {
highest = event.upper
}
if event.lower < lowest {
lowest = event.lower
}
}
if events.length() == 0 {
lowest = 0
}
{
events: events.length(),
perfect,
imperfect,
dissonant,
crossings,
highest,
lowest,
}
}
///|
pub fn perfect_ratio(lower : Array[Note], upper : Array[Note]) -> Int {
let summary = harmony_summary(lower, upper)
if summary.events == 0 {
0
} else {
summary.perfect * 100 / summary.events
}
}
///|
pub fn dissonance_ratio(lower : Array[Note], upper : Array[Note]) -> Int {
let summary = harmony_summary(lower, upper)
if summary.events == 0 {
0
} else {
summary.dissonant * 100 / summary.events
}
}
///|
pub fn interval_class_name(value : Int) -> String {
match interval_class(value) {
0 => "unison"
1 => "minor second"
2 => "major second"
3 => "minor third"
4 => "major third"
5 => "perfect fourth"
6 => "tritone"
7 => "perfect fifth"
8 => "minor sixth"
9 => "major sixth"
10 => "minor seventh"
_ => "major seventh"
}
}
///|
pub fn interval_is_stable(value : Int) -> Bool {
is_perfect_consonance(value) || is_imperfect_consonance(value)
}
///|
pub fn interval_tension(value : Int) -> Int {
match consonance_class(value) {
Perfect => 0
Imperfect => 1
Dissonant => 3
}
}
///|
pub fn total_tension(lower : Array[Note], upper : Array[Note]) -> Int {
let mut result = 0
for event in vertical_events(lower, upper) {
result += interval_tension(event.semitones)
}
result
}
///|
pub fn tension_profile(lower : Array[Note], upper : Array[Note]) -> Array[Int] {
let result : Array[Int] = []
for event in vertical_events(lower, upper) {
result.push(interval_tension(event.semitones))
}
result
}
///|
pub fn stable_event_indices(
lower : Array[Note],
upper : Array[Note],
) -> Array[Int] {
let result : Array[Int] = []
for event in vertical_events(lower, upper) {
if interval_is_stable(event.semitones) {
result.push(event.index)
}
}
result
}
///|
pub fn unstable_event_indices(
lower : Array[Note],
upper : Array[Note],
) -> Array[Int] {
let result : Array[Int] = []
for event in vertical_events(lower, upper) {
if !interval_is_stable(event.semitones) {
result.push(event.index)
}
}
result
}
///|
pub fn first_dissonance(lower : Array[Note], upper : Array[Note]) -> Int? {
for event in vertical_events(lower, upper) {
if event.class == Dissonant {
return Some(event.index)
}
}
None
}
///|
pub fn last_dissonance(lower : Array[Note], upper : Array[Note]) -> Int? {
let mut result : Int? = None
for event in vertical_events(lower, upper) {
if event.class == Dissonant {
result = Some(event.index)
}
}
result
}
///|
pub fn consecutive_perfects(
lower : Array[Note],
upper : Array[Note],
) -> Array[Int] {
let result : Array[Int] = []
let mut run = 0
for event in vertical_events(lower, upper) {
if event.class == Perfect {
run += 1
} else {
if run > 1 {
result.push(run)
}
run = 0
}
}
if run > 1 {
result.push(run)
}
result
}
///|
pub fn harmonic_motion_labels(
lower : Array[Note],
upper : Array[Note],
) -> Array[String] {
let result : Array[String] = []
let n = if lower.length() < upper.length() {
lower.length()
} else {
upper.length()
}
if n < 2 {
return result
}
for i in 1.. "parallel"
Similar => "similar"
Contrary => "contrary"
Oblique => "oblique"
Static => "static"
},
)
}
result
}
///|
pub fn harmonic_motion_count(
lower : Array[Note],
upper : Array[Note],
label : String,
) -> Int {
let mut result = 0
for value in harmonic_motion_labels(lower, upper) {
if value == label {
result += 1
}
}
result
}
///|
pub fn voice_order_is_stable(lower : Array[Note], upper : Array[Note]) -> Bool {
crossing_count(lower, upper) == 0
}
///|
pub fn final_sonority_is_valid(
lower : Array[Note],
upper : Array[Note],
) -> Bool {
let n = if lower.length() < upper.length() {
lower.length()
} else {
upper.length()
}
if n == 0 {
false
} else {
interval_class(harmonic_interval(lower[n - 1], upper[n - 1])) == 0 ||
interval_class(harmonic_interval(lower[n - 1], upper[n - 1])) == 7
}
}
///|
pub fn harmonic_signature(lower : Array[Note], upper : Array[Note]) -> String {
let parts : Array[String] = []
for event in vertical_events(lower, upper) {
parts.push("\{event.interval_class}")
}
parts.join("-")
}