1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Parameter hierarchies in VST3 requires you to define units, which are linearly indexed logical
//! units that have a name, a parent, and then a whole bunch of other data like note numbers and
//! MIDI program state. We'll need to implement some of that to convert our list of slash-separated
//! parameter group paths to units.
//!
//! <https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IUnitInfo.html>

use std::collections::{HashMap, HashSet};

use vst3_sys::vst::kRootUnitId;

/// Transforms a map containing parameter hashes and slash-separated paths to an array of VST3 units
/// and a mapping for each parameter hash to a unit (or to `None` if they belong to the root unit).
/// This is conceptually similar to a prefix tree/trie, but since we don't need any of the lookup
/// properties of those data structures we can brute force it by converting all paths to a set,
/// converting the rightmost component of each unique path to a unit, and then assigning the parent
/// units accordingly.
///
/// <https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IUnitInfo.html>
#[derive(Debug)]
pub struct ParamUnits {
    /// The unique units, with flat indices.
    units: Vec<ParamUnit>,
    /// The index of the unit a parameter belongs to, or `None` if it belongs to the root unit.
    ///
    /// NOTE: The returned unit ID is actually one higher than this index because VST3 uses 0 as a
    ///       no-unit value
    unit_id_by_hash: HashMap<u32, i32>,
}

/// A VST3 'unit'. Repurposed for a bunch of things, but we only care about parameter hierarchies.
///
/// <https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1UnitInfo.html>
#[derive(Debug)]
pub struct ParamUnit {
    /// The name of the unit, without any of the proceeding components.
    pub name: String,
    /// The ID of the parent unit, or `kRootUnitId`/0 if the parent would be the root node. Because
    /// 0 is reserved, these IDs are one higher than the actual index in `ParamUnits::units`.
    pub parent_id: i32,
}

impl ParamUnits {
    /// Construct a [`ParamUnits`] object from an iterator over pairs of `(param_hash, param_group)`
    /// where `param_hash` is the integer hash used to represent a parameter in the VST3 wrapper and
    /// `param_group` is a slash delimited path.
    ///
    /// Returns an error if the iterator contains nested groups without a matching parent.
    pub fn from_param_groups<'a, I>(groups: I) -> Result<Self, &'static str>
    where
        I: Iterator<Item = (u32, &'a str)> + Clone,
    {
        // First we'll build a unit for each unique parameter group. We need to be careful here to
        // expand `foo/bar/baz` into `foo/bar/baz`, `foo/bar` and `foo`, in case the parent groups
        // don't contain any parameters and thus aren't present in `groups`.
        let unique_group_names: HashSet<String> = groups
            .clone()
            .filter_map(|(_, group_name)| {
                // The root should not be included here since that's a special case in VST3
                if !group_name.is_empty() {
                    Some(group_name)
                } else {
                    None
                }
            })
            .flat_map(|group_name| {
                // This is the expansion mentioned above
                let mut expanded_group = String::new();
                let mut expanded_groups = Vec::new();
                for component in group_name.split('/') {
                    if !expanded_group.is_empty() {
                        expanded_group.push('/');
                    }
                    expanded_group.push_str(component);
                    expanded_groups.push(expanded_group.clone());
                }

                expanded_groups
            })
            .collect();
        let mut groups_units: Vec<(&str, ParamUnit)> = unique_group_names
            .iter()
            .map(|group_name| {
                (
                    group_name.as_str(),
                    ParamUnit {
                        name: match group_name.rfind('/') {
                            Some(sep_pos) => group_name[sep_pos + 1..].to_string(),
                            None => group_name.to_string(),
                        },
                        parent_id: kRootUnitId,
                    },
                )
            })
            .collect();

        // Then we need to assign the correct parent IDs. We'll also sort the units so the order is
        // stable.
        groups_units.sort_by(|(group_name_l, _), (group_name_r, _)| group_name_l.cmp(group_name_r));

        // We need to be able to map group names to unit IDs
        // NOTE: Now it starts getting complicated because VST3 units are one indexed, so the unit
        //       IDs are one higher than the index in our vector
        let vst3_unit_id_by_group_name: HashMap<&str, i32> = groups_units
            .iter()
            .enumerate()
            // Note the +1 here
            .map(|(unit_id, (group_name, _))| (*group_name, unit_id as i32 + 1))
            .collect();

        for (group_name, unit) in &mut groups_units {
            // If the group name does not contain any slashes then the unit's parent should stay at
            // the root unit
            if let Some(sep_pos) = group_name.rfind('/') {
                let parent_group_name = &group_name[..sep_pos];
                let parent_unit_id = *vst3_unit_id_by_group_name
                    .get(parent_group_name)
                    .ok_or("Missing parent group")?;
                unit.parent_id = parent_unit_id;
            }
        }

        let unit_id_by_hash: HashMap<u32, i32> = groups
            .map(|(param_hash, group_name)| {
                if group_name.is_empty() {
                    (param_hash, kRootUnitId)
                } else {
                    (param_hash, vst3_unit_id_by_group_name[group_name])
                }
            })
            .collect();
        let units: Vec<ParamUnit> = groups_units.into_iter().map(|(_, unit)| unit).collect();

        Ok(Self {
            units,
            unit_id_by_hash,
        })
    }

    /// Get the number of units.
    pub fn len(&self) -> usize {
        self.units.len()
    }

    /// Get the unit ID and the unit's information for a unit with the given 0-indexed index (to
    /// make everything more confusing).
    pub fn info(&self, index: usize) -> Option<(i32, &ParamUnit)> {
        let info = self.units.get(index)?;

        // NOTE: The VST3 unit indices are off by one because 0 is reserved fro the root unit
        Some((index as i32 + 1, info))
    }

    /// Get the ID of the unit the parameter belongs to. `kRootUnitId`/0 indicates the root unit.
    pub fn get_vst3_unit_id(&self, param_hash: u32) -> Option<i32> {
        self.unit_id_by_hash.get(&param_hash).copied()
    }
}