サウンドフォントの仕様の概要と、見落としがちな注意点を紹介します。
本書はバージョン 2.01 のリファレンスとしてもお読みいただけます(バージョン 2.01 と 2.04 の相違点は24ビットサンプルのサポートがあるかないかのみ)。
サウンドフォントの詳細な仕様は SoundFont Technical Specification にあります。
構造体の具体的な定義はこちらでご確認ください。
本書の内容は RIFFPad や Polyphone で実際の SoundFont ファイルとあわせてご覧になることをおすすめします。
また、010 Editor という有償バイナリエディタの Binary Template Repository ページに SF2.bt というサウンドフォント向けのテンプレートを投稿しました。データの可読性が飛躍的に向上するので、ユーザーの方はぜひご利用ください。
用語の定義・説明
- バンク(Bank)
- プリセットの集合を指す。
- MIDI バンク(MIDI Bank)
- MIDI バンクチェンジメッセージで選択されるプリセット集合のこと。
サウンドフォントの「バンク」と区別する際に使用。 - プリセット(Preset)
- MIDI のプログラムチェンジで指定するプリセットに相当。
1つ以上のインストゥルメントから構成される。 - プリセットゾーン(Preset Zone)
- 特定のキーナンバーとレイヤーについて定義されたプリセットのサブセット。
かつてのバージョンでは「レイヤー(Layer)」と呼んでいたもの。 - インストゥルメント(Instrument)
- プリセットの構成単位。1つ以上のゾーンから構成される。
プリセットと異なりユーザーには公開されない単位。 - ゾーン(Zone)
- インストゥルメントを構成する単位。
特定のキーナンバーとベロシティのもとで鳴るように定義されたアーティキュレーションデータに関連付けられるオブジェクト。複数のゾーンを定義することで、キーレンジごとに異なるサンプルを鳴らしたり、複数のサンプルを重ねたりすることができる。 - サンプル(Sample)
- インストゥルメントを構成する波形データ(無圧縮PCM)のこと。
ゾーンごとに1つのサンプルを割り当て可能。 - Generator
- ゾーンにおける各オペレーターの値(音量、パン、ADSR、etc.)を定数的に定義するためのオブジェクト。
pgen/igen チャンクに出現する。 - Modulator
- ゾーンにおけるコントローラーとオペレーターの間の連続的な変化を定義するオブジェクト。
(たとえば、ベロシティに応じてカットオフフィルタがかかるとか、そういうのです。)
pmod/imod チャンクに出現する。 - ヒドラ、ヒュドラー(Hydra)
- 9つの頭を持つギリシア神話の怪物。
サウンドフォントでは pdta チャンクが持つ9つのサブチャンクを意味する。
フォーマット概要
サウンドフォントは RIFF で構成されています。RIFF は「チャンク」と呼ばれるデータグループの組み合わせで構成されるシンプルな汎用フォーマットです。
RIFF の詳細は割愛しますが、チャンクサイズが奇数の場合、実データサイズは2の倍数になるように、チャンクの末尾にパディングを付加しなければならないことに注意が必要です。
チャンクの一覧と依存関係
ユーザー視点では、サウンドフォントにはおおまかに下記の階層関係があります。
- Presets
- Instruments
- Samples
- Instruments
これに対して、ファイル上のチャンク構造はどうなっているのでしょうか。
RIFF チャンクの階層
- RIFF - sfbk
- LIST - INFO
- [必須] ifil (SoundFont のバージョン)
- [必須] isng (対象サウンドエンジン名)
- [必須] INAM (SoundFont バンク名)
- irom (Sound ROM の名前)
- iver (Sound ROM のバージョン)
- ICRD (作成日時)
- IENG (サウンドデザイナーおよびエンジニア名)
- IPRD (想定する製品名)
- ICOP (Copyright メッセージ)
- ICMT (あらゆるコメント)
- ISFT (作成・変更に使用したツール名)
- LIST - sdta
- [必須] smpl (波形サンプル)
- sm24 (24ビットサンプルの下位8ビット; v2.04)
- LIST - pdta
- [必須] phdr (プリセットヘッダー)
- [必須] pbag (プリセットのインデックスのリスト)
- [必須] pmod (プリセットの Modulator のリスト)
- [必須] pgen (プリセットの Generator のリスト)
- [必須] inst (インストゥルメントの名前とインデックス)
- [必須] ibag (インストゥルメントのインデックスのリスト)
- [必須] imod (インストゥルメントの Modulator のリスト)
- [必須] igen (インストゥルメントの Generator のリスト)
- [必須] shdr (サンプルヘッダー)
- LIST - INFO
また、pdta チャンクについては、サブチャンク(親)のフィールドが他のサブチャンク(子)のインデックス(0 から開始)を保持するものがあります。下記がその階層構造です。
- LIST - pdta
- phdr (プリセットヘッダー)
- pbag (インデックスのリスト)
- pmod (Modulator のリスト)
- pgen (Generator のリスト)
- pbag (インデックスのリスト)
- inst (インストゥルメントの名前とインデックス)
- ibag (インデックスのリスト)
- imod (Modulator のリスト)
- igen (Generator のリスト)
- ibag (インデックスのリスト)
- shdr (サンプルヘッダー)
- smpl チャンクを参照する
- phdr (プリセットヘッダー)
Index Generator による依存関係の定義
pgen/igen チャンクは Generator と呼ばれる単純な構造の属性値を多数持ちます。
struct sfGenList {
SFGenerator sfGenOper; // 16-bit enum value
genAmountType genAmount; // 16-bit integer
};
Generator の中でも重要なのが、チャンク間の参照関係を定義する Index Generator と呼ばれる属性値です。
下記の種類の Index Generator が存在します。
- [pgen] sfGenOper = instrument (41); Preset-Instrument の参照関係を示します。
- [igen] sfGenOper = sampleID (53); Instrument-Sample の参照関係を示します。
チャンク早見表
下記はあらためて各チャンクの名前、型、値の例などを示した表です。
親 | チャンク名 | 必須 | 説明 | 型 | 値の例 |
---|---|---|---|---|---|
INFO | ifil | ○ | SoundFont のバージョン | struct sfVersionTag | 2.01 |
INFO | isng | ○ | 対象サウンドエンジン名 | ZSTR | "EMU8000" |
INFO | INAM | ○ | SoundFont バンク名 | ZSTR | "Fluid R3 GM" |
INFO | irom | Sound ROM の名前 | ZSTR | "1MGM" | |
INFO | iver | Sound ROM のバージョン | struct sfVersionTag | 2.08 | |
INFO | ICRD | 作成日時 | ZSTR | "July 15, 1997" | |
INFO | IENG | サウンドデザイナーおよびエンジニア名 | ZSTR | "John Q. Sounddesigner" | |
INFO | IPRD | バンクが想定する製品名 | ZSTR | "SBAWE64 Gold" | |
INFO | ICOP | Copyright メッセージ | ZSTR | "Copyright (c) 1997 E-mu Systems, Inc." | |
INFO | ICMT | あらゆるコメント | ZSTR | "This is a comment" | |
INFO | ISFT | 作成・変更に使用したツール名 | ZSTR | ":Preditor 2.00a:Vienna SF Studio 2.0:" | |
sdta | smpl | ○ | 波形サンプル(16 bit signed PCM) | SHORT[n >= 1] | - |
sdta | sm24 | 波形サンプル(8 bit part of 24 bit PCM) | BYTE[n >= 1] | - | |
pdta | phdr | ○ | プリセットヘッダー | struct sfPresetHeader[n >= 1] | - |
pdta | pbag | ○ | プリセットのインデックスのリスト | struct sfPresetBag[n >= 1] | - |
pdta | pmod | ○ | プリセットの Modulator のリスト | struct sfModList[n >= 1] | - |
pdta | pgen | ○ | プリセットの Generator のリスト | struct sfGenList[n >= 1] | - |
pdta | inst | ○ | インストゥルメントの名前とインデックス | struct sfInst[n >= 1] | - |
pdta | ibag | ○ | インストゥルメントのインデックスのリスト | struct sfInstBag[n >= 1] | - |
pdta | imod | ○ | インストゥルメントの Modulator のリスト | struct sfInstModList[n >= 1] | - |
pdta | igen | ○ | インストゥルメントの Generator のリスト | struct sfInstGenList[n >= 1] | - |
pdta | shdr | ○ | サンプルヘッダー(波形のオフセットなど) | struct sfSample[numOfSamples >= 1] | - |
サンプルデータはすべてモノラルサンプルです。データ上はステレオサンプルという概念がほぼ存在しませんが、左と右にサンプルを配置するようなゾーンを定義することで、ステレオサンプルを適切に鳴らすことができます。
sm24 チャンクはバージョン 2.04 の追加仕様です。24ビットサンプルの下位8ビットを保持します。
制約・注意事項
サウンドフォントは単純なフォーマットですが、細かな注意点が多々あります。
チャンクの出現順序
INFO チャンクは RIFF の先頭 に出現しなければなりません(これは RIFF のルールです)。INFO 子チャンクの順序は任意ですが、仕様書上の順序が推奨されます。
その他、すなわち sdta, pdta チャンクおよび子チャンクは、仕様書上の順序どおりに出現しなければなりません 。
文字列(ZSTR)は終端文字を保持する、長さは256バイト以下
ZSTR 文字列形式のフィールド値は終端文字(値 0)を含まなければなりません。
また、終端文字はデータサイズが偶数になるように1~2バイトを挿入します。これは RIFF のパディングバイトとは異なる概念で、データ自身を伸長する ZSTR チャンクの仕様です。
「Hello.」という文字列を格納する場合、格納値は「Hello.\0\0」で、チャンクサイズは8バイトです。
データサイズを偶数に調整している関係上、RIFF のパディングバイトは発生しません。これにより、ヘッダ上のデータサイズも、ファイル上の実データサイズも、どちらも8バイトになります。
|+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F |0123456789ABCDEF|
|------------------------------------------------|----------------|
|49 43 4D 54 08 00 00 00 48 65 6C 6C 6F 2E 00 00 |ICMT....Hello...|
また、ZSTR チャンクの長さは256バイト以下 とするように規定されています。
リストの要素の一部は次の要素とあわせて解釈しなければならない
チャンクが保持するインデックス番号は「開始インデックス」であることがほとんどで、次の要素の「開始インデックス」とあわせて「インデックス範囲」を表現する仕様となっています。
たとえば、ある要素のインデックスが 2 で、次の要素のインデックスが 5 の場合、ある要素は 2, 3, 4 のインデックス範囲を参照するということです。
具体的には下記のフィールドがこれに該当します。
- [phdr] sfPresetHeader.wPresetBagNdx
- [pbag] sfPresetBag.wGenNdx
- [pbag] sfPresetBag.wModNdx
- [inst] sfInst.wInstBagNdx
- [ibag] sfInstBag.wInstGenNdx
- [ibag] sfInstBag.wInstModNdx
上記の構造上、複数のゾーンが同一の Generator 要素あるいは Modulator 要素を指すことは絶対にありません。
チャンクによっては終端(terminator)データを挿入しなければならない
上記のようにリスト要素をペアで解釈する都合もあり、チャンクによっては末尾にダミーデータを挿入する必要があります。
下記は必要な終端の一覧です。終端要素は特定のフィールドを除いて値 0 を保持します。
チャンク | 値 |
---|---|
phdr | achPresetName: "EOP" wPresetBagNdx: インデックス(sfPresetBag 総数と同数) |
pbag | wGenNdx: インデックス(sfGenList 総数と同数) wModNdx: インデックス(sfModList 総数と同数) |
pgen | 0 |
pmod | 0 |
inst | achInstName: "EOI" wInstBagNdx: インデックス(sfInstBag 総数と同数) |
ibag | wInstGenNdx: インデックス(sfInstGenList 総数と同数) wInstModNdx: インデックス(sfInstModList 総数と同数) |
igen | 0 |
imod | 0 |
shdr | achSampleName: "EOS" |
サンプルのデータポイント数の制約
さまざまなハードウェア環境における再現性を考慮して、サンプル(波形)には規格上いくつかの制約が設定されています。
下記の制約が満たされない場合、ハードウェアが与えられたパラメータに対してアーチファクトのない再生に対応していないと、サウンドが再生されないことがある(may optionally not be played)とのことです。
The DWORD dwEnd contains the index, in sample data points, from the beginning of the sample data field to the first of the set of 46 zero valued data points following this sample.
The DWORD dwEndloop contains the index, in sample data points, from the beginning of the sample data field to the first data point following the loop of this sample. Note that this is the data point “equivalent to” the first loop data point, and that to produce portable artifact free loops, the eight proximal data points surrounding both the Startloop and Endloop points should be identical.
The values of dwStart, dwEnd, dwStartloop, and dwEndloop must all be within the range of the sample data field included in the SoundFont compatible bank or referenced in the sound ROM. Also, to allow a variety of hardware platforms to be able to reproduce the data, the samples have a minimum length of 48 data points, a minimum loop size of 32 data points and a minimum of 8 valid points prior to dwStartloop and after dwEndloop. Thus dwStart must be less than dwStartloop-7, dwStartloop must be less than dwEndloop-31, and dwEndloop must be less than dwEnd-7. If these constraints are not met, the sound may optionally not be played if the hardware cannot support artifact-free playback for the parameters given.
- サンプルは最小でも 48 以上のデータポイントを持つ。
- ループ区間は最小でも 32 以上のデータポイントを持つ。
- ループの前後はそれぞれ有効な 8 以上のデータポイントを持つ。
- dwEndloop に続くサンプルは、dwStartloop から始まるサンプルと同一であるほうが良い(should)。
- 上記にしたがった場合の大小関係
-
dwStart < dwStartloop-7
を満たす。 -
dwStartloop < dwEndloop-31
を満たす。 -
dwEndloop < dwEnd-7
を満たす。
-
制約の強制力がいまひとつ不明ですが、あくまでより安全な再生のために推奨されることであって、必ず守っていなければ不正なファイルとみなされるというレベルのものではなさそうです。
個人的には、ツールの実装上はこの制約は無視しようかなと思いました。
サンプルの終端データポイントについて
サンプルの終端(dwEnd が指す位置の後ろ)には、値 0 のデータポイントを46個記録しなければなりません。
サンプルの dwEnd より後、次のサンプルの dwStart より前にくるデータなので、この値がサンプルの範囲に含まれることはありません。
The DWORD dwEnd contains the index, in sample data points, from the beginning of the sample data field to the first of the set of 46 zero valued data points following this sample.
グローバルゾーン(global zone)について
グローバルゾーンとは、同インストゥルメントの各ゾーンに共通する Generator/Modulator の値を格納する場所です。
下記のすべての条件に合致するゾーンがグローバルゾーンです。
- 最初のゾーンであること。
- プリセットゾーンの場合:最後の Generator が instrument ではないこと。
- インストゥルメントゾーンの場合:最後の Generator が sampleID ではないこと。
逆に言えば、最初に出現するゾーンであっても、必ずしもグローバルゾーンではないと言えそうです。
Generator/Modulator 列挙子の制約(出現順序など)
各チャンクの章や「10.7 Illegal enumerator」章にいくつかの制約が記載されています。
Generator と Modulator のユニーク制約は下記のとおりです。
- Generator は自身の sfGenOper によって定義される。同一ゾーン内のすべての Generator はユニークな sfGenOper を持たなければならない。もし複数回出現した場合、以前の内容は無視される。
- Modulator は自身の sfModSrcOper, sfModDestOper, sfModSrcAmtOper によって定義される。同一ゾーン内のすべての Modulator はこれらの3つの列挙子のユニークな組を持たなければならない。もし複数回出現した場合、以前の内容は無視される。
また、出現順序に関しても制約があります。
- keyRange および velRange はゾーンの最初の Generator でなくてはならない(keyRange, velRange の順に出現)。
- instrument 列挙子は pgen チャンクに出現すべきであり、インストゥルメントゾーンに出現することは許可されない。
- instrument 列挙子はゾーンの最後の Generator でなくてはならない(グローバルゾーン以外は必ず出現)。それ以降の Generator は無視される(will be)。
- sampleID 列挙子は igen チャンクに出現すべきであり、プリセットゾーンに出現することは許可されない。
- sampleID 列挙子はゾーンの最後の Generator でなくてはならない(グローバルゾーン以外は必ず出現)。それ以降の Generator は無視される(will be)。
- ゾーンは必ず1つ以上の Generator を有する(例外:Modulator のみを持つグローバルゾーン)。
その他の補足情報
Modulator
Moduletor は解説事項が多くて概要理解につまずきやすいので、すこし簡単に説明します。
Modulator はゾーンにおけるコントローラーとオペレーターの間の連続的な変化を定義するオブジェクトです。たとえば、ベロシティに応じてカットオフフィルタがかかるとか、そういう変化を定義します。
PMOD/IMOD には以下の形式のデータが出現します。内容はおおよそコメントのとおりです。
struct sfModList
{
SFModulator sfModSrcOper; // 入力はなにか(例:MIDI CC11)
SFGenerator sfModDestOper; // 出力はなにか(例:音量)
SHORT modAmount; // 変化のスケール(単位は sfModDestOper によって異なる)
SFModulator sfModAmtSrcOper; // 変化のスケールを可変にするコントローラー
SFTransform sfModTransOper; // 出力の変形方法 (0: 直線, 2: 絶対値, 「8.3 Modulator Transform Enumerators」章参照)
};
どのメンバーもただの数値型です。
sfModAmtSrcOper が抽象的でわかりづらいですが、たとえば Pitch Bend Sensitivity に応じて出力ピッチのスケールを変えるとか、そういう用途に使用します。
SFModulator については「8.2 Modulator Source Enumerators」章以降に説明されていますが、端的に書くと以下の 16-bit 整数値です。
ビット | 名前 | 説明 |
---|---|---|
0..6 | Index | コントローラの番号 |
6 | D (Direction) | 0: Increase (増加) 1: Decrease (減少) |
7 | CC | 0: General Controller (ベロシティなど MIDI CC 以外の場合) 1: Midi Controller |
9 | P (Polarity) | 0: Unipolar (単極, 0.0~1.0) 1: Bipolar (双極, -1.0~1.0) |
10..15 | Type | 0: Linear (直線) 1: Concave (凹型) 2: Convex (凸型) 3: Switch |
CC が Midi Controller の場合、Index には MIDI CC 番号を指定します。General Controller の場合の指定値は「8.2.1 Source Enumerator Controller Palettes」を参照。
「ノートオンのベロシティに応じて音量が変化する」とかの基本的変化まで明示するのは冗長なので、デフォルトで適用されるべき Modulator が「8.4 Default Modulators」章に示されています。概要がわかったうえでこの章を読むと、実用例を見ることができて理解が深まります。
本筋からは少し外れた事項のQ&A
本筋からは少し外れた事柄に対するQ&Aです。個人的疑問リストです。
Q. メロディー楽器とドラムセットの区別はないの?
サウンドフォントの規格上は両者に区別はありません。
ただ、General MIDI との親和性のために、パーカッションバンクはバンク番号 128 に配置することになっています。
余談ですが、General MIDI でリズムチャンネルのマッピングを避けるには、チャンネル 10 以外を使用するか、SysEx で割り当てを変更することになると思います。
# GS: USE FOR RHYTHM PART OFF
F0 41 10 42 12 40 10 15 00 1B F7
Q. バンク変更の MIDI CC って CC0/CC32 どっちなの?
結論としては、CC0 または CC0 & CC32 の組み合わせでバンク変更すると記されており、明確な決まりはありません。(「9.2 MIDI Functions」章)
実質的には CC0 で 0~127 のバンクを選択できる実装が多いように思います。
Q. マルチバイト文字の扱いについて
文字エンコーディングについては ASCII と書かれているので、マルチバイト文字は使うべきではありません。
関連ライブラリ
- libgig (C++, SF2以外への対応も含む)
- sf2cute (C++, 自作, 2018年未完成, メンテナンス停滞中)
- NAudio: Audio and MIDI library for .NET (.NET, MIDIファイルやSoundFontが扱える)