SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|

2022-08-03(Wed) ついにモータのコイルが焼けちまったところだぜ

  先日(7/24)、長らく愛用していたカリタのコーヒーミルのヒューズが飛んだ。普段から気に入った豆をちゃんと挽いて淹れたコーヒーを飲んでいると、それ以外のコーヒーが絶対にダメということはないものの、物足りなさを感じてしまうのである。ヒューズが飛んだのは初めてではないが、サッサとヒューズを手配しないとな、と思いつつ、とりあえず刃を外して掃除しておいた。

  翌日、唐突にミルの底に予備のヒューズがセットしてあったことを思い出す。しかし、高確率ですぐ飛んでしまうだろうなぁ、と思って回すと、案の定すぐ飛んでしまった。これは、本格的にオーバホールをする必要がありそうだ。

  カリタのナイスカットミルは、なんだかんだベストセラー機種なので、修理ノウハウも多いのではないかと思ってググると、案の定、事例がたくさんあり、モータをバラして修理した、という動画まで見つけた。なるほど。ブラシモータなのか。直流モータの構造に関する基本的な知識はある。というわけで、週末(7/30)に外でモータをバラしてみた。動画での指摘どおり、ブラシのカスであるカーボンの微粉が内部に溜まっていたので、ウェスで拭いまくって、組み立て直し、通販でヒューズを注文する。

  しばらくして(8/2)ヒューズが届く。早速、試運転。しかし、またヒューズが飛んでしまう。あー、もー。もう一度、念入りにオーバホールしてみるか。

  つうわけで、本日、改めてバラし、パーツクリーナでピッカピカにする。そして、試運転。しかし、またヒューズが飛んでしまう。むーん、そんならモータ単体なら動くのか? と思い、まったくギアを介さない状態で手に持って回すと、数秒回った後に、ヒューズが飛ぶと同時にネジ穴から火を吹いた……チーン。これはおそらく、モータのコイルのどこかが短絡状態になってしまっているのではないか。修理できるレベルではないな。諦めよう。

  画像の説明

  過去のメールを漁ると、購入したのは2011年12月の中旬頃。あ、記事にもしてたな。2万円強。まぁ、10年間以上も問題なく動いてくれていたのだから、文句を言うべきじゃないな。しかし、この色も形も性能も、愛していたのだがなぁ。

  で、ここしばらくはスーパーで粉のコーヒーを買って凌いでいたのだが、到底納得できるレベルではなく、そろそろ我慢も限界である。新しいミルの購入も視野に入れ、製品を探していたのだが、カリタのナイスカットミルは絶版。後継モデルはあるものの4万前後と倍もするし、なぜかオフィシャルサイトに掲載がなく不安しかない。というわけで、少し性能は落ちるらしいが、似たようなプロダクトであるボンマックのコーヒーミルをポチった。あー、早く届いておくれー。


2022-08-04(Thu) CoffeeScriptにて回転機能をmixinにより実装す

  意外と飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、考えれば考えるほど、既存のシューティングに対する気付きが増えていく。

  例えば、R-TYPEは初見でも1面はクリアさせたい作りなんだな、とか、ダライアスはボス以外の作りは適当だな、とかである。反面、ゼビウスは「最初は偵察機が登場する」とか「有人機は自殺的攻撃をしない」とか考えてあることに驚く。そうだよな。それは世界感を感じさせるために有効だよな。

  今回、自分は「ストーリ」を強く感じさせるゲームにしたいので、単に思いつきで敵を作るのではなく、それなりの理由に基づいて登場、行動させたい。そうなると、最初は偵察機だし、その後はミサイル攻撃かな、とか思うわけだ。

  で、ミサイルを作ったのだが、誘導したくなってしまい、斜めに飛ばしたくなってしまった。すると、回転機能が必要になる。先日のテトランはプリレンダだったが、汎用性を考えればリアルタイムレンダを実装するべきだ。しかもキャッシュ付きで、できるだけ軽く。

  とりあえず試験的にCoffeeScriptで実装してみたら、処理はだいぶ軽く、負荷的には問題なさそうだ。後は、どういうパラダイムで実装するか。現状、以下のようなオブジェクト構成になっている。

 class Plane					# 描画面
 class Obj extends Plane			# オブジェクト(スプライト)
 class Ship extends Obj				# 自機
 class Enemy extends Obj			# 敵
 class MissileA extends Enemy			# ミサイル
 class Fixed extends Enemy			# 地形に固定された敵
 class ReconA extends Fixed			# 偵察機

  回転するのは敵に限らず、あらゆるオブジェクトが回転可能にできるべきだ。しかし、そうすると多重継承が必要になってしまう。それを避けるには、一番の根っこであるObjクラスに実装してしまう方法も考えられるが、それは気が進まない。必要に応じて回転機能が追加できるのが望ましい。

  そういう場合、Rubyならミックスイン(mix-in)があるが、CoffeeScriptにはあるのかしらん……と、調べたらあるんだな。というか、正確には「後付けできる」といったところか。

 #---------------------------------------------------------------
 #
 #   クラスの mixin を可能にする
 #
 #       https://coffeescript-cookbook.github.io/chapters/classes_and_objects/mixins
 #
 mixOf = (base, mixins...) ->
     class Mixed extends base
     for mixin in mixins by -1
         for name, method of mixin::
             Mixed::[name] = method
     Mixed

  上記を冒頭に記述しておいて、

 class Rotnscl					# 回転拡大縮小機能
 class RmissileA extends mixOf Enemy, Rotnscl	# 敵+回転拡大縮小機能

  上記のようなオブジェクト構成にする。それにより、RmissileAは、敵であり、回転機能を持つ、という定義となる。これにて一件落着。

  次は、回転パターンのキャッシュについて考える。処理負荷的にリアルタイムレンダは可能な見通しだが、毎回、回転パターンを作るのは無駄なので、一度作った回転パターンは残しておき、二度目からは勝手にそれを使うようにしたい。つまりはキャッシュだ。

  キャッシュはどう実装されるべきか、といえば、そりゃオブジェクトタイプ、パターン、角度毎に残しておくべきで、個体(インスタンス)毎に行うべきではない。つまり、クラス変数に残しておくべきだ。クラス変数はCoffeeScriptにはあるのかしらん……と、思ったら、ちょっと微妙ではあるが、ある。

  クラスの中、メソッドの外に「@xxx」を定義しておき、メソッド内からは「@constructor.xxx」でアクセスする。ただし、利用はそのクラスのみに閉じ、継承したクラスからはアクセスできない。今回の場合、その仕様で問題ないが。

  画像の説明

  つうわけで、こんな感じで気持ちよくグルグルと回転している。いや、canvas要素には、ネイティブな回転機能はあるようなのだが、それはそれとして、実装するのが楽しいのだから、これでいいのだ。コードはこんな感じ。

 #---------------------------------------------------------------
 #
 #   回転拡大縮小可能オブジェクト(mixin用)
 #
 class Rotnscl
 
     rotnscl: (pats, n, v, t, w = null, h = null, dx = null, dy = null) ->
         pat = pats[n]
         w ||= pat.width;  dx ||= -(w >> 1)
         h ||= pat.height; dy ||= -(h >> 1)
 
         return(rpat) if(rpat = @constructor.rpats[rsym = "#{n}_#{t}_#{v}_#{w}_#{h}_#{dx}_#{dy}"])
 
         unless(pdat = @constructor.pdats[psym = "#{n}"])
             src_canvas = document.createElement('canvas')
             src_canvas.width  = pat.width
             src_canvas.height = pat.height
             src_context = src_canvas.getContext('2d')
             src_context.drawImage(pat, 0, 0)
             pdat = @constructor.pdats[psym] = src_context.getImageData(0, 0, w, h).data
 
         rot_canvas = document.createElement('canvas')
         rot_canvas.width  = w
         rot_canvas.height = h
         rot_context = rot_canvas.getContext('2d')
 
         rxys = Vec.v2vxy(v, t); hxys = Vec.v2vxy(v + 32 & 0x3F, t); vxys = Vec.v2vxy(v + 48 & 0x3F, t)
         _vx8 =   dx * rxys[1] - dy * rxys[0]  - (dx << 8)
         _vy8 = -(dx * rxys[0] + dy * rxys[1]) - (dy << 8)
         w8 = w << 8; pw = pat.width
         for y in [0...h]
             _hx8 = _vx8; _hy8 = _vy8
             for x in [0...w]
                 _hx8 += hxys[0]; _hy8 += hxys[1]                # 横方向加算
                 continue if(_hx8 < 0 or _hx8 > w8)
                 p = ((_hy8 >> 8) * pw + (_hx8 >> 8)) << 2
                 continue unless(pdat[p] + pdat[p + 1] + pdat[p + 2])    # 黒は透過
                 rot_context.fillStyle = "rgb(#{pdat[p]}, #{pdat[p + 1]}, #{pdat[p + 2]})"
                 rot_context.fillRect(x, y, 1, 1)
             _vx8 += vxys[0]; _vy8 += vxys[1]                    # 縦方向加算
         return(@constructor.rpats[rsym] = rot_canvas)
 
     cache_status: ->
         console.log([@constructor.name, Object.keys(@constructor.pdats).length, Object.keys(@constructor.rpats).length].toString()) if(tsc % 60 == 0)
 
 #---------------------------------------------------------------
 #
 #   回転オブジェクトサンプル
 #
 class Rsample extends mixOf Obj, Rotnscl
 
     Plane.image_src(@pats = [
         'images/rsample0.png'                   #  0:  46 x  46
     ])
     @pdats = {}; @rpats = {}
 
     constructor: (_s, x, y) ->
         super(_s, x, y)
 
     draw: ->
         n = 0                                   # パターンナンバ
         v = (tsc >> 2) % 64                     # 角度
         t = 0                                   # 倍率
         rpat = @rotnscl(@constructor.pats, n, v, t)
         @context.drawImage(rpat, @pos_x, @pos_y)
 
     d0: ->
         @cache_status()

  さて、別にミサイルはグルグル回したかったわけじゃない。意図した動きをプログラミングしていくことにしよう。


2022-08-06(Sat) 生誕51周年記念のパックマン

  昨年に引き続き、誕生日プレゼントにパックマングッズを贈られた。いや、嬉しいんだが、オレはパックマニア(?)ではないぞ。ナムコファンだが、ダライアス教に入信しているし、グラディウス派に属しているし、ファンタジーゾーンの住人だぞ。

  置物のように見えて、USBから電源を与えると、ナイトライトになるのだ。なかなか美しく光る。

  画像の説明 画像の説明

  が! なんで3匹なんだ。なんでピンキーがいないんだ。なんでアカベイが最後尾なんだ。パックマニアとしては気になって仕方ない。

  画像の説明

  以前に、パックマン柄の2種類のトランクスを買って、喜んで履いているのだが、その片方の柄は、アカベイ、アオスケ、ピンキーで、グズタがいない。もう片方の柄は、隊列を組んでいないのだが、ピンキーがいない。誰か! ちゃんと監修してやってくれ!

  というか、よく見るとこのライト「namco」のマークないな……まー、そういうことか。

  画像の説明

  念のため、オリジナルのドットパターンをオーバレイしてみたら、モンスタは1ドットの違いもなく正確であった。パックマンは口角の1ドットのみ違う。うぅーむ……うぅーむ!!


2022-08-24(Wed) パックマンのナイトライトを修正

  家族から贈られたパックマンのナイトライトだが、なんとなくググったら「買ったけど破損した状態で届いた」という報告を見つけた。併せて、一匹のモンスタがハズれた状態の写真が掲載されている。

  と、そこでヒラメいた。そんなに簡単に外れる仕様ならば、順序を入れ替えてしまえばイイじゃないか。特段パックマンに興味がない人にはどうでもいいことかもしれないが、パックマン好きとしてはこの隊列の順序に、なんともいえない違和感を拭えないのだ。

  画像の説明

  つうわけで、裏のネジを外し、各モンスタを固定しているツメを押し込んで外し、順序を変更した。すると、なんということでしょう。見るたびにイヤというほど違和感に満ち溢れていたアイテムが、心にシミ入る感じに変わっているではありませんか。まさに匠の技。

  ピンキーがいないのはどうしようもないが、そもそも彼女は待ち伏せる性格だから、きっと前方に居るのだろう。去年のアイテムから出張を願おう。よし。と。

  画像の説明

  しかし、こんどは右向きなのが気になりはじめてしまった。コーヒーブレイクの時もそうだが、追いかけられて逃げる方向は左と決まっているのだが……。


2022-08-26(Fri) 色々なノイズをジェネレートする

  シツコく飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、いくつか敵キャラクタを用意したところで、そろそろ効果音が欲しくなってきた。

  以前に「sox」というコマンドラインツールを使って効果音を作ったことがあり、少しイジり始めたのだが、音に対する自分の知識が少ないのに対して、ツールが多機能すぎるので、どうにも勝手がわからない。このパターンは……あー、いかんて、いかんて、次はサウンドエディタ作り始めちま……で、またもや「ヤクの毛刈り」の始まりである。

  で、正弦波、矩形波、三角波、鋸歯状波あたりを生成、合成や変調、周波数、音量を変化させるアルゴリズムを組んだ辺りで、ノイズの実装が必要になってきた。ん? ノイズって、種類があったよな。ピンクノイズとか。フムン。ブラウンノイズなんてのもあるのか。このパターンは……あー、いかんて、いかんて、次はノイズ生成エンジン作り始めちま……と、さらなる「ヤクの毛刈り」の始まりである。

  ホワイトノイズは簡単だ。ブラウンノイズは「ホワイトノイズの積分」らしい。基本、数学が苦手なオイラだが、試行錯誤しているうちにそれっぽいのがデキた。フタを開けてみればこれも単純だ。

  問題はピンクノイズ。1/fゆらぎの実装が必要らしい。1/fゆらぎといえば、このブログを始める前にUSB扇風機にPICマイコンを使ってそれっぽい制御を組み込んだことがあったな……などと、思い出す。弱い変化は頻繁、強い変化は稀、というヤツだ。が、結局よくわからず。ググッてどこかで見つけたサイコロ理論をアレンジして組み込んだ。

  答え合わせは、生成したノイズを「Audacity」に食わせ、周波数解析することで行う。まずはホワイトノイズ。

  画像の説明

  だいぶバラついているように見えるが、左の軸が自動調整により拡大されすぎているからだ。-21dB±1dBの範囲に収まっており、ホワイトノイズの特徴である「平坦なパワースペクトル」が実現できている。次はブラウンノイズ。

  画像の説明

  ブラウンノイズの特徴は「1オクターヴあたりパワーが6dB降下」である。言い換えると「周波数が倍になる都度パワーが6dB降下」。理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。最後はピンクノイズ。

  画像の説明

  ピンクノイズの特徴は「1オクターヴあたりパワーが3dB降下」である。言い換えると「周波数が倍になる都度パワーが3dB降下」。同じく、理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。

  というわけで、調べると他にも「ブルーノイズ」「パープルノイズ」などがあるようであるが、やりだすとキリがないので、今回はここまで。コードを置いておく。ノイズ生成のキモは「when」に続く各数行のみだ。ピンクはちょっとアクロバチックかな。

 #!/usr/bin/env ruby
 # coding: utf-8
 
 # create base.wav
 #   sox -n -r 44100 -b 16 -c 2 base.wav trim 0 1
 
 begin
     $LOAD_PATH.unshift('/usr/local/lib/ruby')
     require 'libwav'
 rescue LoadError
     raise
 end
 
 include Math
 
 ARGV.size < 2 and abort(<<USAGE % $0)
 Usage:
   $ %s base.wav white|brown|pink [sec]
 USAGE
 
 wav = NewWavFile.new(ARGV[0])
 wav.get_info[0].each {|l|
     puts(l)
 }
 type = ARGV[1]
 sec = (it = ARGV[2]) ? it.to_i : 1
 
 case(type)
   when('white')
     gain = Proc.new {
         (rand(0) - 0.5) * 65536
     }
 
   when('brown')
     gain = Proc.new {|g|
         g + (rand(0) - 0.5) * 1024
     }
 
   when('pink')
     gain = Proc.new {|g, pbuf|
         (9 - ('%b' % (rand(255) + 1)).length).times {|n|
             g -= pbuf[n]
             g += (pbuf[n] = (rand(0) - 0.5) * 8192)
         }
         g
     }
 
   else
     raise('Unexpected noise type.')
 end
 
 srand; g = 0; pbuf = [0] * 8
 (n_sample = wav.freq * sec).times {|n|
     wav[n] = [g, g]
     g = gain.call(g, pbuf)
     g < -32768 and g = -32768
     g >  32767 and g =  32767
 }
 
 wav.save_phrase(0, n_sample, '%s_noise.wav' % type)
 
 __END__

  プログラミングの歓喜を無限に味わうために。次のヤクの毛刈りのために。次の次のヤクの毛刈りのために……。