はじめに
ゲームボーイアドバンス(以下「GBA」)のシステムROM(「BIOS」とも呼ばれる)には、ソフトウェア開発者を補助する様々な機能が内蔵されていて、サウンドに関する機能もいくつか内蔵されています。
実際のところは不具合があるらしくゲームソフト製品ではほぼ使われていないようです。
ですが、広く使用されているサウンドドライバーのMusicPlayer2000(以下「MP2k」)とは似た部分が多少なりとも存在し、ワーク領域の基本構造もかなり近いため、改良された機能群がSDK等で開発者に配布されたものと考えられます。
不具合があるらしいのですが、個人的に調査していてそこまで問題になるような挙動にはまだ遭遇していないので、Homebrew用途であればある程度は使い道があるかと思われます。
注意事項
- GBATEKやGBA関連の他資料とは異なる名称を当てている部分があります。適宜読み替えてください
- また、GBATEKのSoundDriverInitの引数の説明の構造体には誤りがあるため併せてお読みになる際にはご注意ください
(なお、libgbaのgba_sound.hの各構造体は正しいメモリ構造をしています) - 今回、直接関係しない部分の構造体内の定義などはreservedといった名前の8ビット整数型の変数や配列などとして省略しています
(特に顕著な省略をしている部分に関してはその度にお知らせします)
準備
割り込みの設定
システムROMのサウンド機能(以下「システムサウンド」)は毎フレーム稼働させる設計をしているため、まずはVBlank割り込みを設定します。
#ifndef DEF_INTERRUPT_ARM_H
#define DEF_INTERRUPT_ARM_H
/**
* 割り込みを設定する
*/
void Interrupt_Initialize(void);
#endif
#include <gba.h>
#include "SystemSound/SystemSound.h"
IWRAM_CODE void IrqHandler(void);
/**
* 割り込みを設定する
*/
void Interrupt_Initialize(void)
{
REG_IME = 0;
// 割り込みハンドラを設定する
INT_VECTOR = IrqHandler;
REG_IME = 1;
}
/**
* 割り込みハンドラ
*/
IWRAM_CODE void IrqHandler(void)
{
REG_IME = 0;
u16 flag = REG_IF;
if(flag & IRQ_VBLANK)
{
// 必ずVBlank割り込みの冒頭で実行する
SoundDriverVSync;
// VBlankIntrWaitの待機を解除する
*(vu16*)0x3007ff8 |= IRQ_VBLANK;
}
REG_IF = flag;
REG_IME = 1;
}
GBAプログラミングではよくある割り込みの有効化とVBlank割り込みの処理ですね。VBlankの割り込み設定はシステムサウンドの初期化の時にします。
VBlank割り込み中の冒頭に記述されているSoundDriverVSyncの定義は、
/**
* VBlank中の冒頭に実行すべきシステムサウンドの処理
*/
#define SoundDriverVSync SystemCall(0x1d)
となっています。引数および戻り値はありません。
このシステム関数は時間経過で必要になるDMAの設定などを行っているようです。
システムサウンドの準備
次にシステムサウンドを初期化します。
ですが、その前に今回使用するさまざまな構造体や列挙体を定義しておきます。
ほぼすべての使い方において必要なもの
システムサウンド制御データ
システムサウンドのワーク領域やチャンネルデータ、サウンドバッファ等をひとまとめにした構造です。
いくつかのコールバックを指定することが可能です。今回はその中でも特に利用価値のありそうなものも載せています。
ここで提示されているフィールドは今回の説明で使用される最小限のもののみの定義となっています。
#ifndef DEF_SYSTEMSOUND_SYSTEMSOUNDAREA_H
#define DEF_SYSTEMSOUND_SYSTEMSOUNDAREA_H
#include <gba.h>
#include "SystemSoundPcmChannel.h"
#include "SystemSoundPlayerArea.h"
/**
* システムサウンドのバッファサイズ
*/
#define SystemSound_BufferSize (1584)
/**
* システムサウンドの制御データ
*/
typedef struct
{
u8 reserved_00[5];
u8 reverb; /** リバーブ */
u8 channelCount; /** チャンネル数 */
u8 masterVolume; /** マスター音量 */
u8 reserved_08[28];
SystemSoundPlayerArea* player; /** シーケンスプレイヤーの制御データ */
u8 reserved_28[40];
SystemSoundPcmChannel pcmChannels[SystemSound_MaxPcmChannel]; /** チャンネルデータ */
s8 bufferA[SystemSound_BufferSize]; /** 右側サウンド用バッファ */
s8 bufferB[SystemSound_BufferSize]; /** 左側サウンド用バッファ */
} ALIGN(4) SystemSoundArea;
#endif
仮想PCMチャンネルデータ
PCM音源用の仮想サウンドチャンネル1本ぶんを表現する構造です。
#ifndef DEF_SYSTEMSOUND_SYSTEMSOUNDPCMCHANNEL_H
#define DEF_SYSTEMSOUND_SYSTEMSOUNDPCMCHANNEL_H
#include <gba.h>
#include "SystemSoundWave.h"
/**
* システムサウンドのPCM音源の最大チャンネル数
*/
#define SystemSound_MaxPcmChannel (12)
/**
* システムサウンドの仮想チャンネルデータ
*/
typedef struct
{
u8 playingStatus; /** 再生状態(キーオン: 80hを書き込む、キーオフ: 40hをOR) */
u8 reserved_01;
u8 volumeRight; /** 右側のボリューム(0~255) */
u8 volumeLeft; /** 左側のボリューム(0~255) */
u8 attackRate; /** アタックレート(エンベロープ値が1/60秒ごとにこの値ぶん増加する 255に達するとディケイ状態へ) */
u8 decayRate; /** ディケイレート(1/60秒ごとにエンベロープ値にこの値の1/256を乗じる サスティンレベルに達するとサスティン状態へ) */
u8 sustainLevel; /** サスティンレベル(サスティン状態でのエンベロープ値) */
u8 releaseRate; /** リリースレート(1/60秒ごとにエンベロープ値にこの値の1/256を乗じる リリース終了レベル以下になるとリリース終了後状態へ) */
u8 reserved_08[4];
u8 postReleaseLevel; /** リリース終了レベル(リリース終了後状態でのエンベロープ値、0の場合はリリース終了後状態に遷移せずに発音終了) */
u8 postReleaseFrame; /** リリース終了後状態の残りフレーム数(リリース終了後状態に入った場合、0でも一瞬だけリリース終了後状態になる) */
vu8 reserved_0e[18];
u32 frequency; /** 周波数(MidiKey2Freqで求めた値を代入する) */
SystemSoundWave* wave; /** 波形データへのポインタ */
u8 reserved_28[24];
} ALIGN(4) SystemSoundPcmChannel;
#endif
音声データ
システムサウンドの仮想チャンネル用の音源データです。
#ifndef DEF_SYSTEMSOUND_SYETEMSOUNDWAVE_H
#define DEF_SYSTEMSOUND_SYSTEMSOUNDWAVE_H
#include <gba.h>
/**
* システムサウンドの音声データ
*/
typedef struct
{
u16 type;
u16 isLoop; /** フラグ(上位2ビットのいずれかがON=ループあり) */
u32 frequency; /** 周波数([周波数] * 2 ^ (180 - [MIDIキー] / 12)) */
u32 loopStartSample; /** ループ開始サンプル */
u32 length; /** サンプル数 */
s8 data[1]; /** 波形データ */
} ALIGN(4) SystemSoundWave;
#endif
一部の使い方において必要になるもの
シーケンスプレイヤーの制御データ
システムサウンドの標準形式のシーケンスを再生するプレイヤーの制御用データです。
単方向の連結リストのように領域を繋げて、同時に再生できるシーケンスプレイヤーの数を増やせます。
ここで提示されているフィールドは今回の説明で使用される最小限のもののみの定義となっています。
#ifndef DEF_SYSTEMSOUND_SYSTEMSOUNDPLAYERAREA_H
#define DEF_SYSTEMSOUND_SYSTEMSOUNDPLAYERAREA_H
#include <gba.h>
/**
* システムサウンドのシーケンスプレイヤー制御データ
*/
typedef ALIGN(4) struct SysSndPlArea
{
u8 reserved[60];
struct SysSndPlArea* next; /** 次のシーケンスプレイヤー */
} ALIGN(4) SystemSoundPlayerArea;
#endif
シーケンスプレイヤーのトラックデータ
システムサウンドの標準形式のシーケンスを再生するプレイヤーのトラックのデータです。
プレイヤーごとにトラックデータの配列を用意する必要があります。
今回はこの構造のデータにアクセスしないため、データ構造のサイズのみ提示する形となっています。
#ifndef DEF_SYSTEMSOUND_SYSTEMSOUNDPLAYERTRACK_H
#define DEF_SYSTEMSOUND_SYSTEMSOUNDPLAYERTRACK_H
#include <gba.h>
/**
* システムサウンドのシーケンスプレイヤーのトラックデータ
*/
typedef struct
{
u8 reserved[80];
} ALIGN(4) SystemSoundPlayerTrack;
#endif
初期化処理
システムサウンドの初期化もシステムROMの機能で手軽に行えます。
システムサウンド全体の初期化はSoundDriverInit(SWI 0x1a)で行い、シーケンスプレイヤーの初期化はSoundWhatever0(SWI 0x20、以下「SoundDriverPlayerInit」)で1件ずつ行います。
シグネチャは以下の通りです
-
SoundDriverInit- 引数:1個
- r0:システムサウンドの制御データへのポインタ
- 戻り値:なし
- 引数:1個
-
SoundDriverPlayerInit(SoundWhatever0)- 引数:3個
- r0:シーケンスプレイヤーの制御データへのポインタ
- r1:r0に紐付けるトラックデータ配列の先頭のポインタ
- r2:r1で指定したトラックデータ配列の要素数(1 - 16)
- 戻り値:なし
- 引数:3個
この関数を実行する前に、SoundDriverInitを実行する必要があります。
この関数を実行すると、
- シーケンスプレイヤー制御データとトラックデータが初期化される
- システムサウンドの制御データに設定されているシーケンスプレイヤーへの参照を、現在初期化中のシーケンスプレイヤー制御データの次のプレイヤーの参照に設定する
- 現在初期化中のシーケンスプレイヤーの参照をシステムサウンドの制御データに設定する
という処理が実行されます。
SoundDriverPlayerInitでの初期化順がそのままシーケンスプレイヤーの処理順になります。
また、GBAプログラミングにおいて、引数や戻り値があるシステムROMの機能については実際の呼び出しはアセンブラで記述した方が何かと楽です。アセンブラ側は基本的にはswi 〇〇とbx lrの2命令が定型になります。
.text
.section .iwram,"ax",%progbits
.thumb
.align 2
@---------------------------------------------------------------------------------
.global SoundDriverInit
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverInit:
@---------------------------------------------------------------------------------
swi 0x1a
bx lr
@---------------------------------------------------------------------------------
.global SoundDriverPlayerInit
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverPlayerInit:
@---------------------------------------------------------------------------------
swi 0x20
bx lr
アセンブラで記述した処理をC側から呼ぶことは簡単で、他のコンパイル単位にある関数を使用するのと同様にプロトタイプ宣言をすれば使用可能になります。
/**
* システムサウンドを初期化する
* @param control システムサウンド制御データのポインタ
*/
IWRAM_CODE void SoundDriverInit(SystemSoundArea* controlData);
/**
* システムサウンドのシーケンスプレイヤーのバッファを初期化する
* @param player シーケンスプレイヤーの制御データ
* @param tracks トラックデータ配列の先頭
* @param trackCount トラック数
*/
IWRAM_CODE void SoundDriverPlayerInit(SystemSoundPlayerArea* player, SystemSoundPlayerTrack* tracks, u32 trackCount);
システムサウンドの初期化処理
SysSnd_PlayerCountやSysSnd_PlayerTrackCount等の値は必要に応じて変更してください。
また、soundControlについているALIGN(256)は除去しても問題ありません(メモリビューアで確認しやすくすべく、256バイト境界で配置されるようにするための記述)。型の定義時点で動作に問題のない配置がされるように指定してあります。
/**
* サウンドプレイヤーの個数
*/
#define SysSnd_PlayerCount 5
/**
* シーケンスプレイヤーの最大トラック数
*/
#define SysSnd_PlayerTrackCount 6
/**
* シーケンスプレイヤーを配置しやすくするための構造体
*/
typedef struct
{
SystemSoundPlayerArea control; /** プレイヤー制御データ */
SystemSoundPlayerTrack tracks[SysSnd_PlayerTrackCount]; /** トラックデータの配列 */
} ALIGN(16) SysSndPlayerDataWrap;
/**
* システムサウンドのシーケンスプレイヤーデータ
*/
SysSndPlayerDataWrap sysSndPlayers[SysSnd_PlayerCount];
/**
* システムサウンド制御データ
*/
ALIGN(256) SystemSoundArea soundControl;
/**
* システムサウンドを初期化する
*/
void SystemSound_Initialize(void)
{
// VBlank割り込みをいったん解除する
REG_DISPSTAT &= ~LCDC_VBL;
REG_IE &= ~IRQ_VBLANK;
// システムサウンドを初期化する
SoundDriverInit(&soundControl);
// シーケンスプレイヤーを初期化する
SysSndPlayerDataWrap* currentPlayer = &sysSndPlayers[SysSnd_PlayerCount - 1];
for (int i = SysSnd_PlayerCount - 1; i >= 0; --i, --currentPlayer)
{
// 各領域を初期化する
SoundDriverPlayerInit(¤tPlayer->control, currentPlayer->tracks, SysSnd_PlayerTrackCount);
}
// VBlank割り込みを設定する
REG_DISPSTAT |= LCDC_VBL;
REG_IE |= IRQ_VBLANK;
}
これだけで最低限の準備が完了します。
メインループ内の最低限の処理
サウンド処理
システムサウンドを使用する場合、シーケンスプレイヤーの再生処理、仮想チャンネルの波形送り処理、波形合成処理等のサウンドの処理を毎フレーム処理を行うことになります。
この毎フレームの処理を行わせるためのシステムROMの機能があります。
/**
* システムサウンドのメイン処理
*/
#define SoundDriverMain SystemCall(0x1c)
SoundDriverMainには引数および戻り値はありません。
システムサウンドを動かすうえで必要最低限のメインループ
まずは、VBlank(スキャンライン方式で160行目を処理し終わってから、次のフレームの1行目の処理を始めるまでの期間)に突入するのを待ちます。
これはシステムサウンドの使用の有無にかかわらず用いられる一般的な処理であるかと思われます。
システムサウンドの処理のタイミングがフレーム毎なので、システムサウンドのみを使う場合でもVBlankを待機させます。
次に、ある場合は(ない場合の方が少ないかと思われますが)、フレーム毎の処理を実行します。
最後に、システムサウンドのフレーム毎処理SoundDriverMainを実行します。
サウンドの処理は画面を書き換えないので、VBlankを終わった後も実行時間的には猶予があるため、各種フレーム処理が終わった後に実行するのがセオリーになるかと思われます。
// メインループ
for(;;)
{
// VBlank突入まで待機する
VBlankIntrWait();
// ここにゲーム等のフレーム内処理を記述する
// システムサウンドドライバーを作動させる
SoundDriverMain;
}
シーケンスを鳴らす
今回はシステムサウンドの初期化時にシーケンスプレイヤーも準備しましたので、シーケンスを再生することができます。
- シーケンスをプレイヤーで再生させるには
SoundWhatever1(SWI 0x21、以下「SoundDriverPlaySequence」) - シーケンスプレイヤーを一時停止させるには
SoundWhatever2(SWI 0x22、以下「SoundDriverPauseSequence」) - 一時停止したシーケンスプレイヤーを再開させるには
SoundWhatever3(SWI 0x23、以下「SoundDriverResumeSequence」) - シーケンスプレイヤーをフェードアウトさせて停止させるには
SoundWhatever4(SWI 0x24、以下「SoundDriverStopWithFadeOut」)
を呼び出します。
シグネチャ
-
SoundDriverPlaySequence(SoundWhatever1)- 引数:2個
- r0:シーケンスを再生させたいプレイヤーの制御データへのポインタ
- r1:シーケンスデータへのポインタ
- 戻り値:なし
- 引数:2個
-
SoundDriverPauseSequence(SoundWhatever2)- 引数:1個
- r0:一時停止するシーケンスプレイヤーの制御データへのポインタ
- 戻り値:なし
- 引数:1個
-
SoundDriverResumeSequence(SoundWhatever3)- 引数:1個
- r0:再開するシーケンスプレイヤーの制御データへのポインタ
- 戻り値:なし
- 引数:1個
-
SoundDriverStopWithFadeOut(SoundWhatever4)- 引数:2個
- r0:一時停止するシーケンスプレイヤーの制御データへのポインタ
- r1:フェードアウトにかかるフレーム数の16分の1
- 戻り値:なし
- 引数:2個
実装(アセンブラ)
@---------------------------------------------------------------------------------
.global SysSnd_SoundDriverPlaySequencePlaySequence
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverPlaySequence:
@---------------------------------------------------------------------------------
swi 0x21
bx lr
@---------------------------------------------------------------------------------
.global SoundDriverPauseSequence
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverPauseSequence:
@---------------------------------------------------------------------------------
swi 0x22
bx lr
@---------------------------------------------------------------------------------
.global SoundDriverResumeSequence
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverResumeSequence:
@---------------------------------------------------------------------------------
swi 0x23
bx lr
@---------------------------------------------------------------------------------
.global SoundDriverStopWithFadeOut
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverStopWithFadeOut:
@---------------------------------------------------------------------------------
swi 0x24
bx lr
C側で利用可能にするためのプロトタイプ宣言
/**
* システムサウンドでシーケンスを再生する
* @param player 対象のシーケンスプレイヤー
* @param sequence シーケンスデータ
*/
IWRAM_CODE void SoundDriverPlaySequence(SystemSoundPlayerArea* player, void* sequence);
/**
* システムサウンドのシーケンスプレイヤーを一時停止する
* @param player 対象のシーケンスプレイヤー
*/
IWRAM_CODE void SoundDriverPauseSequence(SystemSoundPlayerArea* player);
/**
* システムサウンドの一時停止されたシーケンスプレイヤーを再開する
* @param player 対象のシーケンスプレイヤー
*/
IWRAM_CODE void SoundDriverResumeSequence(SystemSoundPlayerArea* player);
/**
* システムサウンドのシーケンスをフェードアウトして停止させる
* @param player シーケンスプレイヤー
* @param fadeOutStepFrame 1段階にかかるフレーム数(16段階進行でフェードアウトが完了する)
*/
IWRAM_CODE void SoundDriverStopWithFadeOut(SystemSoundPlayerArea* player, u16 fadeOutStepFrame);
余談
シーケンスを再生するSoundDriverPlaySequence(SoundWhatever1)はGBAの起動シーケンスで(SWI命令経由ではなく直接的に)呼び出されていますが、SoundDriverPauseSequence(SoundWhatever2)とSoundDriverResumeSequence(SoundWhatever3)、およびSoundDriverStopWithFadeOut(SoundWhatever4)は起動シーケンスでも使用されていません。
シーケンスデータについて
システムサウンド用のシーケンスデータの仕様は、ヘッダーや楽器、楽譜データ等がMP2kとほぼ同じであるため、詳しい仕様はMP2k関連の各種資料をご覧ください。
またシーケンスデータの仕様上、絶対アドレスでの指定が多く存在するため、GBFS等のリンク後にリソースを連結するような方式とは相性がよくありません。
今回は、GBAであれば必ず存在するシーケンスデータを使用します。
以下の表は、GBASPのシステムROMに存在するシーケンスデータの一覧です。
DSのGBAモードではアドレスや各シーケンスデータの存在の有無などが異なるかもしれません。
| アドレス | 備考 |
|---|---|
| 0x3908 | GBAの起動音の前半部分 |
| 0x39c0 | GBAの起動音の後半部分 |
| 0x389c | 起動画面のマルチブート手動待機開始音1 |
| 0x3818 | 起動画面のマルチブート手動待機終了音2 |
| 0x3980 | 起動画面のマルチブート準備完了音3 |
たとえば、以下のように記述することで、起動音の前半部分を鳴らすことができます。
// 変数playerには再生させたいシーケンスプレイヤーのポインタが代入されている
SoundDriverPlaySequence(player, (void*)0x3908);
その他
マスター音量などを設定する
サウンドシステム全体のPCM音源のマスター音量などを設定するには、SoundDriverMode(SWI 0x1b)を使用します。
- 最大チャンネル数を変更するには
((channelCount & 0x0f) << 8) - マスター音量を変更するには
((masterVolume & 0x0f) << 12) - 再生周波数を変更するには
((frequencyIndex & 0x0f) << 16)
を引数に指定してSoundDriverModeを呼び出せばよいです。
また、それぞれの設定値のORを引数に指定すれば複数の項目を一度に設定できます。
シグネチャ
(参考文献:GBATEKのSoundDriverMode)
- 引数:1個
- r0:設定値
ビット 備考 値 0 - 6 リバーブ 0 - 127 7 リバーブを変更するか 1=変更する、0=変更しない 8 - 11 チャンネル数 1 - 12 (0=チャンネル数を変更しない) 12 - 15 マスター音量 1 - 15 (0=マスター音量を変更しない) 16 - 19 再生周波数設定値 1 - 12 (0=再生周波数を変更しない) 20 - 23 D/A変換の量子化ビット数 8 - 11 (0=再量子化ビット数を変更しない)
- r0:設定値
- 戻り値:なし
実装(アセンブラ)
@---------------------------------------------------------------------------------
.global SoundDriverMode
.thumb_func
@---------------------------------------------------------------------------------
SoundDriverMode:
@---------------------------------------------------------------------------------
swi 0x1b
bx lr
C側で利用可能にするためのプロトタイプ宣言
/**
* システムサウンドの各種パラメーターを設定する
* @param mode 設定値
*/
IWRAM_CODE void SoundDriverMode(u32 mode);
周波数設定値と再生周波数の対応表
| 設定値 | 再生周波数 | 備考 |
|---|---|---|
| 1 | 5734 [Hz] | |
| 2 | 7884 [Hz] | |
| 3 | 10512 [Hz] | |
| 4 | 13379 [Hz] | システムサウンド初期化時の既定値 |
| 5 | 15768 [Hz] | |
| 6 | 18157 [Hz] | |
| 7 | 21024 [Hz] | |
| 8 | 26758 [Hz] | |
| 9 | 31536 [Hz] | |
| 10 | 36314 [Hz] | |
| 11 | 40137 [Hz] | |
| 12 | 42048 [Hz] |
D/A変換の量子化ビット数設定値とD/A変換の量子化ビット数の対応表
| 設定値 | 量子化ビット数 | 備考 |
|---|---|---|
| 8 | 9 | |
| 9 | 8 | システムサウンド初期化時の既定値 |
| 10 | 7 | |
| 11 | 6 |
標準のシーケンスプレイヤーを使用しない場合
処理タイミング
SoundDriverMainの実行前に独自のシーケンスの処理を行い、それをもとに仮想チャンネルを設定します。
制御データのポインタは初期化後であれば*(SystemSoundArea**)0x03007ff0で取得できます。
チャンネルデータの設定
鳴らす音の指定
// PCM音源のサウンドチャンネル0を取得
volatile SystemSound_ChannelData* ch = control->pcmChannels[0];
// チャンネル音量の設定
ch->volumeLeft = 255;
ch->volumeRight = 255;
// エンベロープの設定
ch->attackRate = 0xff;
ch->decayRate = 0xe0;
ch->sustainLevel = 0xff;
ch->releaseRate = 0xf0;
// 第1引数に波形データ、第2引数に鳴らしたい音のMIDIキー、
// 第3引数に微調整値(256で半音高くなる)を指定する
ch->frequency = MidiKey2Freq(wave, 60, 0);
ch->wave = wave;
発音を開始させる(キーオン)
再生状態に0x80(128)を書き込むと、再生が開始されます。
既に再生されている場合は再生し直されます。
ch->playingStatus = 0x80;
発音を終了させる(キーオフ)
再生状態に0x40(64)をORすると、リリース状態に移行します。
ch->playingStatus |= 0x40;
発音を終了させる(強制終了)
再生状態に0を書き込むと、そのチャンネルが再生中ではないという扱いになります。
また、標準のシーケンスプレイヤーは再生状態が0x00である場合にそのチャンネルを使われていないものとして扱います。
ch->playingStatus = 0;
発音を一時停止させる
再生状態の8ビットの下位2ビットを0にすると、仮想チャンネルの処理はそのチャンネルが再生中ではないという扱いをします。
下位2ビットが0で、ビット7が0であれば仮想チャンネルの処理はそのチャンネルに対して何もしません。
そのチャンネルが「再生されていない(再生終了した)」「再生中である」「一時停止中である」ことを判別する必要がある場合は、再生状態を何らかの変数に退避させた後、再生状態に0x20を代入することを個人的には推奨します。
(再生状態のビット5はその8ビットの中でシステムサウンドの処理が立てないビットであり、そんなビットだけが立った0x20という値は意図的にしか出現しないので判定が簡素化できるため。また、標準のシーケンスプレイヤーのようにチャンネル確保の探索において再生状態が0x00の場合のみ空きチャンネルとして扱う設計との相性も良いです)
発音を再開させたい場合は、退避させた元の再生状態値を再生状態に書き込むと、一時停止した続きから発音されます。
エンベロープの状態について
発音状態においては、以下の5状態が存在します。
発音直後の状態は、
- アタックレートが255未満であればアタック状態
- アタックレートが255で、ディケイレートが1以上であればディケイ状態
- アタックレートが255で、ディケイレートが0であればサスティン状態
となります。
| 状態 | 説明 | 状態遷移 |
|---|---|---|
| アタック | フレーム毎にアタックレートの値をエンベロープ値に加算する | エンベロープ値が255に達したらディケイ状態(ディケイレートが255の場合はサスティン状態)に遷移する |
| ディケイ | フレーム毎にエンベロープ値にディケイレートの1/256を乗じる | サスティンレベル以下に達したらサスティン状態に遷移する |
| サスティン | ノートオフまたは発音を取り消しされるまでこの状態を維持する | ノートオフされた場合はリリース状態に遷移する |
| リリース | フレーム毎にエンベロープ値にリリースレートの1/256を乗じる | リリース終了レベルが0の場合はエンベロープ値が0に達したら消音し、リリース終了レベルが1以上の場合はエンベロープ値がリリース後レベル以下に達したらリリース終了後状態に遷移する |
| リリース終了後 | フレーム毎にリリース終了後残りフレームをデクリメントする | リリース終了後残りフレームが0に達するか負方向にオーバーフローしたら消音 |
リリース終了レベルを0に指定すれば、よくあるADSRのエンベロープとして機能します。