-
Notifications
You must be signed in to change notification settings - Fork 30
/
poly.cpp
166 lines (131 loc) · 3.52 KB
/
poly.cpp
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
159
160
161
162
163
164
165
166
#ifndef poly_h
#define poly_h
#include "Arduino.h"
// Polyphonic voice allocator with two modes:
// - LRU strategy and voice stealing, i.e. priority to last
// - First-available strategy and priority to first
// Based on Emilie Gillet's CVpal:
// https://github.com/pichenettes/cvpal/blob/master/cvpal/voice_allocator.cc
#define MAX 4
class VoiceAllocator {
public:
enum class Mode { LAST, FIRST };
/**
* Constructor
*/
void init() {
this->setMode(Mode::LAST);
this->setSize(0);
this->clear();
for (byte i = 0; i < MAX; i++) {
this->note[i] = 12; // C0
}
}
/**
* Set the allocation mode.
* - Mode::LAST for LRU strategy and voice stealing, i.e. priority to last
* - Mode::FIRST for first-available strategy and priority to first
*/
void setMode(Mode mode) {
this->mode = mode;
}
/**
* Set the polyphony size, i.e. the total number of available voices
*/
void setSize(byte size) {
this->size = min(MAX, size);
}
/**
* Handle an incoming MIDI note and returns the index of the allocated voice.
* Returns -1 if no voice has been allocated.
*/
int noteOn(byte note) {
if (this->size == 0) return -1;
int voice = -1;
if (this->mode == Mode::LAST) {
// Check if there is a voice currently playing this note
voice = this->find(note);
// Try to find the least recently touched, currently inactive voice
if (voice == -1) {
for (byte i = 0; i < MAX; i++) {
if (this->lru[i] < this->size && !this->active[this->lru[i]]) {
voice = this->lru[i];
}
}
}
// If all voices are active, use the least recently played note
if (voice == -1) {
for (byte i = 0; i < MAX; i++) {
if (this->lru[i] < this->size) {
voice = this->lru[i];
}
}
}
// Mark the chosen voice as recently used
this->touch(voice);
} else if (this->mode == Mode::FIRST) {
// Try to find the first currently inactive voice
for (byte i = 0; i < this->size; i++) {
if (!this->active[i]) {
voice = i;
break;
}
}
// In case all voices are active, the new note will not be played
if (voice == -1) {
return -1;
}
}
// Allocate the note
this->note[voice] = note;
this->active[voice] = true;
return voice;
}
/**
* Handle an outgoing MIDI note and returns the index of the freed voice.
* Returns -1 if no voice has been freed.
*/
int noteOff(byte note) {
int voice = this->find(note);
if (voice != -1) {
this->active[voice] = false;
// Mark the freed voice as recently used
if (this->mode == Mode::LAST) {
this->touch(voice);
}
}
return voice;
}
/**
* Clear allocation state, i.e. sets all voices inactive and resets LRU order.
*/
void clear() {
for (byte i = 0; i < MAX; i++) {
this->active[i] = false;
this->lru[i] = MAX - i - 1;
}
}
private:
int find(byte note) {
for (byte i = 0; i < this->size; i++) {
if (this->note[i] == note) return i;
}
return -1;
}
void touch(byte voice) {
int s = MAX - 1;
int d = MAX - 1;
while (s >= 0) {
if (this->lru[s] != voice) this->lru[d--] = this->lru[s];
s--;
}
this->lru[0] = voice;
}
private:
Mode mode;
byte size; // Number of available voices
byte note[MAX]; // Note values for each voice
bool active[MAX]; // Active state for each voice
byte lru[MAX]; // Indexes of voices sorted by most recent usage
};
#endif