怪文書を執筆する話
注意:本話はあなたを不快にする恐れがあります。
なんか気づいたらリレー小説を書くことになりました。しかし、私は鉄球連盟の皆さんのようにアダルトゲームとか催眠音声とかを体験したことは無いですし、鉄球連盟の皆さんのように触手とかラミアとかが大好きではありません。
というか反出生主義を拗らせて、あらゆる「そういう描写」が無理になっています。
具体的には18禁描写は全部無理です。少年誌のちょっと際どい描写より上は受け付けません。なぜ私がR18リレー小説を書くことに? 鉄球連盟に書かせようと言い出したから……? まあそんなわけなので、あまり期待せずに読んでください。あ、18歳未満の方はお読みいただけません。
解説&感想
自分で提案しておいてなんですが、リレー小説というのは大変です。みんなが好き勝手に好みのキャラや展開を書いたらめちゃくちゃな物語ができるに決まっています。ゆえに、第1話を書く前にきちんと全員の書きたいものを踏まえた上でプロットを練る必要があります。できれば全員で意見交換するべきですが、今回は私一人でプロットを書きました。彼らの性癖談義に混ざりたくなかったので。一人でするにしても、鉄球連盟でもない人間がするべき役割では無い気がしますが…… でもまあ、多分なんとかなるんじゃないですかね。
ということで、私が想定していた物語の展開の話をしたいと思います。
まず、世界観を考えます。今回はリレー小説なので、テンプレート通りの世界観の方が良いでしょう。定番といえば学園ものという気がしますが、鉄球連盟がゲームサークルであることを考えると異世界ファンタジーがふさわしいかもしれません。去年のアドカレを参考にすると、ゆかりおんさんが異世界ファンタジー、カプチーノさんが学園ものの世界観で話を書きそうです。正直彼らはどっちでも大丈夫そうですが。私はどちらかといえば学園ものがいいですね。ゆかりおんさんたちが空の上でバトルしている間、私は現実の学校でバトルしていたので。たぶん読む側が一番読みたいのはR18シーンだと思うので、とにかくどんな話でも書けるように、異世界と現実世界を接続した大体何でもありの世界観にしました。
次に大まかなストーリーを考えます。おそらく毎話ヒロインが非処女になるので、追加ヒロイン加入シーンを最初に考えました。それが最初なのかと思われそうですが、仲間を1人増やすのは結構大変です。単に行動を共にするビジネスライクな関係ならともかく、仲間となると主人公たちと問題意識を共有しなければいけないからです。例えば「俺ら、マジで海外旅行狙ってンだよね〜。つか、パーティ組まね(笑)」みたいなキャラは本質的に目的が違うので一時的な協力関係にしかなりませんし、「こんな数合わせみたいなキャラが本当にストーリーに必要なのか?」と読者から思われてしまいます。
しかし、ヒロインが登場して主人公たちと問題意識を共有、仲間になるまでを1話で描写し、かつR18シーンを割り込ませるのはちょっと困難です。そこで、ヒロイン登場→R18シーン→問題意識共有という一連の流れを自然に行えるように問題意識を逆算する必要があります。それが「恥ずかしいことを無かったことにする」です。こうすれば「本気で海外旅行目指してます」みたいなヒロインが登場してもR18シーンを挟めば自動的に問題意識を共有することができるわけです。
しかしながら、これはあくまでも主人公たちの目標であって、メタ視点から見た物語のゴールではありません。だって、このまま「すごく頑張って優勝したぞ! 都合の悪いことは全部無かったことにしたぞ!」では、何も面白くありませんよね? つまり、恥ずかしいことが起こったのは単に主人公たちが問題解決を目指したきっかけに過ぎず、彼らが解決すべき問題は他にあるわけです。
今回の物語では、主人公たちが真に解決すべき現状の問題は「自分たちが幸せになれないと考えている」ことです。したがって、この物語のゴールは「主人公たちが幸せになりたいと強く願い、幸せを掴む」ことであり、要するにハッピーエンドしか勝たんということです。
ここまで考えたら、あとは細かな設定を考えます。ここでも設定はなるべく共有したいのでアドベントや聖誕祭、それから「決戦!星の古戦場」や「異常性癖」といった鉄球連盟関連のキーワードをベースにして考えました。具体的には「異常性癖……異常……せいへきせい碧星碧星……碧星……」みたいな感じです。碧星祭の内容をモンスター討伐とアイテム採取のどっちにするか迷いましたが、モンスター討伐にすると戦闘シーンが必要になりそうだったのでやめました。
さて、あとは1〜4話の展開を考えるだけです。実際に私が担当するのは1話のみですが、プロットを考える上で2〜4話の展開もいくつか想定したので紹介したいと思います。
物語のゴールが「幸せを掴むこと」なので、何を願うかはさておいて、まず大量の星粒が必要です。しかし、これ自体が物語のゴールというわけではないので、4話の最後でやっと集め終わられても困ります。順当に考えると3話ですが、2話〜3話の間で星粒を集め終わるのはやや無理があります。宝石なら大きな原石を見つけることもあるかもしれませんが、星「粒」である以上、一箇所に大量にあったら普通に怪しいですし物語性もありません。
敵キャラと賭けをして勝った方が総取りみたいな契約を結ぶのも「だったら星粒集めじゃなくて最初から殴り合いのバトル設定にすればいい」という話になります。つまり2話でいきなり大量の星粒が登場してそこから話を広げるか、4話まで結局集められなかったけど土壇場で主人公のお気持ちに反応して不思議なことが起きるかの選択となります。
それを踏まえて、2話はこんな感じでしょう。
〈碧星祭で優勝すれば良い子になれます。良い子になれば良い人生が待っています。さあ、あなたも星粒を集めましょう!〉
ダンジョンの入り口から、楽しげな校内放送が聞こえてくる。
「バカバカしい……」
足下を調べ歩きながら獣のような耳を頭の上につけた少年がつぶやいた。
「晴石! 口じゃなくて鼻を動かせ!」
「そうだ!」
「そうだぞ!」
そっくり同じ顔、同じ明るい色の髪を同じくサイドテールにした少女たちが少年に檄を飛ばす。
晴石は獣化の異常(先天的に習得した魔法のうち、本人の意思で中断・終了できないもの。狭義には異形型のみを指す)を持っている。小学生と見間違えるような体格の晴石だが、この異常によって筋力と敏捷性は学年トップのステータスを誇る。加えて五感も鋭く、探し物には打ってつけの人材といえた。
「朝瀬、やっぱりメンバー増やそうぜ」
晴石は顔を上げ、少女たちにそう呼びかけた。獣化の異常を持つ晴石と分裂の異常を持つ朝瀬は、まさに「探し物に向いている」メンバーだったが、人手不足感は否めない。ただでさえ常時発動している異常をずっと酷使したせいで、すでに2人とも魔力が尽きかかっていた。
「それが無理だったからこうやって2人で潜ってるんだろうが!」
「あんなことがあった後でパーティ組んでくれるやつなんているか!」
「分かったらさっさと星粒を探……」
「あ、あの……」
次第にヒートアップする2人を窺うように、(追加ヒロイン)が声をかけてきた。
▶︎ ▶︎ ▶︎ ▶︎ ▶︎
(追加ヒロインとパーティを組む)
▶︎ ▶︎ ▶︎ ▶︎ ▶︎
(R18シーンが入る)
▶︎ ▶︎ ▶︎ ▶︎ ▶︎
(追加ヒロインと碧星祭のジンクスを共有する)
(今日の星粒集めを終了し、朝瀬と追加ヒロインが帰る)
▶︎ ▶︎ ▶︎ ▶︎ ▶︎
〈……祭で………れば良い…になれ…す。…い子になれば……人生が待……います。さ……あな…も星…を集…ましょう…〉
遠く離れたダンジョンの入り口から、微かに校内放送が聞こえてくる。
「バカバカしい……」
ドローンカメラでも何も見えない暗がりで、晴石はぽつりとつぶやいた。
その声に反応する者は誰もいない。
夜目の利く晴石なら暗闇でも星粒探しに支障は無かったが、しかし足元には目もくれず、真っ暗な狭い道をどんどんと進んでいく。やがて、大きな部屋のような空間にたどり着いた。晴石は立ち止まると、ポケットから今日見つけた数個の星粒を取り出し、無造作に床に投げ捨てた。
「……だってこれだけ集めても、俺の願いは叶わないんだから」
碧色の星粒が床を覆い、天井に届きそうなほど集められた部屋を後に、晴石は吐き捨てるようにつぶやいた。
このような展開にすることで、素直に奇跡を信じている朝瀬と奇跡を信じない晴石の対比関係ができますし、星粒とか碧星祭とかよく分からない設定なんか全部忘れて晴石の心を朝瀬が絆すだけの分かりやすい話にシフトすることができます。ここまで畳めば、後は2話かけて晴石の心変わりの過程を描写するだけです。カプチーノさんは書かなさそうですけど、私が想定する中ではこれが最も安全な展開だと思います。
各話の引きを考慮しつつこの後の展開を考えると、3話は晴石の心理描写から入ってダンジョン内でピンチになったところまで、4話では引き続きピンチの状況で諦めかけた晴石を朝瀬が救い、晴石が心変わりすればあとは星粒パワーで奇跡を起こしてハッピーエンドといった感じでしょうか。
4話構成は言い換えれば1話の時点で最終話の3話前なわけですから、2話以降は話を展開するというよりは畳むことがメインになると思います。まあ、実際カプチーノさんは設定増やしそうですけど…… それから、2話で大量の星粒が登場する展開にもならないと思います。その場合でも「ダンジョン内を巡回するドローンが朝瀬の話を中継し、その熱量(?)でダンジョン内の星粒が反応」とか「星粒かと思ったら赤い花の実で絶望したけど、赤い花の実に涙がかかって本物の星粒に変化」というような展開が考えられるように小道具を配置しているのでそうそうバッドエンドにはならないと思います。
4話はこんな感じでしょう。
晴石は赤く染まった視界の端で、自分の右腕だった肉塊を貪る魔獣を眺めていた。
(追加ヒロイン)はとうに気絶し、朝瀬に抱えられたままだらりと倒れている。
朝瀬も分裂の異常によって大きな欠損こそ無いものの無数の傷口からは今も血が流れ出し、息をするのもおぼつかない様子で(追加ヒロイン)の肩を必死に揺すっている。
「……無理なんだ、やっぱり」
乾いた舌で血の味を感じながら、晴石はぽつりとこぼした。
晴石の声に反応した魔獣が肉塊から顔を上げ、次の獲物に狙いを定める。
「俺は、幸せにはなれないんだ」
「違う!」
晴石を庇うように前に立った朝瀬はふらつきながらも武器を構え、眼前の敵をしっかりと見据えた。
「私たちは、ようやくここまで来たんだ。いろんなことがあったけど、その度に仲間が増えて、今、ここにいる!」
口元を真っ赤な血で染めた魔獣が、悠長に歩み寄ってくる。朝瀬は手のふらつきを抑え、真っ直ぐに切っ先を魔獣に突きつけた。
「──そしてこれからも、私たちは何度だって強くなって、必ず夢を叶えるんだ!!」
魔獣が朝瀬目掛けて大きく飛び掛かる。
同時に朝瀬は3人に分裂すると3方向に分かれ、全員で魔獣に攻撃をしかける。
「異常者でも!」
「良い人間じゃなくても!!」
「何度間違えても!!!」
「「「私たちは、絶対幸せに生きるんだ!!!!」」」
晴石には、その一瞬が走馬灯のようにゆっくりと輝いて見えた。
朝瀬はまだ諦めていない。きっと数瞬の後には魔獣の口元を照らす血に変わってしまうのに、その後ろ姿にどうしようもなく希望を抱いてしまう。
瞬間、視界が碧色に輝いた。
魔獣は後方に跳んで朝瀬の攻撃を躱すと、身をかがめ、なおもこちらを食らおうと鋭く眼光を光らせている。
「そうだったな」
晴石はよろけつつもしっかりと両足で立ち上がり、左腕で剣を構える。その瞳は碧色の光をたたえ、真っ直ぐに前を見つめている。
「ここで死んだら、あのビデオが遺影になるところだった」
「そうですよ」
いつの間にか意識を取り戻した(追加ヒロイン)が晴石の後方、いつもの戦闘陣形に戻ると回復魔法の詠唱を始める。
味方は満身創痍で、状況は相変わらず絶体絶命だ。けれど、不思議と負ける気はしなかった。これからやりたいことがいくつも思い浮かんでは、肩に、脚に、胸に溶けて力に変わる。
朝瀬はちらりと振り向いて一瞬だけ、いつものように不敵な笑みを浮かべると、すぐに前方を向いて高らかに宣言した。
「私たちは、勝つ!」
その言葉はきっと真実になると、心が告げていた。
★
(エピローグ(優勝!!))
以上が、私が考えるベストな物語の展開です。想定解と言ってもいいかもしれません。
あと小説を書く上で気をつけるのは細かい文法事項とかですね。段落の最初は空白を入れるとか、感嘆符や疑問符の後に文章を続けるときも空白を入れるとか、3点リーダは偶数個使うとか、そういうのです。あんまり細かいこと言うと謎マナーと変わらないし別に守らなくてもいい(というか私も知らない)んですけど……
一番何を書くのか分からないのがカプチーノさんで、もしかしたらダンジョン関係なしに学園ものを書くかもしれないし、急に部活ものが始まって異常性愛好会とか作り始めるかもしれません。異常性愛を公開するのは自分だけにしてくれ…… 両性具有展開にするためだけに万能魔法使いキャラを出されるなどすると展開が全部壊れるので止めてほしいです。
すてぃあさんがどういう話を書くのかも怖いですが、肉体的に傷つけるだけなら多分大丈夫ですし、トゥルーエンドを見せてやるみたいなことを言っていたので多分終わり方も考えていると思います。この物語の終わりはハッピーエンドと決まっているので(伝わってさえいれば)。特にすてぃあさん対策として、痛みで心が折れないように反抗的な性格のキャラ(片方は擬似不死)を用意したので大丈夫なはず。
ゆかりおんさんは主人公の情熱パワーで大逆転ハッピーエンドに到達するストーリーが好きそうなので、すてぃあさんが想定より猛威をふるっても何とかしてくれると思っています。そのために星粒集めルールにしてエネルギー源も確保しました。このメンツの中だと一番話をまとめようとしてくれそうですし、読書量から考えてもラストに最も相応しい人だと思います。
あと考えていたことは場面転換の「▶︎」×5が最後に「★」になったら良い……良くない?とかそれくらいです。
それでは実際の物語がどのような展開になるのか、私の考えていたことは彼らにどのくらい伝わったのかにも注目しながら、絶対に見届けてください。
アドベントスターターセット
目次
1. 美少女に転生する話
2. 計算機を設計する話
3. 劇場版を観劇する話
はじめに
対戦よろしくお願いします。本エントリは『美少女に転生する話』『計算機を設計する話』『劇場版を観劇する話』の全3編です。といっても、文字数は3編合わせても『その欲望……解放しろ!』より少ない*1ので、どなたもお気軽にお読みください。異常者集団の王ことゆかりおんさんは別格として、本エントリは鉄球連盟の皆さんに負けずとも劣らない出来合いと自負しております。それではどうか、本エントリをお楽しみいただければ幸いです。
1. 美少女に転生する話
近年話題の「VTuber」をご存知ですか? 実はVTuberとは、アクターの表情や動作を2DCG・3DCGで再現して動画を制作するYouTuberの形態の1つなんです! いったい、どのようにしてイラストを動かしているのでしょうか? 無料でVTuberになる方法は? 調べてみました!
手順1 立ち絵を描く
はい
手順2 Pythonを用いて表情識別・音声変換を行う
音声変換では機械学習による手法や声の高さ・ピッチを変更する手法が一般的ですが、実際に試した結果いずれも高い精度を得られませんでした。よって、音声識別サービスを利用して音声をテキスト化し、テキストを音声合成ソフトに読み上げさせる手順をとります。なお、本エントリではPythonの実行環境にGoogle Colaboratoryを用います。
ドライブのマウント
from google.colab import drive drive.mount('/content/drive')
ライブラリとソフトウェアのインストール
表情検出にはpazライブラリを利用します。が、実装途中でライブラリにバグ(クォータニオンが1次元配列で取得できない)を発見したので、sedコマンドでファイルを直に修正しています。また、動画読み込み・書き込み用のmoviepyにもバグ(書き出した動画が無音になる)があるようなのでこちらもファイルを直に修正しています。これらの処理が最新のバージョンで必要かは都度ご確認ください。年単位で放置されてるから多分修正されないでしょうけど。
ライブラリのインポート
import numpy as np import moviepy.editor as mp from paz.backend.camera import Camera from paz.pipelines import DetectMiniXceptionFER from paz.pipelines import HeadPoseKeypointNet2D32 import speech_recognition as sr import subprocess
動画読込
clip = mp.VideoFileClip(videofile, fps_source='fps') width, height = clip.size frame_num = int(clip.duration * fps)
顔検出・表情識別
camera = Camera() camera.distortion = np.zeros((4, 1)) camera.intrinsics = np.array([[width, 0, width/2], [ 0, width, height/2], [ 0, 0, 1]]) detectPose = HeadPoseKeypointNet2D32(camera) detectFace = DetectMiniXceptionFER() emotions = [] poses = [] for i in range(frame_num): frame = clip.get_frame(i/fps) estimated_faces = detectFace(frame)['boxes2D'] estimated_poses = detectPose(frame)['poses6D'] emotions.append(estimated_faces[0].class_name if estimated_faces else None) poses.append(estimated_poses[0].quaternion.tolist() if estimated_poses else None)
顔の向きをクォータニオンで取得し、オイラー角に直します。表情の識別結果は基本6感情(angry、disgust、fear、happy、sad、surprise)とneutralの7種類の文字列のいずれかです。ちなみに、動画読込に利用したmoviepyはRGB形式、pazが内部で利用するOpenCVはBGR形式で画像を扱うので実はRとBが入れ替わっているのですが、どうせ機械学習モデルに入力するときはグレースケールに自動変換されると思うので無視します。
音声識別
recognizer = sr.Recognizer() tb = 0 audioclip = clip.audio texts = [] start_times = [] for t in range(int(clip.duration*10)): data = audioclip.subclip(t/10, t/10 + 1).to_soundarray()[:, 0] if len(data[np.abs(data) > 0.05]): continue if t == tb: tb = t + 1; continue audioclip.subclip(tb/10, t/10).write_audiofile('tmp.wav') with sr.AudioFile('tmp.wav') as src: audio = recognizer.record(src) txt = recognizer.recognize_google(audio, language='ja-JP') tb = t + 1
テキスト化すると無音時間の情報が失われるので、あらかじめ音声を無音箇所で分割します。音声識別にはSpeech Recognitionライブラリを利用します。Speech RecognitionはGoogle Speech API等の音声識別APIをPythonから呼び出すためのライブラリなので、別途API側でアカウント登録をして引数で認証キーを指定する必要がある……はずなのですが、なぜか引数を指定しなくても動きます。引数を設定しない場合はGoogle Speech APIを利用しているようですが、なぜ動くのか確かなことは分かりませんでした。
いかがでしたか?
一応、ライブラリの公式サンプルには「開発時のみ利用可」と注釈があるため、利用すべきではないでしょう。あと、APIにはwavファイルのパスを渡す必要があるようで、全く好ましくないのですがtmp.wavに書き出してからAPIに渡しています。ここ上手い方法があったら教えてください。
音声合成
音声合成にはOpen JTalkを利用します。別の音声合成ソフトウェアに「ゆっくりしていってね!」で有名なSofTalkがありますが、exeファイルなのでGoogle Colaboratoryで利用するにはwineをインストールする必要があります。
for i, text in enumerate(texts): subprocess.Popen(['open_jtalk', '-x', '/var/lib/mecab/dic/open-jtalk/naist-jdic', '-m', '/usr/share/hts-voice/mei/mei_normal.htsvoice', '-ow', f'{i}.wav'], stdin = subprocess.PIPE).communicate(txt.encode())
手順3 Live2Dでアニメーションを作る
やるだけ
手順4 動画を生成する
アニメーションを連番で書き出したら(別に動画でもいいですがLive2Dのフリープランだと動画にロゴが入るらしいので)GoogleDriveにアップロードして動画を書き出します。
captureclip = mp.VideoFileClip(capturefile, fps_source='fps') scale = np.minimum(1440/captureclip.size[0], 1080/captureclip.size[1]) captureclip = captureclip.resize((captureclip.size[0]*scale, captureclip.size[1]*scale)) background = mp.ImageClip(bg_filename) clips = [] chara_clips = [] ps = 0 for emotion, pose in zip(emotions, poses): em = 'neutral' if emotion == 'happy': em = 'happy' elif emotion == 'sad': em = 'sad' if pose is not None: x, y, z, w = pose theta = np.arctan2(-2*x*y-2*w*z, 1-2*x*x-2*z*z) ps = np.maximum(np.minimum(int(25*theta/0.25), 24), -25) + 25 chara = mp.ImageClip(f'{character_dir}/{em}/{str(ps).zfill(3)}.png') clips.append(background.set_duration(1/fps)) chara_clips.append(chara.set_duration(1/fps)) newclip = mp.concatenate_videoclips(clips, method='compose') newcharaclip = mp.concatenate_videoclips(chara_clips, method='compose') newclip = mp.CompositeVideoClip([newclip, newcharaclip.set_position((1413, 316)), captureclip.set_position(((1440-captureclip.size[0])/2, (1080-captureclip.size[1])/2))]) for i, start in enumerate(start_times): audioclip = mp.AudioFileClip(f'{i}.wav') newclip = newclip.set_audio(mp.CompositeAudioClip([newclip.audio, audioclip.set_start(start)])) newclip.write_videofile('output.mp4', fps=30, codec='libx264', audio_codec='aac', temp_audiofile='temp-audio.m4a', remove_temp=True)
完成動画
@tos pic.twitter.com/YMDCKt6Z6q
— 箒蝙蝠 (@kohmoli) 2021年12月9日
最後になりますが、私はプログラムを書いただけで私自身が今後VTuberとして活動することはありませんので絶対に間違えないでください。本当は高専卒業前にエロゲでも実況してTeamsと紐づいている動画共有サービスに投稿しようと思ったのですが、学生にはアップロード権限が無いようだったので断念しました。
いかがでしたか?
計算機を設計する話
高専の授業で計算機を作ろうとしたことがあるのですが、結局それは完成しませんでした。なので高専卒業前に計算機およびコンパイラ作成に再挑戦したいと思います。
計算機アーキテクチャ
これから作成するのは、いわゆるスタックマシンです。と言いつつ、スタックだけでなくデータレジスタにもデータは格納できます。ご存知の通り、スタックはスタックトップにしかデータを追加できず、1度取り出したデータをもう1度取り出すことはできません。対してデータレジスタは任意の位置にデータを書き込め、何度でも読み込むことができます。一見すると、スタックは不要そうです。実際に1つ上の先輩が授業で作成した計算機はこれと同じアーキテクチャでしたが、私達の授業作品ではスタックを省きました。それによって計算機側の実装コストは軽くなったかもしれません。しかし、コンパイラ側の実装からするとスタックはあった方が楽です。
スタックの利点は、まさしく「一度しか取り出せない」点です。例えば2*3+4*5という式を計算するとき、2*3=6を計算した後に4*5=20を計算するためには、6をどこかに記憶しておく必要があります。けれど6は6+20=26を計算した後は不要になります。このようなデータをいちいちデータレジスタに格納しては削除するのは大変面倒です。しかし、スタックがあればコンパイラの実装は単純になります。
命令セットアーキテクチャ
データ | コード | 説明 |
NUMBER | 1xxxxxxxxxxxxxxxx | 16ビットの符号なし整数 |
命令 | コード1 | 引数 | 説明 |
NOP | 00000 0000 | なし | プログラムカウンタを停止させる |
LOAD | 00001 1100 | あり | データレジスタの引数アドレスを参照して値をスタックに入れる |
STORE | 00010 1010 | あり | スタックの値を1つ取り出してデータレジスタの引数アドレスに格納する |
ADD | 00011 1010 | なし | スタックの値を2つ取り出し和をスタックに入れる |
SUB | 00100 1010 | なし | スタックの値を2つ取り出し差をスタックに入れる |
MUL | 00101 1010 | なし | スタックの値を2つ取り出し積をスタックに入れる |
DIV | 00110 1010 | なし | スタックの値を2つ取り出し商をスタックに入れる |
MOD | 00111 1010 | なし | スタックの値を2つ取り出し剰余をスタックに入れる |
NOT | 01000 1000 | なし | スタックの値を2つ取り出し論理否定をスタックに入れる |
OR | 01001 1010 | なし | スタックの値を2つ取り出し論理和をスタックに入れる |
AND | 01010 1010 | なし | スタックの値を2つ取り出し論理積をスタックに入れる |
EQ | 01011 1010 | なし | スタックの値を2つ取り出し等しければ1、さもなくば0をスタックに入れる |
LT | 01100 1010 | なし | スタックの値を2つ取り出し小さければ1、さもなくば0をスタックに入れる |
GT | 01101 1010 | なし | スタックの値を2つ取り出し大きければ1、さもなくば0をスタックに入れる |
JMP | 01110 0000 | あり | プログラムカウンタを引数の値に設定する |
BEQ | 01111 0010 | あり | スタックの値を1つ取り出しLSBが0であればプログラムカウンタを引数の値に設定する |
先代の授業作品でも私達の授業作品でもNOP(何もしない命令)が仕様に含まれていましたが、今回の計算機にNOPは不要です。プログラムを任意時間停止させる処理はループで実現できるからです。
計算機の実装
細部はVerilogソースコードを見た方が分かりやすいでしょう。今回はコードの短さを重視しましたが、授業課題で取り組むならALUやシーケンサといった機能ごとに分割した方が丁寧かつ作業分担しやすいと思います。ちなみに、たったこれだけのコードをビルドするのに1時間弱かかりました。分割云々というよりはデータの桁数が無駄に大きいのが原因な気がします。
module StackMachine(input wire CLK1_50, output wire [7:0] HEX0, HEX1, HEX2, HEX3); reg [7:0] pc = 0, sp = 0; reg [16:0] instructions[0:255]; reg [19:0] clk = 0; reg signed [15:0] stack[0:255], data[0:255]; initial $readmemb("./Program", instructions); always @(posedge CLK1_50) clk = clk + 1; always @(posedge clk[19]) begin if(instructions[pc][11] || instructions[pc][16]) pc <= pc + 1; if(instructions[pc][10] || instructions[pc][16]) sp <= sp + 1; if(instructions[pc][9] ) sp <= sp - 1; case(instructions[pc][16:12]) 5'b00000: ; 5'b00001: stack[sp] <= data[instructions[pc][7:0]]; 5'b00010: data[instructions[pc][7:0]] <= stack[sp-1]; 5'b00011: stack[sp-2] <= stack[sp-2] + stack[sp-1]; 5'b00100: stack[sp-2] <= stack[sp-2] - stack[sp-1]; 5'b00101: stack[sp-2] <= stack[sp-2] * stack[sp-1]; 5'b00110: stack[sp-2] <= stack[sp-2] / stack[sp-1]; 5'b00111: stack[sp-2] <= stack[sp-2] % stack[sp-1]; 5'b01000: stack[sp-1] <= ~stack[sp-1]; 5'b01001: stack[sp-2] <= stack[sp-2] | stack[sp-1]; 5'b01010: stack[sp-2] <= stack[sp-2] & stack[sp-1]; 5'b01011: stack[sp-2] <= stack[sp-2] == stack[sp-1]; 5'b01100: stack[sp-2] <= stack[sp-2] < stack[sp-1]; 5'b01101: stack[sp-2] <= stack[sp-2] > stack[sp-1]; 5'b01110: pc <= instructions[pc][7:0]; 5'b01111: pc <= stack[sp-1][0] ? pc + 1 : instructions[pc][7:0]; default : stack[sp] <= instructions[pc][15:0]; endcase end BCD_TO_SEG s0(.BCD(data[0][3:0]), .SEG(HEX0)); BCD_TO_SEG s1(.BCD(data[0][7:4]), .SEG(HEX1)); BCD_TO_SEG s2(.BCD(data[0][11:8]), .SEG(HEX2)); BCD_TO_SEG s3(.BCD(data[0][15:12]), .SEG(HEX3)); endmodule module BCD_TO_SEG(input wire [3:0] BCD, output wire [7:0] SEG); reg [7:0] sig; assign SEG = sig; always @* begin case(BCD) 4'h0: sig = 8'b11000000; 4'h1: sig = 8'b11111001; 4'h2: sig = 8'b10100100; 4'h3: sig = 8'b10110000; 4'h4: sig = 8'b10011001; 4'h5: sig = 8'b10010010; 4'h6: sig = 8'b10000010; 4'h7: sig = 8'b11011000; 4'h8: sig = 8'b10000000; 4'h9: sig = 8'b10010000; default: sig = 8'b11111111; endcase end endmodule
コンパイラの実装
授業課題時よりは短くなったとはいえ、200行以上あるのでコードは流石に省略します。逆ポーランド記法については詫間キャンパスの教員が公開していた授業資料*2を参考にしました。スタックマシンを実装するなら逆ポーランド記法の知識はほぼ必須っぽいです。もし授業で取り扱っていて、私が忘れただけだったら申し訳ないですが……
プログラミング
サンプルとして、フィボナッチ数列を第10項まで出力するプログラムを書きます。
a = 0 b = 1 i = 0 while i <= 10 c = a a = b b = b + c print c/10*16 + c%10 i = i + 1
このようなプログラムをProgram
に書いたら、以下のようにコンパイルしてInstruction.v
を生成します。
python compiler.py Program
完成
視認性の都合上、クロックをかなり落として実行しています。どうせなら命令に合わせてLEDが光るようにすればもっと見栄えが良くなったかもしれません。
@tos pic.twitter.com/zMWphy9dRN
— 箒蝙蝠 (@kohmoli) 2021年12月9日
今回のアーキテクチャは命令に無駄なビットがありますね。普通のスタックマシンはオペランドが無いらしいです。関数も実装できていませんし、出来は良くありません。手元にFPGAさえあれば再々挑戦したいところです。それでもまあ一応動きましたし、とりあえず下手な設計書でも残します。何かの役に立てば幸いです。
劇場版を観劇する話
『劇場版 少女☆歌劇レヴュースタァライト』を観ました。とても素晴らしい作品だと思います。ミニ色紙は大場が当たりました。……他に語ることも無いですし、東京タワーがそこら辺に刺さるような作品に考察も何も無いだろうと思うのですが、ゆかりおんさんが書けというのでひとまず感想だけ述べます。
映画を!!!!見る前に!!!!!!!ロンド・ロンド・ロンドは必修です!!!!!!!!!!!!
— ゆかりおん (@Yukarion09) 2021年6月10日
と思いましたが『少女☆歌劇レヴュースタァライト ロンド・ロンド・ロンド』未視聴なので私はそのステージに立てないようです。欲をいうなら劇場で見たいところですが、もうそんな機会は……
イオンシネマ シアタス心斎橋にて
再生産総集編「少女☆歌劇 レヴュースタァライト ロンド・ロンド・ロンド」と「劇場版 少女☆歌劇レヴュースタァライト」の2作品が上映決定いたしました!12月10日(金)より上映開始となります!
ぜひぜひご観劇ください!
大阪で今日から始まるらしいです。じゃあ、感想文書かなきゃ……
狩りのレヴュー(revue song ペン:力:刀)
最初「どっちが狩る側?」ってなりました。そっちか〜。大場は裏ボスっぽいですが、負け方はデータキャラかもしれません。星見は裏主人公っぽいですね、努力と勝利はしても友情要素が無いから愛城みたいになれないところとか。星見はんってえらい賢そうやけど英語とか数学とかでも天堂はんに負けてたん? でも生徒会長!って一言で星見にも積み重ねてきたものが確かにあることが分かるシーンはとても良かったです。実は生徒会長になったのは唯一ネタバレされていたのですが、逆に心の準備ができて良かったと思っています。
『少女☆歌劇レヴュースタァライト』には、脳内で脚色され素敵な思い出に変わる子供の頃の記憶のような雰囲気があるように思います。なので時系列や細部の描写について考察することに私は特段の意味を感じません。もちろん作り込まれてはいるのでしょうけど。
恨みのレヴュー(revue song わがままハイウェイ)
デコトラをバックに立ち止まって歌うところ一番好きだし、急に遊郭編始まるところ面白かったです(石動童貞概念!?)。オーディションのときは花柳流の道場が舞台でしたが、劇場版で突然デコトラが登場したのは2人の関係が石動主導に転じたことを示唆しているんでしょうか。
作中に「落ちていく」という台詞がありますが、私は「大人になる」という意味だと解釈しました。作中に流血の描写がありますが、女性が血を流すことは初経や破瓜といった、急速かつ不可逆な成長および神秘性の喪失を象徴します。青春時代の万能感からの覚醒や、ふわっとした夢をスケールダウンさせて地に足のついた現実的な将来を見ることを指して「落ちていく」と表しているのではないでしょうか。
競演のレヴュー(revue song MEDAL SUZDAL PANIC◎○●)
レヴューソングがホラーになるところが好きです。歌詞だけ見たら滅茶苦茶心配しているだけなんですけどね。本作のテーマが奪うか奪われるかなのに『1等星のプロキオン』ではニコニコ楽しく笑いあって平和に勝敗を決めていたので一番そぐわない気がしましたが、神楽に圧勝していて安心しました。まあスタリラやっていないのでシナリオ知らないんですけど。
本話を書くにあたって他の方の考察を読みましたが、トマト=禁断の果実とする考察が多かったです。『再生讃美曲(movie ver.)』にも「例えばそれがエデンの果実でも」という歌詞があり、リリックビデオでは監督が字書きを担当されています。エデンの果実は2種類あり、アダムとイブが口にしてしまい不死性を失うことになった善悪の実と、永遠の実があります。『再生讃美曲(movie ver.)』で「それが眩しい」と続く方が永遠の果実、「だから眩しい」と続く方が善悪の果実とも解釈できるかもしれません。永遠の木はオリーブやアカシアのような常緑の木とされます。オリーブの冠はオリンピックの優勝者に送られることで有名ですし、オリンピックにも時事ネタ以上の意味があるように感じられます。
魂のレヴュー(revue song 美しき人、或いは其れは)
クライマックスで曲調が『誇りと驕り』みたいになったところが一番テンション上がりました。やっぱり天堂のベストな相手役は西條って感じがして、とても良かったです。歌詞に分かりやすくお互いの名前が入っているのもお互いに分かり合っている感じがして、とても良かったです。天堂が今まで全部演技でしたとか言いだしたシーンは舞台版で激昂する天堂*3*4とか4コマでただの大食いキャラになった天堂*5とかを思い返して首を傾げました。それとも大食いキャラも食材的な伏線だったんですかね。そんなわけないか。
能動的に禁断の果実を口にする行為は、親からの独立やモラトリアムからの卒業を示唆していると考えられます。「自立」はよく肯定的に捉えられますが、一神教において「神の庇護下から独立する」ことを能動的に選択し、それを肯定することは私の中に無かった発想でした。神の存在・非存在を議論せずに無神論の思想を説明できるすごい考え方だと思います。作中では知恵の実を食べて獲得したもの(知性)についての描写はありませんでしたね。あまり知性と野性が結びつかない気もします。
皆殺しのレヴュー(revue song wi(l)d-screen baroque)
自分も本気出してなかったし未来に怯えていたくせに九九組に怒る大場、だいぶ悪くて良かったです。はしゃいじゃう花柳を初手で仕留めるところと、お話に割り込んじゃう西條をガン無視するところが一番愉快でした。あと、突っ立っている星見の横をすっと通り過ぎるところですね。TVアニメでは星見戦から始まったレヴューが劇場版では大場戦から始まるのがエモでした。レヴューソングも格好良かったです。でも話が資本主義社会前提で進みますけど、自然界の弱肉強食ルールをそのまま人間社会に適用するのは早計ではないかと少し思いました。
『少女☆歌劇レヴュースタァライト』に登場する数々のファンタジー設定の根底には青春時代の何にでもなれるような万能感や子供の頃にしかない熱気の共有があるように思います。それはつまり、少女から大人になり舞台”少女”でなくなってしまえば、ファンタジー設定から卒業しなければいけないということです。もしかしたら、トップスタァという夢さえポケモンマスターくらいふわっとした、いつか卒業すべき幻想なのかもしれません。少なくとも、具体的な進路を考える上ではもっとパーソナライズないしスケールダウンが必要でしょう。しかし、青春時代の幻想だとしてもキリンのオーディションは魅力的ですし、現実を見ることは怖くもあります。まだ伝えられていないこと、やり残したこともあるでしょう。各々がそれを解決し、精神的に区切りをつけることが本作のメインストーリーでした。
再生産のレヴュー(revue song スーパースタァスペクタクル)
死の概念自体は「舞台に生かされている」ことの裏返しですが、劇場版からは「死」に「将来への恐怖」という意味が加わりより強くフィーチャーされました。劇場版『少女☆歌劇レヴュースタァライト』という物語自体が九九組の演じる劇中劇であるという考察を立てている人もいるらしいですが、「演じている」ことが事実だとしても比喩以上のものではないと個人的には思います。私だってある日突然「もっとわがままになろう」と思ってこんな性格になったわけですし、普通じゃないですか? なので愛城が本当は朝一人で起きられるようなことはないと思います。
『私たちはもう舞台の上』の「戻れるよあのページ」という歌詞が一番衝撃的でした。作中世界ではファンタジー設定で戻れるかもしれませんけど、現実世界では過ぎ去った時間には二度と戻れませんし、大場関連はそういう話だと思っていました。そしてこれまでの登場人物の思想自体はファンタジー設定によらず、結論は常に現実に則したものでした。だから「戻れる」という言葉が出てきたことは意外でしたが『星のダイアローグ』の「あの頃には戻れない」って歌詞と対比させているんでしょうか。
余談ですが、映画を見終わった後のエレベーターは当然同じ映画を見ていた人ばかりで、そこで誰かのスマートフォンからアニソンが流れ始めたので場の空気がヒリっとしました。あやうくレヴューが始まるかと思いましたし、余韻は吹き飛びました。許しません。
おわりに
本エントリは以上ですが、明日以降のエントリもきっと本エントリ以上に愉快ですので、ぜひご覧いただき、そして共にお楽しみいただければ幸いです。対戦ありがとうございました。