- BACKNUMBERS -
2024年 5月分
2024年 6月分
2024年 7月分
2024年 8月分
2024年 9月分
2024年 10月分
2024年 11月分
2024年 12月分
2025年 1月分
2025年 2月分
最新月

web6047 - 2025年 3月

私がプログラミングを好きなのは、探求心創造性を満たしてくれるからです。

会話 AI の Gemini との会話にて


こういう趣味のプログラミングは、仕事では通用しませんでした。

その辺について ChatGPT で話した内容はこちらです。→

(会話の長さについて、私の発言は前後の挨拶を抜かして、9回です)

- Special Documents -

特別な記事へのリンク

▼3DCG プログラミングの方法
▼書籍「はじめて読む486」の
サンプル動作環境の作成方法
▼3Dお姉さんによるプログラミング解説
▼RPG のルーツ(PDF)

ゲームコーナー 

▼クレイジーバルーン
(↑, ↓, ←, →)
▼テトリス
(←, →, ↓, z, x)

その他 単発のアプリ

▼矩形波は複数の波形の合成です

- 以降は日記です -

2025年3月1日

構文解析 一応完成

今年の1月12日から何となくで始めた構文解析。

1カ月半ほどかけてようやく(一応)完成したので、バグ探しも兼ねて「デモンストレーション」してみます。

デモは1~4まであります。

デモ1

まずはごく簡単な HTML です。

<html> <head> </head> <body> </body> </html>

解析結果。

Object: [html] [0] Object: [kakko<>] [0] "<" [1] "html" [2] ">" [1] "\n" [2] Object: [kakko<>] [0] "<" [1] "head" [2] ">" [3] "\n" [4] Object: [kakko<>] [0] "<" [1] "/head" [2] ">" [5] "\n" [6] Object: [kakko<>] [0] "<" [1] "body" [2] ">" [7] "\n" [8] Object: [kakko<>] [0] "<" [1] "/body" [2] ">" [9] "\n" [10] Object: [kakko<>] [0] "<" [1] "/html" [2] ">" [11] "\n"

Object にはそれぞれ [html] とか [kakko<>] とか名前(種類名)が付けられています。

Object が持つ子要素は配列に格納されており、親子の階層構造を成しています。

"<" などダブルクオートで囲まれたものは文字列です。


HTML 構造としての階層分けはしていません。

HTML のタグごとにオブジェクト化されています。

デモ2

次にこの HTML に JavaScript を追加します。

<html> <head> <script> function test( abc ) { alert( abc ); } </script> </head> <body> </body> </html>

解析結果。

Object: [html] [0] Object: [kakko<>] [0] "<" [1] "html" [2] ">" [1] "\n\t" [2] Object: [kakko<>] [0] "<" [1] "head" [2] ">" [3] "\n\t\t" [4] Object: [kakko<>] [0] "<" [1] "script" [2] ">" [5] Object: [javascript] [0] "\n\t\t\t" [1] Object: [functionStatement] [0] "function test" [1] Object: [kakko()] [0] "(" [1] " abc " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\t\t\talert" [2] Object: [kakko()] [0] "(" [1] " abc " [2] ")" [3] ";" [4] "\n\t\t\t" [5] "}" [2] "\n\t\t" [6] Object: [kakko<>] [0] "<" [1] "/script" [2] ">" [7] "\n\t" [8] Object: [kakko<>] [0] "<" [1] "/head" [2] ">" [9] "\n\t" [10] Object: [kakko<>] [0] "<" [1] "body" [2] ">" [11] "\n\t" [12] Object: [kakko<>] [0] "<" [1] "/body" [2] ">" [13] "\n" [14] Object: [kakko<>] [0] "<" [1] "/html" [2] ">" [15] "\n"

SCRIPT タグで囲まれた部分は、JavaScript として認識されており、「言語の中の別の言語」に対応しています。


『function と来たら引数エリアを示す () と、本体エリアを示す {} が続くだろう』というようなプログラムを書いています。

実際のプログラミング言語の実行においても、

ERROR: unexpected ~(~は期待されていない)

というエラーメッセージはよく見ると思います。

『コンパイラや構文解析などの「言語の処理」においては、どんな単語や記号が「期待される」、という動きは共通してあるんだな』と今回の構文解析を作っていて思いました。

デモ3

前回の記事で問題にしていた、

if( A ) if( B ) test4(); else test5(); else test6();

このピンクの部分は if( A ) にとっての単文であり、「どう認識させるか?」ということでした。

解析結果。

Object: [javascript] [0] Object: [ifStatement] [0] "if" [1] Object: [kakko()] [0] "(" [1] " A " [2] ")" [2] " " [3] Object: [ifStatement] [0] "if" [1] Object: [kakko()] [0] "(" [1] " B " [2] ")" [2] " test4" [3] Object: [kakko()] [0] "(" [1] ")" [4] ";" [5] " " [6] Object: [elseStatement] [0] "else" [1] " test5" [2] Object: [kakko()] [0] "(" [1] ")" [3] ";" [4] " " [5] Object: [elseStatement] [0] "else" [1] " test6" [2] Object: [kakko()] [0] "(" [1] ")" [3] ";"

if( A ) の階層の中に if( B ) があり、if( B ) に係る else 構文は if( B ) の階層に入っており、その後 if( A ) に係る else 構文が if( A ) の階層にて続いています。

このようにちゃんと正しく認識されています。

認識の方法としては、

  • if の中に if が来る時などはオブジェクトの親子関係の「階層」を作成する。
  • 単文の終了を意味する ; やブロックの閉じ } が来たときはどの親子階層が終了するのかを区別する。
  • また else が来たときはどの親子階層の if に係るのかを区別する。

などの処理を行っています。

デモ4

「コンパイラがコンパイラを作る」というのはよく聞く話ですが、今回の構文解析でも「構文解析が自分自身を構文解析」することができます。

実力が試されるところです。

解析結果。

Object: [html] [0] Object: [kakko<>] [0] "<" [1] "!DOCTYPE html" [2] ">" [1] "\n" [2] Object: [kakko<>] [0] "<" [1] "html" [2] ">" [3] "\n" [4] Object: [kakko<>] [0] "<" [1] "head" [2] ">" [5] "\n"
(中略) [6] Object: [kakko<>] [0] "<" [1] "title" [2] ">" [7] Object: [kakko<>] [0] "<" [1] "/title" [2] ">" [8] "\n" [9] Object: [kakko<>] [0] "<" [1] "meta content=" [2] Object: [doubleQuote] [0] """ [1] "text/html; charset=UTF-8" [2] """ [3] " http-equiv=" [4] Object: [doubleQuote] [0] """ [1] "content-type" [2] """ [5] ">" [10] "\n" [11] Object: [kakko<>] [0] "<" [1] "script" [2] ">" [12] Object: [javascript] [0] "\nconsole.clear" [1] Object: [kakko()] [0] "(" [1] ")" [2] ";" [3] "\naddEventListener" [4] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] "load" [2] """ [3] ", " [4] Object: [functionExpression] [0] "function" [1] Object: [kakko()] [0] "(" [1] " e " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\tlet div = document.createElement" [2] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] "div" [2] """ [3] " " [4] ")" [3] ";" [4] "\n\t\tObject.assign" [5] Object: [kakko()] [0] "(" [1] " div.style, " [2] Object: [kakko{}] [0] "{" [1] "\n\t\t\tposition : " [2] Object: [doubleQuote] [0] """ [1] "fixed" [2] """ [3] ",\n\t\t\ttop : 0,\n\t\t\tright : 0,\n\t\t\tfontSize : " [4] Object: [doubleQuote] [0] """ [1] "small" [2] """ [5] ",\n\t\t\tpadding : " [6] Object: [doubleQuote] [0] """ [1] ".5em" [2] """ [7] ",\n\t\t\tcolor : " [8] Object: [doubleQuote] [0] """ [1] "gray" [2] """ [9] ",\n\t\t" [10] "}" [3] " " [4] ")" [6] ";" [7] "\n\t\tdiv.innerHTML = decodeURI" [8] Object: [kakko()] [0] "(" [1] " location.href.match" [2] Object: [kakko()] [0] "(" [1] " " [2] Object: [regExp] [0] "/" [1] "[^\/]+\/[^\/]+$" [2] "/" [3] " " [4] ")" [3] " " [4] ")" [9] ";" [10] "\n\tdocument.body.appendChild" [11] Object: [kakko()] [0] "(" [1] " div " [2] ")" [12] ";" [13] "\n" [14] "}" [5] " " [6] ")" [5] ";" [6] "\n\n\n" [7] Object: [functionStatement] [0] "function createStatement" [1] Object: [kakko()] [0] "(" [1] " name, parent " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\tlet statement = " [2] Object: [kakko{}] [0] "{" [1] "\n\t\tname : name,\n\t\tparent : parent,\n\t\tchildren : new Array" [2] Object: [kakko()] [0] "(" [1] ")" [3] ",\n\t" [4] "}" [3] "\n\treturn statement" [4] ";" [5] "\n" [6] "}" [8] "\n\n" [9] Object: [functionStatement] [0] "function allEscape" [1] Object: [kakko()] [0] "(" [1] " string " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\t" [2] Object: [lineComment] [0] "//" [1] "check." [2] "\n" [3] "\t\t" [4] Object: [ifStatement] [0] "if" [1] Object: [kakko()] [0] "(" [1] " typeof string === " [2] Object: [doubleQuote] [0] """ [1] "undefined" [2] """ [3] " " [4] ")" [2] " return" [3] ";" [5] "\n\t\t\n\tlet res = " [6] Object: [doubleQuote] [0] """ [1] """ [7] ";" [8] "\n\t" [9] Object: [forStatement] [0] "for" [1] Object: [kakko()] [0] "(" [1] " let ch of string " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\t" [2] Object: [ifStatement] [0] "if" [1] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] "[](){}/*.+|^$\\" [2] """ [3] ".indexOf" [4] Object: [kakko()] [0] "(" [1] " ch " [2] ")" [5] " > -1 " [6] ")" [2] "\n\t\t\tres += " [3] Object: [doubleQuote] [0] """ [1] "\\" [2] """ [4] " + ch" [5] ";" [6] "\n\t\t" [7] Object: [elseStatement] [0] "else" [1] "\n\t\t\tres += ch" [2] ";" [3] "\n\t" [4] "}" [10] "\n\treturn res" [11] ";" [12] "\n" [13] "}" [10] "\n\n\n" [11] Object: [functionStatement] [0] "function AL" [1] Object: [kakko()] [0] "(" [1] " array " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\treturn array" [2] Object: [kakko[]] [0] "[" [1] " array.length - 1 " [2] "]" [3] ";" [4] "\n" [5] "}" [12] "\n\n" [13] Object: [functionStatement] [0] "function LOMToText" [1] Object: [kakko()] [0] "(" [1] " LOM " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\tlet text = " [2] Object: [doubleQuote] [0] """ [1] """ [3] ";" [4] "\n\t" [5] Object: [forStatement] [0] "for" [1] Object: [kakko()] [0] "(" [1] " let child of LOM.children " [2] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\t" [2] Object: [ifStatement] [0] "if" [1] Object: [kakko()] [0] "(" [1] " typeof child === " [2] Object: [doubleQuote] [0] """ [1] "string" [2] """ [3] " " [4] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\t\ttext += child" [2] ";" [3] "\n\t\t" [4] "}" [4] " " [5] Object: [elseStatement] [0] "else" [1] " " [2] Object: [kakko{}] [0] "{" [1] "\n\t\t\ttext += LOMToText" [2] Object: [kakko()] [0] "(" [1] " child " [2] ")" [3] ";" [4] "\n\t\t" [5] "}" [3] "\n\t" [4] "}" [6] "\n\treturn text" [7] ";" [8] "\n" [9] "}" [14] "\n\n\n" [15] Object: [lineComment] [0] "//" [1] "===●●● main." [2] "\n" [16] Object: [functionStatement] [0] "function run" [1] Object: [kakko()] [0] "(" [1] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\n\tlet ta0 = document.getElementById" [2] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] "ta0" [2] """ [3] " " [4] ")" [3] ";" [4] "\n\t\tta0.style.backgroundColor = " [5] Object: [doubleQuote] [0] """ [1] "navy" [2] """ [6] ";" [7] "\n\t\tta0.style.color = " [8] Object: [doubleQuote] [0] """ [1] "white" [2] """ [9] ";" [10] "\n\n\tLOM = textToLOM" [11] Object: [kakko()] [0] "(" [1] " text0.value, ta0.value " [2] ")" [12] ";" [13] "\n\n\tconsole.log" [14] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] "//=== ここから debug1" [2] """ [3] " " [4] ")" [15] ";" [16] "\n\ttaLog.value = " [17] Object: [doubleQuote] [0] """ [1] """ [18] ";" [19] "\n\tlog = " [20] Object: [functionExpression] [0] "function" [1] Object: [kakko()] [0] "(" [1] ")" [2] " " [3] Object: [kakko{}] [0] "{" [1] "\n\t\tlet array = Array.from" [2] Object: [kakko()] [0] "(" [1] " arguments " [2] ")" [3] ";" [4] "\n\t\ttaLog.value += array.join" [5] Object: [kakko()] [0] "(" [1] " " [2] Object: [doubleQuote] [0] """ [1] " " [2] """ [3] " " [4] ")" [6] ".replace" [7] Object: [kakko()] [0] "(" [1] " " [2] Object: [regExp] [0] "/" [1] "^ +" [2] "/" [3] ", " [4] Object: [doubleQuote] [0] """ [1] """ [5] " " [6] ")" [8] ";" [9] "\n\t\ttaLog.value += " [10] Object: [doubleQuote] [0] """ [1] "\n" [2] """ [11] ";" [12] "\n\t" [13] "}"
(中略)
[23] "\n" [13] Object: [kakko<>] [0] "<" [1] "/script" [2] ">" [14] "\n" [15] Object: [kakko<>] [0] "<" [1] "style" [2] ">" [16] "\n" [17] Object: [kakko<>] [0] "<" [1] "/style" [2] ">" [18] "\n" [19] Object: [kakko<>] [0] "<" [1] "/head" [2] ">" [20] "\n" [21] Object: [kakko<>] [0] "<" [1] "body onload=" [2] Object: [doubleQuote] [0] """ [1] "run()" [2] """ [3] ">" [22] "\nHello World!" [23] Object: [kakko<>] [0] "<" [1] "span id=" [2] Object: [doubleQuote] [0] """ [1] "gage" [2] """ [3] ">" [24] Object: [kakko<>] [0] "<" [1] "/span" [2] ">" [25] "\n" [26] Object: [kakko<>] [0] "<" [1] "BR" [2] ">" [27] "\n" [28] Object: [kakko<>] [0] "<" [1] "input type=" [2] Object: [doubleQuote] [0] """ [1] "text" [2] """ [3] " id=" [4] Object: [doubleQuote] [0] """ [1] "text0" [2] """ [5] ">" [29] Object: [kakko<>] [0] "<" [1] "BR" [2] ">" [30] "\n" [31] Object: [kakko<>] [0] "<" [1] "textarea id=" [2] Object: [doubleQuote] [0] """ [1] "ta0" [2] """ [3] " cmt=" [4] Object: [doubleQuote] [0] """ [1] "disabled autocomplete=off" [2] """ [5] " style=" [6] Object: [doubleQuote] [0] """ [1] "\n\twidth : 100%;\n\theight : 45vh;\n" [2] """ [7] ">" [32] Object: [taggedByTextarea] [33] Object: [kakko<>] [0] "<" [1] "/textarea" [2] ">" [34] "\n" [35] Object: [kakko<>] [0] "<" [1] "textarea id=" [2] Object: [doubleQuote] [0] """ [1] "taLog" [2] """ [3] " cmt=" [4] Object: [doubleQuote] [0] """ [1] "disabled autocomplete=off" [2] """ [5] " style=" [6] Object: [doubleQuote] [0] """ [1] "\n\twidth : 100%;\n\theight : 45vh;\n" [2] """ [7] ">" [36] Object: [taggedByTextarea] [37] Object: [kakko<>] [0] "<" [1] "/textarea" [2] ">" [38] "\n\n\n" [39] Object: [kakko<>] [0] "<" [1] "/body" [2] ">" [40] "\n" [41] Object: [kakko<>] [0] "<" [1] "/html" [2] ">" [42] "\n"

ちゃんと正しく認識されています。


ところで、文字列認識の「正規表現」は /abc/i と書くことで文字列内に abc や ABC が存在している、その存在の手前の文字列や後の文字列なども参照できるという便利な機能です。

構文解析においては、正規表現の開始と終了を示す / という記号を「割り算」と混同しないように工夫をしなければなりません。

そういう「正しく認識」させるための工夫を最小限に抑えることが必要です。

あまり長くて複雑なプログラムを作っても、バグ修正は大変だし、あとからの機能拡張も大変になってしまうので、最小限を目指したほうがよいです。

この構文解析を行えると何が楽しいのかというと、

  • プログラムを作った後、構文解析を利用してフローチャートを自動的に作成できる。
  • RPG などのゲームプログラム内で使われる「簡易的なスクリプト言語」を、ブロック構造や if や for などの入れ子を正しく認識しつつ実行できる。
  • プログラミング言語を自分で作成できる。


(訪問者のどんなニーズと この記事がつながるか)

  • 構文解析の話を聞きたい。
  • プログラミングの話を聞きたい。
  • 日記を読みたい。

2025年3月2日

絵 デッサン人形

デッサン人形を買いました。

2体セットで、1280円税込でした。

箱から出して、そのまま何もいじらないで立たせたら、普通に「ジョジョ立ち」でした。

ゴゴゴゴゴ

なにィー!コイツ!!最初からジョジョ立ち!

荒木飛呂彦の世界。

ピシャンとさせました。

可動範囲を調べました。その1。

可動範囲を調べました。その2。

股の開きがもっと可動できれば、もっと表現の幅が広がるのにと思いました。

でもデッサン人形としては普通だと思います。

出来る限りの「クローズなポーズ」と、「オープンなポーズ」。

自由ポーズ。

ガンプラでも難しいとされる「片ひざ付きポーズ」をこなしました。

おんぶ。

「パパー!あれみてー!」的な。

冗談で終わりたくないので、ひとつ描きました。

「中世ヨーロッパの戦士」です。

女の子ばっかり描きたくないという思いがあるから、男らしい男を描いた感じです。

今後、私に画家の魂が少しでも入ってくれることを期待します。

もっとリアルな可動範囲抜群のデッサン人形も売られていますが、昔ながらの方が魂が入るかなと思って。

(訪問者のどんなニーズと この記事がつながるか)

  • デッサン人形ってどんなの?
  • 絵の話を読みたい。
  • 日記を読みたい。

2025年3月20日

MIDI 音楽の練習のしかた

練習のためのソフトウェアの準備

私が音楽(作曲)の練習をするときは、まず最初に以下のように準備を行っています。

  1. MIDI キーボードをパソコンの USB に接続、MIDI キーボードの電源を入れる。
    MIDI キーボードには音源が無いので、それだけでは音は鳴りません。
    MIDI キーボードは Amazon にて 15,800 円だったけど、今は 19,800 円に値上がりしています。
    いろいろ経済が不安定だからかな。Amazon 商品ページ
  2. 「Domino」を起動する。

    Domino にも音源はありません。
    Domino はキーボードから MIDI 信号を受け取って他の MIDI ソフトウェアに信号を送る役目があります。
    「Domino」はフリーウェアです。公式サイト
  3. 「MPC beats」を起動する。
    Domino から MIDI 信号を受け取り、ピアノの音を出す音源としての役割です。

    「MPC beats」は日ごろからこのためだけに使っていて、詳しい使い方は全然知りません。
    「MPC beats」はフリーウェアです。公式サイト
  4. 自作ソフトウェア「周波数 間隔表示」を開く。
    こちらも Domino から MIDI 信号を受け取ります。
    このソフトウェアは受け取った信号の内容によって、いろいろ処理を行うプログラムとなっています。

    適当に作ったものですが、ほしければどうぞ。これ
    ブラウザで開くものですが、file:// の URL だとたぶん動きません。
    ローカル(パソコン内)でウェブサーバーを起動するか、外部サーバーにアップロードしてから使います。
    画面の「START」ボタンを押すと開始します。(Web の制約でユーザーに何か操作させてからでないと開始できないため START ボタンを押します)
    その後、MIDI キーボードで鍵盤を押すと、信号を受け取り、画面表示が始まります。

以上の手順を踏むと、毎回の練習の環境が整います。

これでキーボードを弾き始めます。

自作ソフトウェア「周波数 間隔表示」の概要

上記の最後の手順 4 番で起動した自作ソフトウェア「周波数 間隔表示」は JavaScript で作られています。

鍵盤を「ド、レ」と押すと、ドとレの音の周波数としての差を画面に大きく表示します。

その差は単純計算で2なので、2と表示されます。

左下には下図のような2を表す棒グラフが表示されています。


つづいて、「ミ、ファ」と押すと、

レとミの音の周波数としての差は単純計算で2、

そしてすぐにファが押され、

ミとファの周波数の差は1なので、画面に大きく1と表示さます。

左下の棒グラフは「ド・レ・ミ・ファ」のそれぞれのあいだの周波数差ということで、2、2、1のグラフになっています。


「ドレミファソラシド」と押すと、それぞれの音のあいだの周波数の差が、左下に続けて並びます。

周波数差が表示されるから、それが なんだっていうのか なんだっていうのか っていうのか のか のか… ! は次に説明します。

「周波数 間隔表示」で初めて見えること

▼以上のシステムで「ロードモナーク」のメロディを演奏するとこのようになります。

[ミュージック・フロム・ロードモナーク/アドバンスド・ロードモナーク] エンディング(ロードモナーク)/ファルコム・スペシャル BOX’93/Copyright© Nihon Falcom Corporation

動画の左下、

このグラフから読み取れることは、

  • 引く鍵盤の前後の離れ具合(音の周波数差)の最大は5であり、それは2回ある。
  • 次の離れ具合の4も2回ある。
  • 次の離れ具合の3は3回ある。(紫で色分けしてあるもの)
  • 同じ音を繰り返し押している場所0は、9回。(最初の1回はダミー)

離れ具合5や4がそこそこあるのは、音の高低の起伏があるということで、楽しいものは跳ねるようにより楽しく、悲しいものは上げ落とし的どん底的によりショッキングになっているのかなぁと思います。

でも起伏がなければないで、流れるようなメロディということかなぁと思います。

とくに5がいきなり現れると、目が覚めるようなメリハリになっているように聞こえます。


離れ具合3を紫に塗った理由は、最初の3が音の印象として「悩ましいくびれ」みたいな印象があって、「3という離れ具合というのはそういうものなのか?」と思って他の3も紫で色を付けた次第です。

(色を付けるというのはプログラミングで行っていますが、プログラミングってそういう調べものを浮き彫りにさせることができる都合のいい道具なんですよね)

▼この2音目がくびれを思わせる。

(くびれと言われても多くの人には分からないと思いますが、ちょっとそんな感じがするんですよねぇ…)

ですが、調べたところ、3だからと言って必ずしもそうというわけではありませんでした。

ただし、

『黒鍵盤をはさんで音を鳴らしたときの周波数差は必ず2、4、6の偶数となり、"白鍵盤5と6や白鍵盤12と1などの黒鍵盤をはさまない場所" をまたいだときの周波数差は必ず1、3、5などの奇数となる』

…という特性があることに気づきました。

"白鍵盤5と6や白鍵盤12と1などの黒鍵盤をはさまない場所"  をまたいだ時に限って、半音ずれる(音と音の離れ具合が奇数となる)というのが「なんか意味あるのかなー」、「ありそうだなー」という次の考察へとつながっています。

アーニャ、大冒険の予感…!、じゃないけど。

グラフによりそういう観察と勉強ができるということです。

でもそれは副産物であって、このソフトウェアの本当の目的は次です。

「周波数 間隔表示」で絶対音感への手がかり


音階

隣の音階(前の2倍の周波数)

上の図では、ドレミファソラシドは、白鍵盤をたどって、1、3、5、6、8、10、12、隣の1、を押します。

白の1番と、黒の2番の周波数の差は 1.06Hz です。

白と黒を区別せず、個々の鍵盤の隣同士の差はどこでも 1.06Hz なんです。

だからド、レ(1,3)と押すと周波数の差は、黒鍵盤を はさんでいるから 1.06Hz と 1.06Hz で「2の開きがある」と言えます。

ミとファ(5,6)を押すと周波数の差は、黒鍵盤を はさんでいないから 1.06Hz のみで「1の開きがある」と言えます。

音楽の世界では1の開きのことを半音と言っています。2が全音(白鍵盤のみについて全音と言い、黒鍵盤の隣同士は全音の開きがあるとは言わない…かな?)です。


好きなメロディを頭に思い浮かべたとき、音と音の区別とそれが鍵盤のどこに対応するかは、周波数の開き具合を頼りに当てることができると思います。

音と音の差が2ならば、隣の白鍵盤だろう。

音と音の差が1ならば、5→6とか12→1とか、または白鍵盤から黒鍵盤へ移ったときだろう。

音と音の差が3ならば、黒鍵盤を含めて3鍵盤離れたところだろう。

というのが分かります。


作曲をするためには「絶対音感が必要」と言われています。

音を聞いただけで、「この鍵盤だ」とすぐにわかる才能のことです。


一般人にはそういう才能はありませんし、私にもありません。

だからこのソフトウェアを補助として使って才能を育てましょう、というわけです。


▼音の周波数幅を探るようにかみしめるようにメロディを追っている。

動画の途中で「キン!」と音を立てて画面が消えるところがあります。

MIDI のプログラムなので、鍵盤ごとにどんなプログラムにするのか自由にできます。

60個の鍵盤の右端の一番高い音を押すと、グラフをリセットする(消す)、というプログラムになっています。

動画の後半では先述の「くびれ」があるかどうかを調べています。(グラフの紫が連続しています)


そんなふうに練習を重ねて成果はあったのかというと、まぁたぶんありました。

今まで練習したことのない曲を頭に思い浮かべて、ここかな?と思って鍵盤を押したら当たったので、ちょっとは学べたみたいです。「ちょっとは学べた」とかクールに言っていますが、当てたときは「あれっ?!」と驚き喜びました。

ヘッドホンで曲を聴いているときも、音と音の違いがうっすらと分かる感じがします。

ほかのプログラムの案として、問題として2つの音を鳴らして、その周波数差を数字で入力して当てるというプログラムも作れば有効だと思います。


ここで一つ思うのは、仮にも音楽の世界なので、これを読んでヒントを得た人には、音楽の世界をぞんざいに扱わないでほしい、ということです。

『良い音楽家は「芸術家」であり
「職人」でもあります』
  と言われているので、少し距離を置いて大切に扱ったほうが良いと思います。


才能が無くて落ち込んでいる方、何をやってもダメでそのたびに嫌な思いをする。

今回も作曲に手が届くような話をしているけど、それはできる人の話であって、できない人は今回もできない。

そういう失敗の負のスパイラルというものがあり、そこから抜け出せないでいます。

抜け出すためには、ちょっとの才能の芽をほめる人、という存在が必要です。

人生の中でそういう存在に出会わなかったので、いつまでも抜け出せないでいます。

(才能の芽が無いのにほめるっていうのは結構まずいです。芽を確認してからほめる必要があります)

(才能の芽をせっかくほめても、全体がダメだからと蹴飛ばす人がいるのもまずいです。普通に折れますからね)

これについて ChatGPT と話した内容がこちら→

(訪問者のどんなニーズと この記事がつながるか)

  • 作曲の才能を育てるには?
  • MIDI の話を読みたい。
  • 日記を読みたい。

2025年3月30日

RPG開発 「試作品4」 スクリーンショット

2024年12月7日に試作品3を紹介して以来の試作品4の紹介です。

今回はスクリーンショットのみです。


タイトル画面。英語を訳すと、

”以降のプレビューは、日本の web6047 ウェブサイトによって、【すべての視聴者】向けに承認されています。

(勝手に)”

…と書かれています。

どっかで見たかもしれない画面です。

一時期の DVD の特典映像の前に表示されていた「レーティング表示」です。

アメリカ映画協会(the Motion Picture Association of America, Inc.)によるもので、

――略して MPAA、個人的にはムパーと呼んでいます――

緑色は「すべての視聴者」、青色は「一般の映画館放送を想定していない」、赤色は「R指定や18歳以上向け」という意味があるそうです。

冗談でマネてみました。実際、「全ての視聴者向けに」みたいなことを考えてゲームを作っているので、まんざら間違っているわけでもありません。


フィールド画面です。

4人行進。

左上に街があります。

こういうどこか古さを感じさせる画面は現在の若者たちにはあまり通用しないとも言えるので、のちのち 3D とか派手な画面にしないとな…と思っています。


フィールド上で街に乗ると街に入れます。

RPG って冒険が大切ですよね。

謎めいていて、その謎への期待を裏切らず、おどろきの展開があることが大切かな…


フィールド上でボタンを押すとコマンドメニューが開きます。

サブメニューは他のメニューとの不自然な重なりを避けるように自動的に位置調整されて配置されます。

プログラムはなるべく初心者向けに分かりやすく書こうとしているので、こういう高機能、複雑なプログラムはあまり良くないのですが、かといって遊びづらいのも良くありません。

両者を天秤にかけると初心者の方のテンションを上げるためには多少難しくても魅力あるシステムが良いと思って、この自動配置を選んでいます。


選んだコマンドを実行すると、そのコマンドを実行できます。

「ダース」は中学校の部活の先輩が好んで使っていたキャラ名です。

「シリウス」は小学生の友達がやはり好んで使っていたキャラ名です。

「ルック」と「シェネット」という名前は「ネーミングシステム」という自作ツールで自動的に作り出したものです。(後述)


フィールド上ではモンスターに遭遇します。

スライム3匹。

ほとんどドラクエですが、開発中はドラクエにして、リリース時はまったく別のシステムにしたいと思っています。

同じだと、「同じだ」と言われますからね。


シャドウと名付けたモンスターとスライム一匹。

モンスターのサイズを考慮して、位置を自然になるように計算しています。

幅の小さいものと大きいものが混じっていても、背景の左右の余白が同じになるようになっています。

しかしこの計算は初心者向けには不要かもしれません。

微妙な位置調整は、どうしても魅力のためには必要、というわけではありませんからね。


サキュバスというファンタジーモンスター。

いやーん、ばかーん、みたいな悪魔モンスターです。

モンスターのイラストがステータス画面に重なる「ブチ抜き」表現をしています。

漫画でキャラがコマの枠をはみ出すことを「ブチ抜きコマ」と言うそうです。

最初にこれをゲームでやったのは、ファミコン版ドラクエ1でした。

ドラクエの生みの親の堀井雄二氏は「週刊少年ジャンプ」の編集者?だったので、そういう発想があったのかもしれません。

サキュバスは昼間に登場するモンスターではないので、そこは考えたほうが良いかもしれません。

でも開発中ですからね。


ビッグ・フレイムと名付けたモンスター。

どうにもこうにも鳥山明ふうのイラストですが、影響を受けましたので…

とはいえ、影響元が1つだと「パクリだ」と言われる恐れが高まるので、影響元は2つ3つと複数導入するのが良いそうです。

これはジャンプの集英社が出した「描きたい!!を信じる 少年ジャンプがどうしても伝えたい マンガの描き方」という本の P128 で語られています。

絵柄は鳥山明ふうで良いとして、色塗りは水彩画にすればパクリ感が薄れそうですね。


「ルック」と「シェネット」という名前は「ネーミングシステム」という自作ツールで自動的に作り出したものです。

たとえばイースの「アドル」という名前について、「ア」と「ドル」に分割して、それぞれ表の行と列の見出しに置きます。

ジョジョの「ポルナレフ」は「ポル」と「ナレフ」に分割し、表の見出しに配置します。

そうやって名前の構成部位を表に並べ、行と列を結合すると、いまだかつて使われなかった「使える名前」が浮かび上がります。

アドルのアとポルナレフのナレフを結合して、「アナレフ」。まぁ、あっても良さそうな名前です。


その女性版。

使えそうな名前はオレンジ色で塗ってあります。

使えそうな名前を発見したら、そのセルをクリックすれば上部のフォームに入力され、submit を押すとオレンジ色になります。

random ボタンを押すと、オレンジ色からいくつかピックアップ。オレンジ色ではないものからいくつかピックアップします。


その敵キャラ版。

修飾詞と名詞で「ルインズ・ドール」(廃墟の人形)とか使えそうな名前が生成されています。

一つの言葉に対してその他多くの言葉へのつながり、と言えば最近の AI がそういうシステムでした。

網羅して、確率を付与すれば、ありそうな名前、ありそうな会話になるって寸法です。

そういう意味ではこの手法は話題の AI と同じものと言えそうです。

一応言っておきますが、この手法は AI 前に私が自分で発明したものなんですよ…。


こちらはアイテム名です。

「水属性の宝玉」。

これだけでドラマが生まれそうです。

イラストを描いてしまう、物語を作ってしまう、これ以上の無い回答を作ってしまう。そうやっていとも簡単に作ってしまうところはこの表は AI とよく似ています。

みんなが良いものを手にしてしまい、その価値が下がってしまう。

そのあたりもまぁ、AI と同じなんですかね。

この技術を隠さずに皆さんに披露する理由は、技術は独り占めするものではないからです。

このアイデア、自由に使ってください。


そういうわけで、以上で作成中の RPG、たぶんひとつの手順書で作った RPG の試作品は通算 7 作目くらいになるかな。その紹介でした。

手順書さえあれば、RPG の短時間での量産は可能なんです。

これもまた技術であり、独り占めするものではありません。

1 年か 2 年後くらいに手順書を公開出来たらなと思っています。


でも AI に負けそう。AIってこのままいくと普通に RPG 作っちゃいそうですよね。

その解説さえも簡単に行ってしまう…。40年がかりの web6047 の手順書なんていらんわ。

そんな AI を拍手で迎えましょう。人間たちの技術の集大成です。パチパチパチ。

うーーん、人間の領分ってあるんか?

(やる気はまだ無くなんないです)


あ、、忘れてました。手順書を公開すると自分のオリジナルのプログラミングで RPG を作ろうとしている人の夢を奪うんでした。

つまり、自分の力で考えて作るからプログラミングは面白いんです。

だれかの手取り足取りの指示で作ってもあんまりおもしろくないんです。

これを私は「技術のネタバレ」と呼んでいます。

小説を読んでいる人に、結末を教えると楽しみが大きくそがれてしまいますよね。それをネタバレと言います。

同じように、自分の力で考えて何かを成そうとしている人も言わば小説を読んでいるようなもので、その結末、技術の全貌、手順書を公開することは、その人の楽しみを奪う、ネタバレしていまう、ということになるんです。


だから、手順書は本当に力が無くて RPG を作りたいという人向けに公開するとして、その他に、自分の力でやれる人向けに、RPG の周辺技術を教えようかなと考えているんです。

その周辺技術さえもいらないけど、ノウハウはちょっとほしいという人にはさらに周辺の、たとえば JavaScript などのプログラミングの勘所を集めてまとめて公開しようかなと考えていたりします。

そういうケアを現在の AI はやっていないんですよね。

私がやろうとしているのに、なんで AI はやろうとしないのか。無神経なんすかね。

AI はプログラムで神経走ってないからしかたないとか…。