ATMEGA328を搭載した Arduino Duemilanove 互換機で音をPWM D/A変換出力するシンセサイザーライブラリです。
修订版 | 870b0852c22fced83c388aa4e6ad99315ddd2dcc (tree) |
---|---|
时间 | 2020-04-06 05:18:26 |
作者 | Akiyoshi Kamide <kamide@yk.r...> |
Commiter | Akiyoshi Kamide |
Supported the compilation on Arduino IDE on Linux
- Refectored, and "const" added
- Update against performance-down when compiled on Linux
@@ -1,29 +0,0 @@ | ||
1 | -// | |
2 | -// PWM DAC Synthesizer ver.20151003 | |
3 | -// by Akiyoshi Kamide (Twitter: @akiyoshi_kamide) | |
4 | -// http://kamide.b.osdn.me/pwmdac_synth_lib/ | |
5 | -// https://osdn.jp/users/kamide/pf/PWMDAC_Synth/ | |
6 | -// | |
7 | -#include "PWMDAC_Synth.h" | |
8 | - | |
9 | -byte PWMDACSynth::musicalMod7(char x) { | |
10 | - while( x & 0xF8 ) x = (x >> 3) + (x & 7); | |
11 | - if(x==7) return 0; | |
12 | - return x; | |
13 | -} | |
14 | -byte PWMDACSynth::musicalMod12(char x) { | |
15 | - char n = x >> 2; | |
16 | - while( n & 0xFC ) n = (n >> 2) + (n & 3); | |
17 | - x &= 3; | |
18 | - if(n==3||n==0) return x; | |
19 | - return x + (n << 2); | |
20 | -} | |
21 | -byte PWMDACSynth::log2(unsigned int x) { | |
22 | - byte index = 0; | |
23 | - if (x & 0xFF00) { index += 8; x &= 0xFF00; } | |
24 | - if (x & 0xF0F0) { index += 4; x &= 0xF0F0; } | |
25 | - if (x & 0xCCCC) { index += 2; x &= 0xCCCC; } | |
26 | - if (x & 0xAAAA) { index += 1; x &= 0xAAAA; } | |
27 | - return index; | |
28 | -} | |
29 | - |
@@ -1,5 +1,5 @@ | ||
1 | 1 | // |
2 | -// PWM DAC Synthesizer ver.20190330 | |
2 | +// PWM DAC Synthesizer ver.20200405 | |
3 | 3 | // by Akiyoshi Kamide (Twitter: @akiyoshi_kamide) |
4 | 4 | // http://kamide.b.osdn.me/pwmdac_synth_lib/ |
5 | 5 | // https://osdn.jp/users/kamide/pf/PWMDAC_Synth/ |
@@ -36,60 +36,43 @@ | ||
36 | 36 | #define PWMDAC_POLYPHONY 6 |
37 | 37 | #endif |
38 | 38 | |
39 | - | |
40 | 39 | // Built-in wavetable generator |
41 | 40 | // x = Phase angle : 0x00...0x80(PI_radian)...0xFF |
42 | 41 | // f(x) = Wave voltage at the x : 0x00(min)...0x80(center)...0xFF(max) |
43 | 42 | #define PWMDAC_SQUARE_WAVE(x) (((x) < 0x80 ? 0x00 : 0xFF) / PWMDAC_POLYPHONY) |
44 | 43 | #define PWMDAC_SAWTOOTH_WAVE(x) ((x) / PWMDAC_POLYPHONY) |
45 | 44 | #define PWMDAC_TRIANGLE_WAVE(x) (((x) < 0x80 ? (x) : 0x100 - (x)) * 2 / PWMDAC_POLYPHONY) |
46 | - | |
47 | -#define SINPI(x, t) sin(PI * (x) / (t)) | |
48 | -#define PWMDAC_MAX_VOLUME_SINE_WAVE(x) ( 0x80 * ( 1 + SINPI(x,0x80) ) ) | |
49 | -#define PWMDAC_SINE_WAVE(x) (PWMDAC_MAX_VOLUME_SINE_WAVE(x) / PWMDAC_POLYPHONY) | |
50 | -#define PWMDAC_SHEPARD_TONE(x) ( 0x80 * ( 8 \ | |
51 | - +SINPI(x,0x80) \ | |
52 | - +SINPI(x,0x40) \ | |
53 | - +SINPI(x,0x20) \ | |
54 | - +SINPI(x,0x10) \ | |
55 | - +SINPI(x,0x08) \ | |
56 | - +SINPI(x,0x04) \ | |
57 | - +SINPI(x,0x02) \ | |
58 | - +SINPI(x,0x01) \ | |
59 | - ) / ( 8 * PWMDAC_POLYPHONY ) ) | |
45 | +#define PWMDAC_MAX_VOLUME_SINE_WAVE(x) ((byte)( 0x80 * (1 + sin(PI * (x) / 0x80)) )) | |
46 | +#define PWMDAC_SINE_WAVE(x) ((byte)( 0x80 * (1 + sin(PI * (x) / 0x80)) / PWMDAC_POLYPHONY )) | |
47 | +#define PWMDAC_SHEPARD_TONE(x) ((byte)( 0x80 * (8 \ | |
48 | + +sin(PI * (x) / 0x80) \ | |
49 | + +sin(PI * (x) / 0x40) \ | |
50 | + +sin(PI * (x) / 0x20) \ | |
51 | + +sin(PI * (x) / 0x10) \ | |
52 | + +sin(PI * (x) / 0x08) \ | |
53 | + +sin(PI * (x) / 0x04) \ | |
54 | + +sin(PI * (x) / 0x02) \ | |
55 | + +sin(PI * (x) / 0x01) \ | |
56 | + ) / (8 * PWMDAC_POLYPHONY) )) | |
60 | 57 | |
61 | 58 | #define PWMDAC_CREATE_WAVETABLE(table, function) PROGMEM const byte table[] = ARRAY256(function) |
62 | 59 | |
63 | -enum AdsrStatus : byte {ADSR_OFF, ADSR_RELEASE, ADSR_SUSTAIN, ADSR_DECAY, ADSR_ATTACK}; | |
60 | +enum AdsrParam : byte { | |
61 | + ADSR_RELEASE_VALUE, // 0..15 | |
62 | + ADSR_SUSTAIN_VALUE, // 0..255 | |
63 | + ADSR_DECAY_VALUE, // 0..15 | |
64 | + ADSR_ATTACK_VALUE, // 0..15 | |
65 | + NUMBER_OF_ADSR | |
66 | +}; | |
64 | 67 | |
65 | 68 | typedef struct _Instrument { |
66 | - PROGMEM const byte *wavetable; | |
67 | - PROGMEM const byte envelope[ADSR_ATTACK]; | |
69 | + const byte *wavetable; | |
70 | + const byte envelope[NUMBER_OF_ADSR]; | |
68 | 71 | } Instrument; |
69 | 72 | |
70 | -class EnvelopeParam { | |
71 | - public: | |
72 | - EnvelopeParam() { } | |
73 | - EnvelopeParam(PROGMEM const byte *envelope) { | |
74 | - setParam(envelope); | |
75 | - } | |
76 | - EnvelopeParam(byte attack_time, byte decay_time, byte sustain_level, byte release_time) { | |
77 | - *getParam(ADSR_ATTACK) = attack_time; // 0..15 | |
78 | - *getParam(ADSR_DECAY) = decay_time; // 0..15 | |
79 | - *getParam(ADSR_SUSTAIN) = sustain_level; // 0..255 | |
80 | - *getParam(ADSR_RELEASE) = release_time; // 0..15 | |
81 | - } | |
82 | - byte *getParam(AdsrStatus adsr) { | |
83 | - return param + (byte)adsr - 1; | |
84 | - } | |
85 | - void setParam(PROGMEM const byte *envelope) { | |
86 | - memcpy_P(this->param, envelope, sizeof(this->param)); | |
87 | - } | |
88 | - protected: | |
89 | - byte param[ADSR_ATTACK]; | |
90 | -}; | |
91 | - | |
92 | 73 | class MidiChannel { |
74 | + public: | |
75 | + static const byte MAX_NUMBER = 16; | |
93 | 76 | protected: |
94 | 77 | // RPN (Registered Parameter Number) or NRPN(Non-Registered Parameter Number) |
95 | 78 | typedef struct { byte LSB; byte MSB; } ParameterNumber; |
@@ -113,28 +96,30 @@ class MidiChannel { | ||
113 | 96 | byte priority_volume_threshold; |
114 | 97 | #endif |
115 | 98 | byte modulation; // 0 .. 127 (Unsigned 7bit) |
116 | - PROGMEM const byte *wavetable; | |
117 | - EnvelopeParam envelope; | |
118 | - MidiChannel(PROGMEM const Instrument *instrument) { | |
99 | + const byte *wavetable; | |
100 | + byte envelope[NUMBER_OF_ADSR]; | |
101 | + MidiChannel(const Instrument * const instrument) { | |
119 | 102 | #if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) |
120 | 103 | setPriority(0); |
121 | 104 | #endif |
122 | 105 | reset(instrument); |
123 | 106 | } |
124 | - void reset(PROGMEM const Instrument *instrument) { | |
107 | + void reset(const Instrument * const instrument) { | |
125 | 108 | resetAllControllers(); |
126 | 109 | rpn.LSB = rpn.MSB = RPN_Null; |
127 | 110 | data_entry_source = NULL; |
128 | 111 | programChange(instrument); |
129 | 112 | } |
130 | 113 | #if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) |
131 | - void setPriority(byte priority) { this->priority_volume_threshold = ~priority; } | |
114 | + void setPriority(const byte priority) { | |
115 | + this->priority_volume_threshold = ~priority; | |
116 | + } | |
132 | 117 | #endif |
133 | 118 | int getPitchBend() const { return pitch_bend; } |
134 | - unsigned long bendedPitchOf(unsigned long original_pitch) { | |
119 | + unsigned long bendedPitchOf(const unsigned long original_pitch) const { | |
135 | 120 | return pitch_bend ? original_pitch * pitch_rate : original_pitch; |
136 | 121 | } |
137 | - boolean pitchBendChange(int new_pitch_bend) { | |
122 | + boolean pitchBendChange(const int new_pitch_bend) { | |
138 | 123 | int diff = new_pitch_bend - pitch_bend; |
139 | 124 | if( diff < 16 && diff > -16 ) return false; |
140 | 125 | pitch_bend = new_pitch_bend; |
@@ -142,15 +127,15 @@ class MidiChannel { | ||
142 | 127 | return true; |
143 | 128 | } |
144 | 129 | byte getPitchBendSensitivity() const { return pitch_bend_sensitivity; } |
145 | - boolean pitchBendSensitivityChange(byte value) { | |
130 | + boolean pitchBendSensitivityChange(const byte value) { | |
146 | 131 | if ( pitch_bend_sensitivity == value ) return false; |
147 | 132 | pitch_bend_sensitivity = value; |
148 | 133 | updatePitchRate(); |
149 | 134 | return true; |
150 | 135 | } |
151 | - void programChange(PROGMEM const Instrument *instrument) { | |
152 | - this->wavetable = (PROGMEM const byte *)pgm_read_word(&(instrument->wavetable)); | |
153 | - this->envelope.setParam(instrument->envelope); | |
136 | + void programChange(const Instrument * const instrument) { | |
137 | + this->wavetable = (const byte *)pgm_read_word(&(instrument->wavetable)); | |
138 | + memcpy_P(this->envelope, instrument->envelope, sizeof(this->envelope)); | |
154 | 139 | } |
155 | 140 | void resetAllControllers() { |
156 | 141 | modulation = 0; |
@@ -158,7 +143,7 @@ class MidiChannel { | ||
158 | 143 | pitch_rate = 1.0; |
159 | 144 | pitch_bend_sensitivity = 2; |
160 | 145 | } |
161 | - void controlChange(byte number, byte value) { | |
146 | + void controlChange(const byte number, const byte value) { | |
162 | 147 | switch(number) { |
163 | 148 | case 1: modulation = value; break; |
164 | 149 | case 6: // RPN/NRPN Data Entry |
@@ -179,136 +164,6 @@ class MidiChannel { | ||
179 | 164 | } |
180 | 165 | }; |
181 | 166 | |
182 | -// [Phase-correct PWM dual-slope] | |
183 | -// TCNTn = | |
184 | -// 00(BOTTOM) 01 02 03 ... FC FD FE | |
185 | -// FF(TOP) FE FD FC ... 03 02 01 | |
186 | -// -> 0xFF * 2 = 510 values (NOT 512) | |
187 | -// | |
188 | -// ISR()-call interval = (0xFF * 2) / F_CPU(16MHz) = 31.875us | |
189 | -// | |
190 | -// [MIDI Tuning Standard] | |
191 | -// http://en.wikipedia.org/wiki/MIDI_Tuning_Standard | |
192 | -// fn(d) = 440 Hz * 2^( (d - 69) / 12 ) MIDI note # d = 0..127 | |
193 | -// | |
194 | -#define PHASE_SPEED_OF(note_number) ( \ | |
195 | - pow( 2, (double)(note_number - 69)/12 + (sizeof(unsigned long) * 8 + 1) ) \ | |
196 | - * PWMDAC_NOTE_A_FREQUENCY * 0xFF / F_CPU ) | |
197 | - | |
198 | -class VoiceStatus { | |
199 | - public: | |
200 | - boolean isNoteOn(byte note, MidiChannel *cp) { | |
201 | - return this->note == note && adsr > ADSR_RELEASE && channel == cp; | |
202 | - } | |
203 | - boolean isSoundOn(MidiChannel *cp) { | |
204 | - return adsr > ADSR_OFF && channel == cp; | |
205 | - } | |
206 | - inline unsigned int nextPulseWidth() { | |
207 | - // To generate waveform rapidly in ISR(), this method must be inline | |
208 | - phase.v32 += dphase32.real; | |
209 | - return HighestElementOf(volume.v8) * pgm_read_byte( wavetable + HighestElementOf(phase.v8) ); | |
210 | - } | |
211 | - static const byte HIGHEST_PRIORITY = UCHAR_MAX; | |
212 | - static const byte LOWEST_PRIORITY = 0; | |
213 | - byte getPriority() { | |
214 | - byte t = HighestElementOf(volume.v8) >> 1; | |
215 | - if( adsr == ADSR_ATTACK ) t = HIGHEST_PRIORITY - t; | |
216 | -#if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) | |
217 | - if( channel != NULL && t > channel->priority_volume_threshold ) { | |
218 | - t >>= 1; t |= 0x80; | |
219 | - } else { | |
220 | - t >>= 1; | |
221 | - } | |
222 | -#endif | |
223 | - return t; | |
224 | - } | |
225 | - VoiceStatus() { reset(); } | |
226 | - void setChannel(MidiChannel *cp) { if( channel != cp ) reset(cp); } | |
227 | - void noteOn(byte note) { | |
228 | - this->wavetable = channel->wavetable; | |
229 | - this->note = note; | |
230 | - updatePitch(); | |
231 | - adsr = ADSR_ATTACK; | |
232 | - } | |
233 | - void noteOff(byte note, MidiChannel *cp) { | |
234 | - if( isNoteOn(note, cp) ) adsr = ADSR_RELEASE; | |
235 | - } | |
236 | - void allNotesOff(MidiChannel *cp) { | |
237 | - if( channel == cp ) adsr = ADSR_RELEASE; | |
238 | - } | |
239 | - void allSoundOff(MidiChannel *cp) { if( isSoundOn(cp) ) reset(); } | |
240 | - void reset(MidiChannel *cp = NULL) { | |
241 | - adsr = ADSR_OFF; volume.v16 = 0; note = UCHAR_MAX; | |
242 | - phase.v32 = dphase32.real = dphase32.moffset = dphase32.bended = 0L; | |
243 | - channel = cp; | |
244 | - } | |
245 | - void update(int modulation_offset) { | |
246 | - updateModulationStatus(modulation_offset); | |
247 | - updateEnvelopeStatus(); | |
248 | - } | |
249 | - void updatePitch(MidiChannel *cp) { if( isSoundOn(cp) ) updatePitch(); } | |
250 | -protected: | |
251 | - PROGMEM const byte *wavetable; | |
252 | - struct { | |
253 | - unsigned long real; // Real phase speed | |
254 | - unsigned long bended; // Pitch-bended phase speed | |
255 | - long moffset; // Modulation pitch offset | |
256 | - } dphase32; | |
257 | - union { unsigned long v32; byte v8[4]; } phase; | |
258 | - union { unsigned int v16; byte v8[2]; } volume; | |
259 | - MidiChannel *channel; | |
260 | - byte note; // 0..127 | |
261 | - AdsrStatus adsr; | |
262 | - void updatePitch() { | |
263 | - static PROGMEM const unsigned long phase_speed_table[] = ARRAY128(PHASE_SPEED_OF); | |
264 | - dphase32.bended = channel->bendedPitchOf(pgm_read_dword(phase_speed_table + note)); | |
265 | - dphase32.real = dphase32.bended + dphase32.moffset; | |
266 | - } | |
267 | - void updateModulationStatus(char modulation_offset) { | |
268 | - if( channel->modulation <= 0x10 ) { | |
269 | - if( dphase32.moffset == 0 ) return; | |
270 | - dphase32.moffset = 0; | |
271 | - dphase32.real = dphase32.bended; | |
272 | - return; | |
273 | - } | |
274 | - dphase32.moffset = (dphase32.real >> 19) * channel->modulation * modulation_offset; | |
275 | - dphase32.real = dphase32.bended + dphase32.moffset; | |
276 | - } | |
277 | - EnvelopeParam *getEnvelope() { return &(channel->envelope); } | |
278 | - byte *getEnvelope(AdsrStatus adsr) { return getEnvelope()->getParam(adsr); } | |
279 | - void updateEnvelopeStatus() { | |
280 | - switch(adsr) { | |
281 | - case ADSR_ATTACK: { | |
282 | - unsigned long v = volume.v16; | |
283 | - if( (v += (UINT_MAX >> *getEnvelope(ADSR_ATTACK))) > UINT_MAX ) { | |
284 | - volume.v16 = UINT_MAX; adsr = ADSR_DECAY; break; | |
285 | - } | |
286 | - volume.v16 = v; break; | |
287 | - } | |
288 | - case ADSR_DECAY: { | |
289 | - EnvelopeParam *e = getEnvelope(); | |
290 | - unsigned int dv = volume.v16 >> *(e->getParam(ADSR_DECAY)); | |
291 | - if( dv == 0 ) dv = 1; | |
292 | - long v = volume.v16; | |
293 | - unsigned int sustain16 = (unsigned int)(*(e->getParam(ADSR_SUSTAIN))) << 8; | |
294 | - if( (v -= dv) <= sustain16 ) { | |
295 | - volume.v16 = sustain16; adsr = ADSR_SUSTAIN; break; | |
296 | - } | |
297 | - volume.v16 = v; break; | |
298 | - } | |
299 | - case ADSR_RELEASE: { | |
300 | - unsigned int dv = volume.v16 >> *getEnvelope(ADSR_RELEASE); | |
301 | - if( dv == 0 ) dv = 1; | |
302 | - long v = volume.v16; | |
303 | - if( (v -= dv) < 0x100 ) { | |
304 | - reset(); break; | |
305 | - } | |
306 | - volume.v16 = v; break; | |
307 | - } | |
308 | - } | |
309 | - } | |
310 | -}; | |
311 | - | |
312 | 167 | #if PWMDAC_OUTPUT_PIN == 6 || PWMDAC_OUTPUT_PIN == 5 |
313 | 168 | // In Arduino, TIMER0 has been reserved by wiring.c in Arduino core, |
314 | 169 | // so defining PWMDAC_OUTPUT_PIN = 5 or 6 causes compile error |
@@ -338,9 +193,170 @@ protected: | ||
338 | 193 | #endif |
339 | 194 | #endif |
340 | 195 | |
196 | +// [Phase-correct PWM dual-slope] | |
197 | +// TCNTn value changes to: | |
198 | +// 00(BOTTOM) 01 02 03 ... FC FD FE | |
199 | +// FF(TOP) FE FD FC ... 03 02 01 | |
200 | +// -> # of values = 0xFF * 2 = 510 (NOT 512) | |
201 | +// | |
202 | +// ISR() call interval = (# of values) / F_CPU(16MHz) = 31.875us | |
203 | +// | |
204 | +// [MIDI Tuning Standard] | |
205 | +// http://en.wikipedia.org/wiki/MIDI_Tuning_Standard | |
206 | +// fn(d) = 440 Hz * 2^( (d - 69) / 12 ) MIDI note # d = 0..127 | |
207 | +// | |
208 | +#define PHASE_SPEED_OF(note_number) (static_cast<unsigned long>( \ | |
209 | + pow( 2, static_cast<double>(note_number - 69)/12 + (sizeof(unsigned long) * 8 + 1) ) \ | |
210 | + * PWMDAC_NOTE_A_FREQUENCY * 0xFF / F_CPU )) | |
211 | + | |
341 | 212 | class PWMDACSynth { |
213 | + protected: | |
214 | + static PROGMEM const byte maxVolumeSineWavetable[]; | |
215 | + static MidiChannel channels[MidiChannel::MAX_NUMBER]; | |
216 | + static PROGMEM const Instrument * const defaultInstrument; | |
217 | + class Voice { | |
218 | + protected: | |
219 | + enum AdsrStatus : byte { | |
220 | + ADSR_OFF, | |
221 | + ADSR_RELEASE, | |
222 | + ADSR_SUSTAIN, | |
223 | + ADSR_DECAY, | |
224 | + ADSR_ATTACK | |
225 | + }; | |
226 | + union { unsigned long v32; byte v8[4]; } phase; | |
227 | + unsigned long dphase32_real; // Real phase speed | |
228 | + unsigned long dphase32_bended; // Pitch-bended phase speed | |
229 | + unsigned long dphase32_original; // Original phase speed | |
230 | + long dphase32_moffset; // Modulation pitch offset | |
231 | + union { unsigned int v16; byte v8[2]; } volume; | |
232 | + unsigned int dv; // Diff of volume | |
233 | + unsigned int sustain; // Volume when ADSR Sustain | |
234 | + byte note; // 0..127 | |
235 | + AdsrStatus adsr_status; | |
236 | + const byte *wavetable; | |
237 | + const MidiChannel *channel; | |
238 | + const byte *envelope; | |
239 | + const byte *modulation; // 0 .. 127 (Unsigned 7bit) | |
240 | + public: | |
241 | + static const byte HIGHEST_PRIORITY = UCHAR_MAX; | |
242 | + static const byte LOWEST_PRIORITY = 0; | |
243 | + Voice() { allSoundOff(); } | |
244 | + byte getPriority() const { | |
245 | + byte t = HighestElementOf(volume.v8) >> 1; | |
246 | + if( adsr_status == ADSR_ATTACK ) t = HIGHEST_PRIORITY - t; | |
247 | +#if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) | |
248 | + if( channel != nullptr && t > channel->priority_volume_threshold ) { | |
249 | + t >>= 1; t |= 0x80; | |
250 | + } else { | |
251 | + t >>= 1; | |
252 | + } | |
253 | +#endif | |
254 | + return t; | |
255 | + } | |
256 | + void noteOn(const byte note, const MidiChannel * const new_channel = nullptr) { | |
257 | + if( new_channel != nullptr && this->channel != new_channel ) { | |
258 | + volume.v16 = 0; | |
259 | + this->channel = new_channel; | |
260 | + } | |
261 | + wavetable = this->channel->wavetable; | |
262 | + envelope = this->channel->envelope; | |
263 | + modulation = &(this->channel->modulation); | |
264 | + static PROGMEM const unsigned long phase_speed_table[] = ARRAY128(PHASE_SPEED_OF); | |
265 | + dphase32_original = pgm_read_dword(phase_speed_table + (this->note = note)); | |
266 | + dphase32_bended = this->channel->bendedPitchOf(dphase32_original); | |
267 | + dphase32_real = dphase32_bended + dphase32_moffset; | |
268 | + adsr_status = ADSR_ATTACK; | |
269 | + dv = UINT_MAX >> envelope[ADSR_ATTACK_VALUE]; | |
270 | + } | |
271 | + boolean reAttackIfAssigned(const byte note, const MidiChannel * const cp) { | |
272 | + if( adsr_status > ADSR_RELEASE && this->note == note && channel == cp ) { | |
273 | + adsr_status = ADSR_ATTACK; | |
274 | + dv = UINT_MAX >> envelope[ADSR_ATTACK_VALUE]; | |
275 | + return true; | |
276 | + } | |
277 | + return false; | |
278 | + } | |
279 | + void noteOff(const byte note, const MidiChannel * const cp) { | |
280 | + if( adsr_status > ADSR_RELEASE && this->note == note && channel == cp ) | |
281 | + adsr_status = ADSR_RELEASE; | |
282 | + } | |
283 | + void allNotesOff(const MidiChannel * const cp) { | |
284 | + if( adsr_status > ADSR_RELEASE && channel == cp ) adsr_status = ADSR_RELEASE; | |
285 | + } | |
286 | + void allSoundOff(const MidiChannel * const cp) { | |
287 | + if( adsr_status > ADSR_OFF && channel == cp ) allSoundOff(); | |
288 | + } | |
289 | + void allSoundOff() { | |
290 | + volume.v16 = 0; | |
291 | + adsr_status = ADSR_OFF; | |
292 | + note = UCHAR_MAX; | |
293 | + phase.v32 = dphase32_real = dphase32_moffset = dphase32_bended = dphase32_original = 0L; | |
294 | + channel = nullptr; | |
295 | + } | |
296 | + unsigned int nextPulseWidth() { | |
297 | + phase.v32 += dphase32_real; | |
298 | + return HighestElementOf(volume.v8) * | |
299 | + pgm_read_byte(wavetable + HighestElementOf(phase.v8)); | |
300 | + } | |
301 | + void update(const byte modulation_offset) { | |
302 | + // Update volume by ADSR envelope | |
303 | + switch(adsr_status) { | |
304 | + case ADSR_ATTACK: | |
305 | + if( volume.v16 < UINT_MAX - dv ) volume.v16 += dv; | |
306 | + else { | |
307 | + volume.v16 = UINT_MAX; | |
308 | + adsr_status = ADSR_DECAY; | |
309 | + sustain = static_cast<unsigned int>(envelope[ADSR_SUSTAIN_VALUE]) << 8; | |
310 | + } | |
311 | + break; | |
312 | + case ADSR_DECAY: | |
313 | + dv = volume.v16 >> envelope[ADSR_DECAY_VALUE]; | |
314 | + if( dv == 0 ) dv = 1; | |
315 | + if( volume.v16 > sustain + dv ) volume.v16 -= dv; | |
316 | + else { volume.v16 = sustain; adsr_status = ADSR_SUSTAIN; } | |
317 | + break; | |
318 | + case ADSR_SUSTAIN: break; | |
319 | + case ADSR_RELEASE: | |
320 | + dv = volume.v16 >> envelope[ADSR_RELEASE_VALUE]; | |
321 | + if( dv == 0 ) dv = 1; | |
322 | + if( volume.v16 > 0x100 + dv ) volume.v16 -= dv; | |
323 | + else allSoundOff(); | |
324 | + break; | |
325 | + case ADSR_OFF: break; | |
326 | + } | |
327 | + // Update frequency by modulation | |
328 | + if( *modulation <= 0x10 ) { | |
329 | + // When Moduletion OFF | |
330 | + if( dphase32_moffset == 0 ) return; | |
331 | + dphase32_moffset = 0; | |
332 | + dphase32_real = dphase32_bended; | |
333 | + return; | |
334 | + } | |
335 | + dphase32_moffset = (dphase32_real >> 19) * (*modulation) * modulation_offset; | |
336 | + dphase32_real = dphase32_bended + dphase32_moffset; | |
337 | + } | |
338 | + void updatePitch(const MidiChannel * const cp) { | |
339 | + // Must be called at pitch-bend change on the channel | |
340 | + if( adsr_status > ADSR_OFF && channel == cp ) { | |
341 | + dphase32_bended = channel->bendedPitchOf(dphase32_original); | |
342 | + dphase32_real = dphase32_bended + dphase32_moffset; | |
343 | + } | |
344 | + } | |
345 | + }; | |
346 | + static Voice voices[PWMDAC_POLYPHONY]; | |
342 | 347 | public: |
343 | - static void setup() { // must be called from setup() once | |
348 | + static void __updatePulseWidth() { // Only for ISR() | |
349 | + union { unsigned int v16; byte v8[2]; } pw = {0}; | |
350 | + for( byte i = 0; i < NumberOf(voices); ++i ) pw.v16 += voices[i].nextPulseWidth(); | |
351 | + PWMDAC_OCR = HighestElementOf(pw.v8); | |
352 | + } | |
353 | + static void update() { // must be called from your loop() repeatedly | |
354 | + static byte modulation_phase = 0; | |
355 | + const byte *addr = maxVolumeSineWavetable + modulation_phase++; | |
356 | + const byte modulation_offset = pgm_read_byte(addr) - 0x7F; | |
357 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].update(modulation_offset); | |
358 | + } | |
359 | + static void setup() { // must be called from your setup() once | |
344 | 360 | pinMode(PWMDAC_OUTPUT_PIN,OUTPUT); |
345 | 361 | #ifdef PWMDAC_USE_TIMER1 |
346 | 362 | // No prescaling |
@@ -384,86 +400,80 @@ class PWMDACSynth { | ||
384 | 400 | sbi(TIMSK2,TOIE2); // Enable interrupt |
385 | 401 | #endif |
386 | 402 | } |
387 | -#define EACH_VOICE(p) for(VoiceStatus *(p)=voices; (p)<= voices + (PWMDAC_POLYPHONY - 1); (p)++) | |
388 | -#define EACH_CHANNEL(c) for(MidiChannel *(c)=channels; (c)<= &HighestElementOf(channels); (c)++) | |
389 | - inline static void updatePulseWidth() { | |
390 | - // To generate waveform rapidly in ISR(), this method must be inline | |
391 | - unsigned int pw = 0; | |
392 | - EACH_VOICE(v) pw += v->nextPulseWidth(); | |
393 | - PWMDAC_OCR = pw >> 8; | |
403 | + static MidiChannel *getChannel(const char channel) { | |
404 | + return channels + (channel - 1); | |
394 | 405 | } |
395 | - static void update() { // must be called from loop() repeatedly | |
396 | - static byte modulation_phase = 0; | |
397 | - int modulation_offset = pgm_read_byte(maxVolumeSineWavetable + (++modulation_phase)) - 0x7F; | |
398 | - EACH_VOICE(v) v->update(modulation_offset); | |
406 | + static char getChannel(const MidiChannel *cp) { | |
407 | + return (cp - channels) + 1; | |
399 | 408 | } |
400 | - static void noteOff(byte channel, byte pitch, byte velocity) { | |
409 | + static void noteOff(const byte channel, const byte pitch, const byte velocity) { | |
410 | + (void)velocity; | |
401 | 411 | MidiChannel *cp = getChannel(channel); |
402 | - EACH_VOICE(v) v->noteOff(pitch, cp); | |
403 | - } | |
404 | - static void noteOn(byte channel, byte pitch, byte velocity) { | |
405 | - assignVoice(getChannel(channel),pitch)->noteOn(pitch); | |
412 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].noteOff(pitch, cp); | |
413 | + } | |
414 | + static void noteOn(const byte channel, const byte pitch, const byte velocity) { | |
415 | + (void)velocity; | |
416 | + const MidiChannel * const cp = getChannel(channel); | |
417 | + for( byte i = 0; i < NumberOf(voices); ++i ) | |
418 | + if( voices[i].reAttackIfAssigned(pitch, cp) ) return; | |
419 | + // Search voice to assign | |
420 | + byte lowest_priority = Voice::HIGHEST_PRIORITY; | |
421 | + byte lowest_priority_index = 0; | |
422 | + for( byte i = 0; i < NumberOf(voices); ++i ) { | |
423 | + const byte priority = voices[i].getPriority(); | |
424 | + if( priority > lowest_priority ) continue; | |
425 | + lowest_priority = priority; | |
426 | + lowest_priority_index = i; | |
427 | + } | |
428 | + voices[lowest_priority_index].noteOn(pitch, cp); | |
406 | 429 | } |
407 | - static void pitchBend(byte channel, int bend) { | |
430 | + static void pitchBend(const byte channel, const int bend) { | |
408 | 431 | MidiChannel *cp = getChannel(channel); |
409 | 432 | if ( ! cp->pitchBendChange(bend) ) return; |
410 | - EACH_VOICE(v) v->updatePitch(cp); | |
433 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].updatePitch(cp); | |
411 | 434 | } |
412 | - static void controlChange(byte channel, byte number, byte value) { | |
435 | + static void controlChange(const byte channel, const byte number, const byte value) { | |
413 | 436 | MidiChannel *cp = getChannel(channel); |
414 | 437 | cp->controlChange(number, value); |
415 | 438 | switch(number) { |
416 | 439 | case 120: // All sound off |
417 | - EACH_VOICE(v) v->allSoundOff(cp); | |
440 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].allSoundOff(cp); | |
418 | 441 | break; |
419 | 442 | case 123: // All notes off |
420 | - EACH_VOICE(v) v->allNotesOff(cp); | |
443 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].allNotesOff(cp); | |
421 | 444 | break; |
422 | 445 | } |
423 | 446 | } |
424 | 447 | static void systemReset() { |
425 | - EACH_VOICE(v) v->reset(); | |
426 | - EACH_CHANNEL(c) c->reset(defaultInstrument); | |
427 | - } | |
428 | - static MidiChannel *getChannel(char channel) { return channels + (channel - 1); } | |
429 | - static char getChannel(MidiChannel *cp) { return (cp - channels) + 1; } | |
430 | - static byte musicalMod12(char); | |
431 | - static byte musicalMod7(char); | |
432 | - static byte log2(unsigned int); | |
433 | - static int musicalConstrain12(int note, int min_note, int max_note) { | |
434 | - if( max_note < note ) { | |
435 | - note = max_note - musicalMod12(max_note - note); | |
436 | - } | |
437 | - else if( min_note > note ) { | |
438 | - note = min_note + musicalMod12(note - min_note); | |
439 | - } | |
440 | - return note; | |
441 | - } | |
442 | - protected: | |
443 | - static PROGMEM const Instrument * const defaultInstrument; | |
444 | - static MidiChannel channels[16]; | |
445 | - static VoiceStatus voices[PWMDAC_POLYPHONY]; | |
446 | - static PROGMEM const byte maxVolumeSineWavetable[]; | |
447 | - static VoiceStatus *assignVoice(MidiChannel *channel, byte note) { | |
448 | - EACH_VOICE(v) if( v->isNoteOn(note, channel) ) return v; | |
449 | - VoiceStatus *lowest_priority_voice = voices; | |
450 | - byte lowest_priority = VoiceStatus::HIGHEST_PRIORITY; | |
451 | - EACH_VOICE(v) { | |
452 | - byte priority = v->getPriority(); | |
453 | - if( priority > lowest_priority ) continue; | |
454 | - lowest_priority = priority; | |
455 | - lowest_priority_voice = v; | |
456 | - } | |
457 | - lowest_priority_voice->setChannel(channel); | |
458 | - return lowest_priority_voice; | |
448 | + for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].allSoundOff(); | |
449 | + for( byte i = 0; i < NumberOf(channels); i++ ) channels[i].reset(defaultInstrument); | |
459 | 450 | } |
460 | -#undef EACH_VOICE | |
461 | 451 | }; |
462 | 452 | |
463 | 453 | #define PWMDAC_CREATE_INSTANCE(instrument) \ |
464 | - ISR(PWMDAC_OVF_vect) { PWMDACSynth::updatePulseWidth(); } \ | |
465 | - VoiceStatus PWMDACSynth::voices[PWMDAC_POLYPHONY]; \ | |
466 | 454 | PWMDAC_CREATE_WAVETABLE(PWMDACSynth::maxVolumeSineWavetable, PWMDAC_MAX_VOLUME_SINE_WAVE); \ |
467 | - PROGMEM const Instrument * const PWMDACSynth::defaultInstrument = instrument; \ | |
468 | - MidiChannel PWMDACSynth::channels[16] = MidiChannel(instrument); | |
455 | + MidiChannel PWMDACSynth::channels[] = { \ | |
456 | + MidiChannel(instrument), \ | |
457 | + MidiChannel(instrument), \ | |
458 | + MidiChannel(instrument), \ | |
459 | + MidiChannel(instrument), \ | |
460 | +\ | |
461 | + MidiChannel(instrument), \ | |
462 | + MidiChannel(instrument), \ | |
463 | + MidiChannel(instrument), \ | |
464 | + MidiChannel(instrument), \ | |
465 | +\ | |
466 | + MidiChannel(instrument), \ | |
467 | + MidiChannel(instrument), \ | |
468 | + MidiChannel(instrument), \ | |
469 | + MidiChannel(instrument), \ | |
470 | +\ | |
471 | + MidiChannel(instrument), \ | |
472 | + MidiChannel(instrument), \ | |
473 | + MidiChannel(instrument), \ | |
474 | + MidiChannel(instrument), \ | |
475 | + }; \ | |
476 | + const Instrument * const PWMDACSynth::defaultInstrument PROGMEM = instrument; \ | |
477 | + PWMDACSynth::Voice PWMDACSynth::voices[]; \ | |
478 | + ISR(PWMDAC_OVF_vect) { PWMDACSynth::__updatePulseWidth(); } | |
469 | 479 |
@@ -1,7 +1,7 @@ | ||
1 | 1 | |
2 | 2 | [PWMDAC_Synth - PWM DAC synthesizer library for Arduino] |
3 | 3 | |
4 | -ver.20190330 | |
4 | +ver.20200405 | |
5 | 5 | |
6 | 6 | https://osdn.jp/users/kamide/pf/PWMDAC_Synth/wiki/FrontPage |
7 | 7 |
@@ -99,18 +99,13 @@ Arduino | ||
99 | 99 | ●同時発音数(ボイス数) |
100 | 100 | |
101 | 101 | デフォルトは6重和音です。 |
102 | + PWMDAC_POLYPHONY に数値を設定してコンパイルし直すことで増減可能です。 | |
102 | 103 | |
103 | - PWMDAC_POLYPHONY に数値を設定してコンパイルし直すことで、 | |
104 | - 同時発音数を増減させることができます。 | |
104 | + 同時発音数を増やすと、その分、割り込み処理 ISR() の実行に時間がかかります。 | |
105 | + 割り込み以外の処理を行うための時間的余裕がなくなった時点で動作しなくなります。 | |
106 | + そうなる寸前が、事実上の同時発音数の上限です。 | |
105 | 107 | |
106 | - 同時発音数を増やしすぎると、その分、割り込み処理に時間がかかって | |
107 | - 割り込み以外の処理を行う余裕がなくなり、正常に動作しなくなります。 | |
108 | - | |
109 | - 同時発音数の1つの発音は「ボイス」という単位で管理され、ボイスの状態を | |
110 | - 保持するクラスとして VoiceStatus クラスを定義しています。 | |
111 | - VoiceStatus クラスの配列の要素数が、そのまま同時発音数となります。 | |
112 | - | |
113 | - 特定のMIDIチャンネルについてボイスアサインの優先度を上げる方法については、 | |
108 | + 特定のMIDIチャンネルに対しボイスアサインの優先度を上げることもできます。 | |
114 | 109 | 後述の「●チャンネル優先度指定」を参照。 |
115 | 110 | |
116 | 111 |
@@ -222,26 +217,6 @@ Arduino | ||
222 | 217 | 多重和音感が失われることがありますので、適宜調整してください。 |
223 | 218 | |
224 | 219 | |
225 | -●ユーティリティ | |
226 | - byte musicalMod7(char x) | |
227 | - byte musicalMod12(char x) | |
228 | - それぞれ7で割った余り、12で割った余りを返します。 | |
229 | - % 演算子と違い、負数を与えても正数で返します。 | |
230 | - 内部的にはビット演算と加算だけで高速に計算します。 | |
231 | - 音階の計算に便利です。 | |
232 | - | |
233 | - byte log2(unsigned int x) | |
234 | - 2を底とする対数を、小数点以下を切捨てた整数で返します。 | |
235 | - y = log2(x) と x = 1 << y は逆関数の関係になります。 | |
236 | - | |
237 | - int musicalConstrain12(int note, int min_note, int max_note) | |
238 | - Arduino の constrain() を音階用に実装し直した関数。 | |
239 | - note が min_note 〜 max_note の音域にあれば | |
240 | - note をそのまま返します。音域をはみ出していた場合、 | |
241 | - 音階を変えずにオクターブ位置を変えることにより、 | |
242 | - 範囲に収まるよう調整されます。 | |
243 | - | |
244 | - | |
245 | 220 | ●その他の関数 |
246 | 221 | |
247 | 222 | その他、利用できる関数についてはソースコードのヘッダ PWMDAC_Synth.h を参照。 |
@@ -249,3 +224,4 @@ Arduino | ||
249 | 224 | |
250 | 225 | 作者:@きよし - Akiyoshi Kamide |
251 | 226 | http://www.yk.rim.or.jp/~kamide/ |
227 | + |
@@ -25,12 +25,7 @@ controlChange KEYWORD2 | ||
25 | 25 | systemReset KEYWORD2 |
26 | 26 | getChannel KEYWORD2 |
27 | 27 | setEnvelope KEYWORD2 |
28 | -setWave KEYWORD2 | |
29 | 28 | setPriority KEYWORD2 |
30 | -musicalMod12 KEYWORD2 | |
31 | -musicalMod7 KEYWORD2 | |
32 | -log2 KEYWORD2 | |
33 | -musicalConstrain12 KEYWORD2 | |
34 | 29 | |
35 | 30 | ####################################### |
36 | 31 | # Constants (LITERAL1) |