Copyright (C) 2003-2009 デグレファクトリー
この資料は SNES SPC700 Player v2.12.2 以降に準拠しています。
Script700, Script700 SE は、保存しただけでは演奏できない SPC を可能な限り演奏させるようにする拡張規格です。
ほとんどの曲は SPC700 (サウンド CPU) の処理だけで演奏を行っていますが、ごく一部の曲では 65C816 (メイン CPU) とデータをやりとりして演奏しているものがあり、その場合は SPC700 の動作だけでは正しく演奏できません。 そこで、 65C816 と SPC700 との間で行われるデータ交換の作業自体も実機と同じように行うことで、そのような特殊な曲でも演奏可能にします。
この改良版 SNESAPU.DLL は、 41568k 氏が立案された Script700 規格と木下氏が立案された Script700 SE 規格の一部のコマンドと構文に互換性を持たせ、独自拡張の命令を合わせた Script700 互換機能を搭載しています。
この資料では Script700 互換機能で対応している Script700 コマンドの仕様について説明しています。
独自の Script700 エンジンを使用しているため、同じ Script700 コマンドでも、他のプレイヤーと仕様や動作が異なる場合があります。 あらかじめご了承ください。
Script700 を SPC ファイルに適用させるには、以下のように操作します。
1. SPC ファイルと同じ場所に下記のような名前のファイルを作成します。 ファイル名によって優先順位が決まっており、複数存在する場合はより上側のファイルが優先されます。
(1) (SPC ファイル名).700
(2) (SPC ファイル名).7SE
(3) 65816.700
(4) 65816.7SE
(SPC ファイル名) の部分には拡張子 .spc は含みません。 例: sample.spc と同じ場所に sample.700
プレイヤーによって優先順位、または認識可能なファイル名が異なります。
(3), (4) はフォルダ内の全 SPC に対して適用する場合に使用します。
2. 作成したファイルをテキスト エディタで開き、コマンドを入力して保存します。
3. Script700 に対応している SPC プレイヤーで通常通り演奏を開始します。
正常に演奏されない場合は Script700 がプレイヤー側で正しく認識されているか、使用している Script700 がその曲専用のものであるか確認してください。
Script700 互換機能では、以下の 3 つの領域で構成された Script700 を使用しています。
・ スクリプト領域
SPC700 にデータを送ったり、逆に SPC700 からデータを受け取ったり、計算したりなどといったプログラムを記述する場所です。
e コマンドで領域を抜けます。
・ 拡張コマンド領域
DSP の処理に割り込みを行い、制御を変更します。 具体的には特定の音を消したり、音色を変えたり、音程を変えたりすることができます。
e コマンドで領域を抜けます。
・ データ領域
スクリプト領域で使用したいデータ (数値) が比較的大量にある場合、ここにまとめてデータを記述することができます。
ファイルの最後まで続きます。
例) sample.700
m #0 0 ┐ e │スクリプト領域 ┘ m 20 ┐ e │拡張コマンド領域 ┘ 0007CBA380AD4E52 ┐ 79A3C4E7BB524042 │データ領域 ┘
Script700 互換機能で対応しているコマンドとその構文は以下の通りです (注意:独自拡張の機能が含まれています)。
【 スクリプト領域 】
w {PARAM3}
m {PARAM1} {PARAM2}
c {PARAM1} {PARAM1}
a {PARAM1} {PARAM2}
s {PARAM1} {PARAM2}
u {PARAM1} {PARAM2}
d {PARAM1} {PARAM2}
n {PARAM1} +,-,*,/,\,%,$,&,|,^,<,>,_,! {PARAM2}
bra (#)[LABEL],w[WORK]
beq (#)[LABEL],w[WORK]
bne (#)[LABEL],w[WORK]
bge (#)[LABEL],w[WORK]
ble (#)[LABEL],w[WORK]
bgt (#)[LABEL],w[WORK]
blt (#)[LABEL],w[WORK]
bcc (#)[LABEL],w[WORK]
blo (#)[LABEL],w[WORK]
bhi (#)[LABEL],w[WORK]
bcs (#)[LABEL],w[WORK]
r
q
nop
e
※ ただし {PARAM1} ~ {PARAM3} は以下に置き換わります。
{PARAM1} = #[NUM],i[PORT],(o)[PORT],w[WORK],r(b)[RAM],rw[RAM],rd[RAM],x[XRAM], d(b)[DATA],dw[DATA],dd[DATA],
#?,i?,(o)?,w?,r(b)?,rw?,rd?,x?,d(b)?,dw?,dd?
{PARAM2} = [PORT],i[PORT],o[PORT],w[WORK],r(b)[RAM],rw[RAM],rd[RAM],x[XRAM],d(b)[DATA],dw[DATA],dd[DATA],
?,i?,o?,w?,r(b)?,rw?,rd?,x?,d(b)?,dw?,dd?
{PARAM3} = (#)[NUM],i[PORT],o[PORT],w[WORK],r(b)[RAM],rw[RAM],rd[RAM],x[XRAM],d(b)[DATA],dw[DATA],dd[DATA],
(#)?,i?,o?,w?,r(b)?,rw?,rd?,x?,d(b)?,dw?,dd?
() 内は省略可能です。 例えば "bra 3" と "bra #3" は同じ意味です。
"," で区切られた項目の中からひとつだけ使うことができます。 例えば "n #1 +- w0" のように、複数は書けません。 "n #1 + w0" や "n #1 - w0" と書きます。
【 拡張コマンド領域 】
:: m [SOUND],! c [SOUND],! [SOUND] d [SOUND],! [RATE] v [SOUND],!,l,r,vl,vr,el,er [VOLUME] e
【 データ領域 】
(任意の 16 進数データ。 ファイルの最後まで続きます)
各コマンドの説明は以下のとおりです (注意:独自拡張のコマンドが含まれています)。
【 スクリプト領域 】
: | bxx 命令のジャンプ先を指定します。 "bra 0" は ":0" にジャンプします。 |
w | 指定したクロック分 (2,048 kHz) だけ Script700 の動作を停止させます。 |
m | 左側の値をそのまま右側に上書きします。 |
c | 左側の値を [CMP1] に、右側の値を [CMP2] にコピーして、比較などに使います。 |
a | 右側の値+左側の値の結果を右側に上書きします。 |
s | 右側の値-左側の値の結果を右側に上書きします。 |
u | 右側の値×左側の値の結果を右側に上書きします。 |
d | 右側の値÷左側の値の商 (符号あり) を右側に上書きします。 |
n | 2 番目のパラメータによって動作が異なります (後述)。 |
bra | 無条件でラベルへジャンプします。 |
beq | [CMP1] = [CMP2] ならラベルへジャンプします。 |
bne | [CMP1] ≠ [CMP2] ならラベルへジャンプします。 |
bge | [CMP1] ≦ [CMP2] (符号あり) ならラベルへジャンプします。 |
ble | [CMP1] ≧ [CMP2] (符号あり) ならラベルへジャンプします。 |
bgt | [CMP1] < [CMP2] (符号あり) ならラベルへジャンプします。 |
blt | [CMP1] > [CMP2] (符号あり) ならラベルへジャンプします。 |
bcc | [CMP1] ≦ [CMP2] (符号なし) ならラベルへジャンプします。 |
blo | [CMP1] ≧ [CMP2] (符号なし) ならラベルへジャンプします。 |
bhi | [CMP1] < [CMP2] (符号なし) ならラベルへジャンプします。 |
bcs | [CMP1] > [CMP2] (符号なし) ならラベルへジャンプします。 |
r | 最後に実行された bxx コマンドの直後にジャンプします (最大 63 ステップ)。 |
q | スクリプトの実行を中止します。 |
nop | 何も行いません (ウェイトもかかりません) が、コマンドとして認識します。 |
e | スクリプト領域を終了します。 これ以降は拡張コマンド領域となります。 |
+ | 右側の値+左側の値の結果を右側に上書きします (a コマンドと同じ)。 |
- | 右側の値-左側の値の結果を右側に上書きします (s コマンドと同じ)。 |
* | 右側の値×左側の値の結果を右側に上書きします (u コマンドと同じ)。 |
/ | 右側の値÷左側の値の商を右側に上書きします (d コマンドと同じ、符号あり)。 |
\ | 右側の値÷左側の値の商を右側に上書きします (符号なし)。 |
% | 右側の値÷左側の値の余りを右側に上書きします (符号あり)。 |
$ | 右側の値÷左側の値の余りを右側に上書きします (符号なし)。 |
& | 右側の値 AND 左側の値の結果を右側に上書きします。 |
| | 右側の値 OR 左側の値の結果を右側に上書きします。 |
^ | 右側の値 XOR 左側の値の結果を右側に上書きします。 |
< | 右側の値を左側の値分だけ左にシフトした結果を右側に上書きします。 |
> | 右側の値を左側の値分だけ右にシフトした結果を右側に上書きします (論理シフト、符号なし)。 |
_ | 右側の値を左側の値分だけ右にシフトした結果を右側に上書きします (算術シフト、符号あり)。 |
! | 左側の値の NOT を右側に上書きします。 |
【 拡張コマンド領域 】
:: | 拡張コマンド領域 (スタートアップ コマンド領域) を開始します。 効果はスクリプト コマンド領域の e コマンドと同じです。 |
m | 指定した波形番号だけ消します (ミュート)。 再度指定でミュートを解除します。 |
c | 左側の値 (波形番号) を右側の値 (波形番号) に置き換えます (ノートチェンジ)。 |
d | 左側の値 (波形番号) の発音周波数を右側の値だけずらします (デチューン)。 |
v | 左側の値 (波形番号) の音量を右側の値だけ倍率変更します。 |
e | 拡張コマンド領域を終了します。 これ以降はデータ領域となります。 |
【 データ領域 】
すべて 16 進数 (0 ~ 9, A ~ F, a ~ f) で記述します (0x は不要です)。
e コマンドはなく、ファイルの終わりまでデータ領域が続きます。
スペース、 TAB、改行は無視されます。 注釈文も記述可能です。
各パラメータの説明は以下のとおりです (注意:独自拡張のパラメータが含まれています)。
:[LABEL] | ラベル番号。 0 ~ 1023 を指定します。 |
#[NUM] | 数値 (リテラル)。 任意の数値を指定します。 |
i[PORT] | SPC 入力ポート番号。 0 ~ 3 を指定します。 |
o[PORT] | SPC 出力ポート番号。 0 ~ 3 を指定します。 |
[PORT] | 書き込み時は i[PORT]、読み込み時は o[PORT] が自動選択されます。 |
w[WORK] | ユーザー ワーク メモリ。 0 ~ 7 を指定します。 |
r(b)[RAM] | SPC RAM メモリ。 0 ~ 65535 を指定します。 |
rw[RAM] | SPC RAM メモリ。 0 ~ 65535 を指定します。 |
rd[RAM] | SPC RAM メモリ。 0 ~ 65535 を指定します。 |
x[XRAM] | SPC EXTRA RAM メモリ。 0 ~ 63 を指定します。 |
d(b)[DATA] | データ領域のメモリ。 0 以上の値を指定します。 |
dw[DATA] | データ領域のメモリ。 0 以上の値を指定します。 |
dd[DATA] | データ領域のメモリ。 0 以上の値を指定します。 |
xx? | 数値指定の代わりに [CMP1] [CMP2] を使用します (補足説明の 「動的ポインタ機能」 を参照してください)。 |
[SOUND] | 音色番号。 0 ~ 255 を指定します。 |
[RATE] | 発音周波数。 任意の数値を指定します。 |
[VOLUME] | 音量の倍率。 65536 を 1 倍として、任意の数値を指定します。 |
! | すべての波形番号を対象にします。 |
l | マスター音量とエコー音量 (左) を対象にします。 |
r | マスター音量とエコー音量 (右) を対象にします。 |
vl | マスター音量 (左) を対象にします。 |
vr | マスター音量 (右) を対象にします。 |
el | エコー音量 (左) を対象にします。 |
er | エコー音量 (右) を対象にします。 |
[PORT], i[PORT], o[PORT] は SPC700 に入力する、または SPC700 から出力される 4 つのポートにアクセスする際に使用します。 8 ビットの領域が 4 個あり、 8 ビット単位でアクセスできます。 入力ポートに値を設定することで、サウンド ドライバに命令を送信します。 ほとんどは演奏を進めたり、曲を変えたり、音量を変えたり、効果音を鳴らしたりする場合に使用します。
w[WORK] (ユーザー ワーク メモリ) は Script700 を実行する上でユーザーが自由に利用することができる作業用のメモリ領域です。 いくら書き換えても SPC700 の動作に影響はありません。 Script700 実行前にすべての領域が 0 で初期化されます。 32 ビットの領域が 8 個あり、 32 ビット単位でアクセスできます。
r(b)[RAM],rw[RAM],rd[RAM] は SPC700 が使用する 64KB (65,536 バイト) のメモリにアクセスする際に使用します。 r(b)[RAM] は 8 ビット、 rw[RAM] は 16 ビット、 rd[RAM] は 32 ビット単位で任意のアドレスにアクセスできます。 rw65535 や rd65533 など、範囲外になった場合は例外にはなりませんが、値の保証はできません。 SPC700 の動作にかなり影響があり、サウンド ドライバの動作を書き換えたり、ドライバが対応していない出力を直接取得したりできます。
x[XRAM] は SPC700 が使用する 64 バイトのメモリにアクセスする際に使用します。 8 ビット単位で任意のアドレスにアクセスできます。 通常は使用することはありません。
d(b)[DATA],dw[DATA],dd[DATA] は Script700 で定義したデータ領域のメモリにアクセスする際に使用します。 d(b)[DATA] は 8 ビット、 dw[DATA] は 16 ビット、 dd[DATA] は 32 ビット単位で任意のアドレスにアクセスできます。 確保されているデータ領域のサイズを超えてアドレスを指定した場合は例外にはなりませんが、値の保証はできません。
本家 Script700 の p コマンド、 bt0 コマンド、 p パラメータは非対応です。 データ領域を利用することで代用できますが、同じコマンドは使えません。
本家 Script700 SE の f コマンド、 fc コマンドは非対応です。 代用できるコマンド、機能はありません。
ラベル、コマンド間には、必ず 1 つ以上のスペース、改行、または TAB を記述します。 改行で終わらなくてもかまいません。
パラメータ間には、必ず 1 つ以上のスペース、または TAB を記述します (改行は不可)。
改行コードは CR, LF, CRLF に対応しています。
例) 改行で終わらない書き方
m 0 w0 :0 w 16384 a #1 w0 m w0 0 bra 0
例) パラメータ間は改行不可
m #0 w0 ; これは正常に動作します m #0 ; これは正常に動作しません w0 ; (第 1 と第 2 パラメータの間に改行があります)
すべてのコマンド、パラメータのアルファベットに大文字と小文字の区別はありません。 大文字でも小文字でも同じように動作します。
例) 大文字と小文字
m #0 w0 ; 小文字 M #0 W0 ; 大文字 (どちらも同じ)
通常、パラメータに指定する数値はすべて 10 進数ですが、 16 進数の記述も可能です。 16 進数の場合は最初に 0x を付けて記述します (大文字と小文字の区別はありません)。
データ領域を除いて、すべての数値指定で 10 進数・ 16 進数の表記が可能です (データ領域では 16 進数しか記述できません)。 ただし、他のプレイヤーとの互換性を考慮する場合は、すべて 10 進数での記述をお勧めします。
例) 10 進数と 16 進数
m #255 w0 ; 10 進数 【推奨】 m #65535 w0 m rd347 w0 m #0xFF w0 ; 16 進数 m #0xffFF w0 m rd0X15b w0 :0x0F ; ラベルでも可能 e m 0x2F ; ミュート指定でも可能
bxx コマンドを使用して無限ループを作成する際、 w コマンドを使用しないと Script700 の動作が終わらず、応答が停止 (ハング) します。 終わらない処理の場合は、必ずループのどこかに w コマンドを入れてください。
例) ループ中には w コマンド
:1 a #1 0 w 1 ; ここで Script700 の動作を 1 クロックだけ停止 bra 1
コマンドを解析中、対応していないコマンド、パラメータなどが見つかった場合、その行にある残りのコマンドはすべて無視され、次の行の解析に移ります。
この仕組みを利用して、コマンドの対応状況による分岐や、注釈文の処理を行うことができます。
例) s コマンドの対応状況により分岐する場合
s w0 - w1 bra 1 ; s コマンドに対応していない場合、 "bra 1" は実行されずここに来ます。 :1 ; s コマンドに対応している場合、 "bra 1" が実行されてここに来ます。
例) ある 1 文に注釈をつける場合
(存在しないコマンド文字はすべて注釈文の先頭として使用できます。 ; は注釈用で、使用できないコマンドとして予約されており、最も安全です。)
n #1 - w0 ; work[0]-- 【推奨】 n #1 + w0 // work[0]++ n #4 * w0 ' work[0] *= 4 n #4 / w0 # work[0] /= 4
強制的に次の行へ進むケースは以下の通りです。
・ それぞれの領域で対応していないコマンドが出現したとき。
・ すべてのコマンドにおいてサポートされていないパラメータの指定が出現したとき。
何らかの理由でコマンドの実行ができない場合、改行の有無にかかわらず、単純に次のコマンドを実行します。
例) 次の行に移らないケース
d #0 w0 bra 1 ; work[0] /= 0 (0 による除算) は単純に無視され "bra 1" が実行されるため ; ここには来ません。 :1 ; 必ずここにジャンプします。
無視されるケースは以下の通りです。
・ w コマンドで指定された時間が 0 だったとき。
・ d, n コマンドで 0 による除算が行われようとしたとき。
・ bxx コマンドのジャンプ先のラベルが存在しなかったとき。
・ r コマンドで戻り先の bxx コマンドが存在しなかったとき。
・ すべてのコマンドにおいてサポートしているパラメータが指定されていたが、そのコマンドのパラメータとしてはふさわしくないとき ("m #2 #5" など)。
・ すでに拡張コマンド領域になっているときに :: ラベルが出現したとき。
・ nop コマンドが出現したとき。
オーバーフローした場合、あふれた桁は破棄されます。
例) オーバーフロー
m #0xFFFFFFFF w0 ; work[0] = 0xFFFFFFFF ; work[0] はこれ以上加算できませんが... a #1 w0 ; こうやってもエラーにはならず... ; work[0] = 0x00000000 になります。 a #-1 w0 ; work[0] = 0x00000000 で減算すれば... ; work[0] = 0xFFFFFFFF になります。 m #0 w9 ; work[9] は存在しませんが... ; 実際は work[1] に 0 が代入されます。 ; パラメータは必要なビットより上位ビットはすべて 0 になるためエラーになりません。 ; 例えば work[] は 8 個であるため下位 3 ビットのみが使用され、あとは 0 になります。 ; 簡単に言えば、実際に指定された数値を 8 で割った余りが使用されることになります。 ; 5 → 0...0101b → 101b → 5 (そのまま) ; 9 → 0...1001b → 001b → 1 (9 が 1 に変わる)
変数の値を他の変数に代入する場合、消費ビット数が少ないと下位ビットのみがコピーされ、消費ビット数が多いと上位の余分なビットはすべて 0 で埋められます。
例) ビット変換
m #0xF1E7038F w0 ; work[0] = 0xF1E7038F m w0 0 ; port[0] = work[0] ; port[0] == 0x8F ; work は 32 ビット、 port は 8 ビットであるため、 ; 実際に port[0] に入力される値は下位 8 ビット (0x8F) となります。 ; port[1] ~ port[3] の数値には影響しません。 m #0xF1E7038F w0 ; work[0] = 0xF1E7038F m 0 w0 ; work[0] = port[0] (0x4D) ; work[0] == 0x0000004D ; work[0] に 8 ビットを超える値 (0x100 以上) が入っていたとしても ; port の値を書き込むと、上位 24 ビットは 0 で埋まります。 ; 仮に port[0] の値が 0x4D だった場合、 work[0] に代入すると ; work[0] の中身は 0x0000004D になります (0xF1E7034D ではありません)。
Script700 と Script700 SE の互換性を維持するため、スクリプト領域の e コマンドと拡張コマンド領域の :: ラベルを併用する際は、必ず e から先に書いてください。
例) × 悪い例
:: ; ここから拡張コマンド領域 e ; しかし、ここで拡張コマンド領域が終了し... m 3 ; この指定は意味がありません。
例) ○ 良い例
e ; ここから拡張コマンド領域 :: ; 拡張コマンド領域では :: は無視されるため... m 3 ; まだ拡張コマンド領域なので有効です。
各パラメータで、数値指定の代わりに ? を使用することで、読み出す、または書き込む場所を動的に変化させることができます (bxx コマンドでは ? を使用できません。 代わりに work[] を使用できます)。
例) 動的ポインタの例
; (1) [通常の指定] m w0 w1 ; work[1] = work[0] ; (2) [動的ポインタで指定] c #0 #1 ; cmp2 = 1, cmp1 = 0 m w? w? ; work[cmp2] = work[cmp1] ; (1) と (2) は同じ効果です。 c コマンドの第 1 パラメータと ; 第 2 パラメータに指定された値 (0 と 1) が ? と置き換わります。 ; 以下のような書き方をすれば、ネスト処理が可能です。 ; work[work[1]] = work[work[0]] c w0 w1 m w? w? ; work[work[work[1]]] = work[work[0]] c w0 w1 c #? w? ; #? は [CMP1] そのままの値 (=w0) m w? w? ; data[data[port[work[0]]]] = 1 c #? w0 ; 第 1 パラメータは #? 以外でも OK ですが、 c #? ? ; #? は最も処理数が少なく、消費バイト数も 1 バイトで c #? dd? ; 最も効率が良いため、推奨します。 m #1 dd?
改良版 SNESAPU.DLL では、 Script700 コマンドを DLL 内部のパーサーに通すことでテキストからバイナリに変換し、実行時は変換後のバイナリ データを使用します。 バイナリ データを保持するメモリ容量は 1,024KB の制限があるため、容量を超えるスクリプトは実行できません。
バイナリ メモリの使用量は SNES SPC700 Player の Script700 デバッグ機能で確認することができます。 "100000" 表示が最大です。 なお、バイナリ変換前のテキスト スクリプトは 16MBまで読み込むことができます。
m コマンドなどでデータ領域を書き換えることができますが、シーク時の動作は不定です。
常に変化する値を格納する場合は必ずユーザー ワーク メモリをご利用いただき、データ領域は読み取り専用としてください。
以下に Script700 コマンドの記述例を記載します。
1) 定期的 (16,384 クロック経過ごと) にポート 0 の値を 1 ずつ加算する
m 0 w0 ; work[0] に出力ポート 0 の値を代入 :0 w 16384 ; 16,384 クロック待機 ←────┐ a #1 w0 ; work[0] に 1 を加算 │ m w0 0 ; 入力ポート 0 に work[0] をセット │ bra 0 ; 無条件でラベル :0 へ移動 ─────┘
2) 一部の音色 (音色番号 127) のみを出力する
e ; スクリプトなし :: m ! ; すべての音色をミュート m 127 ; 音色番号 127 のみミュートを解除
3) 64KB APU RAM のアドレス 0x1E7A のデータを 0x7F に書き換える
m #0x7F r0x1E7A ; ram[0x1E7A] に 0x7F を代入
4) 64KB APU RAM のアドレス 0x45E から 0x40 バイト分のデータをデータ領域に指定した値に書き換える
m #0 w0 ; work[0] に 0 を代入 m #0x45E w1 ; work[1] に 0x45E を代入 :0 c w0 w1 ; cmp1 = work[0], cmp2 = work[1] m dd? rd? ; ram[cmp2] = data[cmp1] ; つまり ram[work[1]] = data[work[0]] ; コピーは 4 バイト単位 a #4 w0 ; work[0] に 4 を加算 a #4 w1 ; work[1] に 4 を加算 c #0x40 w0 ; cmp1 = 0x40, cmp2 = work[0] bne 0 ; cmp1 と cmp2 が異なる値のとき :0 へ移動 ; つまり work[0] ≠ 0x40 のとき :0 へ移動 e ; 終了 e ; 拡張コマンド領域なし ; ここからデータ領域 ; TAB, 半角スペース, 0 ~ 9, A ~ F, a ~ f 以外の文字が来たら ; 次の行から再度解析が始まります。 6F9147EB 107A7FCD 9813E7CA 9E7F1A4E ; 0x10 バイト F1F131F0 EF1EB341 A674AC45 31DBBE1F ; 0x20 バイト 46FBE1BB F11EBA11 111FEF20 CF220E9A ; 0x30 バイト 0041BB25 30EDFFBA 01012ECE 3300EF20 ; 0x40 バイト ; なお、データ領域は空白や改行は無視されるので、以下のような書き方でも ; 問題ありません。 好きなように記述してください。 6F 91 47 EB 10 7A 7F CD 98 13 E7 CA 9E 7F 1A 4E 6F9147EB107A7FCD9813E7CA9E7F1A4E 6 F 9 1 4 7 E B 1 0 7 A 7 F C D 9 8 1 3 E 7 C A 9 E 7 F 1 A 4 E