OpenWareLaboratory
MidiPolyphonicExpressionProcessor.h
Go to the documentation of this file.
1 #ifndef __MidiPolyphonicExpressionProcessor_h__
2 #define __MidiPolyphonicExpressionProcessor_h__
3 
4 #include "MidiProcessor.h"
5 #include "SignalGenerator.h"
6 #include "VoiceAllocator.h"
7 
15 template<class SynthVoice, int VOICES>
16 class MidiPolyphonicExpressionProcessor : public VoiceAllocator<SynthVoice, VOICES> {
17 // routes five per-note messages (Note On, Note Off, Pitch Bend, CC74 and Channel Pressure/Aftertouch)
18 // to the voice assigned to each channel
20 protected:
21  static const uint16_t TAKEN = 0xffff;
22  uint8_t notes[VOICES];
23  uint16_t allocation[VOICES];
24  uint16_t allocated;
25  float pressure[VOICES];
26  float modulation[VOICES];
27  uint8_t master_channel = 1;
28  // One channel (usually Channel 1) is used for global messages
29  // The global channel is set by the "MPE Zone." When the MPE Zone is the Lower Zone, Channel 1 is used for global messages. When the MPE Zone is Upper Zone is, Channel 16 is used for global messages.
30  float zone_pitchbend = 0;
31  float zone_pressure = 0;
32  float zone_modulation = 0;
34  void take(uint8_t ch, MidiMessage msg){
35  release(ch);
36  notes[ch] = msg.getNote();
37  allocation[ch] = TAKEN;
38  Allocator::voice[ch]->noteOn(msg);
39  }
40  void release(uint8_t ch){
41  allocation[ch] = ++allocated;
43  Allocator::voice[ch]->gate(false);
44  }
45 public:
48 
49  uint8_t findFreeVoice(MidiMessage msg){
50  uint8_t note = msg.getNote();
51  uint16_t minval = USHRT_MAX;
52  uint8_t minidx = 0;
53  // take oldest free voice, to allow voices to ring out
54  for(int i=0; i<VOICES; ++i){
55  if(notes[i] == note){
56  minidx = i;
57  break;
58  }
59  if(allocation[i] < minval){
60  minidx = i;
61  minval = allocation[i];
62  }
63  }
64  // take oldest voice
65  return minidx;
66  }
67 
69  uint8_t note = msg.getNote();
70  for(int i=0; i<VOICES; ++i)
71  if(notes[i] == note)
72  release(i);
73  }
74 
75  void noteOn(MidiMessage msg){
76  if(isMasterChannel(msg)){ // Zone message
77  uint8_t idx = findFreeVoice(msg);
78  take(idx, msg);
79  }else{
80  uint8_t idx = getNoteChannel(msg);
81  take(idx, msg);
82  }
83  }
84 
85  void noteOff(MidiMessage msg){
86  if(isMasterChannel(msg)){ // Zone message
88  }else{
89  uint8_t ch = getNoteChannel(msg);
90  if(ch < VOICES)
91  release(ch);
92  }
93  }
95  switch(msg.getControllerNumber()){
96  case MIDI_CC_MODULATION: // handle modulation same as CC74 for non-MPE compatibility
97  case MIDI_CC_FREQ_CUTOFF: {
98  // All MPE receivers are required to respond to CC #74 at the Zone and Note level. If a device receives CC #74
99  // on both a Master Channel and Member Channel, it must combine such data meaningfully and separately for
100  // each sounding note.
101  float value = Allocator::mod_range * msg.getControllerValue();
102  if(isMasterChannel(msg)){ // Zone message
103  for(int i=0; i<VOICES; ++i)
104  Allocator::voice[i]->setModulation(value + modulation[i]);
105  zone_modulation = value;
106  }else{
107  uint8_t ch = getNoteChannel(msg);
108  if(ch < VOICES){
109  Allocator::voice[ch]->setModulation(zone_modulation + value);
110  modulation[ch] = value;
111  }
112  }
113  break;
114  }
115  default:
117  break;
118  }
119  }
120  void rpn(uint16_t id, uint8_t msb, uint8_t lsb, MidiMessage msg){
121  switch(id){
123  float semitones = msb + lsb/100.0f; // semitones and cents
124  if(isMasterChannel(msg)){
125  Allocator::setPitchBendRange(semitones);
126  }else{
127  note_pitchbend_range = semitones;
128  }
129  break;
130  }
132  uint8_t n = msg.getChannel();
133  uint8_t mm = msg.getControllerValue();
134  // mm=0: MPE is Off (No Channels)
135  // mm=1 to F: Assigns that number of MIDI Channels to the Zone
136  if(mm != 0){
137  if(n == 0x0) // n=0: Lower Zone Master Channel
138  master_channel = 1;
139  else if(n == 0xf) // n=F: Upper Zone Master Channel
140  master_channel = 0xf;
141  // All other channel values are invalid and should be ignored
142  }
143  // Each Zone is activated with its own message, which can be sent in either order.
144  // Sending an MCM with the number of Member Channels set to zero deactivates that Zone.
145  break;
146  }
147  default:
148  Allocator::rpn(id, msb, lsb, msg);
149  break;
150  }
151  }
153  return msg.getChannel()+1 == master_channel;
154  }
156  // The Lower Zone is controlled by Master Channel 1,
157  // with Member Channels assigned sequentially from Channel 2 upwards.
158  if(master_channel == 1)
159  return (msg.getChannel()-1) % VOICES;
160  else
161  return (14-msg.getChannel()) % VOICES;
162  // The Upper Zone is controlled by Master Channel 16,
163  // with Member Channels assigned sequentially from Channel 15 downwards.
164  }
166  // Pitch Bend is both a Zone Message and a Note Level Message. If an MPE synthesizer receives Pitch Bend (for example) on both a Master and a Member Channel, it must combine the data meaningfully. The same is true for Channel Pressure.
167  if(isMasterChannel(msg)){ // Zone message
168  float value = Allocator::getPitchBendRange()*msg.getPitchBend()/8192.0f;
169  float delta = value - zone_pitchbend;
170  for(int i=0; i<VOICES; ++i)
171  Allocator::voice[i]->setPitchBend(Allocator::voice[i]->getPitchBend()+delta);
172  zone_pitchbend = value;
173  }else{
174  float value = note_pitchbend_range*msg.getPitchBend()/8192.0f;
175  uint8_t ch = getNoteChannel(msg);
176  if(ch < VOICES){ // Note level message
177  Allocator::voice[ch]->setPitchBend(zone_pitchbend + value);
178  }
179  }
180  }
182  // All MPE receivers are required to respond to Channel Pressure at the Note and Zone level.
183  // If a device receives Channel Pressure on both a Master Channel and Member Channel it must
184  // combine such data meaningfully and separately for each sounding note.
185  float value = msg.getChannelPressure()/128.0f;
186  if(isMasterChannel(msg)){ // Zone message
187  for(int i=0; i<VOICES; ++i)
188  Allocator::voice[i]->setPressure(value + pressure[i]);
189  zone_pressure = value;
190  }else{
191  uint8_t ch = getNoteChannel(msg);
192  if(ch < VOICES){ // Note level message
193  Allocator::voice[ch]->setPressure(zone_pressure + value);
194  pressure[ch] = value;
195  }
196  }
197  }
199  // Polyphonic Key Pressure may be used with notes on the Master Channel, but not on other Channels
200  if(isMasterChannel(msg)){
201  uint8_t note = msg.getNote();
202  for(int i=0; i<VOICES; ++i)
203  if(Allocator::voice[i]->getNote() == note)
204  Allocator::voice[i]->setPressure(msg.getPolyKeyPressure()/128.0f);
205  }
206  }
207  void sustainOff(){
208  // gate off any sustained (but not active) voices
209  for(int i=0; i<VOICES; ++i){
210  if(allocation[i] != TAKEN)
211  Allocator::voice[i]->gate(false);
212  }
213  }
214 };
215 
216 #endif /* __MidiPolyphonicExpressionProcessor_h__ */
@ MIDI_RPN_PITCH_BEND_RANGE
Definition: MidiStatus.h:93
@ MIDI_RPN_MPE_CONFIGURATION
Definition: MidiStatus.h:97
@ MIDI_CC_MODULATION
Definition: MidiStatus.h:36
@ MIDI_CC_FREQ_CUTOFF
Definition: MidiStatus.h:49
int16_t getPitchBend()
Get pitch bend value as a signed integer between -8192 and 8191.
Definition: MidiMessage.h:84
uint8_t getChannel()
Definition: MidiMessage.h:23
uint8_t getControllerValue()
Definition: MidiMessage.h:69
uint8_t getControllerNumber()
Definition: MidiMessage.h:66
uint8_t getPolyKeyPressure()
Definition: MidiMessage.h:75
uint8_t getChannelPressure()
Definition: MidiMessage.h:72
uint8_t getNote()
Definition: MidiMessage.h:60
Implementation of MPE (MIDI Polyphonic Expression) message processing.
void rpn(uint16_t id, uint8_t msb, uint8_t lsb, MidiMessage msg)
SynthVoice * voice[VOICES]
Definition: VoiceAllocator.h:7
void setPitchBendRange(float range)
Set pitch bend range in semitones.
float getPitchBendRange()
void controlChange(MidiMessage msg)
void rpn(uint16_t id, uint8_t msb, uint8_t lsb, MidiMessage msg)