-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathXMIDI.ASM
2285 lines (1880 loc) · 74.8 KB
/
XMIDI.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
;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ
;ÛÛ ÛÛ
;ÛÛ XMIDI.ASM ÛÛ
;ÛÛ ÛÛ
;ÛÛ IBM Audio Interface Library -- Extended MIDI sound driver shell ÛÛ
;ÛÛ ÛÛ
;ÛÛ Version 1.00 of 27-Sep-91: Initial version for AIL V2.0 release ÛÛ
;ÛÛ 1.01 of 25-Nov-91: Handles not consumed by invalid sequences ÛÛ
;ÛÛ RBRN indexing fixed ÛÛ
;ÛÛ 1.02 of 8-Dec-91: Callback Trigger values readable ÛÛ
;ÛÛ AIL_cancel_callback clears address dword ÛÛ
;ÛÛ 1.03 of 28-Dec-91: Callbacks compatible with Pascal and C ÛÛ
;ÛÛ 1.04 of 31-Jan-92: reset_sequence() clears global sustain ÛÛ
;ÛÛ 1.05 of 2-Feb-92: stop/resume_sequence() validate handles ÛÛ
;ÛÛ Flag "seq_started" added to state table ÛÛ
;ÛÛ Beat/bar count math precision increased ÛÛ
;ÛÛ 1.06 of 15-Feb-92: Includes YAMAHA.INC instead of YM3812.INC ÛÛ
;ÛÛ Copyright message header updated ÛÛ
;ÛÛ Time denominators < 4 counted correctly ÛÛ
;ÛÛ Do shutdown only if initialized ÛÛ
;ÛÛ 1.07 of 15-Mar-92: Beat fraction initialized nonzero ÛÛ
;ÛÛ 1.08 of 3-Apr-92: BRANCH_EXIT equate added ÛÛ
;ÛÛ 1.09 of 4-Jun-92: PASOPL and MMASTER compatibility ÛÛ
;ÛÛ 1.10 of 14-Oct-92: New time signature function for improved ÛÛ
;ÛÛ precision ÛÛ
;ÛÛ ÛÛ
;ÛÛ Author: John Miles ÛÛ
;ÛÛ 8086 ASM source compatible with Turbo Assembler v2.0 or later ÛÛ
;ÛÛ ÛÛ
;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ
;ÛÛ ÛÛ
;ÛÛ Copyright (C) 1991, 1992 Miles Design, Inc. ÛÛ
;ÛÛ ÛÛ
;ÛÛ Miles Design, Inc. ÛÛ
;ÛÛ 10926 Jollyville #308 ÛÛ
;ÛÛ Austin, TX 78759 ÛÛ
;ÛÛ (512) 345-2642 / FAX (512) 338-9630 / BBS (512) 454-9990 ÛÛ
;ÛÛ ÛÛ
;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ
MODEL MEDIUM,C ;Procedures far, data near by default
LOCALS __ ;Enable local labels with __ prefix
JUMPS ;Enable auto jump sizing
;
;Configuration equates
;
FALSE equ 0
TRUE equ -1
QUANT_RATE equ 120 ;Quantization (def=120 intervals/sec.)
QUANT_TIME equ 8333 ;(uS/interval = 1000000/QUANT_RATE)
QUANT_TIME_16_L equ 008d5h ;set to 16,000,000/QUANT_RATE --
QUANT_TIME_16_H equ 00002h ;normally 133333 (208D5H)
MAX_NOTES equ 32 ;Max # of notes "on" simultaneously
FOR_NEST equ 4 ;# of FOR loop nesting levels
NSEQS equ 8 ;# of sequence handles available
QUANT_ADVANCE equ 1 ;Beat/bar counts += n intervals
DEF_PITCH_L equ 00h
DEF_PITCH_H equ 40h ;Default pitch bend = 4000h (mid-way)
BRANCH_EXIT equ TRUE ;TRUE to allow branches out of loops
;
;Macros, non-configurable equates
;
INCLUDE ail.mac
INCLUDE ail.inc
IFDEF SBSTD
YM3812 equ 1
ENDIF
IFDEF ADLIBSTD
YM3812 equ 1
ENDIF
IFDEF PAS
YM3812 equ 1
STEREO equ 1
ENDIF
IFDEF SBPRO1
YM3812 equ 1
STEREO equ 1
ENDIF
IFDEF SBPRO2
YMF262 equ 1
STEREO equ 1
ENDIF
IFDEF PASOPL
YMF262 equ 1
STEREO equ 1
ENDIF
IFDEF ADLIBG
YMF262 equ 1
STEREO equ 1
ENDIF
IFDEF TANDY
SPKR equ 1
ENDIF
IFDEF IBMPC
SPKR equ 1
ENDIF
IFDEF CMS
SPKR equ 1
ENDIF
NUM_CHANS equ 16 ;# of MIDI channels
.CODE
dw OFFSET driver_index
db 'Copyright (C) 1991,1992 Miles Design, Inc.',01ah
driver_index LABEL WORD
dw AIL_DESC_DRVR,OFFSET describe_driver
dw AIL_DET_DEV,OFFSET detect_device
dw AIL_INIT_DRVR,OFFSET init_driver
dw AIL_SERVE_DRVR,OFFSET serve_driver
dw AIL_SHUTDOWN_DRVR,OFFSET shutdown_driver
dw AIL_STATE_TAB_SIZE,OFFSET get_state_size
dw AIL_INSTALL_CB,OFFSET install_callback
dw AIL_CANCEL_CB,OFFSET cancel_callback
dw AIL_REG_SEQ,OFFSET register_seq
dw AIL_REL_SEQ_HND,OFFSET release_seq
dw AIL_START_SEQ,OFFSET start_seq
dw AIL_STOP_SEQ,OFFSET stop_seq
dw AIL_RESUME_SEQ,OFFSET resume_seq
dw AIL_SEQ_STAT,OFFSET get_seq_status
dw AIL_REL_VOL,OFFSET get_rel_volume
dw AIL_SET_REL_VOL,OFFSET set_rel_volume
dw AIL_REL_TEMPO,OFFSET get_rel_tempo
dw AIL_SET_REL_TEMPO,OFFSET set_rel_tempo
dw AIL_CON_VAL,OFFSET get_control_val
dw AIL_SET_CON_VAL,OFFSET set_control_val
dw AIL_CHAN_NOTES,OFFSET get_chan_notes
dw AIL_MAP_SEQ_CHAN,OFFSET map_seq_channel
dw AIL_TRUE_SEQ_CHAN,OFFSET true_seq_channel
dw AIL_BEAT_CNT,OFFSET get_beat_count
dw AIL_BAR_CNT,OFFSET get_bar_count
dw AIL_BRA_INDEX,OFFSET branch_index
dw AIL_SEND_CV_MSG,OFFSET send_cv_msg
dw AIL_SEND_SYSEX_MSG,OFFSET send_sysex_msg
dw AIL_WRITE_DISP,OFFSET write_display
dw AIL_LOCK_CHAN,OFFSET lock_channel
dw AIL_RELEASE_CHAN,OFFSET release_channel
dw AIL_T_CACHE_SIZE,OFFSET get_cache_size
dw AIL_DEFINE_T_CACHE,OFFSET define_cache
dw AIL_T_REQ,OFFSET get_request
dw AIL_INSTALL_T,OFFSET install_timbre
dw AIL_PROTECT_T,OFFSET protect_timbre
dw AIL_UNPROTECT_T,OFFSET unprotect_timbre
dw AIL_T_STATUS,OFFSET timbre_status
dw -1
;
;Synthesizer- and interface-specific routines
;
IFDEF MT32
INCLUDE mt32.inc ;Roland MT-32-compatible synthesizer
ENDIF
IFDEF YM3812
INCLUDE yamaha.inc ;Standard Ad Lib-style chipset
ENDIF
IFDEF YMF262
INCLUDE yamaha.inc ;YMF262 support for Ad Lib Gold et al.
ENDIF
IFDEF MMASTER
INCLUDE mmaster.inc ;ASC MediaMaster and 100% compatibles
ENDIF
IFDEF SPKR
INCLUDE spkr.inc ;Internal speaker support for PC/Tandy
ENDIF
;
;Misc. data
;
ctrl_log STRUC ;XMIDI sequence/global controller log
PV db NUM_CHANS dup (?)
MODUL db NUM_CHANS dup (?)
PAN db NUM_CHANS dup (?)
EXP db NUM_CHANS dup (?)
SUS db NUM_CHANS dup (?)
PBS db NUM_CHANS dup (?)
C_LOCK db NUM_CHANS dup (?)
C_PROT db NUM_CHANS dup (?)
V_PROT db NUM_CHANS dup (?)
ENDS
logged_ctrls LABEL BYTE ;Controllers saved in state table
db PART_VOLUME,MODULATION,PANPOT,EXPRESSION,SUSTAIN
db PATCH_BANK_SEL
db CHAN_LOCK,CHAN_PROTECT,VOICE_PROTECT
NUM_CONTROLS equ ($-logged_ctrls) ;room for 16 max. w/8-bit hash
ctrl_default LABEL BYTE ;default controller/program change
db 127,0,64,127,0 ;values for startup initialization
db 0
db 0,0,0
prg_default db 68,48,95,78 ;(Roland defaults)
db 41,3,110,122,-1
ctrl_hash db 256 dup (-1) ;Controller offsets indexed for speed
state_table STRUC ;XMIDI sequence state table layout
TIMB dd ?
RBRN dd ?
EVNT dd ?
EVNT_ptr dd ?
cur_callback dw ?
ctrl_ptr dd ?
seq_handle dw ?
seq_started dw ?
status dw ?
post_release dw ?
interval_cnt dw ?
note_count dw ?
vol_error dw ?
vol_percent dw ?
vol_target dw ?
vol_accum_l dw ?
vol_accum_h dw ?
vol_period_l dw ?
vol_period_h dw ?
tempo_error dw ?
tempo_percent dw ?
tempo_target dw ?
tempo_accum_l dw ?
tempo_accum_h dw ?
tempo_period_l dw ?
tempo_period_h dw ?
beat_count dw ?
measure_count dw ?
time_numerator dw ?
time_fraction_l dw ?
time_fraction_h dw ?
beat_fraction_l dw ?
beat_fraction_h dw ?
time_per_beat_l dw ?
time_per_beat_h dw ?
FOR_ptrs dd FOR_NEST dup (?)
FOR_loop_cnt dw FOR_NEST dup (?)
chan_map db NUM_CHANS dup (?)
chan_program db NUM_CHANS dup (?)
chan_pitch_l db NUM_CHANS dup (?)
chan_pitch_h db NUM_CHANS dup (?)
chan_indirect db NUM_CHANS dup (?)
chan_controls ctrl_log <>
note_chan db MAX_NOTES dup (?)
note_num db MAX_NOTES dup (?)
note_time_l dw MAX_NOTES dup (?)
note_time_h dw MAX_NOTES dup (?)
ENDS
sequence_state dd NSEQS dup (?)
sequence_count dw ?
current_handle dw ?
service_active dw ?
trigger_ds dw ?
trigger_fn dd ?
global_shadow LABEL BYTE
global_controls ctrl_log <?>
global_program db NUM_CHANS dup (?)
global_pitch_l db NUM_CHANS dup (?)
global_pitch_h db NUM_CHANS dup (?)
GLOBAL_SIZE equ ($-global_shadow)
active_notes db NUM_CHANS dup (?)
lock_status db NUM_CHANS dup (?) ;bit 7: locked
; 6: lock-protected
init_OK dw 0
;****************************************************************************
;* *
;* XMIDI interpreter and related procedures *
;* *
;****************************************************************************
find_seq PROC XMID:FAR PTR,SeqNum ;Find FORM SeqNum in IFF
LOCAL end_addr_l,end_addr_h ;CAT/FORM XMID file
USES ds,si,di
mov cx,[SeqNum]
inc cx ;look for CXth FORM XMID chunk
lds si,[XMID]
__find_XMID: cmp [si],'AC'
jne __chk_FORM
cmp [si+2],' T'
je __found_IFF
__chk_FORM: cmp [si],'OF'
jne __not_found
cmp [si+2],'MR' ;return failure if not an IFF
jne __not_found ;image
__found_IFF: cmp [si+8],'MX'
jne __next_XMID
cmp [si+10],'DI'
je __found_XMID
__next_XMID: mov dx,[si+4] ;find first XMID chunk
mov ax,[si+6]
xchg al,ah
xchg dl,dh
add ax,8
adc dx,0
ADD_PTR ax,dx,ds,si
jmp __find_XMID
__found_XMID: mov dx,[si+4]
mov ax,[si+6]
xchg al,ah
xchg dl,dh
sub ax,5
sbb dx,0 ;DX:AX=last byte of all FORMs
mov end_addr_l,ax
mov end_addr_h,dx
cmp [si],'OF' ;if outer header was a FORM,
jne __scan_CAT ;return successfully if CX=1
cmp [si+2],'MR'
jne __scan_CAT
cmp cx,1
je __seq_found
jmp __not_found
__scan_CAT: add si,12 ;index first FORM chunk
__check_FORM: cmp [si+8],'MX' ;is this a FORM XMID?
jne __next_FORM
cmp [si+10],'DI'
je __next_seq ;yes, dec the loop counter...
__next_FORM: mov dx,[si+4] ;else add length of FORM + 8
mov ax,[si+6] ;and keep looking...
xchg al,ah
xchg dl,dh
add ax,8
adc dx,0
sub end_addr_l,ax ;...unless EOF reached
sbb end_addr_h,dx
jl __not_found
ADD_PTR ax,dx,ds,si
jmp __check_FORM
__next_seq: loop __next_FORM ;look for CXth sequence chunk
__seq_found: mov ax,si
mov dx,ds
jmp __exit ;return pointer to first chunk
__not_found: mov ax,0 ;return NULL if not found
mov dx,0
__exit: ret
ENDP
;****************************************************************************
rewind_seq PROC Sequence ;Reset sequence pointer and invalidate
USES ds,si,di ;all state table entries
mov si,[Sequence]
lds si,sequence_state[si]
mov cx,FOR_NEST
mov bx,0
__init_FOR: mov [si].FOR_loop_cnt[bx],-1
add bx,2
loop __init_FOR
mov bx,NUM_CHANS-1
__init_chans: mov [si].chan_map[bx],bl
mov [si].chan_program[bx],-1
mov [si].chan_pitch_l[bx],-1
mov [si].chan_pitch_h[bx],-1
mov [si].chan_indirect[bx],-1
dec bx
jge __init_chans
mov bx,SIZE chan_controls-1
__init_ctrls: mov BYTE PTR [si].chan_controls[bx],-1
dec bx
jge __init_ctrls
mov bx,MAX_NOTES-1
__init_notes: mov [si].note_chan[bx],-1
dec bx
jge __init_notes
mov [si].cur_callback,-1
mov [si].interval_cnt,0
mov [si].note_count,0
mov [si].vol_percent,DEF_SYNTH_VOL
mov [si].vol_target,DEF_SYNTH_VOL
mov [si].tempo_percent,100
mov [si].tempo_target,100
mov [si].tempo_error,0
mov [si].beat_count,0
mov [si].measure_count,-1
mov [si].beat_fraction_l,0
mov [si].beat_fraction_h,0
mov [si].time_fraction_l,0
mov [si].time_fraction_h,0
mov [si].time_numerator,4 ;default to 4/4 time
mov [si].time_per_beat_l,01200h ;default to 500000 us/beat*16
mov [si].time_per_beat_h,0007ah ;(120 beats/min)
ret
ENDP
;****************************************************************************
flush_channel_notes PROC Chan:BYTE ;Turn all sequences' notes off in a
LOCAL handle,seqcnt ;given channel
USES ds,si,di
mov handle,0 ;for all sequences....
mov cx,sequence_count
mov seqcnt,cx
jcxz __exit
__for_seq: mov di,handle
add handle,4
cmp WORD PTR sequence_state[di+2],0
je __for_seq ;(sequence not registered)
lds si,sequence_state[di]
cmp [si].note_count,0
je __next_seq ;no notes on, don't bother looking
mov bx,0 ;check note queue for active notes
__for_entry: mov al,[si].note_chan[bx]
cmp al,[Chan]
jne __next_entry
mov [si].note_chan[bx],-1
mov cl,[si].note_num[bx]
mov di,bx
mov bl,al
mov bh,0 ;translate logical to physical channel
mov bl,[si].chan_map[bx]
dec active_notes[bx] ;dec # of active notes in channel
or bl,80h ;send MIDI Note Off message
call send_MIDI_message C,bx,cx,0
dec [si].note_count
mov bx,di
__next_entry: inc bx
cmp bx,MAX_NOTES
jb __for_entry
__next_seq: dec seqcnt
jne __for_seq
__exit: ret
ENDP
;****************************************************************************
flush_note_queue PROC State:FAR PTR ;Turn all queued notes off
USES ds,si,di
cld
lds si,[State]
mov bx,0 ;check note queue for active notes
__for_entry: mov al,[si].note_chan[bx]
cmp al,-1
je __next_entry
mov [si].note_chan[bx],-1
mov cl,[si].note_num[bx]
mov di,bx
mov bl,al
mov bh,0 ;translate logical to physical channel
mov bl,[si].chan_map[bx]
dec active_notes[bx] ;dec # of active notes in channel
or bl,80h ;send MIDI Note Off message
call send_MIDI_message C,bx,cx,0
mov bx,di
__next_entry: inc bx
cmp bx,MAX_NOTES
jb __for_entry
mov [si].note_count,0
ret
ENDP
;****************************************************************************
reset_sequence PROC State:FAR PTR ;Abandon all sequence-owned resources
USES ds,si,di
lds si,[State]
mov di,0
__for_chan: mov bx,di
mov al,[si].chan_controls.SUS[bx]
cmp al,64
jl __chk_lock
mov global_controls.SUS[bx],0
or bx,0b0h
call send_MIDI_message C,bx,SUSTAIN,0
mov bx,di
__chk_lock: mov al,[si].chan_controls.C_LOCK[bx]
cmp al,64
jl __chk_cprot
call flush_channel_notes C,di
mov bx,di
mov bl,[si].chan_map[bx]
mov bh,0
inc bx
call release_channel C,0,bx
mov bx,di
mov [si].chan_map[bx],bl
__chk_cprot: mov al,[si].chan_controls.C_PROT[bx]
cmp al,64
jl __chk_vprot
and lock_status[bx],10111111b
__chk_vprot: mov al,[si].chan_controls.V_PROT[bx]
cmp al,64
jl __next_chan
or bx,0b0h
call send_MIDI_message C,bx,VOICE_PROTECT,0
mov bx,di
__next_chan: inc di
cmp di,NUM_CHANS
jne __for_chan
ret
ENDP
;****************************************************************************
restore_sequence PROC State:FAR PTR ;Reassert all "owned" controls
LOCAL con,ctrl,index
USES ds,si,di
lds si,[State]
mov di,0 ;re-lock any channels formerly locked
__for_lock: mov bx,di ;by CHAN_LOCK controllers
mov al,[si].chan_controls.C_LOCK[bx]
cmp al,-1
je __next_lock
cmp al,64
jl __next_lock
call lock_channel C,0 ;lock new channel and map to current
dec ax ;channel in sequence
cmp ax,-1
jne __locked
mov ax,di
__locked: mov bx,di
mov [si].chan_map[bx],al
__next_lock: inc di
cmp di,NUM_CHANS
jne __for_lock
mov con,0 ;re-establish all logged controller
__for_control: mov bx,con ;values
mov bl,logged_ctrls[bx]
cmp bl,CHAN_LOCK ;(except channel locks, which were
je __next_control ;done above)
mov ctrl,bx
mov bl,ctrl_hash[bx]
mov index,bx
mov di,0
__for_channel: mov bx,di
add bx,index
mov al,BYTE PTR [si].chan_controls[bx]
cmp al,-1
je __next_channel
call XMIDI_control C,si,ds,di,ctrl,ax
__next_channel: inc di
cmp di,NUM_CHANS
jne __for_channel
__next_control: inc con
cmp con,NUM_CONTROLS
jne __for_control
mov di,0 ;restore pitch/program # values
__for_p_p: mov bx,di
mov al,[si].chan_pitch_l[bx]
cmp al,-1
je __set_prg
mov dl,[si].chan_pitch_h[bx]
cmp dl,-1
je __set_prg
mov bl,[si].chan_map[bx]
or bx,0e0h
call send_MIDI_message C,bx,ax,dx
mov bx,di
__set_prg: mov dl,[si].chan_program[bx]
cmp dl,-1
je __next_p_p
mov bl,[si].chan_map[bx]
or bx,0c0h
call send_MIDI_message C,bx,dx,0
__next_p_p: inc di
cmp di,NUM_CHANS
jne __for_p_p
ret
ENDP
;****************************************************************************
XMIDI_volume PROC State:FAR PTR ;Send updated volume control messages
USES ds,si,di
lds si,[State]
mov bx,0
__for_chan: mov al,[si].chan_controls.PV[bx]
cmp al,-1
je __next_chan
mov ah,0
mov cx,[si].vol_percent
mul cx ;else get scaled volume value
mov cx,100
div cx
cmp ax,127
jb __send
mov ax,127
__send: mov di,bx ;update global controller shadow
mov global_controls.PV[bx],al
test lock_status[bx],10000000b
jnz __next_chan ;(logical channel locked)
mov di,bx
mov bl,[si].chan_map[bx]
or bl,0b0h ;send the new volume value
call send_MIDI_message C,bx,PART_VOLUME,ax
mov bx,di ;recover channel
__next_chan: inc bx
cmp bx,NUM_CHANS
jne __for_chan
ret
ENDP
;****************************************************************************
XMIDI_control PROC State:FAR PTR,Chan:BYTE,Con:BYTE,Val:BYTE
USES ds,si,di ;Process XMIDI Control Change message
LOCAL prg_sp
NOJUMPS
cld
lds si,[State]
mov bl,[Chan] ;BX=channel #
mov bh,0
mov dl,[Val] ;DX=value
mov dh,0
mov al,[si].chan_indirect[bx]
cmp al,-1
je __value ;no indirection pending, continue
mov [si].chan_indirect[bx],-1
mov bl,al
les di,[si].ctrl_ptr ;else get value from controller table
mov dl,es:[di][bx]
__value: mov bl,[Con] ;BX=controller #
mov bh,0
mov bl,ctrl_hash[bx]
cmp bl,-1
je __interpret ;(not loggable)
add bl,[Chan] ;copy controller value to state tables
mov BYTE PTR global_controls[bx],dl
mov BYTE PTR [si].chan_controls[bx],dl
__interpret: mov al,[Con] ;handle sequence-specific controllers
mov bl,[Chan]
cmp al,PART_VOLUME ;BX=channel, AL=controller #, DX=value
je __scale_volume
cmp al,CLEAR_BEAT_BAR
je __go_cc
cmp al,CALLBACK_TRIG
je __go_cb
cmp al,FOR_LOOP
je __go_for
cmp al,NEXT_LOOP
je __go_next
cmp al,CHAN_PROTECT
je __go_cp
cmp al,CHAN_LOCK
je __go_cl
cmp al,INDIRECT_C_PFX
je __go_indirect
__send: test lock_status[bx],10000000b
jnz __exit ;(logical channel locked)
mov bl,[si].chan_map[bx]
or bl,0b0h ;send the controller value
call send_MIDI_message C,bx,ax,dx
__exit: mov ax,3 ;return total event size
ret
__scale_volume: mov cx,[si].vol_percent
cmp cx,100
je __send ;100% volume, don't scale
mov ax,dx
mul cx ;else get scaled volume value
mov cx,100
div cx
mov dx,ax
mov ax,PART_VOLUME
cmp dx,127
jb __send_volume
mov dx,127
__send_volume: mov global_controls.PV[bx],dl
jmp __send
__go_cc: jmp __clear_cntrs
__go_cb: jmp __callback
__go_for: jmp __for_loop
__go_next: jmp __next_loop
__go_cp: jmp __chan_prot
__go_cl: jmp __chan_lock
__go_indirect: jmp __indirect
JUMPS
__indirect: mov [si].chan_indirect[bx],dl
jmp __exit
__clear_cntrs: mov [si].beat_count,0
mov [si].measure_count,0
mov [si].beat_fraction_l,0
mov [si].beat_fraction_h,0
mov ax,[si].time_fraction_l
mov dx,[si].time_fraction_h
sub [si].beat_fraction_l,ax
sbb [si].beat_fraction_h,dx
jmp __exit
__callback: mov [si].cur_callback,dx
mov ax,WORD PTR trigger_fn
or ax,WORD PTR trigger_fn+2
je __exit ;(callback functions disabled)
pushf
mov prg_sp,sp ;save SP before parameters pushed
push dx ;(use C calling convention)
push [si].seq_handle ;push sequence handle, ctrl value
mov ds,trigger_ds ;restore the application module's DS
call [trigger_fn] ;and call the callback function
mov sp,prg_sp ;restore old SP value
POP_F
jmp __exit
__for_loop: mov bx,0 ;get index of available loop counter
mov cx,FOR_NEST
__for_find: cmp [si].FOR_loop_cnt[bx],-1
je __for_found
add bx,2
loop __for_find
jmp __exit
__for_found: mov [si].FOR_loop_cnt[bx],dx
shl bx,1
les di,[si].EVNT_ptr ;(NEXT controller will skip FOR)
mov WORD PTR [si].FOR_ptrs[bx],di
mov WORD PTR [si].FOR_ptrs[bx+2],es
jmp __exit
__next_loop: cmp dl,64 ;BREAK controller (value < 64)?
jl __exit ;yes, ignore and continue
mov bx,(FOR_NEST*2)-2
mov cx,FOR_NEST ;else get index of inner loop counter
__next_find: cmp [si].FOR_loop_cnt[bx],-1
jne __next_found
sub bx,2
loop __next_find
jmp __exit
__next_found: cmp [si].FOR_loop_cnt[bx],0
je __do_loop ;FOR value 0 = infinite loop
dec [si].FOR_loop_cnt[bx]
jnz __do_loop
mov [si].FOR_loop_cnt[bx],-1
jmp __exit ;remove loop from list if dec'd to 0
__do_loop: shl bx,1
les di,[si].FOR_ptrs[bx]
mov WORD PTR [si].EVNT_ptr,di
mov WORD PTR [si].EVNT_ptr+2,es
jmp __exit
__chan_prot: or lock_status[bx],01000000b
cmp dl,64
jge __exit
and lock_status[bx],10111111b
jmp __exit
__chan_lock: mov di,bx
cmp dl,64
jl __unlock
call lock_channel C,0 ;lock new channel and map to current
dec ax ;channel in sequence
cmp ax,-1
jne __set_chan
mov ax,di
__set_chan: mov bx,di
mov [si].chan_map[bx],al
jmp __exit
__unlock: call flush_channel_notes C,di
mov bx,di
mov bl,[si].chan_map[bx]
inc bx
call release_channel C,0,bx
mov bx,di
mov [si].chan_map[bx],bl
jmp __exit ;release and unmap locked channel
ENDP
;****************************************************************************
XMIDI_note_on PROC State:FAR PTR ;Turn XMIDI note on, add to note queue
LOCAL chan_note,vel,len ;Returns AX=size of Note On event
USES ds,si,di
lds si,[State]
les di,[si].EVNT_ptr ;retrieve event data pointer
mov ax,es:[di]
and al,0fh ;AL=channel, AH=note #
mov chan_note,ax
mov al,es:[di+2] ;AL=velocity
mov vel,ax
mov ax,di ;get VLN duration value in DX:AX
add di,3
mov bx,0
mov dx,0
jmp __calc_VLN
__shift_VLN: mov cx,7
__mul_128: shl bx,1
rcl dx,1
loop __mul_128
__calc_VLN: mov cl,es:[di]
inc di
mov ch,cl
and cl,7fh
or bl,cl
or ch,ch
js __shift_VLN
sub di,ax ;get length of entire event
mov len,di ;DX:BX = duration in q-intervals
mov di,chan_note
and di,0fh
test lock_status[di],10000000b
jnz __exit ;(logical channel locked)
mov ax,ds ;set up to scan the note queue for
mov es,ax ;an empty slot
lea di,[si].note_chan
mov cx,MAX_NOTES
mov al,0ffh
repne scasb
mov ax,di
jne __overflow ;overwrite entry 0 if queue full
inc [si].note_count ;else bump note counter...
lea ax,[si].note_chan+1 ;and index the empty slot
__overflow: sub di,ax ;DI=queue slot [0,MAX_NOTES-1]
mov ax,bx ;DX:AX = duration
mov bx,di ;BX = queue slot
sub ax,1 ;predecrement (note queue watches for
sbb dx,0 ;negative durations)
mov cx,chan_note ;log the note's channel and key #
mov [si].note_chan[bx],cl
mov [si].note_num[bx],ch
shl bx,1 ;log the note's duration
mov [si].note_time_l[bx],ax
mov [si].note_time_h[bx],dx
mov bl,cl
mov bh,0 ;translate logical to physical channel
mov bl,[si].chan_map[bx]
inc active_notes[bx] ;inc # of notes in channel
or bl,90h ;turn the note on
mov cl,ch
call send_MIDI_message C,bx,cx,vel
__exit: mov ax,len ;return event length
ret
ENDP
;****************************************************************************
XMIDI_meta PROC State:FAR PTR ;XMIDI meta-event interpreter
LOCAL event_len,event_type:BYTE
USES ds,si,di
lds si,[State] ;get pointers to state table and event
les di,[si].EVNT_ptr
mov al,es:[di+1]
mov event_type,al
mov bx,di ;get offset of status byte
add di,2 ;adjust for type and status bytes
mov ax,0 ;get variable-length number
mov dx,0
jmp __calc_VLN
__shift_VLN: mov cx,7
__mul_128: shl ax,1
rcl dx,1
loop __mul_128
__calc_VLN: mov cl,es:[di]
inc di
mov ch,cl
and cl,7fh
or al,cl
or ch,ch
js __shift_VLN
mov cx,di
sub cx,bx ;BX = size of meta-event header
add ax,cx ;add size of header to data length
mov event_len,ax ;to determine overall event length
mov al,event_type
cmp al,2fh
je __end_sequence
cmp al,58h
je __time_sig
cmp al,51h
je __set_tempo
__exit: mov ax,event_len ;return total event length
ret
__end_sequence: call reset_sequence C,si,ds
mov [si].status,SEQ_DONE
cmp [si].post_release,0 ;release-on-completion pending?
je __exit
call release_seq C,0,current_handle
jmp __exit
__time_sig: mov ch,0
mov cl,es:[di]
mov [si].time_numerator,cx
mov cl,es:[di+1]
sub cx,2
jae __do_mult
neg cx
mov ax,QUANT_TIME_16_L
mov dx,QUANT_TIME_16_H
__div_quant: shr dx,1
rcr ax,1
loop __div_quant
jmp __end_calc
__do_mult: mov ax,1
shl ax,cl
mov cx,ax
mov ax,0
mov dx,0
__mul_quant: add ax,QUANT_TIME_16_L
adc dx,QUANT_TIME_16_H
loop __mul_quant