///|
pub(all) enum ContourStep {
Rising
Falling
Repeated
LeapUp
LeapDown
} derive(Debug, Eq, ToJson)
///|
pub(all) enum IntervalSize {
Unison
Step
Third
Fourth
Fifth
Sixth
Seventh
Octave
Compound
} derive(Debug, Eq, ToJson)
///|
pub(all) struct ContourSummary {
notes : Int
rising : Int
falling : Int
repeated : Int
leaps : Int
turning_points : Int
first_pitch : Int
last_pitch : Int
range : Int
} derive(Debug, Eq, ToJson)
///|
pub(all) struct TurningPoint {
index : Int
pitch : Int
before : Direction
after : Direction
} derive(Debug, Eq, ToJson)
///|
pub fn interval_size(semitones : Int) -> IntervalSize {
let value = if semitones < 0 { -semitones } else { semitones }
if value == 0 {
Unison
} else if value <= 2 {
Step
} else if value <= 4 {
Third
} else if value <= 5 {
Fourth
} else if value <= 7 {
Fifth
} else if value <= 9 {
Sixth
} else if value <= 11 {
Seventh
} else if value <= 12 {
Octave
} else {
Compound
}
}
///|
pub fn is_step(semitones : Int) -> Bool {
let value = if semitones < 0 { -semitones } else { semitones }
value > 0 && value <= 2
}
///|
pub fn is_leap(semitones : Int) -> Bool {
let value = if semitones < 0 { -semitones } else { semitones }
value > 2
}
///|
pub fn contour_step(left : Note, right : Note) -> ContourStep {
let delta = right.midi - left.midi
if delta == 0 {
Repeated
} else if delta > 2 {
LeapUp
} else if delta < -2 {
LeapDown
} else if delta > 0 {
Rising
} else {
Falling
}
}
///|
pub fn contour_steps(notes : Array[Note]) -> Array[ContourStep] {
let result : Array[ContourStep] = []
if notes.length() < 2 {
return result
}
for i in 1.. Array[TurningPoint] {
let result : Array[TurningPoint] = []
if notes.length() < 3 {
return result
}
for i in 1..<(notes.length() - 1) {
let before = direction(notes[i - 1], notes[i])
let after = direction(notes[i], notes[i + 1])
if before != Same && after != Same && before != after {
result.push({ index: i, pitch: notes[i].midi, before, after })
}
}
result
}
///|
pub fn contour_label(step : ContourStep) -> String {
match step {
Rising => "rise"
Falling => "fall"
Repeated => "repeat"
LeapUp => "leap-up"
LeapDown => "leap-down"
}
}
///|
pub fn count_contour(notes : Array[Note], step : ContourStep) -> Int {
let mut result = 0
for current in contour_steps(notes) {
if current == step {
result += 1
}
}
result
}
///|
pub fn count_direction(notes : Array[Note], direction_value : Direction) -> Int {
let mut result = 0
if notes.length() < 2 {
return result
}
for i in 1.. Array[Int] {
let result : Array[Int] = []
if notes.length() == 0 {
return result
}
let mut run = 1
for i in 1.. Int {
let mut result = 0
for length in repeated_run_lengths(notes) {
if length > result {
result = length
}
}
result
}
///|
pub fn contour_range(notes : Array[Note]) -> Int {
if notes.length() == 0 {
return 0
}
let mut low = notes[0].midi
let mut high = notes[0].midi
for note in notes {
if note.midi < low {
low = note.midi
}
if note.midi > high {
high = note.midi
}
}
high - low
}
///|
pub fn contour_summary(notes : Array[Note]) -> ContourSummary {
let steps = contour_steps(notes)
let turns = turning_points(notes)
let first = first_pitch(notes)
let last = last_pitch(notes)
let mut rising = 0
let mut falling = 0
let mut repeated = 0
let mut leaps = 0
for step in steps {
match step {
Rising => rising += 1
Falling => falling += 1
Repeated => repeated += 1
LeapUp | LeapDown => leaps += 1
}
}
{
notes: notes.length(),
rising,
falling,
repeated,
leaps,
turning_points: turns.length(),
first_pitch: match first {
Some(value) => value
None => 0
},
last_pitch: match last {
Some(value) => value
None => 0
},
range: contour_range(notes),
}
}
///|
pub fn contour_balance(notes : Array[Note]) -> Int {
let summary = contour_summary(notes)
(summary.rising - summary.falling) *
100 /
(if summary.notes == 0 { 1 } else { summary.notes })
}
///|
pub fn is_arch_shape(notes : Array[Note]) -> Bool {
let turns = turning_points(notes)
if turns.length() != 1 || notes.length() < 3 {
return false
}
turns[0].before == Up && turns[0].after == Down
}
///|
pub fn is_valley_shape(notes : Array[Note]) -> Bool {
let turns = turning_points(notes)
if turns.length() != 1 || notes.length() < 3 {
return false
}
turns[0].before == Down && turns[0].after == Up
}
///|
pub fn direction_name(value : Direction) -> String {
match value {
Up => "up"
Down => "down"
Same => "same"
}
}
///|
pub fn interval_size_name(value : IntervalSize) -> String {
match value {
Unison => "unison"
Step => "step"
Third => "third"
Fourth => "fourth"
Fifth => "fifth"
Sixth => "sixth"
Seventh => "seventh"
Octave => "octave"
Compound => "compound"
}
}
///|
pub fn melodic_interval_histogram(notes : Array[Note]) -> Array[Int] {
let result = Array::make(13, 0)
if notes.length() < 2 {
return result
}
for i in 1.. 12 { 12 } else { value }
result[index] += 1
}
result
}
///|
pub fn average_interval(notes : Array[Note]) -> Int {
if notes.length() < 2 {
return 0
}
let mut total = 0
for i in 1.. Int {
let n = if cantus.length() < counter.length() {
cantus.length()
} else {
counter.length()
}
if n < 2 {
return 0
}
let mut result = 0
for i in 1..