forked from MeeBlip/meeblip-synth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
meeblip-se.asm
executable file
·5399 lines (4515 loc) · 185 KB
/
meeblip-se.asm
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;-------------------------------------------------------------------------------------------------------------------
; _ _ _
; | | | |(_)
; ____ _____ _____| |__ | | _ ____ ___ _____
; | \| ___ | ___ | _ \| || | _ \ /___) ___ |
; | | | | ____| ____| |_) ) || | |_| | |___ | ____|
; |_|_|_|_____)_____)____/ \_)_| __/ (___/|_____)
; |_|
;
; meeblip se base - the hackable digital synthesiser
;
; For Version 1 Meeblip hardware (no save/load switches or patch memory - see the V2 hardware code fork)
;
;-------------------------------------------------------------------------------------------------------------------
;
; Changelog
;
;V2.04 2012.01.05 - Added maximum resonance table
; - Envelopes no longer restart on note off release if they're stopped
; - Replace MUL8X8S routine with hardware multiply
; - Updated LPM calls throughout code
; - Implement 2 byte offsets for all table lookups
; - New bandlimited waveforms with matched average amplitude (Axel Werner)
; - New 32x16 bit multiply, 32 bit load (AW)
; - Use signed multiply in DCA (AW)
; - New oscillator mix routine (AW)
; - New TAB_VCF, TAB_VCA and TIMETORATE tables (AW)
; - Fix envelope decay bug (AW)
;V2.03 2011.12.20 - Added MIDI Velocity to VCF Envelope Modulation
; - Changed maximum resonance and resonance scaling limits
; - Updated oscillator mix levels because of mix clipping
;V2.02 2011.12.12 - Corrected code to save pre-filtered waveform
;V2.01 2011.12.01 - Added inverse sawtooth waveform
; - Fixed wavetable overflow in variable pulse calculation
; - Increase Portamento rate
; - Debug MIDI CC switch routine
; - Remove unnecesary opcodes
; - Changed save/load code (save SREG->disable global interrupts->eeprom read/write->restore SREG)
; - Ensured that linked sustain/decay parameters are always updated
;V2.00 2011.11.23 - Bandlimited pulse and PWM wave built from out-of-phase ramps
; - Bandlimited square, saw and triangle oscillators
; - Patch state engine (independent of front panel controls)
; - New knob scan routine (scans only the most recently converted analog channel)
; - Dual ADSD envelopes
; - PWM sweep switch and PWM rate knob (limited between 0-50% pulse duty cycle)
; - Filter envelop amount knob added
; - MIDI control of all parameters
; - Reorganized front panel
; - MIDI knob and switch CC control
; - DCA gate, LFO sync, modulation wheel and filter key track now always on
; - LFO enable switch added
; - anti-alias switch added - off uses bandlimited wave 0, on uses bandlimited wavetable 0..11, based on note
; - Added variable names for switches instead of using bit ops - allows easier reorganization of panel
; - FM switch - off or 100%
; - New sample rate - 36363.63636 Hz, approximately 440 instructions per sample
;V1.05 2011.02.04 - Save dual parameter knob values to eeprom and reload on power up.
;V1.04 2011.01.19 - MIDI CC RAM table added.
; - PWM waveform with dedicated fixed sweep LFO
; - 8-bit multiplies optimized in main loop
; - LFO Sync switch now retriggers LFO on each keypress
; - Initialize FM Depth to zero on power up
;V1.03 - VCA and VCF level tables extended to reduce stairstepping
;V1.02 - Flip DAC write line high immediately after outputting sample
;V1.01 - Optimized DCOA+DCOB summer, outputs signed value
;V1.00 - Power/MIDI status LED remains on unless receiving MIDI
; - Sustain level of ADSR envelope is exponentially scaled
; - Non-resonant highpass filter implemented
; - Filter Q level compensation moved outside audio sample calc interrupt
; - Filter calculations increased to 16x8-bit to reduce noise floor
; - DCA output level calculations are rounded
; - Mod wheel no longer overrides LFO level knob when less than knob value
;V0.90 - Initial release
;
;-------------------------------------------------------------------------------------------------------------------
;
; MeeBlip Contributors
;
; Jarek Ziembicki - Created the original AVRsynth, upon which this project is based.
; Laurie Biddulph - Worked with Jarek to translate his comments into English, ported to Atmega16
; Daniel Kruszyna - Extended AVRsynth (several of his ideas are incorporated in MeeBlip)
; Julian Schmidt - Filter algorithm
; Axel Werner - Code optimization, bug fixes and bandlimited waveforms
; James Grahame - Ported and extended the AVRsynth code to MeeBlip hardware.
;
;-------------------------------------------------------------------------------------------------------------------
;
; Port Mapping
;
; PA0..7 8 potentiometers
; PB0-PB4 Control Panel Switches - ROWS
; PB5-PB7 ISP programming header
; PB7 DAC LDAC signal (load both DAC ports synchronously)
; PC0-PC7 DATA port for DAC
; PD0 RxD (MIDI IN)
; PD1 Power ON/MIDI LED
; PD2 Select DAC port A or B
; PD3 DAC Write line
; PD4-PD7 Control Panel Switches - COLUMNS
;
; Timers
;
; Timer0 not used
; Timer1 Time counter: CK/400 --> TCNT1
; Timer2 Sample timer: (CK/8) / 32 --> 36363.63636 Hz
;
;-------------------------------------------------------------------------------------------------------------------
.NOLIST
; .INCLUDE "m32def.inc"
.LIST
.LISTMAC
.SET cpu_frequency = 16000000
.SET baud_rate = 31250
.SET KBDSCAN = 6250
;
;-------------------------------------------------------------------------------------------------------------------
; V A R I A B L E S & D E F I N I T I O N S
;-------------------------------------------------------------------------------------------------------------------
;registers:
;current phase of DCO A:
.DEF PHASEA_0 = r2
.DEF PHASEA_1 = r3
.DEF PHASEA_2 = r4
;current phase of DCO B:
.DEF PHASEB_0 = r5
.DEF PHASEB_1 = r6
.DEF PHASEB_2 = r7
.DEF ZERO = r8
;DCF:
.def a_L = r9
.def a_H = r10
.def z_L = r18
.def z_H = r19
.def temp = r30
.def temp2 = r31
.DEF OSC_OUT_L = r14 ; pre-filter audio
.DEF OSC_OUT_H = r15
.def LDAC = r16
.def HDAC = r17
;RAM (0060h...025Fh):
.DSEG
;MIDI:
MIDIPHASE: .BYTE 1
MIDICHANNEL: .BYTE 1
MIDIDATA0: .BYTE 1
MIDIVELOCITY: .BYTE 1
MIDINOTE: .BYTE 1
MIDINOTEPREV: .BYTE 1 ; buffer for MIDI note
MIDIPBEND_L: .BYTE 1 ;\
MIDIPBEND_H: .BYTE 1 ;/ -32768..+32766
LED: .BYTE 1 ; On/off status of front panel LED
;current sound parameters:
LFOLEVEL: .BYTE 1 ; 0..255
KNOB_SHIFT: .BYTE 1 ; 0 = Bank 0 (lower), 1 = Bank 1 (upper).
POWER_UP: .BYTE 1 ; 255 = Synth just turned on, 0 = normal operation
KNOB0_STATUS: .BYTE 1 ; Each byte corresponds to a panel knob.
KNOB1_STATUS: .BYTE 1 ; 0 = pot not updated since Knob Shift switch change
KNOB2_STATUS: .BYTE 1 ; 1 = pot has been updated.
KNOB3_STATUS: .BYTE 1
KNOB4_STATUS: .BYTE 1
KNOB5_STATUS: .BYTE 1
KNOB6_STATUS: .BYTE 1
KNOB7_STATUS: .BYTE 1
SWITCH1: .BYTE 1
SWITCH2: .BYTE 1
OLD_SWITCH1: .BYTE 1 ; Previous switch values (used to flag switch changes)
OLD_SWITCH2: .BYTE 1
; Switch value currently used (from front panel, MIDI or last loaded patch)
PATCH_SWITCH1: .BYTE 1
.equ SW_KNOB_SHIFT = 0
.equ SW_OSC_FM = 1
.equ SW_LFO_RANDOM = 2
.equ SW_LFO_WAVE = 3
.equ SW_FILTER_MODE = 4
.equ SW_DISTORTION = 5
.equ SW_LFO_ENABLE = 6
.equ SW_LFO_DEST = 7
PATCH_SWITCH2: .BYTE 1
.equ SW_ANTI_ALIAS = 0
.equ SW_OSCB_OCT = 1
.equ SW_OSCB_ENABLE = 2
.equ SW_OSCB_WAVE = 3
.equ SW_SUSTAIN = 4
.equ SW_OSCA_NOISE = 5
.equ SW_PWM_SWEEP = 6
.equ SW_OSCA_WAVE = 7
SWITCH3: .BYTE 1 ; b0: MIDI SWITCH 1
; b1: MIDI SWITCH 2
; b2: MIDI SWITCH 3
; b3: MIDI SWITCH 4
SETMIDICHANNEL: .BYTE 1 ; selected MIDI channel: 0 for OMNI or 1..15
DETUNEB_FRAC: .BYTE 1 ;\
DETUNEB_INTG: .BYTE 1 ;/ -128,000..+127,996
ATTACKTIME: .BYTE 1 ; 0..255
DECAYTIME: .BYTE 1 ; 0..255
SUSTAINLEVEL: .BYTE 1 ; 0/255
RELEASETIME: .BYTE 1 ; 0..255
ATTACKTIME2: .BYTE 1 ; 0..255
DECAYTIME2: .BYTE 1 ; 0..255
SUSTAINLEVEL2: .BYTE 1 ; 0/255
RELEASETIME2: .BYTE 1 ; 0..255
NOTE_L: .BYTE 1
NOTE_H: .BYTE 1
NOTE_INTG: .BYTE 1
PORTACNT: .BYTE 1 ; 2 / 1 / 0
LPF_I: .BYTE 1
HPF_I: .BYTE 1
LEVEL: .BYTE 1 ; 0..255
PITCH: .BYTE 1 ; 0..96
ADC_CHAN: .BYTE 1 ; 0..7
PREV_ADC_CHAN: .BYTE 1 ; 0..7
ADC_0: .BYTE 1 ; Panel knob values.
ADC_1: .BYTE 1
ADC_2: .BYTE 1
ADC_3: .BYTE 1
ADC_4: .BYTE 1
ADC_5: .BYTE 1
ADC_6: .BYTE 1
ADC_7: .BYTE 1
OLD_ADC_0: .BYTE 1 ; Previous panel knob value
OLD_ADC_1: .BYTE 1
OLD_ADC_2: .BYTE 1
OLD_ADC_3: .BYTE 1
OLD_ADC_4: .BYTE 1
OLD_ADC_5: .BYTE 1
OLD_ADC_6: .BYTE 1
OLD_ADC_7: .BYTE 1
GATE: .BYTE 1 ; 0 / 1
GATEEDGE: .BYTE 1 ; 0 / 1
TPREV_KBD_L: .BYTE 1
TPREV_KBD_H: .BYTE 1
TPREV_L: .BYTE 1
TPREV_H: .BYTE 1
DELTAT_L: .BYTE 1 ;\ Time from former course
DELTAT_H: .BYTE 1 ;/ of the main loop (1 bit = 32 µs)
ENVPHASE: .BYTE 1 ; 0=stop 1=attack 2=decay 3=sustain 4=release
ENV_FRAC_L: .BYTE 1
ENV_FRAC_H: .BYTE 1
ENV_INTEGR: .BYTE 1
ENVPHASE2: .BYTE 1 ; 0=stop 1=attack 2=decay 3=sustain 4=release
ENV_FRAC_L2: .BYTE 1
ENV_FRAC_H2: .BYTE 1
ENV_INTEGR2: .BYTE 1
VELOCITY_ENVMOD: .BYTE 1
LFOPHASE: .BYTE 1 ; 0=up 1=down
LFO_FRAC_L: .BYTE 1 ;\
LFO_FRAC_H: .BYTE 1 ; > -128,000..+127,999
LFO_INTEGR: .BYTE 1 ;/
LFOVALUE: .BYTE 1 ; -128..+127
LFO2PHASE: .BYTE 1 ; 0=up 1=down
LFO2_FRAC_L: .BYTE 1 ;\
LFO2_FRAC_H: .BYTE 1 ; > -128,000..+127,999
LFO2_INTEGR: .BYTE 1 ;/
LFO2VALUE: .BYTE 1 ; -128..+127
OLDWAVEA: .BYTE 1
OLDWAVEB: .BYTE 1
SHIFTREG_0: .BYTE 1 ;\
SHIFTREG_1: .BYTE 1 ; > shift register for
SHIFTREG_2: .BYTE 1 ;/ pseudo-random generator
LFOBOTTOM_0: .BYTE 1 ;\
LFOBOTTOM_1: .BYTE 1 ; > bottom level of LFO
LFOBOTTOM_2: .BYTE 1 ;/
LFOTOP_0: .BYTE 1 ;\
LFOTOP_1: .BYTE 1 ; > top level of LFO
LFOTOP_2: .BYTE 1 ;/
LFO2BOTTOM_0: .BYTE 1 ;\
LFO2BOTTOM_1: .BYTE 1 ; > bottom level of LFO2
LFO2BOTTOM_2: .BYTE 1 ;/
LFO2TOP_0: .BYTE 1 ;\
LFO2TOP_1: .BYTE 1 ; > top level of LFO2
LFO2TOP_2: .BYTE 1 ;/
DCOA_LEVEL: .BYTE 1
DCOB_LEVEL: .BYTE 1
KNOB_DEADZONE: .BYTE 1
; increase phase for DCO A
DELTAA_0: .byte 1
DELTAA_1: .byte 1
DELTAA_2: .byte 1
; increase phase for DCO B
DELTAB_0: .byte 1
DELTAB_1: .byte 1
DELTAB_2: .byte 1
; Wavetable select
WAVETABLE_A: .byte 1 ; Bandlimited wavetable 0..11
WAVETABLE_B: .byte 1 ; Bandlimited wavetable 0..11
; oscillator pulse width
PULSE_WIDTH: .byte 1
PULSE_KNOB_LIMITED: .byte 1
; fm
WAVEB: .byte 1
FMDEPTH: .byte 1
; eeprom
WRITE_MODE: .byte 1
WRITE_OFFSET: .byte 1
; filter
SCALED_RESONANCE: .byte 1
b_L: .byte 1
b_H: .byte 1
VCF_STATUS: .byte 1 ; 0 indicates VCF off, 1 = on
;-------------------------------------------------------------------------------------------------------------------
; MIDI Control Change parameter table
;-------------------------------------------------------------------------------------------------------------------
;
; MIDI CC parameters with an offset from MIDICC. They are automatically
; stored for use, just use the variable name to access their value.
MIDICC: .byte $80
.equ MIDIMODWHEEL = MIDICC + $01
; Unshifted knobs (potentiometer 0 through 7)
.equ RESONANCE = MIDICC + $30
.equ CUTOFF = MIDICC + $31
.equ LFOFREQ = MIDICC + $32
.equ PANEL_LFOLEVEL = MIDICC + $33
.equ VCFENVMOD = MIDICC + $34
.equ PORTAMENTO = MIDICC + $35
.equ PULSE_KNOB = MIDICC + $36
.equ OSC_DETUNE = MIDICC + $37
; Shifted knobs (potentiometer 0 through 7)
;.equ X = MIDICC + $38 ; Undefined
;.equ X = MIDICC + $39 ; Undefined
.equ KNOB_DCF_DECAY = MIDICC + $3A
.equ KNOB_DCF_ATTACK = MIDICC + $3B
.equ KNOB_AMP_DECAY = MIDICC + $3C
.equ KNOB_AMP_ATTACK = MIDICC + $3D
;.equ X = MIDICC + $3E ; Undefined
;.equ X = MIDICC + $3F ; Undefined
; Panel switches 0..15
; Switches 2
.equ S_KNOB_SHIFT = MIDICC + $40
.equ S_OSC_FM = MIDICC + $41
.equ S_LFO_RANDOM = MIDICC + $42
.equ S_LFO_WAVE = MIDICC + $43
.equ S_FILTER_MODE = MIDICC + $44
.equ S_DISTORTION = MIDICC + $45
.equ S_LFO_ENABLE = MIDICC + $46
.equ S_LFO_DEST = MIDICC + $47
; Switches 1
.equ S_ANTI_ALIAS = MIDICC + $48
.equ S_OSCB_OCT = MIDICC + $49
.equ S_OSCB_ENABLE = MIDICC + $4A
.equ S_OSCB_WAVE = MIDICC + $4B
.equ S_SUSTAIN = MIDICC + $4C
.equ S_OSCA_NOISE = MIDICC + $4D
.equ S_PWM_SWEEP = MIDICC + $4E
.equ S_OSCA_WAVE = MIDICC + $4F
;-------------------------------------------------------------------------------------------------------------------
;stack: 0x0A3..0x25F
.ESEG
;-------------------------------------------------------------------------------------------------------------------
; V E C T O R T A B L E
;-------------------------------------------------------------------------------------------------------------------
.CSEG
jmp RESET ; RESET
jmp IRQ_NONE ; INT0
jmp IRQ_NONE ; INT1
jmp IRQ_NONE ; INT2
jmp TIM2_CMP ; TIMEr2 COMP
jmp IRQ_NONE ; TIMEr2 OVF
jmp IRQ_NONE ; TIMEr1 CAPT
jmp IRQ_NONE ; TIMEr1 COMPA
jmp IRQ_NONE ; TIMEr1 COMPB
jmp IRQ_NONE ; TIMEr1 OVF
jmp IRQ_NONE ; TIMEr0 COMPA
jmp IRQ_NONE ; TIMEr0 OVF
jmp IRQ_NONE ; SPI,STC
jmp UART_RXC ; UART, RX COMPLETE
jmp IRQ_NONE ; UART,UDRE
jmp IRQ_NONE ; UART, TX COMPLETE
jmp IRQ_NONE ; ADC CONVERSION COMPLETE
jmp IRQ_NONE ; EEPROM READY
jmp IRQ_NONE ; ANALOG COMPARATOR
jmp IRQ_NONE ; 2-Wire Serial Interface
jmp IRQ_NONE ; STORE PROGRAM MEMORY READY
IRQ_NONE:
reti
;-------------------------------------------------------------------------------------------------------------------
; R O M T A B L E S
;-------------------------------------------------------------------------------------------------------------------
;
; Phase Deltas at 36363.63636 Hz sample rate
;
; NOTE PHASE DELTA = 2 ^ 24 * Freq / SamplingFreq
; So... Note zero calc: 2 ^ 24 * 8.175799 / 36363.63636 = 3772.09651 (stored as 00 0E BC.19)
;
;-------------------------------------------------------------------------------------------------------------------
DELTA_C:
.DW 0xBC19 ;\
.DW 0x000E ;/ note 0 ( 8.175799 Hz)
DELTA_CIS:
.DW 0x9C66 ;\
.DW 0x000F ;/ note 1 ( 8.661957 Hz)
DELTA_D:
.DW 0x8A09 ;\
.DW 0x0010 ;/ note 2 ( 9.177024 Hz)
DELTA_DIS:
.DW 0x85CE ;\
.DW 0x0011 ;/ note 3 ( 9.722718 Hz)
DELTA_E:
.DW 0x908B ;\
.DW 0x0012 ;/ note 4 (10.300861 Hz)
DELTA_F:
.DW 0xAB25 ;\
.DW 0x0013 ;/ note 5 (10.913382 Hz)
DELTA_FIS:
.DW 0xD68D ;\
.DW 0x0014 ;/ note 6 (11.562326 Hz)
DELTA_G:
.DW 0x13C2 ;\
.DW 0x0016 ;/ note 7 (12.249857 Hz)
DELTA_GIS:
.DW 0x63D4 ;\
.DW 0x0017 ;/ note 8 (12.978272 Hz)
DELTA_A:
.DW 0xC7E3 ;\
.DW 0x0018 ;/ note 9 (13.750000 Hz)
DELTA_AIS:
.DW 0x411D ;\
.DW 0x001A ;/ note 10 (14.567618 Hz)
DELTA_H:
.DW 0xD0C5 ;\
.DW 0x001B ;/ note 11 (15.433853 Hz)
DELTA_C1:
.DW 0x7831 ;\
.DW 0x001D ;/ note 12 (16.351598 Hz)
;-----------------------------------------------------------------------------
;
; Lookup Tables
;
; VCF filter cutoff - 128 bytes
; Time to Rate table for calculating amplitude envelopes - 64 bytes
; VCA non-linear level conversion - 256 bytes
;
;-----------------------------------------------------------------------------
; VCF Filter Cutoff
;
; value = (16th root of 2)**(index+1)
;
TAB_VCF:
.db 1, 1, 1, 1, 1, 1, 1, 1 ; 0
.db 1, 1, 1, 1, 1, 1, 1, 2 ; 8
.db 2, 2, 2, 2, 2, 2, 2, 2 ; 16
.db 2, 3, 3, 3, 3, 3, 3, 3 ; 24
.db 4, 4, 4, 4, 4, 5, 5, 5 ; 32
.db 5, 6, 6, 6, 7, 7, 7, 7 ; 40
.db 8, 8, 9, 9, 9, 10, 10, 11 ; 48
.db 11, 12, 12, 13, 14, 14, 15, 16 ; 56
.db 16, 17, 18, 19, 19, 20, 21, 22 ; 64
.db 23, 24, 25, 26, 28, 29, 30, 31 ; 72
.db 33, 34, 36, 38, 39, 41, 43, 45 ; 80
.db 47, 49, 51, 53, 56, 58, 61, 63 ; 88
.db 66, 69, 72, 76, 79, 82, 86, 90 ; 96
.db 94, 98, 103, 107, 112, 117, 122, 127 ; 104
.db 133, 139, 145, 152, 158, 165, 173, 181 ; 112
.db 189, 197, 206, 215, 224, 234, 245, 255 ; 120
;-----------------------------------------------------------------------------
;Time to Rate conversion table for envelope timing.
; lfo:
; update values for 32us update rate
; LFO_INTEGR overflows all 256*32us = 8.192 ms
;
; formula Tof = 256*32us*2^16/N
; LFOfreq = 1/Tof
; Rate value = Rmin * Q^i with Q = (Rmax/Rmin)^(1/31) = 1,286111766
TIMETORATE:
.DW 50957 ; 10.54 mS fast lfo, attack/rise time
.DW 39621 ; 13.55 mS
.DW 30807 ; 17.43 mS
.DW 23953 ; 22.41 mS
.DW 18625 ; 28.83 mS
.DW 14481 ; 37.07 mS
.DW 11260 ; 47.68 mS
.DW 8755 ; 61.32 mS
.DW 6807 ; 78.87 mS
.DW 5293 ; 101.4 mS
.DW 4115 ; 130.5 mS
.DW 3200 ; 167.8 mS
.DW 2488 ; 215.8 mS
.DW 1935 ; 277.5 mS
.DW 1504 ; 356.9 mS
.DW 1170 ; 459.0 mS
.DW 909 ; 590.4 mS
.DW 707 ; 759.3 mS
.DW 550 ; 976.5 mS
.DW 427 ; 1.256 S
.DW 332 ; 1.615 S
.DW 258 ; 2.077 S
.DW 201 ; 2.672 S
.DW 156 ; 3.436 S
.DW 121 ; 4.419 S
.DW 94 ; 5.684 S
.DW 73 ; 7.310 S
.DW 57 ; 9.401 S
.DW 44 ; 12.09 S
.DW 35 ; 15.55 S
.DW 27 ; 20.00 S
.DW 19 ; 28.26 S slow lfo, attack/rise time
;-----------------------------------------------------------------------------
;
; VCA non-linear level conversion
;
; Amplitude level lookup table. Envelopes levels are calculated as linear
; and then converted to approximate an exponential saturation curve.
;
; polynomial y = a + bx + cx2 + dx3
; with coefficients
; a 0
; b 0.210841569
; c 0.000177823
; d 1.14E-05
TAB_VCA:
.db 0, 0, 0, 1, 1, 1, 1, 1 ; 0
.db 2, 2, 2, 2, 3, 3, 3, 3 ; 8
.db 3, 4, 4, 4, 4, 5, 5, 5 ; 16
.db 5, 6, 6, 6, 6, 7, 7, 7 ; 24
.db 7, 8, 8, 8, 8, 9, 9, 9 ; 32
.db 9, 10, 10, 10, 11, 11, 11, 11 ; 40
.db 12, 12, 12, 13, 13, 13, 14, 14 ; 48
.db 14, 15, 15, 15, 16, 16, 16, 17 ; 56
.db 17, 18, 18, 18, 19, 19, 20, 20 ; 64
.db 20, 21, 21, 22, 22, 23, 23, 23 ; 72
.db 24, 24, 25, 25, 26, 26, 27, 27 ; 80
.db 28, 28, 29, 29, 30, 30, 31, 31 ; 88
.db 32, 33, 33, 34, 34, 35, 35, 36 ; 96
.db 37, 37, 38, 39, 39, 40, 41, 41 ; 104
.db 42, 43, 43, 44, 45, 45, 46, 47 ; 112
.db 48, 48, 49, 50, 51, 51, 52, 53 ; 120
.db 54, 55, 56, 56, 57, 58, 59, 60 ; 128
.db 61, 62, 63, 63, 64, 65, 66, 67 ; 136
.db 68, 69, 70, 71, 72, 73, 74, 75 ; 144
.db 76, 77, 78, 80, 81, 82, 83, 84 ; 152
.db 85, 86, 87, 89, 90, 91, 92, 93 ; 160
.db 95, 96, 97, 98, 100, 101, 102, 104 ; 168
.db 105, 106, 108, 109, 110, 112, 113, 115 ; 176
.db 116, 118, 119, 120, 122, 123, 125, 126 ; 184
.db 128, 130, 131, 133, 134, 136, 138, 139 ; 192
.db 141, 142, 144, 146, 148, 149, 151, 153 ; 200
.db 154, 156, 158, 160, 162, 164, 165, 167 ; 208
.db 169, 171, 173, 175, 177, 179, 181, 183 ; 216
.db 185, 187, 189, 191, 193, 195, 197, 199 ; 224
.db 201, 203, 206, 208, 210, 212, 214, 217 ; 232
.db 219, 221, 224, 226, 228, 231, 233, 235 ; 240
.db 238, 240, 243, 245, 247, 250, 252, 255 ; 248
;-----------------------------------------------------------------------------
;
; Limit maximum resonance when filter cutoff is extremely low
;
TAB_REZ:
.db 224, 224, 224, 224, 224, 224, 224, 224 ; 0 - Low value of DE
.db 224, 228, 232, 236, 240, 244, 248, 252 ; 8 - High value of FC
;-------------------------------------------------------------------------------------------------------------------
; I N T E R R U P T S U B R O U T I N E S
;-------------------------------------------------------------------------------------------------------------------
; Timer 2 compare interrupt (sampling)
;
; This is where sound is generated. This interrupt is called 36,363 times per second
; to calculate a single 16-bit value for audio output. There are ~440 instruction cycles
; (16MHZ/36,363) between samples, and these have to be shared between this routine and the
; main program loop that scans controls, receives MIDI commands and calculates envelope,
; LFO, and DCA/DCF levels.
;
; If you use too many clock cycles here there won't be sufficient time left over for
; general housekeeping tasks. The result will be sluggish and lost notes, weird timing and sadness.
;-------------------------------------------------------------------------------------------------------------------
; Push contents of registers onto the stack
;
TIM2_CMP:
push r16
in r16, SREG ;\
push r16 ;/ push SREG
push r17
push r18
push r19
push r20
push r21
push r22
push r23
push r30
push r31
push r0
push r1
lds r21, PATCH_SWITCH1 ; Load the mode flag settings so we can check the selected waveform,
lds r23, PATCH_SWITCH2 ; noise and distortion settings.
;-------------------------------------------------------------------------------------------------------------------
;
; Oscillator A & B
;
; This design uses direct frequency synthesis to generate a ramp wave. A three-byte counter (= phase) is being
; incremented by a value which is proportional to the sound frequency (= phase delta). The
; increment takes place every sampling period. The most significant byte of the counter is a sawtooth ramp.
; This is either used as a pointer to a 256 byte wavetable or for direct waveform synthesis.
; Each oscillator has its own phase and phase delta registers. The contents of each phase delta
; register depends on the frequency being generated:
;
; PHASE DELTA = 2 ^ 24 * Freq / SamplingFreq
;
; where:
; SamplingFreq = 40000 Hz
; Freq = 440 * 2 ^ ((n - 69 + d) / 12)
; where in turn:
; n = MIDI note number. Range limited to 36 to 96 (5 octaves)
; d = transpose/detune (in halftones)
;
;-------------------------------------------------------------------------------------------------------------------
;Calculate DCO A
; If Noise switch is on, use pseudo-random shift register value
sbrs r23, SW_OSCA_NOISE ; Use noise if bit set, otherwise jump to calculate DCO.
rjmp CALC_DCOA
lds r17, SHIFTREG_2
sbrc PHASEA_2,3
com r17
sbrc PHASEA_2,4
com r17
sbrc PHASEA_2,6
com r17
sbrc PHASEA_2,7
com r17
lsl r17
rjmp CALC_DCOB ; skip sample calc for DCO A if noise bit set
CALC_DCOA:
mov r17, PHASEA_2 ; sawtooth ramp for OSCA
sbrs r23, SW_OSCA_WAVE ; 0/1 (DCO A = saw/pwm)
rjmp DCOA_SAW
;Pulse wave generated by subtracting two bandlimited sawtooths, between 0 and 180 degrees out of phase
sbrs r23, SW_PWM_SWEEP
lds r20, PULSE_KNOB_LIMITED ; PWM Sweep switch is off, so load the knob value as PWM width
sbrc r23, SW_PWM_SWEEP
lds r20, PULSE_WIDTH ; PWM Sweep switch is on, load pulse width from LF02
sbrs r23, SW_ANTI_ALIAS
rjmp RAW_PULSE ; Calculate raw pulse/PWM when anti-alias switch is off
lds r22, WAVETABLE_A ; Offset to the correct wavetable, based on note number (0..15)
; r17 phase
; r20 pulse width
; r22 wavetable
; get sample a into r17
ldi ZL, low (2*INV_SAW0) ; Load low part of byte address into ZL
ldi ZH, high(2*INV_SAW0) ; Load high part of byte address into ZH
add ZL, r17 ; Offset the wavetable by the ramp phase (i)
adc ZH, r22 ; Wavetable 0..15
lpm ; Load wave(i) into r0
; get sample b out of phase into r18
mov r16, r20 ; Grab a copy of the pulse width
add r16, r17 ; Add phase offset for second table (pulse width + original sample)
mov r17, r0 ; store previous sample in r17
ldi ZL, low (2*INV_SAW0) ; Load low part of byte address into ZL
ldi ZH, high(2*INV_SAW0) ; Load high part of byte address into ZH
add ZL, r16 ; Add phase offset for second table.
adc ZH, r22 ; Wavetable 0..15
lpm ; Load wave(i) into r0
; subtract wave a-b
; first part b > a, second part b < a
clr r16
sub r17, r0
sbc r16, ZERO
add r17, r20 ; add offset (pulse width)
adc r16, ZERO
brge PULSE_BOUND_CHECK ; non-negative result, so no need to limit the value
ldi r17, 0
ldi r16, 0 ; value was negative, so force to zero
PULSE_BOUND_CHECK:
tst r16 ; Check if we're larger than 255
breq PWM_EXIT ; no need to limit upper bound
ldi r17, $FF
PWM_EXIT:
subi r17, $80 ; sign the result
rjmp CALC_DCOB
; Raw Pulse wave generated on the fly. Aliases like crazy (calc'd only when anti-alias switch is OFF)
RAW_PULSE:
cp r17, r20
brlo PULSE_ZERO
ldi r17, 255
subi r17, $80 ; Sign the sample
rjmp CALC_DCOB
PULSE_ZERO:
ldi r17, 0
subi r17, $80 ; Sign the sample
rjmp CALC_DCOB
; Calculate DCOA sawtooth
DCOA_SAW:
mov r17, PHASEA_2
sbrs r23, SW_ANTI_ALIAS
rjmp DCOA_SAW_SIGN
lds r22, WAVETABLE_A ; Offset to the correct wavetable, based on note number (0..15)
ldi ZL, low (2*INV_SAW0) ; Load low part of byte address into ZL
ldi ZH, high(2*INV_SAW0) ; Load high part of byte address into ZH
add ZL, r17 ; Offset the wavetable by the ramp phase (i)
adc ZH, r22 ; Wavetable 0..15
lpm ; Load wave(i) into r0
mov r17, r0 ; Copy into DCO B
DCOA_SAW_SIGN:
subi r17, $80 ; -127..127 Sign oscillator
;Calculate DCO B
CALC_DCOB:
lds r22, WAVETABLE_B ; Offset to the correct wavetable, based on note number (0..15)
sbrs r23, SW_ANTI_ALIAS
ldi r22, 0 ; Use wavetable 0 when anti-alias switch off
mov r16, PHASEB_2 ; Use ramp value as offset when scanning wavetable
sbrs r23, SW_OSCB_WAVE ; 0/1 (DCO B = saw/squ)
rjmp LIMITED_TRIB
LIMITED_SQB: ; Square wave lookup
ldi ZL, low(2*SQ_LIMIT0) ; Load low part of byte address into ZL
ldi ZH, high(2*SQ_LIMIT0) ; Load high part of byte address into ZH
add ZL, r16 ; Offset the wavetable by the ramp phase (i)
adc ZH, r22 ; Wavetable 0..15
lpm ; Load wave(i) into r0
mov r16, r0 ; Copy into DCO B
rjmp CALC_DIST
LIMITED_TRIB: ; Triangle wave lookup
ldi ZL, low(2*TRI_LIMIT0) ; Load low part of byte address into ZL
ldi ZH, high(2*TRI_LIMIT0) ; Load high part of byte address into ZH
add ZL, r16 ; Offset the wavetable by the ramp phase (i)
adc ZH, r22 ; Wavetable 0..15
lpm ; Load wave(i) into r0
mov r16, r0 ; Copy into DCO B
CALC_DIST:
subi r16, $80 ; -128..127 Sign Oscillator B waveform
sbrc r21, SW_DISTORTION ; 0/1 (OSC DIST = off/on)
eor r17, r16
; Turn off OSCB if not enabled
;sbrs r23, SW_OSCB_ENABLE
;ldi r16, $00 ; Zero OSCB. Oscillator is signed
;-------------------------------------------------------------------------------------------------------------------
; Sum Oscillators
;
; Combines DCOA (in r17) and DCOB (in r16) waves to produce a 16-bit signed result in HDAC:LDAC (r17:r16)
;
sts WAVEB,r16 ; store signed DCO B wave for fm
; Mixer out = (A*x + B*(1-x))/4 x=0..1
ldi r22, $7F ; Set maximum oscillator level to 127 for each oscillator
mulsu r17, r22 ; signed DCO A wave * level
movw r30, r0 ; store value in temp register
sbrs r23, SW_OSCB_ENABLE ; if OSC B disabled add OSC A twice
mov r16, r17 ; (A*x + A*(1-x))/4 x=0..1
mulsu r16, r22 ; signed DCO B wave * level
add r30, r0
adc r31, r1 ; sum scaled waves
movw r16, r30 ; place signed output in HDAC:LDAC
; rotate right a couple of times to make a couple of bits of headroom for resonance.
asr r17 ;\
ror r16 ;/ r17:r16 = r17:r16 asr 1
asr r17 ;\
ror r16 ;/ r17:r16 = r17:r16 asr 1
movw OSC_OUT_L, r16 ; keep a copy for highpass filter
;DCF:
;-------------------------------------------------------------------------------------------------------------------
; Digitally Controlled Filter
;
; A 2-pole resonant low pass filter:
;
; a += f * ((in - a) + q * (a - b));
; b += f * (a - b);
;
; Input 16-Bit signed HDAC:LDAC (r17:r16), already scaled to minimize clipping (reduced to 25% of full code).
;-------------------------------------------------------------------------------------------------------------------
;calc (in - a) ; both signed
sub LDAC, a_L
sbc HDAC, a_H
;check for overflow / do hard clipping
brvc OVERFLOW_1 ;if overflow bit is clear jump to OVERFLOW_1
;sub overflow happened -> set to min
;b1000.0000 b0000.0000 -> min
;0b0111.1111 0b1111.1111 -> max
ldi LDAC, 0b00000000
ldi HDAC, 0b10000000
OVERFLOW_1: ;when overflow is clear
;(in-a) is now in HDAC:LDAC as signed
;now calc q*(a-b)
; Scale resonance based on filter cutoff
lds r22, SCALED_RESONANCE
lds r20, LPF_I ;load 'F' value
ldi r21, 0xff
sub r21, r20 ; 1-F
lsr r21
ldi r18, 0x08 ; changed (4 was original value in V1)
add r21, r18
sub r22, r21 ; Q-(1-f)
brcc OVERFLOW_2 ; if no overflow occured
ldi r22, 0x00 ;0x00 because unsigned
OVERFLOW_2:
mov r20, a_L ;\
mov r21, a_H ;/ load 'a' , signed
lds z_H, b_H ;\
lds z_L, b_L ;/ load 'b', signed
sub r20, z_L ;\
sbc r21, z_H ;/ (a-b) signed
brvc OVERFLOW_3 ;if overflow is clear jump to OVERFLOW_3
;b1000.0000 b0000.0000 -> min
;0b0111.1111 0b1111.1111 -> max
ldi r20, 0b00000000
ldi r21, 0b10000000
OVERFLOW_3:
lds r18, PATCH_SWITCH1 ; Check Low Pass/High Pass panel switch.
sbrs r18, SW_FILTER_MODE
rjmp CALC_LOWPASS
SKIP_REZ:
movw z_L, r20 ; High Pass selected, so just load r21:r20 into z_H:z_L to disable Q
rjmp DCF_ADD ; Skip lowpass calc
CALC_LOWPASS:
; skip resonance calculation if VCF is turned off (value of 0)
lds r18, VCF_STATUS
tst r18
breq SKIP_REZ
; mul signed:unsigned -> (a-b) * Q
; 16x8 into 16-bit
; r19:r18 = r21:r20 (ah:al) * r22 (b)
mulsu r21, r22 ; (signed)ah * b
movw r18, r0
mul r20, r22 ; al * b
add r18, r1
adc r19, ZERO
rol r0 ; r0.7 --> Cy
brcc NO_ROUND ; LSByte < $80, so don't round up
inc r18
NO_ROUND:
clc
lsl r18
rol r19
clc
lsl r18
rol r19
movw z_L,r18 ;Q*(a-b) in z_H:z_L as signed
;add both
;both signed
;((in-a)+q*(a-b))
;=> HDAC:LDAC + z_H:z_L
DCF_ADD:
add LDAC, z_L
adc HDAC, z_H
brvc OVERFLOW_4 ;if overflow is clear
;b1000.0000 b0000.0001 -> min
;0b0111.1111 0b1111.1111 -> max
ldi LDAC, 0b11111111
ldi HDAC, 0b01111111
OVERFLOW_4:
;Result is a signed value in HDAC:LDAC
;calc * f
;((in-a)+q*(a-b))*f
lds r20, LPF_I ;load lowpass 'F' value
lds r18, PATCH_SWITCH1
sbrc r18, SW_FILTER_MODE ; Check LP/HP switch.
lds r20, HPF_I ; Switch set, so load 'F' for HP
; mul signed unsigned HDAC*F
; 16x8 into 16-bit
; r19:r18 = HDAC:LDAC (ah:al) * r20 (b)
mulsu HDAC, r20 ; (signed)ah * b
movw r18, r0
mul LDAC, r20 ; al * b
add r18, r1 ; signed result in r19:r18
adc r19, ZERO
rol r0 ; r0.7 --> Cy
brcc NO_ROUND2 ; LSByte < $80, so don't round up
inc r18
NO_ROUND2:
; Add result to 'a'
; a+=f*((in-a)+q*(a-b))
add a_L, r18
adc a_H, r19
brvc OVERFLOW_5 ; if overflow is clear
; b1000.0000 b0000.0001 -> min
; 0b0111.1111 0b1111.1111 -> max
ldi z_L, 0b11111111
ldi z_H, 0b01111111
mov a_L, z_L
mov a_H, z_H
OVERFLOW_5:
;calculated a+=f*((in-a)+q*(a-b)) as signed value and saved in a_H:a_L
;calc 'b'
;b += f * (a*0.5 - b);
mov z_H, a_H ;\
mov z_L, a_L ;/ load 'a' as signed