30 constexpr bool valid() const noexcept {
44 constexpr bool valid() const noexcept {
57 constexpr std::size_t
size() const noexcept {
61 constexpr std::optional<int>
pitch(std::size_t index)
const noexcept {
64 return pitches_[index];
67 constexpr std::span<const std::uint8_t>
pitches() const noexcept {
68 return {pitches_.data(), count_};
71 constexpr int motion() const noexcept {
84 std::array<std::uint8_t, ChordFormula::kMaxNotes> pitches_{};
85 std::uint8_t count_ = 0;
92 static Voicing make(std::array<std::uint8_t, ChordFormula::kMaxNotes> pitches,
93 std::size_t count,
int motion)
noexcept {
95 result.pitches_ = pitches;
96 result.count_ =
static_cast<std::uint8_t
>(count);
97 result.motion_ = motion;
103 std::span<const int> targets,
MidiRange range,
105 int maximum_spacing)
noexcept {
107 pitch_classes.size() != targets.size() || !range.valid() || minimum_spacing < 1 ||
108 maximum_spacing < minimum_spacing || maximum_spacing > 127)
111 constexpr int unreachable = std::numeric_limits<int>::max() / 4;
113 for (
auto& row : cost)
114 row.fill(unreachable);
115 for (
int previous = -1; previous <= 127; ++previous)
116 cost[pitch_classes.size()][
static_cast<std::size_t
>(previous + 1)] = 0;
118 for (std::size_t
reverse = pitch_classes.size();
reverse-- > 0;) {
119 for (
int previous = -1; previous <= 127; ++previous) {
120 int best = unreachable;
121 const int first = previous < 0 ? range.lowest : previous + minimum_spacing;
123 previous < 0 ? range.highest : std::min(range.highest, previous + maximum_spacing);
124 for (
int pitch = first; pitch <= last; ++pitch) {
127 const int suffix = cost[
reverse + 1][
static_cast<std::size_t
>(pitch + 1)];
128 if (suffix == unreachable)
130 const int local = std::abs(pitch - targets[
reverse]);
131 best = std::min(best, local + suffix);
133 cost[
reverse][
static_cast<std::size_t
>(previous + 1)] = best;
137 const int total = cost[0][0];
138 if (total == unreachable)
140 std::array<std::uint8_t, ChordFormula::kMaxNotes> result{};
142 for (std::size_t note = 0; note < pitch_classes.size(); ++note) {
143 const int wanted = cost[note][
static_cast<std::size_t
>(previous + 1)];
144 const int first = previous < 0 ? range.lowest : previous + minimum_spacing;
146 previous < 0 ? range.highest : std::min(range.highest, previous + maximum_spacing);
147 for (
int pitch = first; pitch <= last; ++pitch) {
150 const int suffix = cost[note + 1][
static_cast<std::size_t
>(pitch + 1)];
151 if (suffix != unreachable && std::abs(pitch - targets[note]) + suffix == wanted) {
152 result[note] =
static_cast<std::uint8_t
>(pitch);
161inline std::optional<Voicing>
163 std::span<const int> previous,
MidiRange range)
noexcept {
165 pitch_classes.size() != previous.size() || !range.valid())
168 constexpr std::uint16_t unreachable = std::numeric_limits<std::uint16_t>::max();
170 std::array<std::array<std::uint16_t, 129>, state_count> cost{};
171 for (
auto& row : cost)
172 row.fill(unreachable);
174 const auto full_mask =
static_cast<std::size_t
>((1u << pitch_classes.size()) - 1u);
175 cost[full_mask].fill(0);
176 for (std::size_t mask = full_mask; mask-- > 0;) {
177 const auto voice =
static_cast<std::size_t
>(std::popcount(mask));
178 for (
int last_pitch = -1; last_pitch <= 127; ++last_pitch) {
179 std::uint16_t best = unreachable;
180 const int first = last_pitch < 0 ? range.lowest : last_pitch + 1;
181 for (std::size_t tone = 0; tone < pitch_classes.size(); ++tone) {
182 const auto tone_bit =
static_cast<std::size_t
>(1u << tone);
183 if ((mask & tone_bit) != 0)
185 for (
int pitch = first; pitch <= range.highest; ++pitch) {
188 const auto suffix = cost[mask | tone_bit][
static_cast<std::size_t
>(pitch + 1)];
189 if (suffix == unreachable)
191 const int candidate = std::abs(pitch - previous[voice]) + suffix;
192 if (candidate < best)
193 best =
static_cast<std::uint16_t
>(candidate);
196 cost[mask][
static_cast<std::size_t
>(last_pitch + 1)] = best;
200 if (cost[0][0] == unreachable)
202 std::array<std::uint8_t, ChordFormula::kMaxNotes> result{};
203 std::size_t mask = 0;
205 for (std::size_t voice = 0; voice < pitch_classes.size(); ++voice) {
206 const auto wanted = cost[mask][
static_cast<std::size_t
>(last_pitch + 1)];
207 const int first = last_pitch < 0 ? range.lowest : last_pitch + 1;
208 bool selected =
false;
209 for (
int pitch = first; pitch <= range.highest && !selected; ++pitch) {
210 for (std::size_t tone = 0; tone < pitch_classes.size(); ++tone) {
211 const auto tone_bit =
static_cast<std::size_t
>(1u << tone);
212 if ((mask & tone_bit) != 0 ||
wrap_pitch_class(pitch) != pitch_classes[tone])
214 const auto suffix = cost[mask | tone_bit][
static_cast<std::size_t
>(pitch + 1)];
215 if (suffix != unreachable && std::abs(pitch - previous[voice]) + suffix == wanted) {
216 result[voice] =
static_cast<std::uint8_t
>(pitch);
229 std::array<PitchClass, ChordFormula::kMaxNotes>& pitch_classes,
230 std::size_t count)
noexcept {
231 for (std::size_t right = 1; right < count; ++right) {
232 const int pitch = pitches[right];
233 const PitchClass pitch_class = pitch_classes[right];
234 std::size_t left = right;
235 while (left > 0 && pitches[left - 1] > pitch) {
236 pitches[left] = pitches[left - 1];
237 pitch_classes[left] = pitch_classes[left - 1];
240 pitches[left] = pitch;
241 pitch_classes[left] = pitch_class;
249 if (!constraints.valid() || formula.
size() == 0 || constraints.inversion >= formula.
size())
251 if (root_midi < 0 || root_midi > 127)
254 std::array<int, ChordFormula::kMaxNotes> targets{};
255 std::array<PitchClass, ChordFormula::kMaxNotes> pitch_classes{};
257 for (std::size_t note = 0; note < formula.
size(); ++note) {
258 const std::size_t source = (note + constraints.inversion) % formula.
size();
259 targets[note] = root_midi + *formula.
interval(source);
260 if (source < constraints.inversion)
261 while (targets[note] <= previous)
264 previous = targets[note];
267 switch (constraints.mode) {
271 for (std::size_t note = 1; note + 1 < formula.
size(); note += 2)
275 if (formula.
size() < 2)
277 targets[formula.
size() - 2] -= 12;
280 if (formula.
size() < 3)
282 targets[formula.
size() - 3] -= 12;
285 constraints.minimum_spacing = std::max(constraints.minimum_spacing, 5);
290 std::span<const PitchClass>(pitch_classes.data(), formula.
size()),
291 std::span<const int>(targets.data(), formula.
size()), constraints.range,
292 constraints.minimum_spacing, constraints.maximum_spacing);
299 if (!
is_valid(target_root) || !range.valid() || previous.empty() ||
302 for (std::size_t voice = 0; voice < previous.size(); ++voice) {
303 if (previous[voice] < 0 || previous[voice] > 127 || !range.valid() ||
304 (voice != 0 && previous[voice - 1] >= previous[voice]))
308 std::array<PitchClass, ChordFormula::kMaxNotes> pitch_classes{};
309 for (std::size_t tone = 0; tone < formula.
size(); ++tone)
312 std::span<const PitchClass>(pitch_classes.data(), formula.
size()), previous, range);
Definition voicing.hpp:55
constexpr auto operator<=>(const Voicing &) const =default
friend std::optional< Voicing > minimum_motion_voice_leading(std::span< const int >, PitchClass, const ChordFormula &, MidiRange) noexcept
Definition voicing.hpp:295
constexpr std::span< const std::uint8_t > pitches() const noexcept
Definition voicing.hpp:67
constexpr int motion() const noexcept
Definition voicing.hpp:71
constexpr std::optional< int > pitch(std::size_t index) const noexcept
Definition voicing.hpp:61
friend std::optional< Voicing > voice_chord(int, const ChordFormula &, VoicingConstraints) noexcept
Definition voicing.hpp:247
constexpr std::size_t size() const noexcept
Definition voicing.hpp:57
std::optional< Voicing > solve_unordered_voice_leading(std::span< const PitchClass > pitch_classes, std::span< const int > previous, MidiRange range) noexcept
Definition voicing.hpp:162
std::optional< Voicing > solve_ordered_voicing(std::span< const PitchClass > pitch_classes, std::span< const int > targets, MidiRange range, int minimum_spacing, int maximum_spacing) noexcept
Definition voicing.hpp:102
void sort_parallel(std::array< int, ChordFormula::kMaxNotes > &pitches, std::array< PitchClass, ChordFormula::kMaxNotes > &pitch_classes, std::size_t count) noexcept
Definition voicing.hpp:228
std::optional< Voicing > voice_chord(int root_midi, const ChordFormula &formula, VoicingConstraints constraints={}) noexcept
Definition voicing.hpp:247
constexpr bool is_valid(PitchClass pitch_class) noexcept
Definition pitch.hpp:34
std::optional< Voicing > minimum_motion_voice_leading(std::span< const int > previous, PitchClass target_root, const ChordFormula &formula, MidiRange range={}) noexcept
Definition voicing.hpp:295
constexpr PitchClass transpose(PitchClass pitch_class, int semitones) noexcept
Definition pitch.hpp:51
@ reverse
Definition pattern.hpp:166
constexpr PitchClass wrap_pitch_class(int index) noexcept
Definition pitch.hpp:44
VoicingMode
Definition voicing.hpp:18
@ closed
Definition voicing.hpp:19
@ open
Definition voicing.hpp:20
@ drop3
Definition voicing.hpp:22
@ spread
Definition voicing.hpp:23
@ drop2
Definition voicing.hpp:21
constexpr int kPitchClassesPerOctave
Definition pitch.hpp:16
PitchClass
Definition pitch.hpp:19
Definition voicing.hpp:26
constexpr auto operator<=>(const MidiRange &) const =default
constexpr bool valid() const noexcept
Definition voicing.hpp:30
int lowest
Definition voicing.hpp:27
int highest
Definition voicing.hpp:28
Definition voicing.hpp:37
std::size_t inversion
Definition voicing.hpp:40
VoicingMode mode
Definition voicing.hpp:38
MidiRange range
Definition voicing.hpp:39
int maximum_spacing
Definition voicing.hpp:42
constexpr bool valid() const noexcept
Definition voicing.hpp:44
int minimum_spacing
Definition voicing.hpp:41
Definition voicing.hpp:91
static Voicing make(std::array< std::uint8_t, ChordFormula::kMaxNotes > pitches, std::size_t count, int motion) noexcept
Definition voicing.hpp:92