HHKB BTを静音化した話

TL;DR

  • HHKB買っちゃった
  • 静音化リングを使って改造した
    • 静音化の流れも雑に説明もするよ
  • 静音化とても良かった
  • 余った静音化リングで青軸キーボードの静音化もした

何も考えずそのまま画像アップロードしちゃったのでクソデカ写真なのは許して

経緯

衝動的買い物ってあるよね

普段青軸キーボードを好んで使っていますが、AmazonプライムデーでHHKBがセールで1割引になっていたので勢いで購入。家ではもちろんそうだけど、職場に持っていって使うつもりでもあったので。購入したモデルはProfessional BTの無刻印/墨(英字配列)。無刻印キーボードってカッコいいですよね。

HHKB最高!だけど…

めちゃくちゃに打ちやすいですね。仕事で一日中キーボード叩いててもまったく不快にならない。もうずっと打ってるのが楽しいくらい。だけど結構音が響いてそこがあまり…職場だと余計響くし…。いや普段青軸キーボード使ってる人が言うことではないんですけどね。 青軸はキーボード〜〜!って感じの音ですけど、HHKBの音はそれと比べてなんか気になるなぁって感じのうるささです。あとどうして青軸使っているかって、指がキーに押されて戻るのが好きなんですよね(もちろんスイッチ感も好き)。指をあげるのに力がいらないので。IIDXでバネなし筐体とか引いた日はもう最悪、結構ガチガチのほうが好きです。だからHHKBの軽めな感じとかストロークがそこそこあるのも少し不満点ではあります。リアフォみたいに60gもあればいいんだけどな。

静音化できるらしい

色々と調べていたらHHKBの静音化を施している人がそこそこいたので試してみることにしました。あとどうやら静音化の影響でストロークも短くなるらしく、この軽さのキーだとストロークが短い方が打ちやすいと個人的に思っているので好都合です。というか静音化の目的はそっちが割と5メインになりました。

静音化していくよ

下調べ

「HHKB 静音化」と調べると静音化の記事がたくさん出てきます。お好きなのを選んで参考にしましょう。一通り見た結果、キーボード用の静音化リングであったり、歯科用の顎間ゴムというのを使うというのが分かりました。まぁ大方そうでしょうね。というわけで早速Amazonで注文。大体どれも1,000円くらいで買えると思います。僕が購入したのはこれです。

選んだ理由は特になし。ちなみにプライムデーでポイントが加算されていたのでそれを使って購入。プライムデーに買ってて正解でした。

静音化作業

雑に作業の流れを説明します。どこの記事でも同じこと書いてあるし、詳しさは断然この記事が劣るからちゃんとしたい人は他の記事見ようね。あと、記事中の画像めちゃくちゃでかいのは気にしないでください。

キートップを外す

適当に工具使ったり、手で頑張ったりして外してください。僕は青軸キーボードを買ったときに付属していた工具を使いました。キートップを外すとこんな感じ。
f:id:TheRay0410:20190819232739j:plain 残ってるキーは内側にスタビライザがついててめんどくさそうだったので今回外してません。せっかくなら全部すればよかったかもしれない。気が向いたらそのうち残りの3キーも静音化します。無刻印で外してしまうとどのキーか分からなくなるのできちんと並べておきましょう。あと狭い机とかで作業しない方が良いです。ふとした拍子に当たってぐちゃぐちゃになります(一敗)。

カバーを外す

次は本体のカバー? を外します。本体の裏側にネジが3本あるのでドライバーを使いましょう。1本は電池に隠れているので注意。カバーを外すと基板が出てきます。コードでカバーと繋がっているので気をつけて外します。他の記事を見ていると、面倒なのでコードを基板から抜きましょうと書かれているものがあります。確かに外せそうですが簡単にできそうになかったので外していません。本当に面倒なだけなので外さなくても支障はないです。 f:id:TheRay0410:20190819235839j:plain

基板を外す

基板もネジで固定されているので外してしまいます。コードの繋がっている小さい基板の下にもネジが隠れているのでそれも基板から外してしまいましょう。小さい基板もネジで固定されていますが、ネジも小さいので精密ドライバーを用意しておきましょう。僕は持っていなかったので近所のダイソーで買ってきました(二敗)。 f:id:TheRay0410:20190822002014j:plain 両面テープの粘着力が強めなので置く場所には気をつけてください。メタルラックはいい感じに両面テープを避けながら置けたので良かったです。ラバードームは一応基板に固定されているみたいですが、触るとすぐズレたので無闇に動かさないほうがいいです。 f:id:TheRay0410:20190823212849j:plain 基板たちはとりあえず関係ないので放置します。次は本体の分解。

単純作業

静音リングを装着するためにまず可動部を取り外します。反対側から力を入れて押し出しましょう。遠慮はいりません。外したときにツメの破片が飛び散りますが、画像の通り全然気にするほどの大きさではないです。 f:id:TheRay0410:20190827001359j:plain でもやっぱり丁寧に外した方が破片も小さくツメの損傷は少ないです。そこはお好みで。終盤は結構適当に外してましたが最終的な動作に影響はありませんでした。
全て外し終えたら次は静音リングを装着していきます。数が多いので根気よくやっていきましょう。 f:id:TheRay0410:20190827003356j:plain 装着するとこんな感じ。撚りを戻しながら根本までしっかりと入れましょう。内径が明らかに合ってませんが問題ないです。ゴムなので伸びます。プラスチック同士の接触部分に潤滑剤が塗ってあるのか、少しヌルヌルします。嫌な人は嫌な感じの微妙なヌルヌル具合です。気になるのであれば拭いたほうがいいかもしれません。それで押しづらくなるかもしれないのは知りません、どちらをとるかです。装着のコツですが、ツメにリングを引っ掛けて伸ばすようにすればとても楽に装着することができます。あとはクルクル回せばそのまま収まります。慣れると片手でできます、いやどうかな… f:id:TheRay0410:20190827004101j:plain 細かい単純作業大好きなので一瞬でこの工程は終わりました。全部装着し終わったら残りは今までと逆順で作業するだけです。お疲れさまでした。

完成

全行程合わせて(精密ドライバー購入を除く)1時間半ほどでHHKBの静音化を施すことができました。余程不器用でない限り誰がやっても大抵これくらいの所要時間だと思います。
さて静音化の結果ですが、最高です(精一杯の語彙力)。静音化を施していないキーと比較したところ体感音量が30%ほどになりました。特に高音が大幅に軽減され、コトコトといったような音からスコスコという音に変化します。職場で使って近くの人にヒアリングすると、マックのパチパチキーボードよりマシと言われる程度です。ストロークも元より浅くなりとてもいい具合に押下しやすくなりました。浅くなる浅くなると言っていますが、劇的に変わるほどではないので浅くなるのが気になる人も大丈夫だと思います。試しにこの記事を静音化したHHKBで書いていますがとても快適です。分解するとどうしても保証が効かなくなってしまうので、それが嫌でなければぜひ静音化をオススメします。初購入で打ち込んだときより感動するはずです。

ついでに

青軸キーボードの静音化

はい、静音リングが大量に余ってしまったのでついでに手持ちの青軸キーボードも静音化してみることにしました。余ったリングの数もこの通りちょうどいい感じに…
f:id:TheRay0410:20190827221635j:plain f:id:TheRay0410:20190827221707j:plain うわああああぁぁあ(三敗)。そのままじゃらじゃらってやったら綺麗に落ちるかなって思ったのが間違いでした。
んでまぁこっちはキートップを外すだけでいいので楽です。作業も15分程度で終わりました。ちなみにリングはこんな感じで装着します。ものによりますけどコイツは小さくて入れるのが結構難しかったです。構造的に叩いていたら勝手に根本までいくでしょう。 f:id:TheRay0410:20190827224447j:plain

結果

青軸はスイッチの音の方が大きいのであまり静音化の意味はないですね。もちろん底打ちの接触音は軽減されます。ただ青軸特有のカチッカチッという音は仕方ないです。一応通話相手にいつもと違うか聞いてみたところ音が軽くなったと言われました。

まとめ

変に青軸キーボード静音化の話を入れたので話が締まらなくなってしまった… 静音化はいいぞ!!みんなもぜひやってくれよな!!おしまい!!

自分のなり損ないを作ったというお話

記事タイトル(cv:森本レオ)

TL;DR

自分のツイートを取得して、そこから作った文章をツイートするBotを作った

これはなに?なんで作ったの?

身の回りで「マルコフ連鎖で生成した文章をツイートするBotを作る」というのが流行って、自分のも見たかったから作った。

いや分からんが

噛み砕いて言うと

噛み砕いて言うとって、一旦咀嚼したものを出すってことだよね…うぇぇ… まず自分のツイートをとってきて、それを形態素っていう単位に分解する。その形態素を組み替えることによって僕みたいな文章を作ろう! そしてツイートしよう! ってことをやってる。マルコフ連鎖については詳しく言わないので適当に調べて。僕も実際には分かってないのかもしれない。

どうやって作るの?

ツイート部分とマルコフ連鎖はKotlinをかきかき、形態素解析MeCabにぶん投げるってことをする。

詳細な構成

開発OS:Windows7,Windows10,Ubuntu 18.04 on WSL on Win10
IDE:IntelliJ IDEA
開発言語:Kotlin 1.3.11
JDK:環境がバラバラでよくわかんない☆
使ったもの:MeCab,Penicillin

実行環境:Ubuntu 18.04(開発のとは違う)

詳しい流れ

といっても流れだけで本当に詳細までは話さないよ。

技術選定

まず第一として自分の使える言語であること。がっつり(と言っていいのか分からないが)何か作ったことがあるのは[C++,C#,Swift,Kotlin,java,(Vue.js)]の4つ。C#で作ろうとしたらなんか「周りが作った言語と被るな」とかいうわけのわからない理不尽な指摘を受けたのでKotlinで作ることにした。C++は使う気しなかったのと、Javaは嫌い生理的に無理、Swiftは自宅にMacがないという理由で消去法的に決定。正直Kotlin/Nativeで書きたかったけど多分JVMで書くことになる。なんか悔しい。
次に形態素解析。KotlinでMeCabって使えるのかな、と調べたらKuromojiとSudachiってのが出てきた。Javaで使えるらしいからJVMで動かせば問題ないね。Kuromojiで試してみよう。が、動かない。なにが悪いんですかね、これで1週間ほど潰しました。しかたがないのでMeCabJavaバインディングも試してみる。が、これも動かず1週間ほど潰す。最終的にライブラリをロードしてなかったことが判明しました。バカですね。ということでMeCabを使います。ちなみにIDEを使わずコマンドライン上でコマンド叩いてやってます。なのでエラーも実行時エラーが出るだけです。クラスパスいじったり~ってのを延々と繰り返してました。
そしてBotも作らないといけないのでそこらへんをごちゃごちゃします。まずTwitterのDeveloper申請。新しくなって400文字英作文と落とされがちってのを聞いてめんどくさがって全然やってなかったんだけど、申請したら12時間後には申請通ってた。ビビらせやがって…。というわけでトークンをGet! ただトークン持ってるだけじゃどうにもならないのでKotlinでTwitterAPIを叩きます。検索するとトップにKotlinで使えるAPIラッパーがヒット。Penicillinってやつです。 これですね。Twitterではお馴染みのみりあやんないよbot元締めのプロジェクトチームの方が書いたやつらしいです。みりやんないよbotがKotlinで動いてるの今回で初めて知った。とりあえずREADME.mdのサンプルソースをコピペして試してみるもまったく動かない。なんでやねんと思いつつGitHubのソースを読み解いておかしい部分に変更を加える。ここでもIDEを使わずにmaven centralから直接jarを落としてきてクラスパスに追加してコマンドラインで開発したりしてます。しかしタイミングが悪くて、上がってるjarはver上がりたて4.0.0-eapなのにGitHubのmasterブランチは3.x.xのまま(覚えてない)。違うブランチに気づかなければ泣き寝入りするところだった。幸い更新が毎日のようにあったのですぐmasterに上がってきましたけど。逆に言えば更新がめちゃくちゃ早くて開発途中にはもうどういう変更がされているか追いきれなった。まぁ軽く使うだけなので問題なし。でもこの行動のおかげでつよつよの人が実装してるソースをしっかりと読むことになったのでだいぶ理解も深まった。これは良い経験。ちなみにREADME.mdのサンプルソースは1/24のコミットでようやく修正されてました。何がダメで動かなかったのか書こうと思ってたのに。閑話休題。無事にソースも分かって書き直してテストツイートもできた。ここまでで5日間くらい。時間かけすぎ。ここらへんのタイミングで開発環境をIntelli Jに変える。やべぇ、IDEめちゃくちゃ便利…最初から使っておけばよかった…。結局途中からコマンドラインでGradle使ってたからマジでIntelli J使えよって話。しかしUbuntu on WSL on Win10からWin7IDEに実行環境を移した結果、Penicillinサンプルソースを変更したことをすっかり忘れてまたエラーに苦しむことになりました。ここで5日ほど潰します。まごうことなきバカ。さっさと自分のコミット確認すればここまで潰すことはなかったのに。はい、ここまできたらもうあとはゴリゴリ愚直に書いていくだけですね。できたらデプロイして(まぁビルドしたjarをサーバーに置くだけなんだけど)cronで定期的に実行させるだけ。

実装

Kotlin初学者なのでおかしい点もあるだろうけど多めに見てね。自分でももやっとするので書き直したいなぁとは思ってる。とりあえず完成させることを目標に。開発はWin7のIntelli Jでやってたんだけど、MeCabUbuntu on WSL on Win10の方にしか入ってないのでデバッグできずにいきなりjarを実行してます。めんどくさがりなので実行後のエラーを見て詳細なデバッグもしておらずそこはよしなに。

Tweet Class

class Tweet {
    val key = KeyData()
    val morphoAnalysis = MorphoAnalysis()
    val markovChain = MarkovChain()
    val client = PenicillinClient {
        account {
            application(key.ConsumerKey, key.ConsumerSecret)
            token(key.AccessToken, key.AccessTokenSecret)
        }
        emulationMode = EmulationMode.TwitterForiPhone

        maxRetries = 5
        retry(1, TimeUnit.SECONDS)
    }
    fun getTweet(user: String) {
        runBlocking  {
            client.timeline.user(screenName = user, count = 200,includeRTs = false,excludeReplies = true).await().forEach { status ->
                morphoAnalysis.makeBlock(morphoAnalysis.blockList,morphoAnalysis.text2morphene(status.text.replace("""http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?|#(w*[一-龠_ぁ-ん_ァ-ヴーa-zA-Za-zA-Z0-9]+|[a-zA-Z0-9_]+|[a-zA-Z0-9_]w*)""".toRegex(),"")))
            }
        }
    }
    fun post(text: String = "テスト") {
        client.statuses.update(status = markovChain.genText(morphoAnalysis.blockList)).complete()
    }
}

getTweet()はその名の通りTweetを取得してくる。ついでにそのままtext2morphene()に渡して形態素解析を行ってmakeBlock()でブロックに変換する。URLとかハッシュタグはいらないので正規表現で先になくしておく。post()もちろんそのままでツイートをする。ツイート文章はgenTextで生成。

MorphoAnalysis Class

class MorphoAnalysis {
    init {
        System.loadLibrary("MeCab")
    }
    val tagger = Tagger("-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd")
    var blockList = mutableListOf<Triple<String, String, String>>()
    fun text2morphene(text: String): List<String> {
        tagger.parse(text)
        var node = tagger.parseToNode(text).next
        var morphene: MutableList<String> = mutableListOf()
        while(node.run { next != null }) {
            morphene.add(node.surface)
            node = node.next
        }
        return morphene
    }
    fun makeBlock(blockList: MutableList<Triple<String, String, String>>, morphene: List<String>) {
        blockList.add(if(morphene.run { count() == 1 }) {
            Triple("_START_", morphene[0], "_END_")
        } else {
            Triple("_START_", morphene[0], morphene[1])
        })
        if(morphene.run { count() != 1 }) {
            for (index in morphene.indices) {
                if (index < morphene.lastIndex - 1) {
                    blockList.add(Triple(morphene[index], morphene[index + 1], morphene[index + 2]))
                } else {
                    blockList.add(Triple(morphene[index], morphene[index + 1], "_END_"))
                    break
                }
            }
        }
    }
}

text2morphene()はさっきも言ったとおり形態素解析を行う。まぁMeCabが全部やってくれるんだけどね。なぜかnodeを作る前にtagger.parse()をしないとちゃんとやってくれないという謎挙動があって変なソースになってるけど許して。makeBlock()は形態素をブロックとしてListに保存する。例えば「私はかにです、嘘ですエビです」という文章が[私,は,かに,です,、,嘘,です,エビ,です]と形態素解析されたとする。それを[_START_,私,は],[私,は,かに]…[です,エビ,です][エビ,です,_END_]というブロックにする。適当に書いたんだけど形態素が2つだけのときに対応してない気がする。

MarkovChain Class

class MarkovChain {
    fun genText(blockList: List<Triple<String, String, String>>): String {
        var (text: String, block: Triple<String, String, String>) = findStartBlock(blockList)
        do {
            block = findBlock(blockList, block.third)
            text += block.first + block.second
        } while(text.length < 140 && block.third !=  "_END_")
        return text
    }
    fun findStartBlock(blockList: List<Triple<String, String, String>>): Pair<String, Triple<String, String, String>> {
        val startBlock = blockList.filter { it.first == "_START_" }[Random.nextInt(blockList.filter { it.first == "_START_" }.indices)]
        return Pair(startBlock.second, startBlock)
    }
    fun findBlock(blockList: List<Triple<String, String, String>>,firstWord: String): Triple<String, String, String> {
        val block = blockList.filter { it.first == firstWord }[Random.nextInt(blockList.filter { it.first == firstWord }.indices)]
        return block
    }
}

genText()は(多分)マルコフ連鎖をして文章を生成する。findBlock()はマルコフ連鎖するために必要なブロックを見つけるために使う。_START_から始まって_END_で終わると終了。while()の条件式が140文字以下となっているがもちろんこのソースだと140文字以上の文章が生成されることもあるのでたまにエラーがでる。とりあえずできればいいと思ってやったのであとで修正する。

まとめ

終わってみれば結構時間かかったなぁという感じ。正直ソースだけ見るとこんなもん1日で書き終わります。いかに自分に集中力がないかっていうのを実感させられる。実装自体は1週間くらいでやったので全部合わせると1ヶ月半くらい。3/4以上も環境構築だけで潰してるのはさすがに頭が悪すぎる。ちなみにKuromojiとかMeCabの導入あたりは去年の11月頃で、そこから1月の初めに開発を始める間にテストやらkosenハッカソン、名古屋カンファが入ってきます。時間を無駄に消費してるのが手に取るように分かる。本実装は本当に簡単で常人ならマジで1日で終わります。僕が異常なだけです、はい。もっとやる気出してやろうね(環境構築の時点でモチベを失っていたのは確か)。
完成品もいい感じに動いてくれて達成感もあるし、なおかつ面白いのでとりあえずやってみるのはオススメです。
展望としてだけど、現状都度最新200のツイートを引っ張ってきて生成してるのでできれば過去のツイートも合わせてDBに保存してそれから生成したいところ。あとはなんか他の機能もつけれたら面白そうかなって感じ。なんか気持ち悪いソースも少しは書き直します。文章生成の精度については、もっと良くしてもいいんだけど適当な方が面白い文章が生成されやすくて迷ってる。
そんなところですかね。ぜひみんなフォローして楽しんだり自分で作ったりしてみてね。ブログ書くの飽きたのでここらへんで適当に締めます。