use std::iter::Iterator;
use itertools::Itertools;
use petgraph::algo::all_simple_paths;
use petgraph::prelude::NodeIndex;
use petgraph::Graph;
use crate::{Chord, ChordSequence, Distance, Semitones, Voicing, VoicingConfig};
const MAX_DIST: Semitones = 10;
pub struct VoicingGraph {
graph: Graph<Voicing, Distance>,
start_node: NodeIndex,
end_node: NodeIndex,
config: VoicingConfig,
}
impl VoicingGraph {
pub fn new(config: VoicingConfig) -> Self {
let mut graph = Graph::new();
let start_node = graph.add_node(Voicing::default());
let end_node = graph.add_node(Voicing::default());
Self {
graph,
start_node,
end_node,
config,
}
}
fn add_nodes(&mut self, chord: &Chord) -> Vec<NodeIndex> {
let voicings: Vec<Voicing> = chord.voicings(self.config).collect();
voicings
.iter()
.rev()
.map(|voicing| self.graph.add_node(*voicing))
.collect()
}
fn add_edges(&mut self, left_nodes: &[NodeIndex], right_nodes: &[NodeIndex]) {
for (l, r) in left_nodes.iter().cartesian_product(right_nodes.iter()) {
let l_voicing = self.graph[*l];
let r_voicing = self.graph[*r];
let dist = match l {
l if *l == self.start_node => Distance::default(),
_ => l_voicing.distance(r_voicing),
};
if dist.semitone_distance() <= MAX_DIST {
self.graph.add_edge(*l, *r, dist);
}
}
}
pub fn add(&mut self, chord_seq: &ChordSequence) {
let mut prev_nodes = vec![self.start_node];
for chord in chord_seq.chords() {
let nodes = self.add_nodes(chord);
self.add_edges(&prev_nodes, &nodes);
prev_nodes = nodes;
}
for node in prev_nodes.iter() {
self.graph
.add_edge(*node, self.end_node, Distance::default());
}
let end_node = self.end_node;
self.graph
.retain_nodes(|g, n| g.neighbors(n).count() > 0 || n == end_node);
}
pub fn paths(
&self,
max_suggestions: usize,
) -> impl Iterator<Item = (Vec<Voicing>, Distance)> + '_ {
let all_paths = all_simple_paths::<Vec<NodeIndex>, &Graph<Voicing, Distance>>(
&self.graph,
self.start_node,
self.end_node,
0,
None,
);
let weight_sum = |path: &Vec<NodeIndex>| -> Distance {
path.iter()
.tuple_windows()
.filter_map(|(n1, n2)| self.graph.find_edge(*n1, *n2))
.filter_map(|e| self.graph.edge_weight(e))
.sum()
};
let mut paths_with_dist = vec![];
for path in all_paths.sorted_by_key(weight_sum).take(max_suggestions) {
let voicing_path: Vec<_> = path
.iter()
.enumerate()
.filter(|(i, _node)| *i > 0 && *i < path.len() - 1)
.map(|(_i, node)| self.graph[*node])
.collect();
paths_with_dist.push((voicing_path, weight_sum(&path)))
}
paths_with_dist.into_iter()
}
}