forked from vgmrips/vgmplay-legacy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOotake_PSG.c
1055 lines (877 loc) · 41.1 KB
/
Ootake_PSG.c
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
/******************************************************************************
Ootake
・キューの参照処理をシンプルにした。テンポの安定性および音質の向上。
・オーバーサンプリングしないようにした。(筆者の主観もあるが、PSGの場合、響きの
美しさが損なわれてしまうケースが多いため。速度的にもアップ)
・ノイズの音質・音量を実機並みに調整した。v0.72
・ノイズの周波数に0x1Fが書き込まれたときは、0x1Eと同じ周波数で音量を半分にして
鳴らすようにした。v0.68
・現状は再生サンプルレートは44.1KHz固定とした。(CD-DA再生時の速度アップのため)
・DDA音の発声が終了したときにいきなり波形を0にせず、フェードアウトさせるように
し、ノイズを軽減した。v0.57
・DDAモード(サンプリング発声)のときの波形データのノイズが多く含まれている部分
をカットしして、音質を上げた。音量も調節した。v0.59
・ノイズ音の音質・音量を調整して、実機の雰囲気に近づけた。v0.68
・waveIndexの初期化とDDAモード時の動作を見直して実機の動作に近づけた。v0.63
・waveIndexの初期化時にwaveテーブルも初期化するようにした。ファイヤープロレス
リング、F1トリプルバトルなどの音が実機に近づいた。v0.65
・waveの波形の正負を実機同様にした。v0.74
・waveの最小値が-14になるようにし音質を整えた。v0.74
・クリティカルセクションは必要ない(書き込みが同時に行われるわけではない)ような
ので、省略し高速化した。v1.09
・キュー処理(ApuQueue.c)をここに統合して高速化した。v1.10
・低音領域のボリュームを上げて実機並みの聞こえやすさに近づけた。v1.46
・LFO処理のの実装。"はにいいんざすかい"のOPや、フラッシュハイダースの効果音が
実機の音に近づいた。v1.59
Copyright(C)2006-2012 Kitao Nakamura.
改造版・後継版を公開なさるときは必ずソースコードを添付してください。
その際に事後でかまいませんので、ひとことお知らせいただけると幸いです。
商的な利用は禁じます。
あとは「GNU General Public License(一般公衆利用許諾契約書)」に準じます。
*******************************************************************************
[PSG.c]
PSGを実装します。
Implements the PSG.
Copyright (C) 2004 Ki
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // for memset
#include <math.h>
#include "mamedef.h"
#include "Ootake_PSG.h"
//#include "MainBoard.h" //Kitao追加
//#include "App.h" //Kitao追加
//#include "PRINTF.h" //Kitao追加
//#define PRINTF printf
#define N_CHANNEL 6
//#define SAMPLE_RATE 44100.0 //Kitao更新。現状は速度優先でサンプルレートを44100固定とした。
#define OVERSAMPLE_RATE 1.0 //Kitao更新。PSGはオーバーサンプリングすると響きの美しさが損なわれてしまうのでオーバーサンプリングしないようにした。速度的にもアップ。
#define PSG_DECLINE (21.8500*6.0) //21.8500。Kitao追加。PSG音量の減少値。*6.0は各チャンネル足したぶんを割る意味。大きいほど音は減る。CDDAが100%のときにちょうど良いぐらいの音量に合わせよう。v2.19,v2.37,v2.39,v2.62更新
#define VOL_TABLE_DECLINE -1.05809999010 //-1.05809999010で雀探物語2OK。Kitao追加。音量テーブルの減少値。マイナスが大きいほど小さい音が聞こえづらくなる。マイナスが小さすぎると平面的な音になる。v2.19,v2.37,v2.39,v2.40,v2.62,v2.65更新
// ※PSG_DECLINEの値を変更した場合、減退率のベスト値も変更する必要がある。雀探物語2(マイナスが小さいとPSGが目立ちすぎてADPCMが聴きづらい),大魔界村(マイナスが大きいと音篭り),ソルジャーブレイドで、PSG_DECLINE=(14.4701*6.0)で減退率-1.0498779900db前後が飛び抜けていい響き(うちの環境で主観)。
// モトローダー(マイナスやや大き目がいい),1941(マイナス小さめがいい)なども微妙な値変更で大きく変わる。
#define NOISE_TABLE_VALUE -18 : -1 //キレと聴きやすさで-18:-1をベストとした。最大値が大きい(+に近い)と重い音に。2つの値が離れていると重い音に。フォーメーションサッカー,大魔界村のエンディングのドラムなどで調整。v1.46,v2.40,v2.62更新
// ※VOL_TABLE_DECLINEによってこの値の最適値も変化する。
#define SAMPLE_FADE_DECLINE 0.305998999951 //0.30599899951。Kitao追加。サンプリング音の消音時の音の減退量。ソルジャーブレイド,将棋初心者無用の音声で調整。基本的にこの値が小さいほうがノイズが減る(逆のケースもある)。v2.40
// サンプリングドラムの音色が決まるので大事な値。値が大きすぎるとファイナルソルジャーやソルジャーブレイド,モトローダーなどでドラムがしょぼくなる。
//#define RESMPL_RATE PSG_FRQ / OVERSAMPLE_RATE / SAMPLE_RATE // the lack of () is intentional
/*-----------------------------------------------------------------------------
[DEV NOTE]
MAL --- 0 - 15 (15 で -0[dB], 1減るごとに -3.0 [dB])
AL --- 0 - 31 (31 で -0[dB], 1減るごとに -1.5 [dB])
LAL/RAL --- 0 - 15 (15 で -0[dB], 1減るごとに -3.0 [dB])
次のように解釈しなおす。
MAL*2 --- 0 - 30 (30 で -0[dB], 1減るごとに -1.5 [dB])
AL --- 0 - 31 (31 で -0[dB], 1減るごとに -1.5 [dB])
LAL/RAL*2 --- 0 - 30 (30 で -0[dB], 1減るごとに -1.5 [dB])
dB = 20 * log10(OUT/IN)
dB / 20 = log10(OUT/IN)
OUT/IN = 10^(dB/20)
IN(最大出力) を 1.0 とすると、
OUT = 10^(dB/20)
-91 <= -(MAL*2 + AL + LAL(RAL)*2) <= 0
だから、最も小さい音は、
-91 * 1.5 [dB] = -136.5 [dB] = 10^(-136.5/20) ~= 1.496236e-7 [倍]
となる。
1e-7 オーダーの値は、固定小数点で表現しようとすると、小数部だけで
24 ビット以上必要で、なおかつ16ビットの音声を扱うためには +16ビット
だから 24+16 = 40ビット以上必要になる。よって、32 ビットの処理系で
PCEの音声を固定小数点で表現するのはつらい。そこで、波形の計算は
float で行なうことにする。
float から出力形式に変換するのはAPUの仕事とする。
[2004.4.28] やっぱり Sint32 で実装することにした(微小な値は無視する)。
CPUとPSGは同じICにパッケージしてあるのだが、
実際にはPSGはCPUの1/2のクロックで動作すると考えて良いようだ。
よって、PSGの動作周波数 Fpsg は、
Fpsg = 21.47727 [MHz] / 3 / 2 = 3.579545 [MHz]
となる。
たとえば32サンプルを1周期とする波形が再生されるとき、
この周波数の周期でサンプルを1つずつ拾い出すと、
M = 3579545 / 32 = 111860.78125 [Hz]
というマジックナンバーが得られる(ファミコンと同じ)。
ただし、再生周波数が固定では曲の演奏ができないので、
FRQ なる周波数パラメータを用いて再生周波数を変化させる。
FRQ はPSGのレジスタに書き込まれる12ビット長のパラメータで、
↑で得られたマジックナンバーの「割る数」になっている。
上の32サンプルを1周期とする波形が再生されるとき、
この波形の周波数 F は、FRQ を用いて、
F = M / FRQ [Hz] (FRQ != 0)
となる。
PCの再生サンプリング周波数が Fpc [Hz] だとすると、
1周期32サンプルの波形の再生周波数 F2 は F2 = Fpc / 32 [Hz]。
よって、PCの1サンプルに対して、PCEの1サンプルを拾い出す
カウンタの進み幅 I は
I = F / F2 = 32 * F / Fpc = Fpsg / FRQ / Fpc [単位なし]
となる。
[NOISE CHANNEL]
擬似ノイズの生成にはM系列(maximum length sequence)が用いられる。
M系列のビット長は未調査につき不明。
ここでは仮に15ビットとして実装を行なう。
出力は1ビットで、D0 がゼロのときは負の値、1のときは正の値とする。
PCの1サンプルに対して、PCEの1サンプルを拾い出す
カウンタの進み幅 I は、
I = Fpsg / 64 / FRQ / Fpc (FRQ != 0)
となる。
[再生クオリティ向上について] 2004.6.22
エミュレータでは、PSGのレジスタにデータが書き込まれるまで、
次に発声すべき音がわからない。レジスタにデータが書き込まれたときに、
サウンドバッファを更新したいのだけど、あいにく現在の実装では、
サウンドバッファの更新は別スレッドで行なわれていて、
エミュレーションスレッドから任意の時間に更新することができない。
これまでの再生では、サウンドバッファの更新時のレジスタ設定のみが
有効だったが、これだと例えばサウンドバッファ更新の合間に一瞬だけ
出力された音などが無視されてしまう。これは特にDDAモードやノイズが
リズムパートとして使用される上で問題になる。
レジスタに書き込まれた値をきちんと音声出力に反映させるには、
過去に書き込まれたレジスタの値(いつ、どのレジスタに、何が書き込まれたか)
を保存しておいて、サウンドバッファ更新時にこれを参照する方法が
考えられる。どのくらい過去までレジスタの値を保存しておくかは、
サウンドバッファの長さにもよると思われるが、とりあえずは試行錯誤で
決めることにする。
PSGレジスタへの書き込み動作はエミュレーションスレッドで
行なわれ、サウンドバッファ更新はその専用スレッドで行なわれる。
これだと、エミュレーションスレッドがレジスタのキューに書き込みを
行なっている最中に、サウンドバッファ更新スレッドがキューから
読み出しを行なってしまい、アクセスが衝突する。この問題を解決するには、
1.サウンドバッファの更新を別スレッドで行なわない
2.キューのアクセス部分を排他処理にする
の2とおりが考えられる。とりあえず2の方法をとることにする。
---------------------------------------------------------------------------*/
typedef struct
{
Uint32 frq;
BOOL bOn;
BOOL bDDA;
Uint32 volume;
Uint32 volumeL;
Uint32 volumeR;
Sint32 outVolumeL;
Sint32 outVolumeR;
Sint32 wave[32];
Uint32 waveIndex;
Sint32 ddaSample;
Uint32 phase;
Uint32 deltaPhase;
BOOL bNoiseOn;
Uint32 noiseFrq;
Uint32 deltaNoisePhase;
} PSG;
typedef struct
{
double SAMPLE_RATE;
double PSG_FRQ;
double RESMPL_RATE;
PSG Psg[8]; // 6, 7 is unused
Sint32 DdaFadeOutL[8]; //Kitao追加
Sint32 DdaFadeOutR[8]; //Kitao追加
Uint32 Channel; // 0 - 5;
Uint32 MainVolumeL; // 0 - 15
Uint32 MainVolumeR; // 0 - 15
Uint32 LfoFrq;
BOOL bLfoOn; //v1.59から非使用。過去verのステートロードのために残してある。
Uint32 LfoCtrl;
Uint32 LfoShift; //v1.59から非使用。過去verのステートロードのために残してある。
Sint32 PsgVolumeEffect; // = 0;//Kitao追加
double Volume; // = 0;//Kitao追加
double VOL; // = 0.0;//Kitao追加。v1.08
// BOOL _bPsgMute[8] = {FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE};//Kitao追加。v1.29
BOOL bPsgMute[8];
Uint8 Port[16]; // for debug purpose
BOOL bWaveCrash; //Kitao追加。DDA再生中にWaveデータが書き換えられたらTRUE
BOOL bHoneyInTheSky; //はにいいんざすかいパッチ用。v2.60
} huc6280_state;
static Sint32 _VolumeTable[92];
static Sint32 _NoiseTable[32768];
//static BOOL _bPsgInit = FALSE;
static BOOL _bTblInit = FALSE;
//Kitao更新。v1.10。キュー処理をここに統合して高速化。
/*
APU専用キューの仕様
レジスタに書き込みが行なわれるごとに、
キューにその内容を追加する。
サウンドバッファ更新時に経過時間をみて、
過去に書き込まれたレジスタ内容を
書き込まれた順にキューから取り出し、
PSGレジスタを更新する。
(なおPSGレジスタは全て write only とみなす)
↑要確認
キューに追加するときには write index を用い、
取り出すときには read index を用いる。
// 追加
queue[write index++] = written data
// 取り出し
data = queue[read index++]
キューから値を取り出したときに read index が
write index と一致したときは queue underflow。
→とりあえずなにもしない。
キューに値を追加したときに write index が
read index と一致したときは queue overflow。
→とりあえずリセットすることにする。
*/
/*#define APUQUEUE_SIZE 65536*2 // must be power of 2 v1.61更新。65536だとA列車3をオーバークロックしてプレイしたときに足りなかった。
typedef struct //Kitao更新。clockは非使用とした。v1.61からステートセーブのサイズを減らすために変数上からもカット。
{
Uint8 reg; // 0-15
Uint8 data; // written data
} ApuQueue;
typedef struct //v1.60以前のステートロードのため残してある。
{
Uint32 clock; // cpu cycles elapsed since previous write Kitao更新。clockは現在非使用。
Uint8 reg; // 0-15
Uint8 data; // written data
} OldApuQueue;
static ApuQueue _Queue[APUQUEUE_SIZE];
static Uint32 _QueueWriteIndex;
static Uint32 _QueueReadIndex;*/
//ボリュームテーブルの作成
//Kitao更新。低音量の音が実機より聞こえづらいので、減退率をVOL_TABLE_DECLINE[db](試行錯誤したベスト値)とし、ノーマライズ処理をするようにした。v1.46
// おそらく、実機もアンプを通って出力される際にノーマライズ処理されている。
static void
create_volume_table()
{
int i;
double v;
_VolumeTable[0] = 0; //Kitao追加
for (i = 1; i <= 91; i++)
{
v = 91 - i;
_VolumeTable[i] = (Sint32)(32768.0 * pow(10.0, v * VOL_TABLE_DECLINE / 20.0)); //VOL_TABLE_DECLINE。小さくしすぎると音が平面的な傾向に。ソルジャーブレイドで調整。v1.46。
}
}
//ノイズテーブルの作成
static void
create_noise_table()
{
Sint32 i;
Uint32 bit0;
Uint32 bit1;
Uint32 bit14;
Uint32 reg = 0x100;
for (i = 0; i < 32768; i++)
{
bit0 = reg & 1;
bit1 = (reg & 2) >> 1;
bit14 = (bit0 ^ bit1);
reg >>= 1;
reg |= (bit14 << 14);
_NoiseTable[i] = (bit0) ? NOISE_TABLE_VALUE; //Kitao更新。ノイズのボリュームと音質を調整した。
}
}
/*-----------------------------------------------------------------------------
[write_reg]
PSGポートの書き込みに対する動作を記述します。
-----------------------------------------------------------------------------*/
//static inline void
INLINE void
write_reg(
huc6280_state* info,
Uint8 reg,
Uint8 data)
{
Uint32 i;
Uint32 frq;//Kitao追加
PSG* PSGChn;
info->Port[reg & 15] = data;
switch (reg & 15)
{
case 0: // register select
info->Channel = data & 7;
break;
case 1: // main volume
info->MainVolumeL = (data >> 4) & 0x0F;
info->MainVolumeR = data & 0x0F;
/* LMAL, RMAL は全チャネルの音量に影響する */
for (i = 0; i < N_CHANNEL; i++)
{
PSGChn = &info->Psg[i];
PSGChn->outVolumeL = _VolumeTable[PSGChn->volume + (info->MainVolumeL + PSGChn->volumeL) * 2];
PSGChn->outVolumeR = _VolumeTable[PSGChn->volume + (info->MainVolumeR + PSGChn->volumeR) * 2];
}
break;
case 2: // frequency low
PSGChn = &info->Psg[info->Channel];
PSGChn->frq &= ~0xFF;
PSGChn->frq |= data;
//Kitao更新。update_frequencyは、速度アップのためサブルーチンにせず直接実行するようにした。
frq = (PSGChn->frq - 1) & 0xFFF;
if (frq)
PSGChn->deltaPhase = (Uint32)((double)(65536.0 * 256.0 * 8.0 * info->RESMPL_RATE) / (double)frq +0.5); //Kitao更新。速度アップのためfrq以外は定数計算にした。精度向上のため、先に値の小さいOVERSAMPLE_RATEのほうで割るようにした。+0.5は四捨五入で精度アップ。プチノイズ軽減のため。
else
PSGChn->deltaPhase = 0;
break;
case 3: // frequency high
PSGChn = &info->Psg[info->Channel];
PSGChn->frq &= ~0xF00;
PSGChn->frq |= (data & 0x0F) << 8;
//Kitao更新。update_frequencyは、速度アップのためサブルーチンにせず直接実行するようにした。
frq = (PSGChn->frq - 1) & 0xFFF;
if (frq)
PSGChn->deltaPhase = (Uint32)((double)(65536.0 * 256.0 * 8.0 * info->RESMPL_RATE) / (double)frq +0.5); //Kitao更新。速度アップのためfrq以外は定数計算にした。精度向上のため、先に値の小さいOVERSAMPLE_RATEのほうで割るようにした。+0.5は四捨五入で精度アップ。プチノイズ軽減のため。
else
PSGChn->deltaPhase = 0;
break;
case 4: // ON, DDA, AL
PSGChn = &info->Psg[info->Channel];
if (info->bHoneyInTheSky) //はにいいんざすかいのポーズ時に、微妙なボリューム調整タイミングの問題でプチノイズが載ってしまうので、現状はパッチ処理で対応。v2.60更新
{
if ((PSGChn->bOn)&&(data == 0)) //発声中にdataが0の場合、LRボリュームも0にリセット。はにいいんざすかいのポーズ時のノイズが解消。(data & 0x1F)だけが0のときにリセットすると、サイレントデバッガーズ等でNG。発声してない時にリセットするとアトミックロボでNG。v2.55
{
//PRINTF("test %X %X %X %X",info->Channel,PSGChn->bOn,info->MainVolumeL,info->MainVolumeR);
if ((info->MainVolumeL & 1) == 0) //メインボリュームのbit0が0のときだけ処理(はにいいんざすかいでイレギュラーな0xE。他のゲームは0xF。※ヘビーユニットも0xEだった)。これがないとミズバク大冒険で音が出ない。実機の仕組みと同じかどうかは未確認。v2.53追加
PSGChn->volumeL = 0;
if ((info->MainVolumeR & 1) == 0) //右チャンネルも同様とする
PSGChn->volumeR = 0;
}
}
PSGChn->bOn = ((data & 0x80) != 0);
if ((PSGChn->bDDA)&&((data & 0x40)==0)) //DDAからWAVEへ切り替わるとき or DDAから消音するとき
{
//Kitao追加。DDAはいきなり消音すると目立つノイズが載るのでフェードアウトする。
info->DdaFadeOutL[info->Channel] = (Sint32)((double)(PSGChn->ddaSample * PSGChn->outVolumeL) *
((1 + (1 >> 3) + (1 >> 4) + (1 >> 5) + (1 >> 7) + (1 >> 12) + (1 >> 14) + (1 >> 15)) * SAMPLE_FADE_DECLINE)); //元の音量。v2.65更新
info->DdaFadeOutR[info->Channel] = (Sint32)((double)(PSGChn->ddaSample * PSGChn->outVolumeR) *
((1 + (1 >> 3) + (1 >> 4) + (1 >> 5) + (1 >> 7) + (1 >> 12) + (1 >> 14) + (1 >> 15)) * SAMPLE_FADE_DECLINE));
}
PSGChn->bDDA = ((data & 0x40) != 0);
//Kitao追加。dataのbit7,6が01のときにWaveインデックスをリセットする。
//DDAモード時にWaveデータを書き込んでいた場合はここでWaveデータを修復(初期化)する。ファイヤープロレスリング。
if ((data & 0xC0) == 0x40)
{
PSGChn->waveIndex = 0;
if (info->bWaveCrash)
{
for (i=0; i<32; i++)
PSGChn->wave[i] = -14; //Waveデータを最小値で初期化
info->bWaveCrash = FALSE;
}
}
PSGChn->volume = data & 0x1F;
PSGChn->outVolumeL = _VolumeTable[PSGChn->volume + (info->MainVolumeL + PSGChn->volumeL) * 2];
PSGChn->outVolumeR = _VolumeTable[PSGChn->volume + (info->MainVolumeR + PSGChn->volumeR) * 2];
break;
case 5: // LAL, RAL
PSGChn = &info->Psg[info->Channel];
PSGChn->volumeL = (data >> 4) & 0xF;
PSGChn->volumeR = data & 0xF;
PSGChn->outVolumeL = _VolumeTable[PSGChn->volume + (info->MainVolumeL + PSGChn->volumeL) * 2];
PSGChn->outVolumeR = _VolumeTable[PSGChn->volume + (info->MainVolumeR + PSGChn->volumeR) * 2];
break;
case 6: // wave data //Kitao更新。DDAモードのときもWaveデータを更新するようにした。v0.63。ファイヤープロレスリング
PSGChn = &info->Psg[info->Channel];
data &= 0x1F;
info->bWaveCrash = FALSE; //Kitao追加
if (!PSGChn->bOn) //Kitao追加。音を鳴らしていないときだけWaveデータを更新する。v0.65。F1トリプルバトルのエンジン音。
{
PSGChn->wave[PSGChn->waveIndex++] = 17 - data; //17。Kitao更新。一番心地よく響く値に。ミズバク大冒険,モトローダー,ドラゴンスピリット等で調整。
PSGChn->waveIndex &= 0x1F;
}
if (PSGChn->bDDA)
{
//Kitao更新。ノイズ軽減のため6より下側の値はカットするようにした。v0.59
if (data < 6) //サイバーナイトで6に決定
data = 6; //ノイズが多いので小さな値はカット
PSGChn->ddaSample = 11 - data; //サイバーナイトで11に決定。ドラムの音色が最適。v0.74
if (!PSGChn->bOn) //DDAモード時にWaveデータを書き換えた場合
info->bWaveCrash = TRUE;
}
break;
case 7: // noise on, noise frq
if (info->Channel >= 4)
{
PSGChn = &info->Psg[info->Channel];
PSGChn->bNoiseOn = ((data & 0x80) != 0);
PSGChn->noiseFrq = 0x1F - (data & 0x1F);
if (PSGChn->noiseFrq == 0)
PSGChn->deltaNoisePhase = (Uint32)((double)(2048.0 * info->RESMPL_RATE) +0.5); //Kitao更新
else
PSGChn->deltaNoisePhase = (Uint32)((double)(2048.0 * info->RESMPL_RATE) / (double)PSGChn->noiseFrq +0.5); //Kitao更新
}
break;
case 8: // LFO frequency
info->LfoFrq = data;
//Kitaoテスト用
//PRINTF("LFO Frq = %X",info->LfoFrq);
break;
case 9: // LFO control Kitao更新。シンプルに実装してみた。実機で同じ動作かは未確認。はにいいんざすかいの音が似るように実装。v1.59
if (data & 0x80) //bit7を立てて呼ぶと恐らくリセット
{
info->Psg[1].phase = 0; //LfoFrqは初期化しない。はにいいんざすかい。
//Kitaoテスト用
//PRINTF("LFO control = %X",data);
}
info->LfoCtrl = data & 7; //ドロップロックほらホラで5が使われる。v1.61更新
if (info->LfoCtrl & 4)
info->LfoCtrl = 0; //ドロップロックほらホラ。実機で聴いた感じはLFOオフと同じ音のようなのでbit2が立っていた(負の数扱い?)ら0と同じこととする。
//Kitaoテスト用
//PRINTF("LFO control = %X, Frq =%X",data,info->LfoFrq);
break;
default: // invalid write
break;
}
return;
}
//Kitao追加
static void
set_VOL(huc6280_state* info)
{
//Sint32 v;
if (info->PsgVolumeEffect == 0)
//info->VOL = 0.0; //ミュート
info->VOL = 1.0 / 128.0;
else if (info->PsgVolumeEffect == 3)
info->VOL = info->Volume / (double)(OVERSAMPLE_RATE * 4.0/3.0); // 3/4。v1.29追加
else
info->VOL = info->Volume / (double)(OVERSAMPLE_RATE * info->PsgVolumeEffect); //Kitao追加。_PsgVolumeEffect=ボリューム調節効果。
/*if (!APP_GetCDGame()) //Huカードゲームのときだけ、ボリューム101~120を有効化。v2.62
{
v = APP_GetWindowsVolume();
if (v > 100)
_VOL *= ((double)(v-100) * 3.0 + 100.0) / 100.0; //101~120は通常の3.0倍の音量変化。3.0倍のVol120でソルジャーブレイド最適。ビックリマンワールドOK。3.1倍以上だと音が薄くなる&音割れの心配もあり。
}*/
}
/*-----------------------------------------------------------------------------
[Mix]
PSGの出力をミックスします。
-----------------------------------------------------------------------------*/
void
PSG_Mix(
// Sint16* pDst, // 出力先バッファ //Kitao更新。PSG専用バッファにしたためSint16に。
void* chip,
Sint32** pDst,
Sint32 nSample) // 書き出すサンプル数
{
huc6280_state* info = (huc6280_state*)chip;
PSG* PSGChn;
Sint32 i;
Sint32 j;
Sint32 sample; //Kitao追加
Sint32 lfo;
Sint32 sampleAllL; //Kitao追加。6chぶんのサンプルを足していくためのバッファ。精度を維持するために必要。6chぶん合計が終わった後に、これをSint16に変換して書き込むようにした。
Sint32 sampleAllR; //Kitao追加。上のRチャンネル用
Sint32 smp; //Kitao追加。DDA音量,ノイズ音量計算用
Sint32* bufL = pDst[0];
Sint32* bufR = pDst[1];
// if (!_bPsgInit)
// return;
for (j=0; j<nSample; j++)
{
sampleAllL = 0;
sampleAllR = 0;
for (i=0; i<N_CHANNEL; i++)
{
PSGChn = &info->Psg[i];
if ((PSGChn->bOn)&&((i != 1)||(info->LfoCtrl == 0))&&(!info->bPsgMute[i])) //Kitao更新
{
if (PSGChn->bDDA)
{
smp = PSGChn->ddaSample * PSGChn->outVolumeL;
sampleAllL += smp + (smp >> 3) + (smp >> 4) + (smp >> 5) + (smp >> 7) + (smp >> 12) + (smp >> 14) + (smp >> 15); //Kitao更新。サンプリング音の音量を実機並みに調整。v2.39,v2.40,v2.62,v2.65再調整した。
smp = PSGChn->ddaSample * PSGChn->outVolumeR;
sampleAllR += smp + (smp >> 3) + (smp >> 4) + (smp >> 5) + (smp >> 7) + (smp >> 12) + (smp >> 14) + (smp >> 15); //Kitao更新。サンプリング音の音量を実機並みに調整。v2.39,v2.40,v2.62,v2.65再調整した。
}
else if (PSGChn->bNoiseOn)
{
sample = _NoiseTable[PSGChn->phase >> 17];
if (PSGChn->noiseFrq == 0) //Kitao追加。noiseFrq=0(dataに0x1Fが書き込まれた)のときは音量が通常の半分とした。(ファイヤープロレスリング3、パックランド、桃太郎活劇、がんばれゴルフボーイズなど)
{
smp = sample * PSGChn->outVolumeL;
sampleAllL += (smp >> 1) + (smp >> 12) + (smp >> 14); //(1/2 + 1/4096 + (1/32768 + 1/32768))
smp = sample * PSGChn->outVolumeR;
sampleAllR += (smp >> 1) + (smp >> 12) + (smp >> 14);
}
else //通常
{
smp = sample * PSGChn->outVolumeL;
sampleAllL += smp + (smp >> 11) + (smp >> 14) + (smp >> 15); //Kitao更新。ノイズの音量を実機並みに調整(1 + 1/2048 + 1/16384 + 1/32768)。この"+1/32768"で絶妙(主観。大魔界村,ソルジャーブレイドなど)になる。v2.62更新
smp = sample * PSGChn->outVolumeR;
sampleAllR += smp + (smp >> 11) + (smp >> 14) + (smp >> 15); //Kitao更新。ノイズの音量を実機並みに調整
}
PSGChn->phase += PSGChn->deltaNoisePhase; //Kitao更新
}
else if (PSGChn->deltaPhase)
{
//Kitao更新。オーバーサンプリングしないようにした。
sample = PSGChn->wave[PSGChn->phase >> 27];
if (PSGChn->frq < 128)
sample -= sample >> 2; //低周波域の音量を制限。ブラッドギアのスタート時などで実機と同様の音に。ソルジャーブレイドなども実機に近くなった。v2.03
sampleAllL += sample * PSGChn->outVolumeL; //Kitao更新
sampleAllR += sample * PSGChn->outVolumeR; //Kitao更新
//Kitao更新。Lfoオンが有効になるようにし、Lfoの掛かり具合を実機に近づけた。v1.59
if ((i==0)&&(info->LfoCtrl>0))
{
//_LfoCtrlが1のときに0回シフト(そのまま)で、はにいいんざすかいが実機の音に近い。
//_LfoCtrlが3のときに4回シフトで、フラッシュハイダースが実機の音に近い。
lfo = info->Psg[1].wave[info->Psg[1].phase >> 27] << ((info->LfoCtrl-1) << 1); //v1.60更新
info->Psg[0].phase += (Uint32)((double)(65536.0 * 256.0 * 8.0 * info->RESMPL_RATE) / (double)(info->Psg[0].frq + lfo) +0.5);
info->Psg[1].phase += (Uint32)((double)(65536.0 * 256.0 * 8.0 *info-> RESMPL_RATE) / (double)(info->Psg[1].frq * info->LfoFrq) +0.5); //v1.60更新
}
else
PSGChn->phase += PSGChn->deltaPhase;
}
}
//Kitao追加。DDA消音時はノイズ軽減のためフェードアウトで消音する。
// ベラボーマン(「わしがばくだはかせじゃ」から数秒後)やパワーテニス(タイトル曲終了から数秒後。点数コール),将棋初心者無用(音声)等で効果あり。
if (info->DdaFadeOutL[i] > 0)
--info->DdaFadeOutL[i];
else if (info->DdaFadeOutL[i] < 0)
++info->DdaFadeOutL[i];
if (info->DdaFadeOutR[i] > 0)
--info->DdaFadeOutR[i];
else if (info->DdaFadeOutR[i] < 0)
++info->DdaFadeOutR[i];
sampleAllL += info->DdaFadeOutL[i];
sampleAllR += info->DdaFadeOutR[i];
}
//Kitao更新。6ch合わさったところで、ボリューム調整してバッファに書き込む。
sampleAllL = (Sint32)((double)sampleAllL * info->VOL);
//if ((sampleAllL>32767)||(sampleAllL<-32768)) PRINTF("PSG Sachitta!");//test用
// if (sampleAllL> 32767) sampleAllL= 32767; //Volをアップしたのでサチレーションチェックが必要。v2.39
// if (sampleAllL<-32768) sampleAllL=-32768; // パックランドでUFO等にやられたときぐらいで、通常のゲームでは起こらない。音量の大きなビックリマンワールドもOK。パックランドも通常はOKでサチレーションしたときでもわずかなので音質的に大丈夫。
// なので音質的には、PSGを2つのDirectXチャンネルに分けて鳴らすべき(処理は重くなる)だが、現状はパックランドでもサチレーション処理だけで音質的に問題なし(速度優先)とする。
sampleAllR = (Sint32)((double)sampleAllR * info->VOL);
//if ((sampleAllR>32767)||(sampleAllR<-32768)) PRINTF("PSG Satitta!");//test用
// if (sampleAllR> 32767) sampleAllR= 32767; //Volをアップしたのでサチレーションチェックが必要。v2.39
// if (sampleAllR<-32768) sampleAllR=-32768; //
*bufL++ = sampleAllL;
*bufR++ = sampleAllR;
//キューを参照しPSGレジスタを更新する。Kitao更新。高速化のためサブルーチンにせずここで処理。キューの参照はシンプルにした(テンポの安定性向上)。
/*while (_QueueReadIndex != _QueueWriteIndex) //v1.10更新。キュー処理をここへ統合し高速化。
{
write_reg(_Queue[_QueueReadIndex].reg, _Queue[_QueueReadIndex].data);
_QueueReadIndex++; //Kitao更新
_QueueReadIndex &= APUQUEUE_SIZE-1; //Kitao更新
}*/
}
}
//Kitao更新
static void
psg_reset(huc6280_state* info)
{
int i,j;
memset(info->Psg, 0, sizeof(info->Psg));
memset(info->DdaFadeOutL, 0, sizeof(info->DdaFadeOutL)); //Kitao追加
memset(info->DdaFadeOutR, 0, sizeof(info->DdaFadeOutR)); //Kitao追加
info->MainVolumeL = 0;
info->MainVolumeR = 0;
info->LfoFrq = 0;
info->LfoCtrl = 0;
info->Channel = 0; //Kitao追加。v2.65
info->bWaveCrash = FALSE; //Kitao追加
//Kitao更新。v0.65.waveデータを初期化。
for (i=0; i<N_CHANNEL; i++)
for (j=0; j<32; j++)
info->Psg[i].wave[j] = -14; //最小値で初期化。ファイプロ,フォーメーションサッカー'90,F1トリプルバトルで必要。
for (j=0; j<32; j++)
info->Psg[3].wave[j] = 17; //ch3は最大値で初期化。F1トリプルバトル。v2.65
//Kitao更新。v1.10。キュー処理をここに統合
// _QueueWriteIndex = 0;
// _QueueReadIndex = 0;
}
static void PSG_SetVolume(huc6280_state* info);
/*-----------------------------------------------------------------------------
[Init]
PSGを初期化します。
-----------------------------------------------------------------------------*/
//Sint32
void*
PSG_Init(
Sint32 clock,
Sint32 sampleRate)
{
huc6280_state* info;
info = (huc6280_state*)malloc(sizeof(huc6280_state));
if (info == NULL)
return NULL;
info->PSG_FRQ = clock & 0x7FFFFFFF;
PSG_SetHoneyInTheSky(info, (clock >> 31) & 0x01);
// PSG_SetHoneyInTheSky(0x01);
info->PsgVolumeEffect = 0;
info->Volume = 0;
info->VOL = 0.0;
//PSG_SetVolume(APP_GetPsgVolume());//Kitao追加
PSG_SetVolume(info);
psg_reset(info);
if (! _bTblInit)
{
create_volume_table();
create_noise_table();
_bTblInit = TRUE;
}
//PSG_SetSampleRate(sampleRate);
info->SAMPLE_RATE = sampleRate;
info->RESMPL_RATE = info->PSG_FRQ / OVERSAMPLE_RATE / info->SAMPLE_RATE;
// _bPsgInit = TRUE;
return info;
}
/*-----------------------------------------------------------------------------
[SetSampleRate]
-----------------------------------------------------------------------------*/
/*void
PSG_SetSampleRate(
Uint32 sampleRate)
{
//_SampleRate = sampleRate;
}*/
/*-----------------------------------------------------------------------------
[Deinit]
PSGを破棄します。
-----------------------------------------------------------------------------*/
void
PSG_Deinit(void* chip)
{
huc6280_state* info = (huc6280_state*)chip;
/*memset(info->Psg, 0, sizeof(_Psg));
memset(info->DdaFadeOutL, 0, sizeof(_DdaFadeOutL)); //Kitao追加
memset(info->DdaFadeOutR, 0, sizeof(_DdaFadeOutR)); //Kitao追加
info->MainVolumeL = 0;
info->MainVolumeR = 0;
info->LfoFrq = 0;
info->LfoCtrl = 0;
info->bWaveCrash = FALSE; //Kitao追加
// _bPsgInit = FALSE;*/
free(info);
}
/*-----------------------------------------------------------------------------
[Read]
PSGポートの読み出しに対する動作を記述します。
-----------------------------------------------------------------------------*/
Uint8
PSG_Read(
void* chip,
Uint32 regNum)
{
huc6280_state* info = (huc6280_state*)chip;
if (regNum == 0)
return (Uint8)info->Channel;
return info->Port[regNum & 15];
}
/*-----------------------------------------------------------------------------
[Write]
PSGポートの書き込みに対する動作を記述します。
-----------------------------------------------------------------------------*/
void
PSG_Write(
void* chip,
Uint32 regNum,
Uint8 data)
{
huc6280_state* info = (huc6280_state*)chip;
//v1.10更新。キュー処理をここに統合して高速化。
//Kitao更新。clockは考慮せずにシンプルにして高速化した。
/* if (((_QueueWriteIndex + 1) & (APUQUEUE_SIZE-1)) == _QueueReadIndex)
{
PRINTF("PSG Queue Over!"); // キューが満タン
return;
}
_Queue[_QueueWriteIndex].reg = (Uint8)(regNum & 15);
_Queue[_QueueWriteIndex].data = data;
_QueueWriteIndex++; //Kitao更新
_QueueWriteIndex &= APUQUEUE_SIZE-1; //Kitao更新
*/
write_reg(chip, regNum, data);
}
/*Sint32
PSG_AdvanceClock(
Sint32 clock)
{
return 0;
}*/
//Kitao追加。PSGのボリュームも個別に設定可能にした。
/*static void
PSG_SetVolume(
Uint32 volume) // 0 - 65535*/
static void PSG_SetVolume(huc6280_state* info)
{
/*if (volume < 0) volume = 0;
if (volume > 65535) volume = 65535;*/
//_Volume = (double)volume / 65535.0 / PSG_DECLINE;
info->Volume = 1.0 / PSG_DECLINE;
set_VOL(info);
}
//Kitao追加。ボリュームミュート、ハーフなどをできるようにした。
/*static void
PSG_SetVolumeEffect(
Uint32 volumeEffect)
{
_PsgVolumeEffect = (Sint32)volumeEffect; //※数値が大きいほどボリュームは小さくなる
set_VOL();
}*/
//Kitao追加
void
PSG_ResetVolumeReg(void* chip)
{
huc6280_state* info = (huc6280_state*)chip;
int i;
info->MainVolumeL = 0;
info->MainVolumeR = 0;
for (i = 0; i < N_CHANNEL; i++)
{
info->Psg[i].volume = 0;
info->Psg[i].outVolumeL = 0;
info->Psg[i].outVolumeR = 0;
info->DdaFadeOutL[i] = 0;
info->DdaFadeOutR[i] = 0;
}
}
//Kitao追加
void
PSG_SetMutePsgChannel(
void* chip,
Sint32 num,
BOOL bMute)
{
huc6280_state* info = (huc6280_state*)chip;
info->bPsgMute[num] = bMute;
if (bMute)
{
info->DdaFadeOutL[num] = 0;
info->DdaFadeOutR[num] = 0;
}
}
void PSG_SetMuteMask(void* chip, Uint32 MuteMask)
{
Uint8 CurChn;
for (CurChn = 0x00; CurChn < N_CHANNEL; CurChn ++)
PSG_SetMutePsgChannel(chip, CurChn, (MuteMask >> CurChn) & 0x01);
return;
}
//Kitao追加
BOOL
PSG_GetMutePsgChannel(
void* chip,
Sint32 num)
{
huc6280_state* info = (huc6280_state*)chip;
return info->bPsgMute[num];
}
//Kitao追加。v2.60
void
PSG_SetHoneyInTheSky(
void* chip,
BOOL bHoneyInTheSky)
{
huc6280_state* info = (huc6280_state*)chip;
info->bHoneyInTheSky = bHoneyInTheSky;
}
/*// save variable
#define SAVE_V(V) if (fwrite(&V, sizeof(V), 1, p) != 1) return FALSE
#define LOAD_V(V) if (fread(&V, sizeof(V), 1, p) != 1) return FALSE
// save array
#define SAVE_A(A) if (fwrite(A, sizeof(A), 1, p) != 1) return FALSE
#define LOAD_A(A) if (fread(A, sizeof(A), 1, p) != 1) return FALSE*/
/*-----------------------------------------------------------------------------
[SaveState]
状態をファイルに保存します。
-----------------------------------------------------------------------------*/
/*BOOL
PSG_SaveState(
FILE* p)
{
BOOL bFlashHiders = FALSE; //Kitao更新。現在非使用。旧バージョンのステートセーブとの互換のため
if (p == NULL)
return FALSE;
SAVE_A(_Psg);
SAVE_V(_Channel);
SAVE_V(_MainVolumeL);
SAVE_V(_MainVolumeR);
SAVE_V(_LfoFrq);
SAVE_V(_bLfoOn); //v1.59から非使用に。
SAVE_V(_LfoCtrl);
SAVE_V(_LfoShift); //v1.59から非使用に。
SAVE_V(_bWaveCrash); //Kitao追加。v0.65
SAVE_V(bFlashHiders); //Kitao追加。v0.62
//v1.10追加。キュー処理をここへ統合。
SAVE_A(_Queue); //v1.61からサイズが2倍になった。
SAVE_V(_QueueWriteIndex);
SAVE_V(_QueueReadIndex);
return TRUE;
}*/
/*-----------------------------------------------------------------------------
[LoadState]
状態をファイルから読み込みます。
-----------------------------------------------------------------------------*/
/*BOOL
PSG_LoadState(
FILE* p)
{
Uint32 i;
double clockCounter; //Kitao更新。現在非使用。旧バージョンのステートセーブとの互換のため
BOOL bInit; //Kitao更新。現在非使用。旧バージョンのステートセーブとの互換のため
Sint32 totalClockAdvanced; //Kitao更新。現在非使用。旧バージョンのステートセーブとの互換のため
BOOL bFlashHiders; //Kitao更新。現在非使用。旧バージョンのステートセーブとの互換のため
OldApuQueue oldQueue[65536]; //v1.60以前のステートを読み込み用。
if (p == NULL)
return FALSE;
LOAD_A(_Psg);
LOAD_V(_Channel);
LOAD_V(_MainVolumeL);
LOAD_V(_MainVolumeR);
LOAD_V(_LfoFrq);
LOAD_V(_bLfoOn); //v1.59から非使用に。
LOAD_V(_LfoCtrl);
if (MAINBOARD_GetStateVersion() >= 3) //Kitao追加。v0.57以降のセーブファイルなら
LOAD_V(_LfoShift); //v1.59から非使用に。
if (MAINBOARD_GetStateVersion() >= 9) //Kitao追加。v0.65以降のセーブファイルなら
{
LOAD_V(_bWaveCrash);
}
else