SVX日記
2022-08-03(Wed) ついにモータのコイルが焼けちまったところだぜ
先日(7/24)、長らく愛用していたカリタのコーヒーミルのヒューズが飛んだ。普段から気に入った豆をちゃんと挽いて淹れたコーヒーを飲んでいると、それ以外のコーヒーが絶対にダメということはないものの、物足りなさを感じてしまうのである。ヒューズが飛んだのは初めてではないが、サッサとヒューズを手配しないとな、と思いつつ、とりあえず刃を外して掃除しておいた。
翌日、唐突にミルの底に予備のヒューズがセットしてあったことを思い出す。しかし、高確率ですぐ飛んでしまうだろうなぁ、と思って回すと、案の定すぐ飛んでしまった。これは、本格的にオーバホールをする必要がありそうだ。
カリタのナイスカットミルは、なんだかんだベストセラー機種なので、修理ノウハウも多いのではないかと思ってググると、案の定、事例がたくさんあり、モータをバラして修理した、という動画まで見つけた。なるほど。ブラシモータなのか。直流モータの構造に関する基本的な知識はある。というわけで、週末(7/30)に外でモータをバラしてみた。動画での指摘どおり、ブラシのカスであるカーボンの微粉が内部に溜まっていたので、ウェスで拭いまくって、組み立て直し、通販でヒューズを注文する。
つうわけで、本日、改めてバラし、パーツクリーナでピッカピカにする。そして、試運転。しかし、またヒューズが飛んでしまう。むーん、そんならモータ単体なら動くのか? と思い、まったくギアを介さない状態で手に持って回すと、数秒回った後に、ヒューズが飛ぶと同時にネジ穴から火を吹いた……チーン。これはおそらく、モータのコイルのどこかが短絡状態になってしまっているのではないか。修理できるレベルではないな。諦めよう。
過去のメールを漁ると、購入したのは2011年12月の中旬頃。あ、記事にもしてたな。2万円強。まぁ、10年間以上も問題なく動いてくれていたのだから、文句を言うべきじゃないな。しかし、この色も形も性能も、愛していたのだがなぁ。
で、ここしばらくはスーパーで粉のコーヒーを買って凌いでいたのだが、到底納得できるレベルではなく、そろそろ我慢も限界である。新しいミルの購入も視野に入れ、製品を探していたのだが、カリタのナイスカットミルは絶版。後継モデルはあるものの4万前後と倍もするし、なぜかオフィシャルサイトに掲載がなく不安しかない。というわけで、少し性能は落ちるらしいが、似たようなプロダクトであるボンマックのコーヒーミルをポチった。あー、早く届いておくれー。
2022-08-04(Thu) CoffeeScriptにて回転機能をMixinにより実装す
意外と飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、考えれば考えるほど、既存のシューティングに対する気付きが増えていく。
例えば、R-TYPEは初見でも1面はクリアさせたい作りなんだな、とか、ダライアスはボス以外の作りは適当だな、とかである。反面、ゼビウスは「最初は偵察機が登場する」とか「有人機は自殺的攻撃をしない」とか考えてあることに驚く。そうだよな。それは世界感を感じさせるために有効だよな。
今回、自分は「ストーリ」を強く感じさせるゲームにしたいので、単に思いつきで敵を作るのではなく、それなりの理由に基づいて登場、行動させたい。そうなると、最初は偵察機だし、その後はミサイル攻撃かな、とか思うわけだ。
で、ミサイルを作ったのだが、誘導したくなってしまい、斜めに飛ばしたくなってしまった。すると、回転機能が必要になる。先日のテトランはプリレンダだったが、汎用性を考えればリアルタイムレンダを実装するべきだ。しかもキャッシュ付きで、できるだけ軽く。
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クラスに実装してしまう方法も考えられるが、それは気が進まない。必要に応じて回転機能が追加できるのが望ましい。
#---------------------------------------------------------------
#
# クラスの 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 # 敵+回転拡大縮小機能
次は、回転パターンのキャッシュについて考える。処理負荷的にリアルタイムレンダは可能な見通しだが、毎回、回転パターンを作るのは無駄なので、一度作った回転パターンは残しておき、二度目からは勝手にそれを使うようにしたい。つまりはキャッシュだ。
キャッシュはどう実装されるべきか、といえば、そりゃオブジェクトタイプ、パターン、角度毎に残しておくべきで、個体(インスタンス)毎に行うべきではない。つまり、クラス変数に残しておくべきだ。クラス変数は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周年記念のパックマン
昨年に引き続き、誕生日プレゼントにパックマングッズを贈られた。いや、嬉しいんだが、オレはパックマニア(?)ではないぞ。ナムコファンだが、ダライアス教に入信しているし、グラディウス派に属しているし、ファンタジーゾーンの住人だぞ。
2022-08-24(Wed) パックマンのナイトライトを修正
家族から贈られたパックマンのナイトライトだが、なんとなくググったら「買ったけど破損した状態で届いた」という報告を見つけた。併せて、一匹のモンスタがハズれた状態の写真が掲載されている。
と、そこでヒラメいた。そんなに簡単に外れる仕様ならば、順序を入れ替えてしまえばイイじゃないか。特段パックマンに興味がない人にはどうでもいいことかもしれないが、パックマン好きとしてはこの隊列の順序に、なんともいえない違和感を拭えないのだ。
つうわけで、裏のネジを外し、各モンスタを固定しているツメを押し込んで外し、順序を変更した。すると、なんということでしょう。見るたびにイヤというほど違和感に満ち溢れていたアイテムが、心にシミ入る感じに変わっているではありませんか。まさに匠の技。
ピンキーがいないのはどうしようもないが、そもそも彼女は待ち伏せる性格だから、きっと前方に居るのだろう。去年のアイテムから出張を願おう。よし。と。
2022-08-26(Fri) 色々なノイズをジェネレートする
シツコく飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、いくつか敵キャラクタを用意したところで、そろそろ効果音が欲しくなってきた。
以前に「sox」というコマンドラインツールを使って効果音を作ったことがあり、少しイジり始めたのだが、音に対する自分の知識が少ないのに対して、ツールが多機能すぎるので、どうにも勝手がわからない。このパターンは……あー、いかんて、いかんて、次はサウンドエディタ作り始めちま……で、またもや「ヤクの毛刈り」の始まりである。
で、正弦波、矩形波、三角波、鋸歯状波あたりを生成、合成や変調、周波数、音量を変化させるアルゴリズムを組んだ辺りで、ノイズの実装が必要になってきた。ん? ノイズって、種類があったよな。ピンクノイズとか。フムン。ブラウンノイズなんてのもあるのか。このパターンは……あー、いかんて、いかんて、次はノイズ生成エンジン作り始めちま……と、さらなる「ヤクの毛刈り」の始まりである。
問題はピンクノイズ。1/fゆらぎの実装が必要らしい。1/fゆらぎといえば、このブログを始める前にUSB扇風機にPICマイコンを使ってそれっぽい制御を組み込んだことがあったな……などと、思い出す。弱い変化は頻繁、強い変化は稀、というヤツだ。が、結局よくわからず。ググッてどこかで見つけたサイコロ理論をアレンジして組み込んだ。
だいぶバラついているように見えるが、左の軸が自動調整により拡大されすぎているからだ。-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__