ATMEGA328を搭載した Arduino Duemilanove 互換機で音をPWM D/A変換出力するシンセサイザーライブラリです。
Revision | 817c85c7ad84d568cfb2a92c5d75bbf19ce0efb0 (tree) |
---|---|
Zeit | 2020-04-14 01:14:12 |
Autor | Akiyoshi Kamide <kamide@yk.r...> |
Commiter | Akiyoshi Kamide |
Performance dramatically revived by specifying #pragma GCC optimize ("-O3")
@@ -1,17 +1,18 @@ | ||
1 | 1 | // |
2 | -// PWM DAC Synthesizer ver.20200405 | |
2 | +// PWM DAC Synthesizer ver.20200413 | |
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/ |
6 | 6 | // |
7 | 7 | #pragma once |
8 | +#pragma GCC push_options | |
9 | +#pragma GCC optimize ("-O3") | |
8 | 10 | |
9 | 11 | #include <Arduino.h> |
10 | 12 | #include <wiring_private.h> |
11 | 13 | #include <limits.h> |
12 | 14 | |
13 | 15 | #define NumberOf(array) (sizeof(array)/sizeof(*(array))) |
14 | -#define HighestElementOf(array) ((array)[NumberOf(array)-1]) | |
15 | 16 | #define cbi16(sfr, bit) (_SFR_WORD(sfr) &= ~_BV(bit)) |
16 | 17 | #define sbi16(sfr, bit) (_SFR_WORD(sfr) |= _BV(bit)) |
17 | 18 |
@@ -57,6 +58,52 @@ | ||
57 | 58 | |
58 | 59 | #define PWMDAC_CREATE_WAVETABLE(table, function) PROGMEM const byte table[] = ARRAY256(function) |
59 | 60 | |
61 | +// [Phase-correct PWM dual-slope] | |
62 | +// TCNTn value changes to: | |
63 | +// 00(BOTTOM) 01 02 03 ... FC FD FE | |
64 | +// FF(TOP) FE FD FC ... 03 02 01 | |
65 | +// -> # of values = 0xFF * 2 = 510 (NOT 512) | |
66 | +// | |
67 | +// ISR() call interval = (# of values) / F_CPU(16MHz) = 31.875us | |
68 | +// | |
69 | +// [MIDI Tuning Standard] | |
70 | +// http://en.wikipedia.org/wiki/MIDI_Tuning_Standard | |
71 | +// fn(d) = 440 Hz * 2^( (d - 69) / 12 ) MIDI note # d = 0..127 | |
72 | +// | |
73 | +#define PHASE_SPEED_OF(note_number) (static_cast<unsigned long>( \ | |
74 | + pow( 2, static_cast<double>(note_number - 69)/12 + (sizeof(unsigned long) * 8 + 1) ) \ | |
75 | + * PWMDAC_NOTE_A_FREQUENCY * 0xFF / F_CPU )) | |
76 | + | |
77 | +// PWM Port macros | |
78 | +#if PWMDAC_OUTPUT_PIN == 6 || PWMDAC_OUTPUT_PIN == 5 | |
79 | +// In Arduino, TIMER0 has been reserved by wiring.c in Arduino core, | |
80 | +// so defining PWMDAC_OUTPUT_PIN = 5 or 6 causes compile error | |
81 | +// (multiple definition of `__vector_16') | |
82 | +#define PWMDAC_USE_TIMER0 | |
83 | +#define PWMDAC_OVF_vect TIMER0_OVF_vect | |
84 | +#if PWMDAC_OUTPUT_PIN == 6 | |
85 | +#define PWMDAC_OCR OCR0A | |
86 | +#else | |
87 | +#define PWMDAC_OCR OCR0B | |
88 | +#endif | |
89 | +#elif PWMDAC_OUTPUT_PIN == 9 || PWMDAC_OUTPUT_PIN == 10 | |
90 | +#define PWMDAC_USE_TIMER1 | |
91 | +#define PWMDAC_OVF_vect TIMER1_OVF_vect | |
92 | +#if PWMDAC_OUTPUT_PIN == 9 | |
93 | +#define PWMDAC_OCR OCR1A // OC1A maybe used as MOSI for SPI | |
94 | +#else | |
95 | +#define PWMDAC_OCR OCR1B // OC1B maybe used as SS for SPI | |
96 | +#endif | |
97 | +#elif PWMDAC_OUTPUT_PIN == 11 || PWMDAC_OUTPUT_PIN == 3 | |
98 | +#define PWMDAC_USE_TIMER2 | |
99 | +#define PWMDAC_OVF_vect TIMER2_OVF_vect | |
100 | +#if PWMDAC_OUTPUT_PIN == 11 | |
101 | +#define PWMDAC_OCR OCR2A | |
102 | +#else | |
103 | +#define PWMDAC_OCR OCR2B // OC2B maybe used as INT1 | |
104 | +#endif | |
105 | +#endif | |
106 | + | |
60 | 107 | enum AdsrParam : byte { |
61 | 108 | ADSR_RELEASE_VALUE, // 0..15 |
62 | 109 | ADSR_SUSTAIN_VALUE, // 0..255 |
@@ -164,51 +211,6 @@ class MidiChannel { | ||
164 | 211 | } |
165 | 212 | }; |
166 | 213 | |
167 | -#if PWMDAC_OUTPUT_PIN == 6 || PWMDAC_OUTPUT_PIN == 5 | |
168 | -// In Arduino, TIMER0 has been reserved by wiring.c in Arduino core, | |
169 | -// so defining PWMDAC_OUTPUT_PIN = 5 or 6 causes compile error | |
170 | -// (multiple definition of `__vector_16') | |
171 | -#define PWMDAC_USE_TIMER0 | |
172 | -#define PWMDAC_OVF_vect TIMER0_OVF_vect | |
173 | -#if PWMDAC_OUTPUT_PIN == 6 | |
174 | -#define PWMDAC_OCR OCR0A | |
175 | -#else | |
176 | -#define PWMDAC_OCR OCR0B | |
177 | -#endif | |
178 | -#elif PWMDAC_OUTPUT_PIN == 9 || PWMDAC_OUTPUT_PIN == 10 | |
179 | -#define PWMDAC_USE_TIMER1 | |
180 | -#define PWMDAC_OVF_vect TIMER1_OVF_vect | |
181 | -#if PWMDAC_OUTPUT_PIN == 9 | |
182 | -#define PWMDAC_OCR OCR1A // OC1A maybe used as MOSI for SPI | |
183 | -#else | |
184 | -#define PWMDAC_OCR OCR1B // OC1B maybe used as SS for SPI | |
185 | -#endif | |
186 | -#elif PWMDAC_OUTPUT_PIN == 11 || PWMDAC_OUTPUT_PIN == 3 | |
187 | -#define PWMDAC_USE_TIMER2 | |
188 | -#define PWMDAC_OVF_vect TIMER2_OVF_vect | |
189 | -#if PWMDAC_OUTPUT_PIN == 11 | |
190 | -#define PWMDAC_OCR OCR2A | |
191 | -#else | |
192 | -#define PWMDAC_OCR OCR2B // OC2B maybe used as INT1 | |
193 | -#endif | |
194 | -#endif | |
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 | - | |
212 | 214 | class PWMDACSynth { |
213 | 215 | protected: |
214 | 216 | static PROGMEM const byte maxVolumeSineWavetable[]; |
@@ -223,12 +225,26 @@ class PWMDACSynth { | ||
223 | 225 | ADSR_DECAY, |
224 | 226 | ADSR_ATTACK |
225 | 227 | }; |
226 | - union { unsigned long v32; byte v8[4]; } phase; | |
228 | + union { | |
229 | + unsigned long v32; | |
230 | + struct { | |
231 | + byte lowest; | |
232 | + byte lowest2; | |
233 | + byte highest2; | |
234 | + byte highest; | |
235 | + } v8; | |
236 | + } phase; | |
227 | 237 | unsigned long dphase32_real; // Real phase speed |
228 | 238 | unsigned long dphase32_bended; // Pitch-bended phase speed |
229 | 239 | unsigned long dphase32_original; // Original phase speed |
230 | 240 | long dphase32_moffset; // Modulation pitch offset |
231 | - union { unsigned int v16; byte v8[2]; } volume; | |
241 | + union { | |
242 | + unsigned int v16; | |
243 | + struct { | |
244 | + byte low; | |
245 | + byte high; | |
246 | + } v8; | |
247 | + } volume; | |
232 | 248 | unsigned int dv; // Diff of volume |
233 | 249 | unsigned int sustain; // Volume when ADSR Sustain |
234 | 250 | byte note; // 0..127 |
@@ -242,7 +258,7 @@ class PWMDACSynth { | ||
242 | 258 | static const byte LOWEST_PRIORITY = 0; |
243 | 259 | Voice() { allSoundOff(); } |
244 | 260 | byte getPriority() const { |
245 | - byte t = HighestElementOf(volume.v8) >> 1; | |
261 | + byte t = volume.v8.high >> 1; | |
246 | 262 | if( adsr_status == ADSR_ATTACK ) t = HIGHEST_PRIORITY - t; |
247 | 263 | #if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) |
248 | 264 | if( channel != nullptr && t > channel->priority_volume_threshold ) { |
@@ -295,8 +311,7 @@ class PWMDACSynth { | ||
295 | 311 | } |
296 | 312 | unsigned int nextPulseWidth() { |
297 | 313 | phase.v32 += dphase32_real; |
298 | - return HighestElementOf(volume.v8) * | |
299 | - pgm_read_byte(wavetable + HighestElementOf(phase.v8)); | |
314 | + return volume.v8.high * pgm_read_byte(wavetable + phase.v8.highest); | |
300 | 315 | } |
301 | 316 | void update(const byte modulation_offset) { |
302 | 317 | // Update volume by ADSR envelope |
@@ -345,18 +360,8 @@ class PWMDACSynth { | ||
345 | 360 | }; |
346 | 361 | static Voice voices[PWMDAC_POLYPHONY]; |
347 | 362 | public: |
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 | |
363 | + static void setup() { | |
364 | + // must be called from your setup() once | |
360 | 365 | pinMode(PWMDAC_OUTPUT_PIN,OUTPUT); |
361 | 366 | #ifdef PWMDAC_USE_TIMER1 |
362 | 367 | // No prescaling |
@@ -409,18 +414,19 @@ class PWMDACSynth { | ||
409 | 414 | static void noteOff(const byte channel, const byte pitch, const byte velocity) { |
410 | 415 | (void)velocity; |
411 | 416 | MidiChannel *cp = getChannel(channel); |
412 | - for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].noteOff(pitch, cp); | |
417 | + byte i = NumberOf(voices); while( i > 0 ) voices[--i].noteOff(pitch, cp); | |
413 | 418 | } |
414 | 419 | static void noteOn(const byte channel, const byte pitch, const byte velocity) { |
415 | 420 | (void)velocity; |
416 | 421 | const MidiChannel * const cp = getChannel(channel); |
417 | - for( byte i = 0; i < NumberOf(voices); ++i ) | |
418 | - if( voices[i].reAttackIfAssigned(pitch, cp) ) return; | |
422 | + byte i = NumberOf(voices); | |
423 | + while(i) if( voices[--i].reAttackIfAssigned(pitch, cp) ) return; | |
419 | 424 | // Search voice to assign |
420 | 425 | byte lowest_priority = Voice::HIGHEST_PRIORITY; |
421 | 426 | byte lowest_priority_index = 0; |
422 | - for( byte i = 0; i < NumberOf(voices); ++i ) { | |
423 | - const byte priority = voices[i].getPriority(); | |
427 | + i = NumberOf(voices); | |
428 | + while(i) { | |
429 | + const byte priority = voices[--i].getPriority(); | |
424 | 430 | if( priority > lowest_priority ) continue; |
425 | 431 | lowest_priority = priority; |
426 | 432 | lowest_priority_index = i; |
@@ -430,23 +436,38 @@ class PWMDACSynth { | ||
430 | 436 | static void pitchBend(const byte channel, const int bend) { |
431 | 437 | MidiChannel *cp = getChannel(channel); |
432 | 438 | if ( ! cp->pitchBendChange(bend) ) return; |
433 | - for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].updatePitch(cp); | |
439 | + byte i = NumberOf(voices); while(i) voices[--i].updatePitch(cp); | |
434 | 440 | } |
435 | 441 | static void controlChange(const byte channel, const byte number, const byte value) { |
436 | 442 | MidiChannel *cp = getChannel(channel); |
437 | 443 | cp->controlChange(number, value); |
444 | + byte i; | |
438 | 445 | switch(number) { |
439 | 446 | case 120: // All sound off |
440 | - for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].allSoundOff(cp); | |
447 | + i = NumberOf(voices); while(i) voices[--i].allSoundOff(cp); | |
441 | 448 | break; |
442 | 449 | case 123: // All notes off |
443 | - for( byte i = 0; i < NumberOf(voices); ++i ) voices[i].allNotesOff(cp); | |
450 | + i = NumberOf(voices); while(i) voices[--i].allNotesOff(cp); | |
444 | 451 | break; |
445 | 452 | } |
446 | 453 | } |
447 | 454 | static void systemReset() { |
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); | |
455 | + byte i = NumberOf(voices); while(i) voices[--i].allSoundOff(); | |
456 | + i = NumberOf(channels); while(i) channels[--i].reset(defaultInstrument); | |
457 | + } | |
458 | + static byte __nextPulseWidth() { | |
459 | + union { unsigned int v16; struct {byte low; byte high;} v8; } pw = {0}; | |
460 | + byte i = NumberOf(voices); | |
461 | + while(i) pw.v16 += voices[--i].nextPulseWidth(); | |
462 | + return pw.v8.high; | |
463 | + } | |
464 | + static void update() { | |
465 | + // must be called from your loop() repeatedly | |
466 | + static byte modulation_phase = 0; | |
467 | + const byte *addr = maxVolumeSineWavetable + modulation_phase++; | |
468 | + const byte modulation_offset = pgm_read_byte(addr) - 0x7F; | |
469 | + byte i = NumberOf(voices); | |
470 | + while(i) voices[--i].update(modulation_offset); | |
450 | 471 | } |
451 | 472 | }; |
452 | 473 |
@@ -475,5 +496,7 @@ class PWMDACSynth { | ||
475 | 496 | }; \ |
476 | 497 | const Instrument * const PWMDACSynth::defaultInstrument PROGMEM = instrument; \ |
477 | 498 | PWMDACSynth::Voice PWMDACSynth::voices[]; \ |
478 | - ISR(PWMDAC_OVF_vect) { PWMDACSynth::__updatePulseWidth(); } | |
499 | + ISR(PWMDAC_OVF_vect) { PWMDAC_OCR = PWMDACSynth::__nextPulseWidth(); } | |
500 | + | |
501 | +#pragma GCC pop_options | |
479 | 502 |