キー入力における同時打鍵の扱い

筆者は「[漢直WS](https://github.com/oktopus1959/KanchokuWS)」という、キーボード入力による漢直配列やかな配列のエミュレータを作成して公開しています。本記事は、漢直WSの [FAQ-基本編](https://github.com/oktopus1959/KanchokuWS/blob/main/FAQ/FAQ-%E5%9F%BA%E6%9C%AC.md#faq-home)の「[同時打鍵](https://github.com/oktopus1959/KanchokuWS/blob/main/FAQ/FAQ-%E5%9F%BA%E6%9C%AC.md#%E5%90%8C%E6%99%82%E6%89%93%E9%8D%B5)」の節を抜粋し、編集したものです。

# 同時打鍵とは
キーボード入力における同時打鍵とは、2つ以上のキーが同時に押下された時に、それらのキーの組み合わせに対して定義された文字列を入力する機能です。

通常のキーボードでシフトキーを押しながら A のキーを押すと大文字の「A」が入力されますが、これを一般の文字キーにもシフト機能を持たせるようなものです。たとえば、W と J が単打の時はそれぞれ「き」と「あ」が出力されるが、J にシフト機能を持たせて、同時に押下された場合は「ぎ」を出力する、というようなことが可能になります。

通常のシフトキーの場合はシフトキーを先に押しておく必要がありますが、文字キーにシフト機能を持たせる場合は、シフト機能を持った文字キーと一般の文字キーのどちらを先に打鍵してもかまわないとするシステムもあります。本稿では、後者のシステムについて解説します。

文字キーにもシフトキーの機能を持たせる同時打鍵を採用した配列を使用する場合は、キーロールオーバー[^rollover]しながら高速打鍵していると意図せざる同時打鍵判定がなされることがよく起こります。これは複数のキーの押下状態の重なりが頻繁に発生するためです。

[^rollover]: 複数キーを連続的に押下しているときに、前に打鍵したキーを解放する前に次のキーを打鍵してもそれを認識してくれる機能。最大何個まで同時押しを認識するかはキーボードによって異なります。USBの仕様的には6個という上限があるが、その上限をキーボード側の仕掛けで回避しているものもあります。たとえば、東プレのRealforceの場合は、「Nキーロールオーバー」と称して、同時押し可能なキーの個数に制限がありません。

同時打鍵と判定されるためには、一般的には2つのキーが近接して打鍵されることが必要ですが、実際に同時打鍵として判定するための条件はシステムによって様々です。

たとえば、親指シフトだと下図のようにキーの押下に重なり合う時間帯がなくても、打鍵間隔が一定時間（たとえば100ms）以下なら同時打鍵とみなす、という判定条件になっています。[^nicola]

[^nicola]: 「[NICOLA配列規格書 5.同時打鍵](http://nicola.sunicom.co.jp/spec/kikaku.htm#0407)」参照。図において、A, B のどちらか一方は親指シフトキーである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/533b21af-9ca1-c6ad-f286-dc4096fd3a04.png)

一方、漢直WSで「同時打鍵」と判定されるためには、下図のように W を押している時間帯と J を押している時間帯が一瞬でも重なってる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/c19faf74-f57a-d738-24f8-e8ac0d08a8f4.png)

漢直WS では、この「同時」の時間帯が無い場合は、どんなに近接した打鍵であっても「同時打鍵」とはみなしません。ただし、「同時」の時間帯がある、というのは必要条件にすぎず、それを「同時打鍵」として扱うかどうかに関しては、さらに別の判定条件があります。

同時打鍵にはどのようなパターンがあるか、そして利用者の打鍵感覚に合わせて単打と同時打鍵をうまく使い分けられるようにするにはどのような判定条件があればよいか、といった点について、興味を持っていただければ幸いです。

## 必ず同時打鍵として扱うケース
下図のように第1打鍵の解放が第2打鍵の解放よりも後の場合は無条件で（たとえ W から J までの打鍵間隔がどれだけ長くても）同時打鍵として扱われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/9baf70c7-c9e0-18ec-79b9-62e06dc99dd9.png)

この条件は、わりと直感的に納得できるのではないでしょうか。

## 同時打鍵と判定したくないケース

「単打のつもりでキーを連打していたら意図せず同時打鍵と判定されて別の文字が入力された」というケースがよく生じます。これをある程度解消するために、「同時に打鍵されている（キーの重なりがある）けど、こういう場合は同時打鍵とみなさない」という制約を考察していきます。

### 第1打鍵から第2打鍵までの時間に制約をかける
まず思いつくのは、「ある程度ゆっくり打鍵している時は同時打鍵と判定しない」という制約です。

下図のように、第1打鍵の押下から第2打鍵の押下までの時間が 80ms だったら同時打鍵ではなく、単打の連続として扱いたいというような場合ですね。打鍵速度の速い方だと、単打のつもりでもこれくらいの時間間隔でキー入力することがあるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/c22d948b-dbcb-3604-3679-ac1d2cbe5fce.png)

このようなケースでは、2つのキーの押下間隔に制約をかけて、たとえば

- 70ms 以下で2つのキーが押下された場合に限り同時打鍵と判定する

という条件を設定することが考えられます。そのような条件を設定すれば、上記のケースは W と J の打鍵間隔が70msを超えているので単打として判定されることになります。[^settings1]

[^settings1]: 漢直WSでの実際の設定画面は下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/fef7f301-dc29-446a-1ec2-f80cb57b3933.png)

### 第1打鍵と第2打鍵の重なり時間の割り合いに制約をかける

下図のように、第1打鍵の押下時間(100ms)と第2打鍵の押下との重なり時間(80ms)を比べて、重なり時間の割り合いが、設定した率（たとえば 70%）以上なら同時打鍵と判定するものです[^ratio-settings]。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/ee14f43b-9b75-3b2d-b399-0a3796bf3956.png)

[^ratio-settings]: 漢直WS では、この制約条件はサポートしていませんが、「[紅皿](https://qiita.com/kenichiro_ayaki/items/03ac81a20137c3d91554)」はこの機能をサポートしています。

### 第2打鍵がシフトキーの場合に、さらに短い時間制約をかける
NICOLA(親指シフト)のようにシフトキーと文字キーのどちらが先に押下されてもシフトが有効になるような「相互シフト」方式の場合、なるべくシフトをその直後の文字キーにかけたい、ということがあるかと思います。

たとえば下図において、第1打鍵(J)と左親指シフトキーの押下間隔は 70ms、一方、左親指シフトと第3打鍵(K)の押下間隔は 80msです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/97ec2d8b-299e-dec6-001d-1ef88ec14eb2.png)

このようなケースでは、通常、左親指シフトはより押下間隔の短い第1打鍵のほうにかかりますが、

- 第2打鍵がシフトキーだった場合には第1打鍵との間にさらに短い時間制約を設定する

ことで、なるべく第3打鍵のほうにかかるようにすることができます。

たとえば、第2打鍵のシフトキーが第1打鍵にかかるためには、その打鍵間隔が 50ms 以下でなければならない、というような制約です。この場合、上図例では第1打鍵(J)～左シフトキーの押下間隔が 70ms なので、「50ms」という制約によって同時打鍵とは判定されなくなり、左親指シフトは第3打鍵(K)にかかるようになります。

### 第2打鍵の押下から第1打鍵の解放までの時間に制約をかける
たとえば下図のような感じで、キー押下の重なり時間を短くしてキーを連打しているときには同時打鍵と判定せず、単打判定してほしい、というケースです。「第1打鍵から第2打鍵までの時間制約が100ms」のような条件だけだと、このケースは同時打鍵と判定されてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/503f7b56-a607-3cff-41d7-b33893f7b44f.png)

このようなケースを制御するために、

- キー押下の重なり時間がある一定時間以上の場合に限り同時打鍵と判定する

という条件を設けることが考えられます。たとえばこの条件を 90ms に設定すれば、上図のケースは単打と判定されます。[^settings2]

[^settings2]: 漢直WSでの実際の設定画面は下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/15de60ca-7402-02a9-7530-1a3995c59c01.png)

この条件は、3キー以上の同時押し状態の場合に発動する同時打鍵定義に対しても同様に適用できます。この場合は、下図のように末尾キーの押下からどれかのキーの解放までの時間で判定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/3808fd9c-9434-44ed-027c-4c05de7a184b.png)
（この例では3キーの同時打鍵ではなく、E,M,K の順次打鍵と判定される）


### 連続シフト可のときに2文字めを同時打鍵と判定したくない
たとえば「[薙刀式](http://oookaworks.seesaa.net/article/456099128.html)」というかな配列では、 J のキーは単打だと「あ」になりますが、同時打鍵の場合は相手キーを濁音化するシフトキーになります。この機能は「連続シフト」が可能になっており、たとえば J を押しながら W, R と連続して押すとそれそれの単打入力文字「き」「し」が濁音化されて、「ぎ」「じ」と入力されます。この時、本当は「ぎし」と出したかったのだけど意図せず「ぎじ」になってしまった、ということもあろうかと思います。

このようなケースも前項で説明した条件で制御できます。前項のように条件を変更すると、下図のようなケースでは2文字目の R を打鍵してから最初のシフトキー J を解放するまでの時間 80ms が前項条件の制約時間(90ms)を下回っているので、同時打鍵ではなく単打と判定され「し」が出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/b8d73ea0-b1eb-27ed-a560-c9ebec1cae5a.png)

### 連続シフト可のときに同時打鍵と単打の交互打鍵をうまく判定できるようにしたい
「薙刀式」のような相互シフトかつ連続シフト可の場合に、たとえば「どろどろ」と入力するときは下図のような打鍵列になるでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/3a4efa97-6779-21d8-be6e-bb2ba034ae92.png)

上図のケースだと、「A(ろ)」を押してから次の「J(シフト)」を押すまでの時間が 50ms なので、通常であれば、J が A にかかって「ぜ」になってしまいます(薄い青で網かけした部分)。このようなケースで、「シフトキーはなるべくその後に打鍵されるキーにかけたい」ということがあるでしょう。

このようなケースに対応するには

- シフトキーの解放後、シフトキーを前のキーにかける「後置シフト」を無効にする時間を設ける

という方策が考えれらます。

たとえばこの時間を 300ms に設定してやる[^settings3]と、上図のように最初のシフトキー「J」の解放後、300ms 以内に押された最初の文字キー（「A」が該当）は、その直後にシフトキーが押下されたとしても、単打として扱われます。言いかえると、この時間帯に文字キーが押下された場合、その直後に同時打鍵シフトキーが押下されても、それは直前の文字キーにかかりません。

[^settings3]: 漢直WSでの実際の設定画面は下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/da835f7d-8715-babe-3a2f-96e20c290895.png)


## 3キー同時打鍵
3キーの同時打鍵の判定も基本的には2キーの場合と同様です。

下図は、筆者が開発している漢直指向かな配列「[のにいると](https://note.com/kanchokker/n/n213d76654101)」で `Space`, `E`, `O` の3キーを同時押しして「しょ」を出す場合のキーの押下と解放のタイミングの例です。この例では、

- `Space` を押してから3キー目の `O` を押すまでの時間が 70ms で、第1打鍵の押下から第2打鍵の押下までの時間制約(100ms)以下である
- `O` が押されてから `E` が離されるまでの時間が 80ms で、第2打鍵の押下から第1打鍵の解放までの時間制約(70ms)以上である

ことから同時打鍵の条件を満たしていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/f9f651a1-3870-adf2-3aab-aced44874ab8.png)

次図では1番目の条件が満たされていませんが、末尾キーである `O` が先に離されており、「必ず同時打鍵として扱うケース」に該当するので、これも同時打鍵として扱われることになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/5f809cf3-b0c4-ee68-88f6-319477a6bde0.png)

## タイマーを使って一定時間後に同時打鍵判定をする
親指シフトでは、キーを押してから一定時間(100msとか200msとか)が経過すると、**そのキーが押下中であっても**それを単打と判定して文字の出力を行います。

この挙動を実現するには下図のようなタイマーが必要になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/8fbe5f11-f5a0-24cc-8331-93d1ca6fbf40.png)

何かキーが押下されたらタイマーを起動し、設定された時間が経過したら疑似的にキーを解放してやるわけです[^settings4]。

[^settings4]: 漢直WSでの実際の設定画面は下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/bd3ca22f-0946-f589-ec8a-0918eb6606ef.png)

## （おまけ）自分がどのようなタイミングで打鍵しているかを知りたい
いろいろと同時打鍵判定のための条件を考察してきましたが、実際にどのように条件を設定してやればよいかは、なかなかつかめないかもしれません。利用者自身がどのようなタイミングでキーを打鍵しているかが分かると、条件を設定しやすくなるでしょう。

漢直WSでは、このような用途のために「打鍵ログ」を表示する機能を用意してあります。下図のような感じでキーの押下・解放および文字出力の様子がタイムスタンプとともに表示されます[^settings5]。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/6b6e5769-2532-2f3d-e58d-dfe68ead83be.png)

[^settings5]: 漢直WSでの実際の設定画面は下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2375976/4599b276-59cc-fec7-b703-53ec94cd071d.png)


以上、同時打鍵判定の難しさと面白さを感じとっていただけたら幸いです。