-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathOPL3.java
executable file
·1671 lines (1381 loc) · 67 KB
/
OPL3.java
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
/*
* File: OPL3.java
* Software implementation of the Yamaha YMF262 sound generator.
* Copyright (C) 2008 Robson Cozendey <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* One of the objectives of this emulator is to stimulate further research in the
* OPL3 chip emulation. There was an explicit effort in making no optimizations,
* and making the code as legible as possible, so that a new programmer
* interested in modify and improve upon it could do so more easily.
* This emulator's main body of information was taken from reverse engineering of
* the OPL3 chip, from the YMF262 Datasheet and from the OPL3 section in the
* YMF278b Application's Manual,
* together with the vibrato table information, eighth waveform parameter
* information and feedback averaging information provided in MAME's YMF262 and
* YM3812 emulators, by Jarek Burczynski and Tatsuyuki Satoh.
* This emulator has a high degree of accuracy, and most of music files sound
* almost identical, exception made in some games which uses specific parts of
* the rhythm section. In this respect, some parts of the rhythm mode are still
* only an approximation of the real chip.
* The other thing to note is that this emulator was done through recordings of
* the SB16 DAC, so it has not bitwise precision. Additional equipment should be
* used to verify the samples directly from the chip, and allow this exact
* per-sample correspondence. As a good side-effect, since this emulator uses
* floating point and has a more fine-grained envelope generator, it can produce
* sometimes a crystal-clear, denser kind of OPL3 sound that, because of that,
* may be useful for creating new music.
*
* Version 1.0.6
*
*/
package com.cozendey.opl3;
public final class OPL3 {
static int registers[] = new int[0x200];
static Operator[][] operators;
static Channel2op[][] channels2op;
static Channel4op[][] channels4op;
static Channel[][] channels;
static DisabledChannel disabledChannel;
static BassDrumChannel bassDrumChannel;
static HighHatSnareDrumChannel highHatSnareDrumChannel;
static TomTomTopCymbalChannel tomTomTopCymbalChannel;
static HighHatOperator highHatOperator;
static SnareDrumOperator snareDrumOperator;
static TomTomOperator tomTomOperator;
static TopCymbalOperator topCymbalOperator;
static Operator highHatOperatorInNonRhythmMode;
static Operator snareDrumOperatorInNonRhythmMode;
static Operator tomTomOperatorInNonRhythmMode;
static Operator topCymbalOperatorInNonRhythmMode;
static int nts, dam, dvb, ryt, bd, sd, tom, tc, hh, _new, connectionsel;
static int vibratoIndex, tremoloIndex;
// The methods read() and write() are the only
// ones needed by the user to interface with the emulator.
// read() returns one frame at a time, to be played at 49700 Hz,
// with each frame being four 16-bit samples,
// corresponding to the OPL3 four output channels CHA...CHD.
public short[] read() {
short[] output = new short[4];
double[] outputBuffer = new double[4];
double[] channelOutput;
for(int outputChannelNumber=0; outputChannelNumber<4; outputChannelNumber++)
outputBuffer[outputChannelNumber] = 0;
// If _new = 0, use OPL2 mode with 9 channels. If _new = 1, use OPL3 18 channels;
for(int array=0; array < (_new + 1); array++)
for(int channelNumber=0; channelNumber < 9; channelNumber++) {
// Reads output from each OPL3 channel, and accumulates it in the output buffer:
channelOutput = channels[array][channelNumber].getChannelOutput();
for(int outputChannelNumber=0; outputChannelNumber<4; outputChannelNumber++)
outputBuffer[outputChannelNumber] += channelOutput[outputChannelNumber];
}
// Normalizes the output buffer after all channels have been added,
// with a maximum of 18 channels,
// and multiplies it to get the 16 bit signed output.
for(int outputChannelNumber=0; outputChannelNumber<4; outputChannelNumber++)
output[outputChannelNumber] =
(short)(outputBuffer[outputChannelNumber] / 18 * 0x7FFF);
// Advances the OPL3-wide vibrato index, which is used by
// PhaseGenerator.getPhase() in each Operator.
vibratoIndex++;
if(vibratoIndex >= OPL3Data.vibratoTable[dvb].length) vibratoIndex = 0;
// Advances the OPL3-wide tremolo index, which is used by
// EnvelopeGenerator.getEnvelope() in each Operator.
tremoloIndex++;
if(tremoloIndex >= OPL3Data.tremoloTable[dam].length) tremoloIndex = 0;
return output;
}
public void write(int array, int address, int data) {
// The OPL3 has two registers arrays, each with adresses ranging
// from 0x00 to 0xF5.
// This emulator uses one array, with the two original register arrays
// starting at 0x00 and at 0x100.
int registerAddress = (array<<8) | address;
// If the address is out of the OPL3 memory map, returns.
if(registerAddress<0 || registerAddress>=0x200) return;
registers[registerAddress] = data;
switch(address&0xE0) {
// The first 3 bits masking gives the type of the register by using its base address:
// 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0
// When it is needed, we further separate the register type inside each base address,
// which is the case of 0x00 and 0xA0.
// Through out this emulator we will use the same name convention to
// reference a byte with several bit registers.
// The name of each bit register will be followed by the number of bits
// it occupies inside the byte.
// Numbers without accompanying names are unused bits.
case 0x00:
// Unique registers for the entire OPL3:
if(array==1) {
if(address==0x04)
update_2_CONNECTIONSEL6();
else if(address==0x05)
update_7_NEW1();
}
else if(address==0x08) update_1_NTS1_6();
break;
case 0xA0:
// 0xBD is a control register for the entire OPL3:
if(address==0xBD) {
if(array==0)
update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1();
break;
}
// Registers for each channel are in A0-A8, B0-B8, C0-C8, in both register arrays.
// 0xB0...0xB8 keeps kon,block,fnum(h) for each channel.
if( (address&0xF0) == 0xB0 && address <= 0xB8) {
// If the address is in the second register array, adds 9 to the channel number.
// The channel number is given by the last four bits, like in A0,...,A8.
channels[array][address&0x0F].update_2_KON1_BLOCK3_FNUMH2();
break;
}
// 0xA0...0xA8 keeps fnum(l) for each channel.
if( (address&0xF0) == 0xA0 && address <= 0xA8)
channels[array][address&0x0F].update_FNUML8();
break;
// 0xC0...0xC8 keeps cha,chb,chc,chd,fb,cnt for each channel:
case 0xC0:
if(address <= 0xC8)
channels[array][address&0x0F].update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1();
break;
// Registers for each of the 36 Operators:
default:
int operatorOffset = address&0x1F;
if(operators[array][operatorOffset] == null) break;
switch(address&0xE0) {
// 0x20...0x35 keeps am,vib,egt,ksr,mult for each operator:
case 0x20:
operators[array][operatorOffset].update_AM1_VIB1_EGT1_KSR1_MULT4();
break;
// 0x40...0x55 keeps ksl,tl for each operator:
case 0x40:
operators[array][operatorOffset].update_KSL2_TL6();
break;
// 0x60...0x75 keeps ar,dr for each operator:
case 0x60:
operators[array][operatorOffset].update_AR4_DR4();
break;
// 0x80...0x95 keeps sl,rr for each operator:
case 0x80:
operators[array][operatorOffset].update_SL4_RR4();
break;
// 0xE0...0xF5 keeps ws for each operator:
case 0xE0:
operators[array][operatorOffset].update_5_WS3();
}
}
}
public OPL3() {
nts = dam = dvb = ryt = bd = sd = tom = tc = hh = _new = connectionsel = 0;
vibratoIndex = tremoloIndex = 0;
channels = new Channel[2][9];
initOperators();
initChannels2op();
initChannels4op();
initRhythmChannels();
initChannels();
}
private void initOperators() {
int baseAddress;
// The YMF262 has 36 operators:
operators = new Operator[2][0x20];
for(int array=0; array<2; array++)
for(int group = 0; group<=0x10; group+=8)
for(int offset=0; offset<6; offset++) {
baseAddress = (array<<8) | (group+offset);
operators[array][group+offset] = new Operator(baseAddress);
}
// Create specific operators to switch when in rhythm mode:
highHatOperator = new HighHatOperator();
snareDrumOperator = new SnareDrumOperator();
tomTomOperator = new TomTomOperator();
topCymbalOperator = new TopCymbalOperator();
// Save operators when they are in non-rhythm mode:
// Channel 7:
highHatOperatorInNonRhythmMode = operators[0][0x11];
snareDrumOperatorInNonRhythmMode = operators[0][0x14];
// Channel 8:
tomTomOperatorInNonRhythmMode = operators[0][0x12];
topCymbalOperatorInNonRhythmMode = operators[0][0x15];
}
private void initChannels2op() {
// The YMF262 has 18 2-op channels.
// Each 2-op channel can be at a serial or parallel operator configuration:
channels2op = new Channel2op[2][9];
for(int array=0; array<2; array++)
for(int channelNumber=0; channelNumber<3; channelNumber++) {
int baseAddress = (array<<8) | channelNumber;
// Channels 1, 2, 3 -> Operator offsets 0x0,0x3; 0x1,0x4; 0x2,0x5
channels2op[array][channelNumber] = new Channel2op(baseAddress, operators[array][channelNumber], operators[array][channelNumber+0x3]);
// Channels 4, 5, 6 -> Operator offsets 0x8,0xB; 0x9,0xC; 0xA,0xD
channels2op[array][channelNumber+3] = new Channel2op(baseAddress+3, operators[array][channelNumber+0x8], operators[array][channelNumber+0xB]);
// Channels 7, 8, 9 -> Operators 0x10,0x13; 0x11,0x14; 0x12,0x15
channels2op[array][channelNumber+6] = new Channel2op(baseAddress+6, operators[array][channelNumber+0x10], operators[array][channelNumber+0x13]);
}
}
private void initChannels4op() {
// The YMF262 has 3 4-op channels in each array:
channels4op = new Channel4op[2][3];
for(int array=0; array<2; array++)
for(int channelNumber=0; channelNumber<3; channelNumber++) {
int baseAddress = (array<<8) | channelNumber;
// Channels 1, 2, 3 -> Operators 0x0,0x3,0x8,0xB; 0x1,0x4,0x9,0xC; 0x2,0x5,0xA,0xD;
channels4op[array][channelNumber] = new Channel4op(baseAddress, operators[array][channelNumber], operators[array][channelNumber+0x3], operators[array][channelNumber+0x8], operators[array][channelNumber+0xB]);
}
}
private void initRhythmChannels() {
bassDrumChannel = new BassDrumChannel();
highHatSnareDrumChannel = new HighHatSnareDrumChannel();
tomTomTopCymbalChannel = new TomTomTopCymbalChannel();
}
private void initChannels() {
// Channel is an abstract class that can be a 2-op, 4-op, rhythm or disabled channel,
// depending on the OPL3 configuration at the time.
// channels[] inits as a 2-op serial channel array:
for(int array=0; array<2; array++)
for(int i=0; i<9; i++) channels[array][i] = channels2op[array][i];
// Unique instance to fill future gaps in the Channel array,
// when there will be switches between 2op and 4op mode.
disabledChannel = new DisabledChannel();
}
private void update_1_NTS1_6() {
int _1_nts1_6 = OPL3.registers[OPL3Data._1_NTS1_6_Offset];
// Note Selection. This register is used in Channel.updateOperators() implementations,
// to calculate the channel´s Key Scale Number.
// The value of the actual envelope rate follows the value of
// OPL3.nts,Operator.keyScaleNumber and Operator.ksr
nts = (_1_nts1_6 & 0x40) >> 6;
}
private void update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1() {
int dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 = OPL3.registers[OPL3Data.DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1_Offset];
// Depth of amplitude. This register is used in EnvelopeGenerator.getEnvelope();
dam = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x80) >> 7;
// Depth of vibrato. This register is used in PhaseGenerator.getPhase();
dvb = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x40) >> 6;
int new_ryt = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x20) >> 5;
if(new_ryt != ryt) {
ryt = new_ryt;
setRhythmMode();
}
int new_bd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x10) >> 4;
if(new_bd != bd) {
bd = new_bd;
if(bd==1) {
bassDrumChannel.op1.keyOn();
bassDrumChannel.op2.keyOn();
}
}
int new_sd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x08) >> 3;
if(new_sd != sd) {
sd = new_sd;
if(sd==1) snareDrumOperator.keyOn();
}
int new_tom = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x04) >> 2;
if(new_tom != tom) {
tom = new_tom;
if(tom==1) tomTomOperator.keyOn();
}
int new_tc = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x02) >> 1;
if(new_tc != tc) {
tc = new_tc;
if(tc==1) topCymbalOperator.keyOn();
}
int new_hh = dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x01;
if(new_hh != hh) {
hh = new_hh;
if(hh==1) highHatOperator.keyOn();
}
}
private void update_7_NEW1() {
int _7_new1 = OPL3.registers[OPL3Data._7_NEW1_Offset];
// OPL2/OPL3 mode selection. This register is used in
// OPL3.read(), OPL3.write() and Operator.getOperatorOutput();
_new = (_7_new1 & 0x01);
if(_new==1) setEnabledChannels();
set4opConnections();
}
private void setEnabledChannels() {
for(int array=0; array<2; array++)
for(int i=0; i<9; i++) {
int baseAddress = channels[array][i].channelBaseAddress;
registers[baseAddress+ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] |= 0xF0;
channels[array][i].update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1();
}
}
private void update_2_CONNECTIONSEL6() {
// This method is called only if _new is set.
int _2_connectionsel6 = OPL3.registers[OPL3Data._2_CONNECTIONSEL6_Offset];
// 2-op/4-op channel selection. This register is used here to configure the OPL3.channels[] array.
connectionsel = (_2_connectionsel6 & 0x3F);
set4opConnections();
}
private void set4opConnections() {
// bits 0, 1, 2 sets respectively 2-op channels (1,4), (2,5), (3,6) to 4-op operation.
// bits 3, 4, 5 sets respectively 2-op channels (10,13), (11,14), (12,15) to 4-op operation.
for(int array=0; array<2; array++)
for(int i=0; i<3; i++) {
if(_new == 1) {
int shift = array*3 + i;
int connectionBit = (connectionsel >> shift) & 0x01;
if(connectionBit == 1) {
channels[array][i] = channels4op[array][i];
channels[array][i+3] = disabledChannel;
channels[array][i].updateChannel();
continue;
}
}
channels[array][i] = channels2op[array][i];
channels[array][i+3] = channels2op[array][i+3];
channels[array][i].updateChannel();
channels[array][i+3].updateChannel();
}
}
private void setRhythmMode() {
if(ryt==1) {
channels[0][6] = bassDrumChannel;
channels[0][7] = highHatSnareDrumChannel;
channels[0][8] = tomTomTopCymbalChannel;
operators[0][0x11] = highHatOperator;
operators[0][0x14] = snareDrumOperator;
operators[0][0x12] = tomTomOperator;
operators[0][0x15] = topCymbalOperator;
}
else {
for(int i=6; i<=8; i++) channels[0][i] = channels2op[0][i];
operators[0][0x11] = highHatOperatorInNonRhythmMode;
operators[0][0x14] = snareDrumOperatorInNonRhythmMode;
operators[0][0x12] = tomTomOperatorInNonRhythmMode;
operators[0][0x15] = topCymbalOperatorInNonRhythmMode;
}
for(int i=6; i<=8; i++) channels[0][i].updateChannel();
}
}
//
// Channels
//
abstract class Channel {
int channelBaseAddress;
double[] feedback;
int fnuml, fnumh, kon, block, cha, chb, chc, chd, fb, cnt;
// Factor to convert between normalized amplitude to normalized
// radians. The amplitude maximum is equivalent to 8*Pi radians.
final static double toPhase = 4;
Channel (int baseAddress) {
channelBaseAddress = baseAddress;
fnuml = fnumh = kon = block = cha = chb = chc = chd = fb = cnt = 0;
feedback = new double[2];
feedback[0] = feedback[1] = 0;
}
void update_2_KON1_BLOCK3_FNUMH2() {
int _2_kon1_block3_fnumh2 = OPL3.registers[channelBaseAddress+ChannelData._2_KON1_BLOCK3_FNUMH2_Offset];
// Frequency Number (hi-register) and Block. These two registers, together with fnuml,
// sets the Channel´s base frequency;
block = (_2_kon1_block3_fnumh2 & 0x1C) >> 2;
fnumh = _2_kon1_block3_fnumh2 & 0x03;
updateOperators();
// Key On. If changed, calls Channel.keyOn() / keyOff().
int newKon = (_2_kon1_block3_fnumh2 & 0x20) >> 5;
if(newKon != kon) {
if(newKon == 1) keyOn();
else keyOff();
kon = newKon;
}
}
void update_FNUML8() {
int fnuml8 = OPL3.registers[channelBaseAddress+ChannelData.FNUML8_Offset];
// Frequency Number, low register.
fnuml = fnuml8&0xFF;
updateOperators();
}
void update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1() {
int chd1_chc1_chb1_cha1_fb3_cnt1 = OPL3.registers[channelBaseAddress+ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset];
chd = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x80) >> 7;
chc = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x40) >> 6;
chb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x20) >> 5;
cha = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x10) >> 4;
fb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x0E) >> 1;
cnt = chd1_chc1_chb1_cha1_fb3_cnt1 & 0x01;
updateOperators();
}
void updateChannel() {
update_2_KON1_BLOCK3_FNUMH2();
update_FNUML8();
update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1();
}
protected double[] getInFourChannels(double channelOutput) {
double[] output = new double[4];
if( OPL3._new==0)
output[0] = output[1] = output[2] = output[3] = channelOutput;
else {
output[0] = (cha==1) ? channelOutput : 0;
output[1] = (chb==1) ? channelOutput : 0;
output[2] = (chc==1) ? channelOutput : 0;
output[3] = (chd==1) ? channelOutput : 0;
}
return output;
}
abstract double[] getChannelOutput();
protected abstract void keyOn();
protected abstract void keyOff();
protected abstract void updateOperators();
}
class Channel2op extends Channel {
Operator op1, op2;
Channel2op (int baseAddress, Operator o1, Operator o2) {
super(baseAddress);
op1 = o1;
op2 = o2;
}
double[] getChannelOutput() {
double channelOutput = 0, op1Output = 0, op2Output = 0;
double[] output;
// The feedback uses the last two outputs from
// the first operator, instead of just the last one.
double feedbackOutput = (feedback[0] + feedback[1]) / 2;
switch(cnt) {
// CNT = 0, the operators are in series, with the first in feedback.
case 0:
if(op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
channelOutput = op2.getOperatorOutput(op1Output*toPhase);
break;
// CNT = 1, the operators are in parallel, with the first in feedback.
case 1:
if(op1.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
channelOutput = (op1Output + op2Output) / 2;
}
feedback[0] = feedback[1];
feedback[1] = (op1Output * ChannelData.feedback[fb])%1;
output = getInFourChannels(channelOutput);
return output;
}
protected void keyOn() {
op1.keyOn();
op2.keyOn();
feedback[0] = feedback[1] = 0;
}
protected void keyOff() {
op1.keyOff();
op2.keyOff();
}
protected void updateOperators() {
// Key Scale Number, used in EnvelopeGenerator.setActualRates().
int keyScaleNumber = block*2 + ((fnumh>>OPL3.nts)&0x01);
int f_number = (fnumh<<8) | fnuml;
op1.updateOperator(keyScaleNumber, f_number, block);
op2.updateOperator(keyScaleNumber, f_number, block);
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
int f_number = (fnumh<<8)+fnuml;
str.append(String.format("channelBaseAddress: %d\n", channelBaseAddress));
str.append(String.format("f_number: %d, block: %d\n", f_number, block));
str.append(String.format("cnt: %d, feedback: %d\n", cnt, fb));
str.append(String.format("op1:\n%s", op1.toString()));
str.append(String.format("op2:\n%s", op2.toString()));
return str.toString();
}
}
class Channel4op extends Channel {
Operator op1, op2, op3, op4;
Channel4op (int baseAddress, Operator o1, Operator o2, Operator o3, Operator o4) {
super(baseAddress);
op1 = o1;
op2 = o2;
op3 = o3;
op4 = o4;
}
double[] getChannelOutput() {
double channelOutput = 0,
op1Output = 0, op2Output = 0, op3Output = 0, op4Output = 0;
double[] output;
int secondChannelBaseAddress = channelBaseAddress+3;
int secondCnt = OPL3.registers[secondChannelBaseAddress+ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] & 0x1;
int cnt4op = (cnt << 1) | secondCnt;
double feedbackOutput = (feedback[0] + feedback[1]) / 2;
switch(cnt4op) {
case 0:
if(op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(op1Output*toPhase);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
channelOutput = op4.getOperatorOutput(op3Output*toPhase);
break;
case 1:
if(op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(op1Output*toPhase);
op3Output = op3.getOperatorOutput(Operator.noModulator);
op4Output = op4.getOperatorOutput(op3Output*toPhase);
channelOutput = (op2Output + op4Output) / 2;
break;
case 2:
if(op1.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
op4Output = op4.getOperatorOutput(op3Output*toPhase);
channelOutput = (op1Output + op4Output) / 2;
break;
case 3:
if(op1.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op3.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
op4Output = op4.getOperatorOutput(Operator.noModulator);
channelOutput = (op1Output + op3Output + op4Output) / 3;
}
feedback[0] = feedback[1];
feedback[1] = (op1Output * ChannelData.feedback[fb])%1;
output = getInFourChannels(channelOutput);
return output;
}
protected void keyOn() {
op1.keyOn();
op2.keyOn();
op3.keyOn();
op4.keyOn();
feedback[0] = feedback[1] = 0;
}
protected void keyOff() {
op1.keyOff();
op2.keyOff();
op3.keyOff();
op4.keyOff();
}
protected void updateOperators() {
// Key Scale Number, used in EnvelopeGenerator.setActualRates().
int keyScaleNumber = block*2 + ((fnumh>>OPL3.nts)&0x01);
int f_number = (fnumh<<8) | fnuml;
op1.updateOperator(keyScaleNumber, f_number, block);
op2.updateOperator(keyScaleNumber, f_number, block);
op3.updateOperator(keyScaleNumber, f_number, block);
op4.updateOperator(keyScaleNumber, f_number, block);
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
int f_number = (fnumh<<8)+fnuml;
str.append(String.format("channelBaseAddress: %d\n", channelBaseAddress));
str.append(String.format("f_number: %d, block: %d\n", f_number, block));
str.append(String.format("cnt: %d, feedback: %d\n", cnt, fb));
str.append(String.format("op1:\n%s", op1.toString()));
str.append(String.format("op2:\n%s", op2.toString()));
str.append(String.format("op3:\n%s", op3.toString()));
str.append(String.format("op4:\n%s", op4.toString()));
return str.toString();
}
}
// There's just one instance of this class, that fills the eventual gaps in the Channel array;
class DisabledChannel extends Channel {
DisabledChannel() {
super(0);
}
double[] getChannelOutput() { return getInFourChannels(0); }
protected void keyOn() { }
protected void keyOff() { }
protected void updateOperators() { }
}
//
// Operators
//
class Operator {
PhaseGenerator phaseGenerator;
EnvelopeGenerator envelopeGenerator;
double envelope, phase;
int operatorBaseAddress;
int am, vib, ksr, egt, mult, ksl, tl, ar, dr, sl, rr, ws;
int keyScaleNumber, f_number, block;
final static double noModulator = 0;
Operator(int baseAddress) {
operatorBaseAddress = baseAddress;
phaseGenerator = new PhaseGenerator();
envelopeGenerator = new EnvelopeGenerator();
envelope = 0;
am = vib = ksr = egt = mult = ksl = tl = ar = dr = sl = rr = ws = 0;
keyScaleNumber = f_number = block = 0;
}
void update_AM1_VIB1_EGT1_KSR1_MULT4() {
int am1_vib1_egt1_ksr1_mult4 = OPL3.registers[operatorBaseAddress+OperatorData.AM1_VIB1_EGT1_KSR1_MULT4_Offset];
// Amplitude Modulation. This register is used int EnvelopeGenerator.getEnvelope();
am = (am1_vib1_egt1_ksr1_mult4 & 0x80) >> 7;
// Vibrato. This register is used in PhaseGenerator.getPhase();
vib = (am1_vib1_egt1_ksr1_mult4 & 0x40) >> 6;
// Envelope Generator Type. This register is used in EnvelopeGenerator.getEnvelope();
egt = (am1_vib1_egt1_ksr1_mult4 & 0x20) >> 5;
// Key Scale Rate. Sets the actual envelope rate together with rate and keyScaleNumber.
// This register os used in EnvelopeGenerator.setActualAttackRate().
ksr = (am1_vib1_egt1_ksr1_mult4 & 0x10) >> 4;
// Multiple. Multiplies the Channel.baseFrequency to get the Operator.operatorFrequency.
// This register is used in PhaseGenerator.setFrequency().
mult = am1_vib1_egt1_ksr1_mult4 & 0x0F;
phaseGenerator.setFrequency(f_number, block, mult);
envelopeGenerator.setActualAttackRate(ar, ksr, keyScaleNumber);
envelopeGenerator.setActualDecayRate(dr, ksr, keyScaleNumber);
envelopeGenerator.setActualReleaseRate(rr, ksr, keyScaleNumber);
}
void update_KSL2_TL6() {
int ksl2_tl6 = OPL3.registers[operatorBaseAddress+OperatorData.KSL2_TL6_Offset];
// Key Scale Level. Sets the attenuation in accordance with the octave.
ksl = (ksl2_tl6 & 0xC0) >> 6;
// Total Level. Sets the overall damping for the envelope.
tl = ksl2_tl6 & 0x3F;
envelopeGenerator.setAtennuation(f_number, block, ksl);
envelopeGenerator.setTotalLevel(tl);
}
void update_AR4_DR4() {
int ar4_dr4 = OPL3.registers[operatorBaseAddress+OperatorData.AR4_DR4_Offset];
// Attack Rate.
ar = (ar4_dr4 & 0xF0) >> 4;
// Decay Rate.
dr = ar4_dr4 & 0x0F;
envelopeGenerator.setActualAttackRate(ar, ksr, keyScaleNumber);
envelopeGenerator.setActualDecayRate(dr, ksr, keyScaleNumber);
}
void update_SL4_RR4() {
int sl4_rr4 = OPL3.registers[operatorBaseAddress+OperatorData.SL4_RR4_Offset];
// Sustain Level.
sl = (sl4_rr4 & 0xF0) >> 4;
// Release Rate.
rr = sl4_rr4 & 0x0F;
envelopeGenerator.setActualSustainLevel(sl);
envelopeGenerator.setActualReleaseRate(rr, ksr, keyScaleNumber);
}
void update_5_WS3() {
int _5_ws3 = OPL3.registers[operatorBaseAddress+OperatorData._5_WS3_Offset];
ws = _5_ws3 & 0x07;
}
double getOperatorOutput(double modulator) {
if(envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return 0;
double envelopeInDB = envelopeGenerator.getEnvelope(egt, am);
envelope = Math.pow(10, envelopeInDB/10.0);
// If it is in OPL2 mode, use first four waveforms only:
ws &= ((OPL3._new<<2) + 3);
double[] waveform = OperatorData.waveforms[ws];
phase = phaseGenerator.getPhase(vib);
double operatorOutput = getOutput(modulator, phase, waveform);
return operatorOutput;
}
protected double getOutput(double modulator, double outputPhase, double[] waveform) {
outputPhase = (outputPhase + modulator) % 1;
if(outputPhase<0) {
outputPhase++;
// If the double could not afford to be less than 1:
outputPhase %= 1;
}
int sampleIndex = (int) (outputPhase * OperatorData.waveLength);
return waveform[sampleIndex] * envelope;
}
protected void keyOn() {
if(ar > 0) {
envelopeGenerator.keyOn();
phaseGenerator.keyOn();
}
else envelopeGenerator.stage = EnvelopeGenerator.Stage.OFF;
}
protected void keyOff() {
envelopeGenerator.keyOff();
}
protected void updateOperator(int ksn, int f_num, int blk) {
keyScaleNumber = ksn;
f_number = f_num;
block = blk;
update_AM1_VIB1_EGT1_KSR1_MULT4();
update_KSL2_TL6();
update_AR4_DR4();
update_SL4_RR4();
update_5_WS3();
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
double operatorFrequency = f_number * Math.pow(2, block-1) * OPL3Data.sampleRate / Math.pow(2,19)*OperatorData.multTable[mult];
str.append(String.format("operatorBaseAddress: %d\n", operatorBaseAddress));
str.append(String.format("operatorFrequency: %f\n", operatorFrequency));
str.append(String.format("mult: %d, ar: %d, dr: %d, sl: %d, rr: %d, ws: %d\n", mult, ar, dr, sl, rr, ws));
str.append(String.format("am: %d, vib: %d, ksr: %d, egt: %d, ksl: %d, tl: %d\n", am, vib, ksr, egt, ksl, tl));
return str.toString();
}
}
//
// Envelope Generator
//
class EnvelopeGenerator {
final static double[] INFINITY = null;
enum Stage {ATTACK,DECAY,SUSTAIN,RELEASE,OFF};
Stage stage;
int actualAttackRate, actualDecayRate, actualReleaseRate;
double xAttackIncrement, xMinimumInAttack;
double dBdecayIncrement;
double dBreleaseIncrement;
double attenuation, totalLevel, sustainLevel;
double x, envelope;
EnvelopeGenerator() {
stage = Stage.OFF;
actualAttackRate = actualDecayRate = actualReleaseRate = 0;
xAttackIncrement = xMinimumInAttack = 0;
dBdecayIncrement = 0;
dBreleaseIncrement = 0;
attenuation = totalLevel = sustainLevel = 0;
x = dBtoX(-96);
envelope = -96;
}
void setActualSustainLevel(int sl) {
// If all SL bits are 1, sustain level is set to -93 dB:
if(sl == 0x0F) {
sustainLevel = -93;
return;
}
// The datasheet states that the SL formula is
// sustainLevel = -24*d7 -12*d6 -6*d5 -3*d4,
// translated as:
sustainLevel = -3*sl;
}
void setTotalLevel(int tl) {
// The datasheet states that the TL formula is
// TL = -(24*d5 + 12*d4 + 6*d3 + 3*d2 + 1.5*d1 + 0.75*d0),
// translated as:
totalLevel = tl*-0.75;
}
void setAtennuation(int f_number, int block, int ksl) {
int hi4bits = (f_number>>6)&0x0F;
switch(ksl) {
case 0:
attenuation = 0;
break;
case 1:
// ~3 dB/Octave
attenuation = OperatorData.ksl3dBtable[hi4bits][block];
break;
case 2:
// ~1.5 dB/Octave
attenuation = OperatorData.ksl3dBtable[hi4bits][block]/2;
break;
case 3:
// ~6 dB/Octave
attenuation = OperatorData.ksl3dBtable[hi4bits][block]*2;
}
}
void setActualAttackRate(int attackRate, int ksr, int keyScaleNumber) {
// According to the YMF278B manual's OPL3 section, the attack curve is exponential,
// with a dynamic range from -96 dB to 0 dB and a resolution of 0.1875 dB
// per level.
//
// This method sets an attack increment and attack minimum value
// that creates a exponential dB curve with 'period0to100' seconds in length
// and 'period10to90' seconds between 10% and 90% of the curve total level.
actualAttackRate = calculateActualRate(attackRate, ksr, keyScaleNumber);
double period0to100inSeconds = EnvelopeGeneratorData.attackTimeValuesTable[actualAttackRate][0]/1000d;
int period0to100inSamples = (int)(period0to100inSeconds*OPL3Data.sampleRate);
double period10to90inSeconds = EnvelopeGeneratorData.attackTimeValuesTable[actualAttackRate][1]/1000d;
int period10to90inSamples = (int)(period10to90inSeconds*OPL3Data.sampleRate);
// The x increment is dictated by the period between 10% and 90%:
xAttackIncrement = OPL3Data.calculateIncrement(percentageToX(0.1), percentageToX(0.9), period10to90inSeconds);
// Discover how many samples are still from the top.
// It cannot reach 0 dB, since x is a logarithmic parameter and would be
// negative infinity. So we will use -0.1875 dB as the resolution
// maximum.
//
// percentageToX(0.9) + samplesToTheTop*xAttackIncrement = dBToX(-0.1875); ->
// samplesToTheTop = (dBtoX(-0.1875) - percentageToX(0.9)) / xAttackIncrement); ->
// period10to100InSamples = period10to90InSamples + samplesToTheTop; ->
int period10to100inSamples = (int) (period10to90inSamples + (dBtoX(-0.1875) - percentageToX(0.9)) / xAttackIncrement);
// Discover the minimum x that, through the attackIncrement value, keeps
// the 10%-90% period, and reaches 0 dB at the total period:
xMinimumInAttack = percentageToX(0.1) - (period0to100inSamples-period10to100inSamples)*xAttackIncrement;
}
void setActualDecayRate(int decayRate, int ksr, int keyScaleNumber) {
actualDecayRate = calculateActualRate(decayRate, ksr, keyScaleNumber);
double period10to90inSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[actualDecayRate][1]/1000d;
// Differently from the attack curve, the decay/release curve is linear.
// The dB increment is dictated by the period between 10% and 90%:
dBdecayIncrement = OPL3Data.calculateIncrement(percentageToDB(0.1), percentageToDB(0.9), period10to90inSeconds);
}
void setActualReleaseRate(int releaseRate, int ksr, int keyScaleNumber) {
actualReleaseRate = calculateActualRate(releaseRate, ksr, keyScaleNumber);
double period10to90inSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[actualReleaseRate][1]/1000d;
dBreleaseIncrement = OPL3Data.calculateIncrement(percentageToDB(0.1), percentageToDB(0.9), period10to90inSeconds);
}
private int calculateActualRate(int rate, int ksr, int keyScaleNumber) {
int rof = EnvelopeGeneratorData.rateOffset[ksr][keyScaleNumber];
int actualRate = rate*4 + rof;
// If, as an example at the maximum, rate is 15 and the rate offset is 15,
// the value would
// be 75, but the maximum allowed is 63:
if(actualRate > 63) actualRate = 63;
return actualRate;
}
double getEnvelope(int egt, int am) {
// The datasheets attenuation values
// must be halved to match the real OPL3 output.
double envelopeSustainLevel = sustainLevel / 2;
double envelopeTremolo =