mirror of
https://github.com/ZDoom/ZMusic.git
synced 2024-12-03 17:12:08 +00:00
72c23d98a3
## 1.5.0 2020-09-28 * Drum note length expanding is now supported in real-time mode (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added support for OPNA chip with Neko Project II Kai YM2602 emulator usage (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added VGM file dumper which allows to output OPN2 commands into VGM file. (A new MIDI to VGM tool is now created with basing on libOPNMIDI) * Fixed an incorrect work of CC-121 (See https://github.com/Wohlstand/libADLMIDI/issues/227 for details) * Internality has been refactored and improved
523 lines
13 KiB
C++
523 lines
13 KiB
C++
// ---------------------------------------------------------------------------
|
||
// OPN/A/B interface with ADPCM support
|
||
// Copyright (C) cisc 1998, 2003.
|
||
// ---------------------------------------------------------------------------
|
||
// $Id: opna.h,v 1.33 2003/06/12 13:14:37 cisc Exp $
|
||
|
||
#ifndef FM_OPNA_H
|
||
#define FM_OPNA_H
|
||
|
||
#include "fmgen_fmgen.h"
|
||
#include "fmgen_fmtimer.h"
|
||
#include "fmgen_psg.h"
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// class OPN/OPNA
|
||
// OPN/OPNA に良く似た音を生成する音源ユニット
|
||
//
|
||
// interface:
|
||
// bool Init(uint clock, uint rate, bool, const char* path);
|
||
// 初期化.このクラスを使用する前にかならず呼んでおくこと.
|
||
// OPNA の場合はこの関数でリズムサンプルを読み込む
|
||
//
|
||
// clock: OPN/OPNA/OPNB のクロック周波数(Hz)
|
||
//
|
||
// rate: 生成する PCM の標本周波数(Hz)
|
||
//
|
||
// path: リズムサンプルのパス(OPNA のみ有効)
|
||
// 省略時はカレントディレクトリから読み込む
|
||
// 文字列の末尾には '¥' や '/' などをつけること
|
||
//
|
||
// 返り値 初期化に成功すれば true
|
||
//
|
||
// bool LoadRhythmSample(const char* path)
|
||
// (OPNA ONLY)
|
||
// Rhythm サンプルを読み直す.
|
||
// path は Init の path と同じ.
|
||
//
|
||
// bool SetRate(uint clock, uint rate, bool)
|
||
// クロックや PCM レートを変更する
|
||
// 引数等は Init を参照のこと.
|
||
//
|
||
// void Mix(FM_SAMPLETYPE* dest, int nsamples)
|
||
// Stereo PCM データを nsamples 分合成し, dest で始まる配列に
|
||
// 加える(加算する)
|
||
// ・dest には sample*2 個分の領域が必要
|
||
// ・格納形式は L, R, L, R... となる.
|
||
// ・あくまで加算なので,あらかじめ配列をゼロクリアする必要がある
|
||
// ・FM_SAMPLETYPE が short 型の場合クリッピングが行われる.
|
||
// ・この関数は音源内部のタイマーとは独立している.
|
||
// Timer は Count と GetNextEvent で操作する必要がある.
|
||
//
|
||
// void Reset()
|
||
// 音源をリセット(初期化)する
|
||
//
|
||
// void SetReg(uint reg, uint data)
|
||
// 音源のレジスタ reg に data を書き込む
|
||
//
|
||
// uint GetReg(uint reg)
|
||
// 音源のレジスタ reg の内容を読み出す
|
||
// 読み込むことが出来るレジスタは PSG, ADPCM の一部,ID(0xff) とか
|
||
//
|
||
// uint ReadStatus()/ReadStatusEx()
|
||
// 音源のステータスレジスタを読み出す
|
||
// ReadStatusEx は拡張ステータスレジスタの読み出し(OPNA)
|
||
// busy フラグは常に 0
|
||
//
|
||
// bool Count(uint32 t)
|
||
// 音源のタイマーを t [μ秒] 進める.
|
||
// 音源の内部状態に変化があった時(timer オーバーフロー)
|
||
// true を返す
|
||
//
|
||
// uint32 GetNextEvent()
|
||
// 音源のタイマーのどちらかがオーバーフローするまでに必要な
|
||
// 時間[μ秒]を返す
|
||
// タイマーが停止している場合は ULONG_MAX を返す… と思う
|
||
//
|
||
// void SetVolumeFM(int db)/SetVolumePSG(int db) ...
|
||
// 各音源の音量を+−方向に調節する.標準値は 0.
|
||
// 単位は約 1/2 dB,有効範囲の上限は 20 (10dB)
|
||
//
|
||
namespace FM
|
||
{
|
||
// OPN Base -------------------------------------------------------
|
||
struct OPNBaseData {
|
||
struct TimerData timer;
|
||
int fmvolume;
|
||
uint clock;
|
||
uint rate;
|
||
uint psgrate;
|
||
uint status;
|
||
uint8 prescale;
|
||
struct ChipData chip;
|
||
struct PSGData psg;
|
||
};
|
||
|
||
class OPNBase : public Timer
|
||
{
|
||
public:
|
||
OPNBase();
|
||
|
||
bool Init(uint c, uint r);
|
||
virtual void Reset();
|
||
|
||
void SetVolumeFM(int db);
|
||
void SetVolumePSG(int db);
|
||
|
||
void DataSave(struct OPNBaseData* data);
|
||
void DataLoad(struct OPNBaseData* data);
|
||
|
||
protected:
|
||
void SetParameter(Channel4* ch, uint addr, uint data);
|
||
void SetPrescaler(uint p);
|
||
void RebuildTimeTable();
|
||
|
||
int fmvolume;
|
||
|
||
uint clock; // OPN クロック
|
||
uint rate; // FM 音源合成レート
|
||
uint psgrate; // FMGen 出力レート
|
||
uint status;
|
||
Channel4* csmch;
|
||
|
||
|
||
static uint32 lfotable[8];
|
||
|
||
private:
|
||
void TimerA();
|
||
uint8 prescale;
|
||
|
||
protected:
|
||
Chip chip;
|
||
PSG psg;
|
||
};
|
||
|
||
// OPN2 Base ------------------------------------------------------
|
||
struct OPNABaseData {
|
||
struct OPNBaseData opnbase;
|
||
uint8 pan[6];
|
||
uint16 panvolume_l[6]; // libOPNMIDI: soft panning
|
||
uint16 panvolume_r[6]; // libOPNMIDI: soft panning
|
||
uint8 fnum2[9];
|
||
uint8 reg22;
|
||
uint reg29;
|
||
uint stmask;
|
||
uint statusnext;
|
||
uint32 lfocount;
|
||
uint32 lfodcount;
|
||
uint fnum[6];
|
||
uint fnum3[3];
|
||
bool is_adpcmbuf;
|
||
uint8 adpcmbuf[0x40000];
|
||
uint adpcmmask;
|
||
uint adpcmnotice;
|
||
uint startaddr;
|
||
uint stopaddr;
|
||
uint memaddr;
|
||
uint limitaddr;
|
||
int adpcmlevel;
|
||
int adpcmvolume;
|
||
int adpcmvol;
|
||
uint deltan;
|
||
int adplc;
|
||
int adpld;
|
||
uint adplbase;
|
||
int adpcmx;
|
||
int adpcmd;
|
||
int adpcmout;
|
||
int apout0;
|
||
int apout1;
|
||
uint adpcmreadbuf;
|
||
bool adpcmplay;
|
||
int8 granuality;
|
||
bool adpcmmask_;
|
||
uint8 control1;
|
||
uint8 control2;
|
||
uint8 adpcmreg[8];
|
||
int rhythmmask_;
|
||
struct Channel4Data ch[6];
|
||
};
|
||
|
||
class OPNABase : public OPNBase
|
||
{
|
||
public:
|
||
OPNABase();
|
||
~OPNABase();
|
||
|
||
uint ReadStatus() { return status & 0x03; }
|
||
uint ReadStatusEx();
|
||
void SetChannelMask(uint mask);
|
||
|
||
// libOPNMIDI: soft panning
|
||
void SetPan(uint c, uint8 p);
|
||
|
||
void DataSave(struct OPNABaseData* data);
|
||
void DataLoad(struct OPNABaseData* data);
|
||
|
||
private:
|
||
virtual void Intr(bool) {}
|
||
|
||
void MakeTable2();
|
||
|
||
protected:
|
||
bool Init(uint c, uint r, bool);
|
||
bool SetRate(uint c, uint r, bool);
|
||
|
||
void Reset();
|
||
void SetReg(uint addr, uint data);
|
||
void SetADPCMBReg(uint reg, uint data);
|
||
uint GetReg(uint addr);
|
||
|
||
protected:
|
||
void FMMix(Sample* buffer, int nsamples);
|
||
void Mix6(Sample* buffer, int nsamples, int activech);
|
||
|
||
void MixSubS(int activech, ISample**);
|
||
void MixSubSL(int activech, ISample**);
|
||
|
||
void SetStatus(uint bit);
|
||
void ResetStatus(uint bit);
|
||
void UpdateStatus();
|
||
void LFO();
|
||
|
||
void DecodeADPCMB();
|
||
void ADPCMBMix(Sample* dest, uint count);
|
||
|
||
void WriteRAM(uint data);
|
||
uint ReadRAM();
|
||
int ReadRAMN();
|
||
int DecodeADPCMBSample(uint);
|
||
|
||
// FM 音源関係
|
||
uint8 pan[6];
|
||
uint16 panvolume_l[6]; // libOPNMIDI: soft panning
|
||
uint16 panvolume_r[6]; // libOPNMIDI: soft panning
|
||
uint8 fnum2[9];
|
||
|
||
uint8 reg22;
|
||
uint reg29; // OPNA only?
|
||
|
||
uint stmask;
|
||
uint statusnext;
|
||
|
||
uint32 lfocount;
|
||
uint32 lfodcount;
|
||
|
||
uint fnum[6];
|
||
uint fnum3[3];
|
||
|
||
// ADPCM 関係
|
||
uint8* adpcmbuf; // ADPCM RAM
|
||
uint adpcmmask; // メモリアドレスに対するビットマスク
|
||
uint adpcmnotice; // ADPCM 再生終了時にたつビット
|
||
uint startaddr; // Start address
|
||
uint stopaddr; // Stop address
|
||
uint memaddr; // 再生中アドレス
|
||
uint limitaddr; // Limit address/mask
|
||
int adpcmlevel; // ADPCM 音量
|
||
int adpcmvolume;
|
||
int adpcmvol;
|
||
uint deltan; // N
|
||
int adplc; // 周波数変換用変数
|
||
int adpld; // 周波数変換用変数差分値
|
||
uint adplbase; // adpld の元
|
||
int adpcmx; // ADPCM 合成用 x
|
||
int adpcmd; // ADPCM 合成用
|
||
int adpcmout; // ADPCM 合成後の出力
|
||
int apout0; // out(t-2)+out(t-1)
|
||
int apout1; // out(t-1)+out(t)
|
||
|
||
uint adpcmreadbuf; // ADPCM リード用バッファ
|
||
bool adpcmplay; // ADPCM 再生中
|
||
int8 granuality;
|
||
bool adpcmmask_;
|
||
|
||
uint8 control1; // ADPCM コントロールレジスタ1
|
||
uint8 control2; // ADPCM コントロールレジスタ2
|
||
uint8 adpcmreg[8]; // ADPCM レジスタの一部分
|
||
|
||
int rhythmmask_;
|
||
|
||
Channel4 ch[6];
|
||
|
||
static void BuildLFOTable();
|
||
static int amtable[FM_LFOENTS];
|
||
static int pmtable[FM_LFOENTS];
|
||
static int32 tltable[FM_TLENTS+FM_TLPOS];
|
||
static bool tablehasmade;
|
||
};
|
||
|
||
// YM2203(OPN) ----------------------------------------------------
|
||
struct OPNData {
|
||
struct OPNBaseData opnbase;
|
||
uint fnum[3];
|
||
uint fnum3[3];
|
||
uint8 fnum2[6];
|
||
struct Channel4Data ch[3];
|
||
};
|
||
|
||
class OPN : public OPNBase
|
||
{
|
||
public:
|
||
OPN();
|
||
virtual ~OPN() {}
|
||
|
||
bool Init(uint c, uint r, bool=false, const char* =0);
|
||
bool SetRate(uint c, uint r, bool=false);
|
||
|
||
void Reset();
|
||
void Mix(Sample* buffer, int nsamples);
|
||
void SetReg(uint addr, uint data);
|
||
uint GetReg(uint addr);
|
||
uint ReadStatus() { return status & 0x03; }
|
||
uint ReadStatusEx() { return 0xff; }
|
||
|
||
void SetChannelMask(uint mask);
|
||
|
||
int dbgGetOpOut(int c, int s) { return ch[c].op[s].dbgopout_; }
|
||
int dbgGetPGOut(int c, int s) { return ch[c].op[s].dbgpgout_; }
|
||
Channel4* dbgGetCh(int c) { return &ch[c]; }
|
||
|
||
void DataSave(struct OPNData* data);
|
||
void DataLoad(struct OPNData* data);
|
||
|
||
private:
|
||
virtual void Intr(bool) {}
|
||
|
||
void SetStatus(uint bit);
|
||
void ResetStatus(uint bit);
|
||
|
||
uint fnum[3];
|
||
uint fnum3[3];
|
||
uint8 fnum2[6];
|
||
|
||
Channel4 ch[3];
|
||
};
|
||
|
||
// YM2608(OPNA) ---------------------------------------------------
|
||
struct Rhythm
|
||
{
|
||
uint8 pan; // ぱん
|
||
int8 level; // おんりょう
|
||
int volume; // おんりょうせってい
|
||
int16* sample; // さんぷる
|
||
uint size; // さいず
|
||
uint pos; // いち
|
||
uint step; // すてっぷち
|
||
uint rate; // さんぷるのれーと
|
||
};
|
||
|
||
struct OPNAData {
|
||
struct OPNABaseData opnabase;
|
||
Rhythm rhythm[6];
|
||
int8 rhythmtl;
|
||
int rhythmtvol;
|
||
uint8 rhythmkey;
|
||
};
|
||
|
||
class OPNA : public OPNABase
|
||
{
|
||
public:
|
||
OPNA();
|
||
virtual ~OPNA();
|
||
|
||
bool Init(uint c, uint r, bool = false, const char* rhythmpath=0);
|
||
bool LoadRhythmSample(const char*);
|
||
|
||
bool SetRate(uint c, uint r, bool = false);
|
||
void Mix(Sample* buffer, int nsamples);
|
||
|
||
void Reset();
|
||
void SetReg(uint addr, uint data);
|
||
uint GetReg(uint addr);
|
||
|
||
void SetVolumeADPCM(int db);
|
||
void SetVolumeRhythmTotal(int db);
|
||
void SetVolumeRhythm(int index, int db);
|
||
|
||
uint8* GetADPCMBuffer() { return adpcmbuf; }
|
||
|
||
int dbgGetOpOut(int c, int s) { return ch[c].op[s].dbgopout_; }
|
||
int dbgGetPGOut(int c, int s) { return ch[c].op[s].dbgpgout_; }
|
||
Channel4* dbgGetCh(int c) { return &ch[c]; }
|
||
|
||
void DataSave(struct OPNAData* data);
|
||
void DataLoad(struct OPNAData* data);
|
||
|
||
private:
|
||
void RhythmMix(Sample* buffer, uint count);
|
||
|
||
// リズム音源関係
|
||
Rhythm rhythm[6];
|
||
int8 rhythmtl; // リズム全体の音量
|
||
int rhythmtvol;
|
||
uint8 rhythmkey; // リズムのキー
|
||
};
|
||
|
||
// YM2610/B(OPNB) ---------------------------------------------------
|
||
struct ADPCMA
|
||
{
|
||
uint8 pan; // ぱん
|
||
int8 level; // おんりょう
|
||
int volume; // おんりょうせってい
|
||
uint pos; // いち
|
||
uint step; // すてっぷち
|
||
|
||
uint start; // 開始
|
||
uint stop; // 終了
|
||
uint nibble; // 次の 4 bit
|
||
int adpcmx; // 変換用
|
||
int adpcmd; // 変換用
|
||
};
|
||
|
||
struct OPNBData {
|
||
struct OPNABaseData opnabase;
|
||
// uint8* adpcmabuf;
|
||
int adpcmasize;
|
||
ADPCMA adpcma[6];
|
||
int8 adpcmatl;
|
||
int adpcmatvol;
|
||
uint8 adpcmakey;
|
||
int adpcmastep;
|
||
uint8 adpcmareg[32];
|
||
struct Channel4Data ch[6];
|
||
};
|
||
|
||
class OPNB : public OPNABase
|
||
{
|
||
public:
|
||
OPNB();
|
||
virtual ~OPNB();
|
||
|
||
bool Init(uint c, uint r, bool = false,
|
||
uint8 *_adpcma = 0, int _adpcma_size = 0,
|
||
uint8 *_adpcmb = 0, int _adpcmb_size = 0);
|
||
|
||
bool SetRate(uint c, uint r, bool = false);
|
||
void Mix(Sample* buffer, int nsamples);
|
||
|
||
void Reset();
|
||
void SetReg(uint addr, uint data);
|
||
uint GetReg(uint addr);
|
||
uint ReadStatusEx();
|
||
|
||
void SetVolumeADPCMATotal(int db);
|
||
void SetVolumeADPCMA(int index, int db);
|
||
void SetVolumeADPCMB(int db);
|
||
|
||
// void SetChannelMask(uint mask);
|
||
|
||
void DataSave(struct OPNBData* data, void* adpcmdata);
|
||
void DataLoad(struct OPNBData* data, void* adpcmdata);
|
||
|
||
private:
|
||
int DecodeADPCMASample(uint);
|
||
void ADPCMAMix(Sample* buffer, uint count);
|
||
static void InitADPCMATable();
|
||
|
||
// ADPCMA 関係
|
||
uint8* adpcmabuf; // ADPCMA ROM
|
||
int adpcmasize;
|
||
ADPCMA adpcma[6];
|
||
int8 adpcmatl; // ADPCMA 全体の音量
|
||
int adpcmatvol;
|
||
uint8 adpcmakey; // ADPCMA のキー
|
||
int adpcmastep;
|
||
uint8 adpcmareg[32];
|
||
|
||
static int jedi_table[(48+1)*16];
|
||
|
||
Channel4 ch[6];
|
||
};
|
||
|
||
// YM2612/3438(OPN2) ----------------------------------------------------
|
||
class OPN2 : public OPNBase
|
||
{
|
||
public:
|
||
OPN2();
|
||
virtual ~OPN2() {}
|
||
|
||
bool Init(uint c, uint r, bool=false, const char* =0);
|
||
bool SetRate(uint c, uint r, bool);
|
||
|
||
void Reset();
|
||
void Mix(Sample* buffer, int nsamples);
|
||
void SetReg(uint addr, uint data);
|
||
uint GetReg(uint addr);
|
||
uint ReadStatus() { return status & 0x03; }
|
||
uint ReadStatusEx() { return 0xff; }
|
||
|
||
void SetChannelMask(uint mask);
|
||
|
||
private:
|
||
virtual void Intr(bool) {}
|
||
|
||
void SetStatus(uint bit);
|
||
void ResetStatus(uint bit);
|
||
|
||
uint fnum[3];
|
||
uint fnum3[3];
|
||
uint8 fnum2[6];
|
||
|
||
// 線形補間用ワーク
|
||
int32 mixc, mixc1;
|
||
|
||
Channel4 ch[3];
|
||
};
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
|
||
inline void FM::OPNBase::RebuildTimeTable()
|
||
{
|
||
int p = prescale;
|
||
prescale = -1;
|
||
SetPrescaler(p);
|
||
}
|
||
|
||
inline void FM::OPNBase::SetVolumePSG(int db)
|
||
{
|
||
psg.SetVolume(db);
|
||
}
|
||
|
||
#endif // FM_OPNA_H
|