Repository navigation
Releases: mokume-metal/mokume
Release list
v0.39.0
破壊的変更
createVideo(_:) で開いた動画が、縦に撮った動画 (iPhone など) を正しい向きで映すようになりました (#2451)。
動画ファイルが持つ回転の印 (preferredTransform) を当てるので、これまで横倒しで映っていた縦の動画が起き上がり、Movie.width・Movie.height・Movie.image の大きさも、当てた後の値 (縦と横が入れ替わったもの) になります。90° 刻みの回転と左右・上下の反転を当て、斜めの回転など向きとして読めない印は、これまでどおり記録された向きのまま映します。
| これまで | これから | |
|---|---|---|
| 縦に撮った動画 (1920×1080 に 90° の印) | 横倒しで映る。width は 1920・height は 1080 |
起きて映る。width は 1080・height は 1920 |
| 印の無い動画 | そのまま | 変わらない |
移行: 動画の向きを自分で直していた (rotate を掛ける、width/height を入れ替えて使う、など) スケッチは、その補正を外してください。外さないと二重に回ります。
公開の列挙型 ShaderSurface に .previousFrame の場合が増えた。
網羅した switch で ShaderSurface を分けているコードは、コンパイルが止まる。
移行: switch に case .previousFrame: を足す。ShaderSurface を値として渡すだけのコード (surfaces: ["pattern": .image(checks)] など) は変わらない。
switch surface {
case .image(let image): …
case .graphics(let canvas): …
case .previousFrame: … // 足す
}Sketch の文字列から断片を作る口を、makeShader から p5.js の createShader と同じ綴りの createShader に改めた。
createGraphics・createImage・createShape と並ぶ綴りで、旧い makeShader は残していない。引数 (_ body:・name:・values:・surfaces:)・返す Shader・投げる ShaderFailure は変わらない。
移行: スケッチと SketchPart の中の makeShader( を createShader( に置き換える。
// 前
stripes = try makeShader("float4 paint(Fragment in, Values values) { return float4(in.uv, 0, 1); }")
// 後
stripes = try createShader("float4 paint(Fragment in, Values values) { return float4(in.uv, 0, 1); }")Canvas.makeShader (canvas.makeShader(…)・createGraphics で得た描き場所の makeShader) は変わらない — 下の層は Swift の慣行 (make で始める) のまま残す。makeEffect・makeComputation・makeNumbers・makeParticles も変えない。
走っていない所 (init() の中など) から呼んだときに投げる ShaderFailure.notRunning(call:) の call は、"createShader(_:name:values:surfaces:)" になる。
絵は変わらない。 名前が変わるだけで、描く結果は同じである。
新機能
作ったばかりのスケッチを初めて mokume run / render / watch するとき、依存を取りに行く間に Resolving the sketch's dependencies… (the first one takes a while) と 1 行名乗るようになった。
これまでは Tool: の行の後、数秒のあいだ何も出ず、止まったように見えていた。名乗るのは依存の解決を始める回だけで、Package.resolved がある 2 回目以降の出力は変わらない。watch では、走らせている作品の速さの行を消してから書く。
どの口でも --help (-h) と help <口> で、その口の使い方だけが読めるようになった。
mokume render --help は、これまで Unknown option: --help と 60 行の全体の使い方を出して 1 で終わっていた。いまは render の使い方だけを標準出力に書いて 0 で終わる。new・examples・run・watch・render・mcp・bundle・doctor のどれも、ヘルプを頼まれたらビルド・診断・窓口の待ち受け・ファイルの作成をしない (doctor --help は診断を走らせない)。run . --help のように場所の後ろでも効く。口のヘルプは全体の使い方と同じ説明から作るので、2 つがずれない。
使い方を誤ったときは、誤りの文と See mokume help <口> for what <口> takes. の 1 行だけを出す。 これまで誤りのたびに全体の使い方 (約 60 行) が付いていた。知らない口は Unknown command: <綴り> と See mokume help for the commands. の 2 行になる。--x=値 を受けない決まりと、その案内の文は変わらない。
help と version が、後ろの引数を黙って捨てなくなった。 help <知らない口> は全体を黙って出さず Unknown command で断り、help run extra や version extra は余りを名指して 1 で終わる。help <口> はその口の使い方を出す。
mokume run / mokume watch の端末に出る速さの行が、直近 30 秒の推移を 1 秒 1 文字で添えるようになった。
Rate: 60.0 fps ██▆▆█·█ (release) のように、現在値の直後に低下と回復が読める。縦軸の上端はスケッチが宣言した frameRate で、noLoop() や止まっている間の秒は棒にならず · で示す。行は 1 行のままで、窓口 (mokume mcp) から起こしたスケッチの出力は変わらない。推移はライブラリが描くので、古い版の mokume に固定したスケッチでは出ない。
MIDI 機材の入力を受け取れるようになりました。
midiDevices() が繋がっている機材を初めて繋がった順に MIDIDevice の並びで返し (id は「機材名 #番号」で、抜いて挿し直しても同じ値になります)、createMIDI(_:) で 1 台を選ぶと MIDIPort が返ります。draw() の前に、前のフレームの後に届いたノートオン・ノートオフ・コントロールチェンジが port.messages に全部、届いた順に入り、コントロールチェンジの現在値は port.controlChange(1) (0〜1。まだ届いていなければ nil) で読めます。fill(color(hue: (midi?.controlChange(1) ?? 0) * 360, saturation: 90, brightness: 100)) のように、つまみ 1 本で円の色相を回せます (見張りが始まった後に動いたつまみの値は、後から作った入り口からも読めます。MIDI にはつまみの位置を尋ねる手段が無いので、見張りが始まる前の位置は、つまみを動かすまで nil です)。チャンネルは 1〜16 で数え、省けばどのチャンネルでも最後に届いた値を返します。plugins には何も書かず、許可も要りません。
機材を抜くと port.state が disconnected を名乗り、押していたノートの noteOff が最後に入ります (コントロールチェンジの現在値は残ります)。挿し直せば同じ物が running に戻ります。ピッチベンドなどほかのメッセージは「読めずに捨てた」と数えて診断に出します。クロックと出力は続けて足します。
createMIDI(messages:) は、記録したメッセージの列 ([MIDIMessage] をフレームごとに 1 組。nil のフレームは抜かれている) を流し、機材が無くても同じスケッチを確かめられます。MIDIMessage は Codable です。
Processing・p5.js の形の func mouseDragged() (引数なし) が、呼ばれるようになりました。
これまでは、手本のとおりに引数なしで書いてもコンパイルは通り、一度も呼ばれませんでした (量を受ける mouseDragged(deltaX:deltaY:) だけが呼ばれていました)。押したまま動いた 1 件ごとに、mouseDragged() が 1 回、続けて mouseDragged(deltaX:deltaY:) が 1 回呼ばれます (mouseMoved() と同じ順序と回数です)。どちらか片方だけを書いても、書いた側が 1 件につき 1 回呼ばれ、量付きだけを書いていたスケッチの呼ばれ方は変わりません。
func mouseDragged() { x = mouseX } // ドラッグで呼ばれ、x が mouseX に付いてくる引数なしの中で dragX / dragY を読んではいけません。 あちらはフレームの頭から足し込む合計なので、1 フレームに複数件届くと部分累計を足し込む形になります。1 件ぶんの量は mouseDragged(deltaX:deltaY:) の引数で受けます。引数なしの mouseWheel() は足していません (引数なしで読める正しい量が無いためです)。
Movie に、流す速さを変える speed(_:) を足しました (#2451)。
clip.speed(2) で倍速、clip.speed(0.5) で半分、clip.speed(0) で位置が進まなくなります。流している途中で変えても位置は跳ばず (変えた時点の位置から新しい速さで進みます)、毎フレーム呼んでもよいので、マウスの横の位置で速さを変える、といった使い方が 1 行で書けます。位置は引き続きスケッチの時刻から決まるので、固定の時計で書き出すと、同じフレームには同じコマが出ます。0 以上の有限の値だけを受け、負 (逆再生)・.nan・無限は速さを変えず、1 度だけ知らせます。
clip.speed(map(mouseX, 0, width, 0.25, 4)) // マウスの横の位置で 0.25 倍から 4 倍@Param の既定値をコードで書き換えると、つまみで合わせた保存値より勝つようになりました。
これまでは、既定値を 80 から 60 に書き換えても、つまみで 120 に合わせた保存が黙って勝ち、触っていない値の既定値を書き換えても保存の古い値に凍っていました。いまは保存 (.mokume/state/params.json) が書いた時点の既定値を併記していて、起動時にコードの既定値と比べ、変わった値だけ保存を捨ててコードの既定値で始まります。
- 捨てたときは、名前・捨てた保存値・前の既定値・新しい既定値を挙げて標準エラーへ 1 度だけ言い、観測の最初の応答 (
.mokume/params/report.json) のdiscardedにreason: "defaultChanged"で載せます (discardedのreasonの列挙が 1 つ増えました。スキーマの版は動かしていません) - 触っていない値の既定値を書き換えたとき (新しい既定値になります) と、合わせた値をそのままコードの既定値へ書き戻したとき (120 のまま) は、失うものが無いので黙ります
- コードを変えずに起動し直したときは、いままでどおり合わせた値が戻ります
- 既定値を併記する前の保存 (
defaultsの列が無い) は、いままでどおり名前と型で戻し、次に書くときに列を足します。新しい保存を古い版の mokume が読んでも、値はいままでどおり戻ります - 代償: 起動のたびに既定値が変わる宣言 (
@Param var seed = Double.random(in: 0...1)など) は、保存が毎回捨てられて効かなくなります。合わせた値を残したい宣言は、既定値を固定して書きます
mokume render --scale N で、座標を変えずに出す画素だけを縦横 N 倍にして書き出せるようになりました (#2441)。
1920×1080 で書いた作品を 4K で出すには、settings と絶対座標と線の太さをすべて書き換えていましたが、mokume render --fps 30 --seconds 1 --scale 2 --out out/f-####.png と打つだけで、構図はそのまま 3840×2160 の絵が書けます (印刷用に 4 倍なら --scale 4)。
width / height は倍率の下でも 1920 / 1080 のままで、線の太さ・効果の半径 (blur(radius: 10) は倍率 2 で 20 画素ぶん)・clip の矩形・断片が受け取る位置と大きさ・字 (textSize(24) は倍率 2 で 48 画素の大きさで焼きます) は、座標と同じく倍率を掛けて描かれます。実際に刻む画素の pixelWidth / pixelHeight と pixels・get・set の索引は、これまでの約束どおり実際の画素で数えるので倍率の画素になります (width で画素を回す作品は、倍率の下で絵が変わります)。倍率を書かなければ、また --scale 1 なら、これまでと同じ画素です。倍率ごとに決定論は保たれ (同じ倍率・同じ引数から同じ画素)、倍率 1 と 2 の画素は一致しません。大きな面のメモリが要ります。
倍率は 1 以上の整数だけで、--scale 0・--scale 1.5・倍率だけで描ける上限 (面の一辺 16384 画素) を越える指定は、ビルドの前に使い方の誤りとして止まります。作品の大きさを掛けて 16384 画素を越える組は、作品の大きさがコードの中にあるので、ビルドの後・最初のフレームを描く前に、掛けた大きさを名乗って止まります。pixelDensity の説明は「出す細かさの画素」から「座標の単位」へ言い直し、細かさと書き出しの倍率のどちらにもよらないと名乗ります。
音声ファイルを、フレームを止めずに読む requestSound(_:) を追加した。
requestImage(_:) と同じ async の口で、走っているスケッチの中で起こした Task から呼べる。selectInput(_:) で選んだ音声を Task { guard let path = await selectInput("音声を選ぶ") else { return }; song = try? await requestSound(path) } の形で、警告を出さずに読んで鳴らせる (これまでは Task の中で loadSound(_:) が notRunning を投げ、次の draw() で読むと「draw() の中の同期の読み込み」の警告が出た)。draw() の中で loadSound(_:) を呼んだときの警告も、requestSound(_:) へ案内するようになった。selectInput(_:) の説明も、Task の中で読むなら requestImage(_:)・requestTable(_:header:)・requestSound(_:) などの待つ口へ渡す、と直した。
断片が、前のフレームの絵を読めるようにした (ShaderSurface.previousFrame)。
残像やフィードバック (前の絵を少し薄めて、回して、描き足す) が、描き場所 2 枚を毎フレーム入れ替えずに書ける。surfaces: ["last": .previousFrame] と宣言した断片の中で mokume_sample(surfaces.last, in.place) と読む。
「前の絵」は、その面が次のフレームへ持ち越す絵である。フレームの終わりの effects() を通す前の絵で (効果を 2 度掛けしない)、noLoop() の後の redraw() では最後に描いた枚、1 枚目は透明になる。先に background() で消してから読んでよい。描き場所で当てれば、その描き場所の前の絵を読む。
払うのは、宣言した断片を当てた面だけで、面と同じ大きさの写し 1 枚と、毎フレーム全面の写し 1 回である。宣言しない面は何も変わらない。参照スケッチは echo-and-feedback。
SwiftUI のアプリの中にスケッチを映す SketchView を追加した。
VStack { Text("My Game"); SketchView(Game()) } のように置くと、Game が絵の面に映って動く。引数は @autoclosure で受け、ビューの同一性ごとに 1 度だけ作る (SwiftUI の StateObject(wrappedValue:) と同じ約束) ので、body が評価し直されても Game() は作り直されず、状態は消えない。NSApplication は回さず、アプリの寿命はホストのものに従う。同時に動かせるスケッチは 1 つで、Sketch.main() で動かす既存のスケッチの振る舞いは変わらない。
合成した音の発音と包絡を、音の時計の秒で先に予約できるようになった。
env.play(osc, 0.002, 0.03, 0.7, 0.1, at: t)・osc.play(at: t)・osc.play(440, 0.5, at: t)・noise.play(0.3, at: t) の at: に Sketch.soundTime を元にした秒を渡すと、draw() が呼ばれたフレームの区切り (60 fps で約 17 ミリ秒の粒) ではなく、その標本の頭から鳴り始める。
120 BPM の 16 分音符を刻んでも、音の間隔が 117 ミリ秒と 133 ミリ秒で交互に揺れず、書き出しでも実時間でも 125 ミリ秒ちょうどに揃う。書き出しでは同じ操作から同じ標本が出る。今の音の時計は新しい soundTime で読める (合成した音を作ってから 0 秒を起点に、書き出しではフレームの数、窓では実際に鳴った標本の数から進み、pauseTime()・jumpTime(_:) に動かされない)。at: は Double で、長く動かしても標本に揃う。これまでの play()・play(440, 0.5)・env.play(…) はそのまま使え、at: を省くか今より前の値を渡すと、これまでどおりすぐ鳴る。予約中の音に `st...
v0.38.0
新機能
空間の 2 点の距離 dist(x1, y1, z1, x2, y2, z2) と、3 成分の大きさ mag(x, y, z) を追加した。
3 次元の距離をピタゴラスの定理で手書きしなくてよくなる。
数は Float・Double・Int のどれでも渡せて、返すのは Float。平面の dist・mag と同じく、2 乗が溢れる大きさでも距離は有限で返り、数でない値・無限の値が混じったときは 0 を返して 1 度だけ注意を言う (平面の口とは別に数える)。
窓の大きさを var settings: SketchSettings { .size(800, 600) } と、型の名前を繰り返さずに書けるようにした (SketchSettings.size(_:_:frameRate:title:))。
.fullScreen() と同じ書き方で、Processing の size(w, h) に当たる。
1 秒あたりのフレーム数と窓の題名は .size(640, 360, frameRate: 30, title: "Waves") と続けて渡せる。描く細かさや窓の倍率など、ここに無い設定は従来どおり SketchSettings(…) を使うか、返った値へ代入して変える。
縦横と奥行きを同じ倍率で伸ばす scale(s) を追加した。
scale(2, 2) や scale(s, s, s) と軸の数だけ同じ値を書かずに、Processing と同じ scale(2) や scale(0.5) で書ける。
scale(s, s, s) と同じ変換を積むので、平面だけを描いているときは scale(s, s) と同じ絵になり、立体を置くときは奥行きも一緒に伸びる。負の値は反転で、0 は潰れる。
Vector2 / Vector3 を、描く口と変換の口へそのまま渡せるようにした。
circle(pos.x, pos.y, 40) や line(a.x, a.y, b.x, b.y) と毎回 .x・.y へ開かずに、circle(pos, 40)・line(a, b) と書ける。
足したのは circle(_:_:)・point(_:)・line(_:_:)・translate(_:)・vertex(_:) (Vector2 と Vector3 の両方。3 次元の口が無い circle は Vector2 だけ) と、距離の dist(_:_:) (Vector2 どうし・Vector3 どうし)。Canvas を直に呼ぶときも同じ口がある。成分を渡した数の口と同じ絵・同じ変換になる (dist は distance(to:) と同じ値)。Vector2 と Vector3 の両方がある translate・point・line・vertex は、translate([10, 20]) のように配列リテラルで渡すとどちらか決められず型が曖昧になる (コンパイルエラー)。translate(Vector2(10, 20)) と型を名乗る。
Vector3 に length・normalized()・limited(to:) を追加し、Vector2 との非対称を無くした。
3 次元のベクトルの長さを求めて正規化するのに simd を取り出さなくてよくなる。0 ベクトルの正規化は変えずに返し、2 乗が溢れる大きさでも有限の長さで測る (Vector2 と同じ約束)。
Vector2 と Vector3 に、Processing の PVector にある基本の演算も足した。2 点の距離 distance(to:)・内積 dot(_:)・補間 interpolated(to:amount:) は両方に、外積 cross(_:) は Vector3 に、向きの角度 angle (Processing の heading())・回転 rotated(by:)・角度から作る Vector2(angle:length:) (p5 の fromAngle) は Vector2 にある。名前は Swift の慣行に従い、新しい値を返す (rotated・interpolated)。角度はラジアンで、正の角度は画面の上で時計回りになる (rotate() と同じ)。
修正
save("x.svg") で、変換を当てた後の座標が約 1e12 以上の図形 (たとえば translate(2e12, 0) で置いた円) が、座標を 0 と書いて (0, 0) へ潰れた形で残っていたのを直した。
SVG の数は 1/1000 まで整数で書く都合で約 1e12 を越える数は書けず、書けない数を黙って 0 に置き換えていた。そういう図形は、0 へ潰さずに図形ごと SVG に書かず、大きさが原因だと分かる文面 (座標・線の太さ・破線の長さが 1e12 以上) で種類と数を知らせる (画面には出る)。線の太さと破線の長さが同じ大きさを越える図形も、塗りと輪郭の両方を図形ごと落とす (破線の有無・図形の種類によらない)。見えない線 (透明な stroke) の太さだけは数えないので、書ける塗りが残る。頂点の形の穴や弧のように 1 つの図形が複数の部品になるものは、揃って書くか揃って落とす。頂点ごとに塗りの色が違う形が落ちたときに、「最初の頂点の色で書いた」とも知らせていたのも直した。数でない座標の図形は画面にも出ないので、今までどおり知らせずに書かない。1e12 未満の座標の書き方は変わらない。
save("x.svg") で、有限の入力が拡大率との積で Float の上限 (約 3.4e38) を越えて無限になった図形 (たとえば strokeWeight(1e30) の線を scale(1e10) で拡大したもの、座標に 1e19 の拡大率を掛けたもの、破線の長さに拡大率を掛けたもの) が、数でない図形と同じに何も知らせず書かれずに消えていたのを直した。
そういう図形は、約 1e12 以上の座標・線の太さ・破線の長さを持つ図形と同じに、書かずに種類と数を知らせる。入力そのものが数でない・無限の図形 (translate(.nan, 0) など) は、画面にも出ないので今までどおり知らせずに書かない。
v0.37.0
破壊的変更
音の解析 (AudioIn・SoundFile・SynthSound の rms・decibels・level・spectrum・spectrumLevels) が、前のフレームからこのフレームまでの全標本の区間を見るようになり、区間の最大の振幅 peak を足しました (#3136)。
これまではフレームの終わりで終わる 1024 標本の窓 1 つだけを見ていたので、48 kHz で 24 fps なら 1 フレームの 2000 標本の約半分が、どの窓にも入りませんでした。立ち上がりが 10 ms ほどのキックが窓の外に落ちたフレームでは level が跳ねず、mokume render --fps 24 の書き出しや、実時間でフレームが落ちたときに拍を見落としていました。
値の意味は次のとおりに変わります。
| 値 | これまで | これから |
|---|---|---|
rms・decibels・level |
フレームの終わりの 1024 標本 | 前のフレーム以後の区間全体 |
peak (新) |
なし | 区間の標本の絶対値の最大 (振幅の生の値。全振幅の音で 1) |
spectrum・spectrumLevels |
1024 標本の窓 1 つ | 音源の標本 0 から 512 標本ごとに置いた窓のうち、終わりが区間に入るものの平均 |
- 低い fps (24・30 fps の書き出しなど) で、窓の外にあった立ち上がりに
levelが反応するようになります。逆に高い fps では区間が短くなります。60 fps は 1 フレーム 800 標本で、窓 (1024 標本) より短いので、rms・levelは窓 1 つのときと同じにはなりません。120 fps なら 400 標本で、低い音 (55 Hz の 1 周期は約 870 標本) のrms・levelは区間の中の位相で揺れます。滑らかな値が要るなら、spectrum(窓の平均)・energy(from:to:)を使うか、自分で平滑化してください - 1 フレームに解析する標本が増え、fps が低いほど窓の本数 (FFT) も増えます (24 fps で 1 フレームに 4 本ほど)。鳴っている音の数に掛かります
- 標本が進まなかったフレーム (開いた最初のフレーム・マイクに新しい標本が届かなかったフレーム) は、値が前のフレームのままです。
jump(_:)を挟んだフレームの区間は、飛ぶ前の位置で鳴り続けていた分で、飛び先の区間は次のフレームからです。1 度だけ鳴らして鳴り終わったフレームは、ファイルの終わりまでの区間を解析してから止まります。標本列の入り口 (createAudioIn(samples:sampleRate:)・createAudioIn(file:)) は、開いた最初のフレームには何も届かず (isNewFrameがfalse) 無音のままになります SoundFile・SynthSoundの値は読まれたときに解くので、draw()が読まなかったフレームの区間は解かれず、次に読む値にも混ざりません。各フレームの値はそのフレームの区間のものです。if 条件 { use(song.peak) }のように条件つきで読むと、条件が偽だったフレームのキックは落ちたままです。取りこぼしたくない値は毎フレーム読んでください (AudioInは毎フレーム解くので影響しません)SoundFileのpause()・stop()をdraw()から呼んだとき、止める前に鳴っていた分の区間が次のフレームで解析されてから、無音になります- 長く止まった後 (7.8 fps より遅いとき) は、区間の終わりから 6144 標本 (48 kHz で約 0.128 秒) までを解析します
- 書き出し (固定の時計) の鳴らし始めた位置は、鳴らし始めたフレームからの差ではなく、絶対のフレーム番号から求めた標本の位置の差で数えます。48000 Hz・144 fps のように 1 フレームが整数の標本にならないとき、載せる窓の継ぎ目と 1 標本ずれなくなります
移行: 立ち上がりを見たい level の使い方は、そのままで低い fps でも拾うようになります。区間に収まらない鋭い立ち上がりを逃さず使いたいときは、peak を直に使ってください (例: circle(width / 2, height / 2, 40 + song.peak * 400))。窓 1 つの値に頼って、level が低い fps で動かないことを前提にしていた絵は、見え方が変わります。
窓へ落としたファイルを、スケッチが受け取れるようになりました。
func fileDropped(_ paths: [String]) を書くと、ファイルを窓へ落としたフレームの頭で 1 回呼ばれ、落とされたファイルの絶対パスが並んで届きます。落とした所へ mouseX・mouseY が動くので、呼ばれた中から読めます (mouseMoved() は呼ばれません)。パスは loadImage・loadTable などにそのまま渡せます。
var photo: Image?
func fileDropped(_ paths: [String]) { photo = try? loadImage(paths[0]) }ファイルの URL だけを受けます (ブラウザから落とした文字列や URL は受けません)。macOS の自前の窓と、mokume watch の道具の窓で受けます。道具の窓では、1 回の落としが子へ渡る 1 行が 1 MiB (1,048,576 バイト・長いパスでおよそ 5000 本以上) を越えると、子へ流さずに捨て、mokume watch が 1 度だけ Dropped an input event of N bytes: … と名乗ります (分けて送ることはしません)。iOS と SketchView のホストからの受け方は未定です。ラベルを違えて func fileDropped(paths: [String]) と書くと、コンパイルは通りますが呼ばれません。 外から送る線 (.mokume/input・MCP の input) にも {"type": "fileDropped", "paths": ["/tmp/a.png"], "x": 10, "y": 20} が加わりました (paths は fileDropped でだけ読み、空の配列や、/ で始まらない要素 (空文字列・URL・相対パス) を含むものは、読めない 1 件として ignored に数えます。位置が有限でないときは位置だけを捨てて、パスは届きます)。スキーマの版は動かしていません。
公開の InputEvent を広げたので、破壊的な変更である。 InputEvent に場合 fileDropped(paths:x:y:) を足した。移行: InputEvent を網羅して switch していたなら、.fileDropped の場合を足す (落としたパスと位置を持つ)。値を組み立てる側は書き換えなくてよい。
ファイルを選ばせるダイアログを出せるようになりました。
selectInput(_:) は、Processing の selectInput(prompt, callback) に当たる口で、コールバックの代わりに async で、選んだファイルの絶対パスを返します。取り消すと nil です。
func mousePressed() {
Task {
if let path = await selectInput("CSV を選ぶ") {
table = try? loadTable(path, header: true)
}
}
}requestImage(_:) などと同じく、setup() かコールバックの中で Task を起こし、そこから呼びます。待っている間もフレームは回ります。ダイアログを出すのは窓を持つ側です — 直に走らせた窓ではその窓が、mokume watch の下では (スケッチは窓を持たないので) 道具の作品の窓が出します。1 度に待てる selectInput は 1 つで、待っている間にもう 1 度呼ぶと、前の待ちは nil で終わります。await が戻る時刻がどのフレームの境界か、noLoop() の間や render で届いた結果の扱いは、request… の口と同じく #2760 と合わせて決めるので約束ではありません。macOS の窓で出します (iOS と SketchView のホストからの出し方は未定)。保存先やフォルダを選ばせる口はまだありません。道具の窓が古い版だとダイアログは出ず、待ちは終わりません (外から結果を送れば終わります)。実行が終わった後に呼ぶと、待たずに nil を返します。
結果は入力の出来事 fileSelected として届くので、外から差し替えられます (.mokume/input と MCP の input): {"type": "fileSelected", "paths": ["/tmp/a.csv"]} で待ちが終わり、"paths": [] は取り消し (nil) です。省ける鍵 serial は、どの頼みへの答えかを表す頼みの通し番号で、道具の窓が子へ送る結果には載ります。番号が今の待ちと違う結果 (頼み直す前の古い答え・終わった待ちの送り直し) は黙って捨てられ、道具の窓は頼みが残っている間、落ちた結果を送り直します。省いて送る結果は今の待ちへ届きます。paths の無いもの・2 本以上・/ で始まらない要素を含むものは、取り消しとは読まず、読めない 1 件として ignored に数えます。待っている selectInput が無いときに送った結果は捨て、応答の warnings で名乗ります。スキーマの版は動かしていません。
公開の InputEvent を広げたので、破壊的な変更である。 InputEvent に場合 fileSelected(path:serial:) を足した (serial は省ける)。移行: InputEvent を網羅して switch していたなら、.fileSelected の場合を足す (選んだパス。取り消しは nil)。値を組み立てる側は書き換えなくてよい。
新機能
音を解析する AudioIn・SoundFile・合成した音 (SynthSound) に、低音の脈動・拍・波形を自作せずに描ける 3 つの口を足しました: energy(from:to:) (周波数の範囲の大きさ)・waveform (区間の終わりの 1024 標本)・isBeat (そのフレームの区間に拍があったか) (#3137)。
func draw() {
guard let song else { return }
if song.isBeat { flash = 1 }
flash *= 0.9
background(30 + flash * 200, 30, 40)
circle(width / 2, height / 2, 100 + song.energy(from: 20, to: 140) * 300)
}energy(from:to:)は 0〜1 のFloat。範囲に中心が入る帯域の振幅の平均を、levelと同じ dB の固定範囲 (-60 dBFS が 0、0 dBFS が 1) で写した値で、帯域の番号を自分で割り出さずに低音を取れる。逆順は入れ替え、はみ出す分は端へ寄せ、数でない値は 0 を返して 1 度だけ知らせるisBeatは、hop (512 標本) ごとのブロックのエネルギーが直近 1 秒の平均を急に上回ったところを拍にする。判定は標本の順にだけ進むので、同じ標本列からは、fps や届くチャンクの長さが違っても同じ標本位置で拍が出る (ファイル・標本列・合成音は固定の時計の書き出しで同じ拍で光る。マイクは到着が実時間で決まるので除く)。履歴は音が鳴り始めたときから、読まなくても毎フレーム溜める (どのフレームから読み始めても同じ。費用は標本あたり二乗和 1 回)。飛ぶ・頭から鳴らし直すと履歴を捨てて始め直し、止めた位置から続けたり、ループで頭へ戻ったり、エフェクトへ通したりしても履歴は続く。SoundFileの拍は音量を掛けた後の標本で判定する (音量を動かすと拍が出る・消える)。約 7.8 fps より遅いと、区間の上限で履歴を捨て続け、拍は fps に依る。1 つの区間に拍が複数あってもtrue1 回- 参照スケッチ
beat-and-waveformを足しました (低音で円が脈打ち、拍で背景が光り、下に波形が流れる)
push { … } で、閉包の間だけ変換とスタイルを積んで戻せるようになりました。
閉包を抜けると、途中で return しても投げても、積んだときの移動・回転・塗り・線へ戻ります。部品の早期 return が pop() を飛ばして、後に描く図形へ移動や色が漏れることがなくなります。描き場所 (createGraphics) でも同じく使えます。
対の崩れた push() / pop() を、書いた行を名指して 1 度だけ知らせるようになりました。 積んでいないのに pop() を呼んだとき (pop() at MySketch/main.swift:12 had nothing to restore …) と、push() を降ろさないままフレームが終わったときです。これまではどちらも黙って何もしませんでした。push()・pop()・pushMatrix()・popMatrix()・pushStyle()・popStyle() は呼んだ場所を受ける既定の引数 (fileID:line:) を持つようになりましたが、呼び方は変わりません。
固定の時計で .mov へ書き出すと、音の流れ全体 (再生した音声ファイル・合成した音・エフェクトを通した音) が動画の音声トラックに載るようになりました (#3125)。
これまでは映像だけで、絵は音に反応して動くのに、動画に音は入りませんでした。
mokume render --fps 60 --seconds 2 --out beat.mov- 音声トラックは LPCM・32 ビット浮動小数点・48 kHz・2 チャンネルで、時刻 0 から映像と同じ長さで始まる。
draw()で頼んだplay()・loop()・pause()・stop()・amp(_:)と合成した音の変更は、そのフレームの窓の先頭の標本から効き、鳴っている間のjump(_:)は次のフレームの窓の先頭から効く (飛んだ次のフレームでpositionが飛び先になる、今までの約束のまま)。窓の先頭はフレームの番号から直に求める (⌊N·48000/fps⌋) ので、144 fps のように 1 フレームが整数の標本にならなくても継ぎ目に欠けも重なりも無く、同じスケッチを 2 回書き出すと標本が全部一致する - 載るのは、録りを頼んだフレームの
draw()が終わった時点で音源があるスケッチの、固定の時計の.movだけ。音源が無いスケッチは今までと同じ (音声トラックを作らず、映像も変わらない)。実時間の時計・画面へ出す録り・MP4・GIF・連番の録りには載らず、音源があればそのことを 1 度だけ知らせる。録りの途中で作った音源は、音声トラックがあれば載り、無ければ知らせて載せない。マイク (AudioIn) は載らない - 音声の書き損じは、映像と同じ道で知らせ、
mokume renderの終了コードに現れる。最初の絵が書けなかったときと、窓の書き込みが断られたときは、音声トラックの書き込みを止める (窓が欠けたり二重に書かれたりしない)。fps が 48000 を越えて 0 標本になる窓は、書かずに飛ばす - 外のパッケージも、公開の
SoundSource(音を出す入り口の印) とSoundWindow(1 フレームぶんの窓) で、同じ口から音を載せられる - 音声トラックの長さは映像と同じ。書き出しで絵が描けず落ちた末尾のフレームは、窓は描くが動画の長さで切られるので、その音は動画に残らない。音源が窓を描けなかったこと (音の流れが続けて失敗するなど) は診断に 1 度だけ出て、
mokume renderの終了コードには現れない
save("plot.svg") で、描いた線を SVG として書き出せるようになった。
ペンプロッタやレーザーカッターへ渡すための、画素でなく線そのものの書き出しで、図形 1 つが <path> 1 つになり、線の太さ・色・端・折れ目・破線・不透明度は値として残る (流れ場の線 1 万本が折れ線 1 万個になる)。画面の絵は、書き出しを頼んでも変わらない。
書かれるのは、呼んだ時点から draw() の終わりまでで、PNG の save() が「頼んだフレームの完成した絵」を書くのと意味が違う。draw() の先頭で呼ぶのが作法で、呼ぶ前に図形を置いていれば 1 度だけ知らせる。setup() や keyPressed() で呼べば、次の draw() を丸ごと書く。
字・画像・保持した形・立体・画素の書き込み・シェーダ・通常以外の混ぜ方・切り抜きの下の図形は SVG に書けないので、捨てた種類と数を 1 度だけ知らせる (画面には出る)。貼る絵と勾配は塗りにしか効かないので、塗りだけを書かず輪郭は書く。background() の塗りは書かず、知らせもしない (SVG を開く道具の地の色になる)。ただし画面を塗り直す background() は、それより前に置いた図形も画面から消すので、SVG にも残さない。mokume render --out・連番の beginRecord("f-####.svg")・Image.save("x.svg") は .svg を受けず、作品の中の save("plot.svg") へ案内して断る。
mokume watch のプレビュー(道具の窓)に、走らせているスケッチが言った警告の件数と最後の 1 行が出るようになりました。
これまで警告の理由は端末にだけ出ていて、エディタと道具の窓を並べて端末を隠していると見えませんでした。件数はスケッチが起動してからの累計で、保存で子が入れ替わると数え直します。端末の行は今までどおり 1 度だけ出て、MCP の observe の warnings からも行は消えません。作り直しが通らなかったときは、プレビューの行の 2 行目に最初の <ファイル>:<行>:<桁>: error: <文> の行も出ます。
修正
加算 (.add) などで作った柔らかい粒が、奥行きへ散っても、手前の粒の透明な角で奥の粒を切らなくなった。
.wander で揺らした加算の煙に出ていた四角い欠けと、煙全体の暗さが消え、.wander を外した煙と同じく、丸い粒が重なるほど明るくなる。
順に依らない混ぜ方 (.add・.subtract・.multiply・.screen) で作った粒は、奥行きを読むが書かない。粒より前に描いた立体の奥にある粒はこれまでどおり隠れるが、粒の後に描いた奥の立体は粒の上に出るので、立体を先に描いてから particles(_:) を呼ぶ。.blend など順に依る混ぜ方の粒は変わらない。
立体の稜線を GPU の骨で広げた絵で、辺 2 本が画面で同じ向きへ折り返す先端 (視線を稜に沿わせたときの三角形の頂点など) の端の形が、CPU の帯と食い違っていたのを直した (#3165)。
CPU の帯は折り返しの先に太さの半分の √2 倍だけ延ばした長方形を置くが、GPU の骨は腕の向きが 1 ビット食い違って延びを落としていた。三角形 (...
v0.36.0
破壊的変更
引数なしの arc の輪郭が、扇の枠 (中心への 2 本の半径つき) から開いた弧になりました (#2428)。
Processing・p5.js の既定と同じで、塗りは今までどおり扇、輪郭は弧だけを引きます。noFill() で arc(200, 200, 160, 160, 0, .pi) と書くと、これまでは弧に加えて中心への半径が引かれていましたが、これからは弧だけが出ます。
あわせて、閉じ方 ArcMode (.open・.chord・.pie) を 7 つ目の引数として足しました。手本の OPEN・CHORD・PIE に当たります。
| 呼び方 | 塗り | 輪郭 |
|---|---|---|
arc(…) (6 つの数だけ) |
扇 | 開いた弧 (これまでは扇の枠) |
arc(…, .pie) |
扇 | 扇の枠 (これまでの既定と同じ) |
arc(…, .chord) |
弧の両端を弦で結んだ形 | 弦で閉じた形の周 |
arc(…, .open) |
弧の両端を弦で結んだ形 | 開いた弧 |
開いた弧の端には strokeCap の端の形が付き、strokeDash の破線にもできます。一周ぶんの弧は、どの閉じ方でも楕円と同じ形です。輪郭を引かない (noStroke()) arc の絵は変わりません。どの閉じ方も、これまでの arc と同じく塗りと輪郭を 1 つの図形として縁を滑らかに描き、閉じ方の違う arc を何個並べても 1 回の描画に収まります。.chord の弦と弧の継ぎ目は、扇の角と同じく strokeJoin によらず丸く繋ぎます。
createShape が返す Shape の bounds は、.chord・.open の arc では中心を含みません (弧の両端と弦の箱)。閉じ方を渡さない arc と .pie は、これまでどおり中心を含みます。noFill() や noStroke() にしても、texture()・shader()・strokeDash を付けても bounds は変わりません (閉じ方だけで決まります)。
移行: 輪郭付きの arc で、これまでの扇の枠が要るなら、7 つ目の引数に .pie を渡してください (arc(200, 200, 160, 160, 0, .pi, .pie))。絵はこれまでと同じです。noFill() で弧だけを引くつもりで書いていたコードは、書き換えなしで意図どおりの絵になります。
性能
不透明な box() や sphere() を奥から手前へ何層も重ねる 3D スケッチが速くなりました。
不透明と分かる塗りはブレンドを使わずに描くので、GPU が隠れた面の断片を省けます。絵は、下に書いた例外を除いて 1 画素も変わりません。半透明の塗り・texture()・shader()・blendMode() を使った塗りは、これまでと同じ描き方のままです。
不透明と分かる塗りには、box() や sphere() のほか、頂点を並べて作った形・createShape() で保持した形・loadModel() で読み込んだモデルも含みます。不透明度は 1 ちょうどの塗りだけが対象で、1 を超える不透明度 (LinearRGBA の alpha を直に書いた値) や半透明の頂点が 1 つでも混ざる列は、これまでと同じ描き方のままです。
ひとつだけ、絵が変わる場面があります。set() などで下地の画素に数でない値 (NaN) や無限の値を書いてあるとき、その上に重ねた不透明な立体は、これまでは数でない値に染まっていましたが、立体本来の色で出るようになります。下地が有限の値の画素は 1 画素も変わりません。
保持した形 (createShape) の立体の塗りを毎フレーム置くときの費用が減りました。
2 フレーム続けて置かれた形の頂点と添字を GPU の置き場に 1 度だけ持ち、使い回すようにしたためです。細かさ 96 の球を 1 つ置く場面で、影を再利用するフレームの draw が約 0.31 ms から 0.04 ms に、8 つ置くと約 2.0 ms から 0.05 ms になります (release・Apple M3 Max)。影を落とす形も、静止影の再利用の判定で頂点と添字の全バイトを読み直さなくなりました。絵は変わりません。
透ける色で置く形・絵を貼った形・断片や重ねる混ぜ方を載せた形 (裏面が絵に出うる形) も同じです。細かさ 96 の透ける球を 1 つ置く場面で、影を再利用するフレームの draw が約 0.22 ms から 0.02 ms に、8 つ置くと約 1.8 ms から 0.04 ms になります。透ける置き場所を多数置いて 1 回の描く呼び出しに畳む場面では、畳みの添字の組み直しが毎フレーム残るので、得は小さくなります (最大でも約 1.4 倍)。
線 (stroke) を含む区間・記録の中・粒は、これまでどおりの経路で描きます。
shader() や texture() を付けた細い rect / ellipse を、向きをばらばらにして何千個も置く絵が速くなりました。
1 画素より細い塗りの広げ方を CPU が 1 個ずつ組み直すのをやめ、GPU が置き場所ごとに広げるので、回転が全部違っても 1 つの雛形に畳まれます。絵は変わりません。保持した形 (createShape) の細い塗りは、これまでどおり置いた向きごとに 1 度組み直します。
光も周囲も置かない 3D スケッチで、box() や sphere() の塗りが速くなりました。
光の計算を抜いた専用の描き方で塗るためで、絵は 1 画素も変わりません。光・底上げの光・周囲を 1 つでも置いた絵は、これまでと同じ描き方のままです。専用の描き方は、初めて使うフレームで 1 度だけ組みます。
ドキュメント
mokume new が置く案内 (AGENTS.md) に、@Param の既定値を書き換えても絵が変わらないときは .mokume/state/params.json の保存値を疑う、という行を足した。
起動時に名前と型が一致する保存値がコードの既定値に勝つので、つまみで一度でも合わせた値は既定値を直しても残る。保存値を消す・書き換えるのは、合わせた値が失われるため人に確かめてからにする。
push() と pop() の説明の Note を、変換だけでなくスタイルの履歴もフレームを越えないと書き直した。
積んだ事実は draw() の頭で捨てられるので同じフレームの中で釣り合わせること、塗りや混ぜ方そのものはフレームを越えるので、push(); blendMode(.add) のまま pop() を忘れると次のフレームの頭から加算が効き続けることを書いている。振る舞いは変わらない。
この版の絵
描画が変わった PR が 1 本ある。
v0.35.0
破壊的変更
呼んだ時点の絵に効果をその場で掛ける filter([…])、絵ごとに掛けて新しい絵を返す Image.filtered([…])、2 値化の効果 Effect.threshold(level:) を足しました (#2455)。
filter() は呼んだ後に描くものには掛からず、掛けた絵は次のフレームへ持ち越します (Processing の filter() と同じ。フレームの終わりに掛けて持ち越さない effects() とは逆です)。ぼかした場面の上に、くっきりした計器や字を置けます。掛けた絵は残るので、filter() は図形・絵・背景と同じ「置いたもの」の寿命です — draw() の中のほか、setup() と止まっている間のコールバックでも掛けられ、setup() { image(photo, 0, 0); filter([.blur(radius: 6)]); noLoop() } と書けます (止まっている間は、次のフレームの入りになる効果を通す前の絵に掛け、画面の絵はそこから最後のフレームの effects() を通し直すので、何も描き足さずに redraw() しても画面は変わりません)。区間の外と形の組み立ての中では、effects() とは別の注意を 1 度だけ出して何もしません。Image.filtered(_:) は元の絵を変えません。作業の面は大きさが同じ絵どうしで使い回しますが (走っているスケッチが持ちます)、効果を通して読み戻す費用 (投入済みの描画が全部終わるのを待ちます) と新しい絵の確保は毎回かかります。.custom の効果が読む秒は、走っているスケッチの時刻です。threshold(level:) は R・G・B の最大が level (0…1) 以上の画素を白、未満を黒にし、アルファは動かしません。不透明度 0 の画素 (透明な地へ加算した光) は、ほかの効果と同じくそのまま通します。fill(128) に level: 128 / 255 のようなちょうどの値は白になります。
移行: 公開の列挙 Effect に場合 threshold が増えたので、Effect を switch で網羅しているコードは、枝が足りないとコンパイル時に指摘されます。case .threshold(let level): の枝を足してください。default: で受けているコードは変更が要りません。
窓に出して動かしている間の実時間の時計で、time と deltaTime が表すものが、「draw() が走った時刻」から「その枚が表示される予定の時刻」に変わりました (#2390)。
ディスプレイが渡す表示の目標時刻 (targetTimestamp) を、その枚の時刻にします。
これまでは、main がイベントや SwiftUI・タイマーで数ミリ秒遅れると、そのぶんが deltaTime の揺れとして作品に見え、表示の時刻とずれた細かなカクつきになっていました。60 Hz の窓で main が 5 ミリ秒ごとに 0〜3 ミリ秒塞がる場面では、deltaTime の標準偏差が 1〜3 ミリ秒 (最大の外れ 5 ミリ秒) だったのが、0.002 ミリ秒ほどになります。表示が刻みを飛ばした枚は、飛んだ枚数ぶん deltaTime が跳び、time は表示の時刻と合ったまま進みます。
- 表示の時刻と合うのは、その刻みに間に合った枚です。描き終えるのが遅れた枚は後の刻みで表示されますが、
timeは予定のままです - 共有面 (
mokume watchのプレビュー) のtimeは、スケッチ側の画面の刻みの予定の時刻です。揺れは窓と同じく取れますが、道具の窓に出る時刻とは 1〜2 刻みずれうります - 表示のリフレッシュが止まって予備のタイマーが進めた回と、名乗りのメニューが開いている間にプレビューを動かす回は、これまでどおり現在時刻で進みます。どちらの回でも
timeは前の枚より小さくなりません - 最初の枚は、これまでどおり
timeが 0、deltaTimeが目標の 1 フレームぶんです。setup()にかかった時間は 2 枚目に乗りません - 窓の数字 (fps・1 枚の ms) と観測の応答の
frameRate・frameTimeMsは、これまでどおり描いた時刻で数えます - フレームの時計 (
SketchSettings.clockを.framesにした窓・書き出し・観測の指定秒) の値は変わりません
移行: 多くの作品は何も直す必要がなく、deltaTime で動かしているものは滑らかになります。deltaTime の揺れに合わせて何かを補正していたり、time と draw() の実行時刻 (Date() や CACurrentMediaTime() など) を突き合わせていたりするコードは、time が表示の予定の時刻 (実行時刻より先の、表示の刻みに揃った値) になった前提で見直してください。実行時刻と合わせたい処理は、Date() などを直に読みます。
setup() を throws で書けるようになりました (#2401)。
読み込みや組み立ての失敗を try? で捨てて、Optional の資源と毎フレームの guard let で持つ代わりに、var cat: Image! と func setup() throws { cat = try loadImage("assets/cat.png") } と書けます。投げたら、draw() を 1 度も呼ばずにスケッチは起動の失敗として止まり (終了コードは 1)、標準エラーに The sketch could not start. と、投げた失敗の全文 (何を読めなかったかと、次にすること) が出ます。窓で走らせても mokume render で書き出しても同じ文面で止まります。投げる型は問わず、ImageFailure・ShaderFailure などをそのまま通せます。setup() で noLoop() を呼んでから投げても、以後のフレームは同じ失敗を投げ続け、入力は失敗したスケッチへ配られません (観測の応えにも理由が載ります)。窓の経路は、setup() で開いたカメラ・音・シリアル・ソケットを閉じてから終わります。
投げない func setup() を書いたスケッチは、書き換えずにこれまでどおり組めて、呼ばれます。async にはしていません (async にすると、既存の同期の setup() が呼ばれなくなるため)。
移行: setup() が投げたことを運ぶため、公開の列挙 RenderFailure に場合 setupFailed(reason:) が増えました。RenderFailure を switch で網羅しているコードは、枝が足りないとコンパイル時に指摘されます。case .setupFailed(let reason): の枝を足してください (reason は投げた失敗の全文です)。default: で受けているコードは変更が要りません。
新機能
draw(into: pg) { … } で、閉包の中の描く呼び出しが描き場所へ向くようになりました。
本体向けに書いた brush() のような関数を、pg. を付けて書き直さずに、そのまま描き場所へ描かせられます (draw(into: trail) { brush() })。閉包を抜けると描く先は本体へ戻り、入れ子にもできます。width / height は描き場所の大きさを返し、mouseX などの入力は本体の窓のままです。
フレームは pg.draw { } と同じ規則で開きます。ただし描き切りの失敗は投げず、警告で知らせます。Canvas の force も、Sketch と同じ可変長の形 (pg.force(dust, .gravity(0, 90), .drag(0.6))) で書けるようになりました。
揺らぎの種と細かさ・時刻・粒の刻みは、向き先が Canvas(target:gpu:) で直に作った面でも、閉包の間はスケッチのものを読み書きします。閉包を抜けた後も面のフレームが開いていれば、そのフレームが閉じるまで時刻はスケッチのままです。beginDraw() で開いたままの直に作った面も、開き直さずその続きとして走ります。orbitControl() の平行移動の量と usesFrameHistory は、向き先によらず本体の面で決まります。本体の面を渡したときは、フレームを開き直さず向き先だけを立てます。
値だけを受けて値を返す数と色の関数が、nonisolated の文脈から呼べるようになりました (#2389)。
map・lerp・constrain・norm・smoothstep・radians・degrees・damp・pingpong・lerpAngle・dist・mag・sq・fract・mod・bezierPoint・bezierTangent・color の各口・lerpColor・paletteLerp・色の成分の読み出し (red・hue ほか)・LinearRGBA.display(...)・作業空間の画素を表示形式へ移す OutputStage.encode(_:)・円周率の定数 (PI ほか) が対象です。
画素を計算するループを、DispatchQueue.concurrentPerform や TaskGroup で分けて回せます。1920×1080 の全画素を map と color で埋めるループは、release で 1 本から 64 分割にすると、246 ms が 23 ms になり、約 10 倍速くなりました (map の説明文に例があります)。
DispatchQueue.concurrentPerform(iterations: 256) { x in
_ = color(map(x, 0, 255, 0, 255), 64, 128)
}スケッチを書くだけなら、何も変わりません。注釈を書かずに、いつもどおり書けます。数でない値を渡したときの注意は、複数のスレッドから同時に呼んでも 1 度だけ出ます。スケッチの状態を読む口 (fill・circle などのメンバーと、種を読む noise) は main actor のままで、分けた先の閉包からは呼べません。
文字列を組版して描く口 shapedText(_:_:_:) と、組版した幅 shapedTextWidth(_:)・墨の範囲 shapedTextBounds(_:_:_:) を足した。
肌色の修飾子を付けた 👍🏽・国旗の 🇯🇵・ZWJ で繋いだ 👨👩👧・異体字セレクタを付けた ❤️・囲みを付けた 1️⃣ が、部品に分かれず 1 字で描かれ、結合文字 (e + アクセント) は基の字に重なる。ラテンの字にはカーニングが掛かる (#2404)。
既存の text()・textWidth()・textBounds() は変わらない (スカラ 1 つずつ並べ、部分の幅を足すと全体の幅になる)。描く・幅・整列・墨の範囲は、新しい口では 1 つの組版結果から出る。書体が覆えない字を CoreText が選ぶ代わりの書体は、text() と同じに字ごとに扱う (頼んだ太さ・傾きの面へ引き直し、面が無ければ textStyle() の警告で知らせる)。絵文字でない字から始まる書記素は、後ろに書体が持たない印や修飾 (異体字セレクタ 16・ZWJ・タグ文字・肌色の修飾子・囲み記号など) が付いても、基の字の書体を替えない。タブは text() と同じ送り幅で進む。右から左へ書く文字の並べ替えと文脈による字形の変化は保証せず、矩形への流し込み (text(_:_:_:_:_:))・折り返し (textWrap())・textOutline() は組版しない (スカラ 1 つずつのまま)。
mokume watch --keep-time を足した。
Swift を保存して作り直しても、新しい版の time が 0 へ戻らず、前の版が描いていた秒の続きから出る。pauseTime() で止めていれば、止めた秒のまま出る (#2414)。12 秒で一巡する作品の 8 秒目で止めたまま、色を書き換えて保存して詰められる。.metal の保存が時刻を残すのと揃う。
- 運ぶのは作者の時計の秒と、止めているかの 2 つだけ。
frameCount・乱数の列・deltaTimeで積んだ状態 (Countdownを含む)・作者の変数は運ばない - フレームの時計 (
SketchSettingsのclockを.framesにした作品) でも同じ。timeは引き継いだ秒から1/fpsずつ進み、frameCountは 1 から数え直す setup()で呼んだjumpTime(_:)・pauseTime()・playTime()は、前の版が止めていても回っていても引き継ぎに勝つ。setup()の中で読むtimeも前の版の続きになるsetup()が時計に触れなければ、最初の枚を出す時点の前の版の秒で始める (ビルドと起動の間に前の版で止めた・飛んだ分も届く)。前の版が落ちて居なくなっている・noLoop()で枚が止まっているときは、居なかった・止まっていた間は進めず、最後に書いた秒・窓に出ている枚の秒から始める- 時計を書くのは、止めた・飛んだなど必要なときと 0.5 秒おきだけで、本体の書き込みの口 (
AtomicFileとWriteLanes) で main の外に置く。書けない・読めないときは、原因と次にすることを 1 度だけ言う - 旗の無い
watch・run・renderは変わらず 0 秒から始まる。mokume renderで書き出した絵は変わらない
修正
scale() で拡大した字が、ぼけずに鮮明に出るようになりました。
これまで字は textSize の大きさの画素に焼いてから置いたので、scale(9, 9) の下の textSize(24) は、24 画素で焼いた字を 9 倍に引き伸ばした絵 (縁がぼけた字) になっていました。いまは「textSize × 変換の拡大」の大きさ (この例は約 210 画素) の画素で焼いて置くので、縁は鮮明なままです (#2446)。字形の形と並び (送り幅・整列・textWidth・textBounds・textOutline) は textSize のままで、拡大では変わりません。
焼く大きさは、変換の x・y の写りの最大の伸びを描く画素で測り (pixelDensity を下げた面では描く画素が粗いぶん小さく数え、細かさ 0.5 の scale(2) はこれまでどおり焼き直しません)、1 オクターブ 8 段の段に丸めたものです。拡大を動かし続ける絵 (scale(1 + 8 * pow(sin(time), 2)) のように語を膨らませ、縮める) は、段が変わるたびに焼き直します。焼く量は 3 つで頭打ちにし、どれも字は欠けず、低い段か近い段の画素で描きます。1 字の画素は 4 MiB まで (越える段は収まる段まで下げ、拡大が原因だと名乗って初回だけ知らせます)。1 フレームに新しく焼く拡大した字は 64 字・2 MiB までで、越えた字は焼いてある近い段 (無ければ textSize の大きさ) で描いて次のフレームで先に焼きます (描く順で後ろの字も飢えません)。拡大した字は焼き場の面の上から 4 分の 3 までで、拡大しない字の分を残します。この先送りは、窓に出しながら回していて、次のフレームがその絵を描き直すときだけです。絵が残るとき (noLoop() で止めたフレーム・save() や beginRecord() で撮るフレーム・mokume render や 1 枚の書き出し・時刻を指した 1 枚・観測が撮るフレーム・描き場所 (createGraphics) と保持した形 (createShape) の中) は、先送りした字を描き切る前に焼き直し、1 フレームの上限なしで要る段で焼き切ります。拡大が 1 以下の絵と、拡大を使わない絵は変わりません。rotateY などで傾けた字の遠近は、これまでどおり解けません (平面の層は奥行きを見ません)。
保持した形 (createShape) の中の字は、記録した時点の変換で焼きます。 記録の中で scale() した字はその拡大で焼きますが、記録の外で shape() に掛けた拡大では焼き直しません (拡大して置くと、これまでどおり焼いた画素の拡大になります)。
これまで「大きく出す字は textSize で」と案内していた説明文 (text・textSize) を、書き直しました。scale で伸ばしても、textSize を上げても、鮮明さは同じです。
detach(_:) で外した入り口を attach(_:) で足し直しても、足す前と同じに動くようになった。
これまで Gamepad・OSCPort・TextPort (TCPServer・TCPClient・WebSocketServer・WebSocketClient・UDPPort)・Movie は、外したことを最後と思い込んでいて、足し直した後に食い違っていた (#3122)。
-
Gamepad: 足し直した後も、gamepads()が同じ 1 台を返す。これまでは同じ機材のパッドがもう 1 台作られ、2 台が生きていた -
OSCPort・TextPortの仲間: 足し直した後もsend(_:)・write(_:)が送れる。これまでは受信だけが戻り、送るとThis ... is stoppedと知らせて送らなかった -
Movie: 足し直した後、stateと観測のinputsがrunningに戻る。これまでは映っているのにstoppedと名乗り続けた -
外して足し直した入り口は、外す前に届いて読まれなかったものを出さない。これまでは、足し直した最初のフレームに古いメッセージ・押されたままの釦・古い絵や音が出た (
OSCPort・TextPortの仲間・Serial・Gamepad・Capture・AudioIn) -
利用者が呼んだ
stop()は、足し直しても受け取りを戻さない (OSCPort・TextPortの仲間・Serial・Capture・AudioIn)。戻るのは、ランタイムが外しただけ (detach(_:)) のときだけ。受け始めるには新しく作る。走っている実行の外で呼んだstop()は、警告するだけで何も止めない -
実機のマイク・カメラも、外す前に届いて読まれなかった音・絵を、足し直した最初のフレームに出さない。これまでは、マイクの環に残った標本がそのまま「新しい音」として出た。止める手が、絵の列に積まれていた届きかけの 1 枚が済むまで戻るようになった
-
読めなくなっていた
Movieを外して足し直すと、足し直した直後に読みに行く。これまでは、約 1 秒のあいだ読みに行かず、絵が止まったまま動いていると名乗った -
足していない入り口に呼んだ
stop()は、何も止めない。これまでは、その入り口を後で足しても受け取らないままだった -
入り口が
close()で溜まりを捨てるためのExternalInput.discardWaiting()・ExternalQueue.discardWaiting()を公開した。 自作の入り口は、close()の中で呼ぶと、足し直した最初のフレームに外す...
v0.34.0
破壊的変更
トラックパッドのピンチと押した回数 (ダブルクリック) が、スケッチへ届くようになった。
ピンチはフレームの合計を pinch で読める (開くと正、閉じると負で、0.1 は「1.1 倍に開いた」)。orbitControl() は、ピンチで寄り (件ごとの倍率 1 + 量 を掛け合わせた分、距離がその逆数倍。1 件なら 1 / (1 + 量) 倍で、閉じる件が束ねられても効く) 、左ボタンのダブルクリックで、最初に orbitControl() を呼んだフレームの視点 (setup() で orbit に書いた視点か、既定の視点) へ戻る。戻すと慣性の速さも 0 になる。外から送る線は {"type": "pinched", "amount": 0.1} と、押下の clickCount (省くと 1) を運べる (amount は pinched で、clickCount は mouseDown でだけ読む。ほかの種別に付けると、これまでの知らない鍵と同じく応答の warnings で名乗り、その 1 件は通す)。
公開の InputEvent を広げたので、破壊的な変更である。 InputEvent に場合 pinched(amount:) を足し、mouseDown に押した回数 clickCount を足した。移行: InputEvent を網羅して switch していたなら、.pinched の場合を足す。.mouseDown(let x, let y, let button) と位置で取り出していたなら、.mouseDown(let x, let y, let button, _) と書くか、欄の名前で取り出す。値を組み立てる側は、clickCount の既定が 1 なので書き換えなくてよい。
Placement.scale が Float から Vector3 になり、軸ごとの倍率を持てるようになりました (#2460)。
Placement(x: 100, y: 200, scale: Vector3(1, 4, 1)) のように渡すと、shape(_:at:) で置いた形が、push()・translate(100, 200, 0)・scale(1, 4, 1) を同じ順で書いて置いたものと同じ絵になります。高さだけ伸ばした箱 (棒グラフの街並み・音の柱) を、箱 1 つの形から [Placement] でまとめて置けます。倍率は形自身の軸に掛かり、回転より先に効きます (大きさ → 回転 → 位置)。数でない値・無限の成分を含む置き場所は、これまでの scale と同じく置かれません。
どの軸にも同じ倍率で作る書き方は、そのまま使えます。Placement(x: 40, scale: 2) は Placement(x: 40, scale: Vector3(2, 2, 2)) と等しく、絵も変わりません (行列は 1 ビットも変わりません)。
壊れるのは、置き場所の scale を Float として読む・書くコードだけです。
| これまで | これから |
|---|---|
let size: Float = place.scale |
let size = place.scale.x (どの軸も同じ値で作ったなら、.y・.z も同じ) |
place.scale = 3 |
place.scale = Vector3(3, 3, 3) |
place.scale += 0.1 |
place.scale += Vector3(0.1, 0.1, 0.1) (1 軸だけなら place.scale.x += 0.1) |
canvas.scale(place.scale, place.scale, place.scale) |
canvas.scale(place.scale.x, place.scale.y, place.scale.z) |
place.scale * 2 は Vector3 を返します (Float を期待していた所は、軸を選んで読みます)。Placement(x: …, scale: 2) のように作る側の書き方は変わらないので、置き場所を作って shape(_:at:) へ渡すだけのコードは書き換えなしで組み上がります。
新機能
粒の塗りの断片が、その粒の年齢 in.particle.age (寿命に沿った 0…1) を読めるようにした。
出して最初に描かれるフレームで 0、寿命が尽きる直前に 1 へ近づくので、(1.0 - in.particle.age) を色に掛けるだけで、粒が寿命の終わりへ向けて薄くなって溶ける。これまでは粒の色と大きさが生まれたときのまま変わらず、寿命の終わりにぷつりと消えていた。
粒でない形 (平面・立体・字・画像) に同じ断片を当てたときは in.particle.age が 0 として読めるので、断片を使い回せる。粒を描くときに渡す幅は増えない (置き場所の空きに年齢を載せている)。年齢を使わない断片の粒の絵は変わらない。丸く柔らかい粒は、粒の板の in.uv が板の中で変わらないので、in.shapePosition.xy (板の中の位置) から作る。
周期のある動きを、何日走らせても粗くならない位相で書ける phase(period:) を追加した。
phase(period: 4) は、いまの時刻を 4 秒で 1 巡する位相 (0 以上 1 未満の Float) にして返す。time は Float なので約 1.5 日を超えると刻みが 15.6 ms (約 6 日で 62.5 ms) に粗くなるが、こちらは時計の中の Double の秒から取るので、刻みは周期の長さで決まる。負の秒 (jumpTime(-1) の後) も巡り、周期が 0 以下・数でない値・無限のときは 0 を返して 1 度だけ知らせる。time の説明にも、長く走らせたときの刻みの上限を書いた。
乱数の列を値として持てる RandomSource と、種と細かさを値として持つ揺らぎ NoiseField を追加し、Swift 標準の randomElement()・shuffled()・Float.random(in:) が種に従わないことを案内した (#2418)。
RandomSource(seed: 7, index: i) は花ごと・部品ごとに独立した列で、スケッチの random() を何回引いても動かない。RandomNumberGenerator に適合するので、Swift 標準の口に using: で渡せる。
flowers = (0..<200).map { i in
var r = RandomSource(seed: 7, index: i) // 花ごとに独立した列
return Flower(x: Float.random(in: 0..<width, using: &r),
y: Float.random(in: 0..<height, using: &r),
tint: palette.randomElement(using: &r)!)
}スケッチの列は randomSource として出る。palette.randomElement(using: &randomSource) は randomSeed(_:) と同じ列を引き、引いた分だけ次の random() が変わる。新しい乱数源を代入すると種を書いたのと同じで、形の組み立て (createShape) の中なら抜けると外へ残らない。using: を省いた Swift 標準の口は、これまでどおり種に従わず、走らせるたびに絵が変わる。 種の無い init() は置かない — 時刻から種を作る道を置くと、同じ入力から同じ絵にならず、記録した入力の再生でも再現できない。
NoiseField(seed: 100, octaves: 4, falloff: 0.5).value(x, y) は、同じ種と細かさのスケッチの noise(x, y) と同じ値を返し、スケッチの noiseSeed(_:) に触れない (板ごと・タイルごとに別の種で引ける)。断片の mokume_noise は、これまでどおりスケッチの種を読む。ドキュメントの「数の型」と、新しいスケッチに入る AGENTS.md に、種に従わない標準の口の案内を足した。参照スケッチ seeds-and-fields が、塵が点いたり消えたりしても 200 本の花が動かないことを示す。
Shape.bounds を追加した。
createShape { } で組んだ形や loadShape で読んだ SVG の、平面の部分の外接矩形を Rect? で返す。たとえば createShape { rect(10, 20, 100, 50) }.bounds は Rect(left: 10, top: 20, width: 100, height: 50)、circle(0, 0, 40) は (-20, -20, 40, 40) で、読み込んだ SVG の中心を原点へ寄せるなら shape(read, -box.center.x, -box.center.y) と書ける。ファイルを開いて確かめた viewBox の寸法をコードへ写さずに済む。
外接は幾何のもので、線の太さを含まない (strokeWeight(8) で描いても同じ)。基本図形はその形が描く幾何で外接する (arc は楕円全体ではなく中心と弧が作る扇の箱)。組み立ての中の変換は掛かった後の座標で外接し、group(_:) と + で繋いだ形は子の外接の和になる。立体は見ず、平面の部分が無い形 (Shape.empty・立体だけの形・塗りも線も描かない図形だけの形) は nil を返す。焼くときに求めて持つので、何度問うても費用は変わらない。参照スケッチ shapes-from-svg は、手で写していた viewBox の中心を bounds から求める形にした (絵は変わらない)。
音声ファイル (SoundFile) を、合成した音と同じエフェクト (LowPass・HighPass・BandPass・Reverb・Delay) に通せるようにした。
filter.process(song, 800) のように、process が受けるのは合成した音だけでなく SoundFile にもなり、曲にフィルタやリバーブを掛けた絵と音の作品が、曲を合成し直さずに書ける。process の引数の型は、SynthSound と SoundFile が準拠する新しい protocol EffectInput になった (Processing Sound の SoundObject に当たる。マイクの AudioIn は通せない)。
通した SoundFile は、エフェクトの出口を通ってだけ聞こえる。通すまでは今までどおり直に鳴り、鳴っている最中に通しても頭へ戻らず、play()・loop()・pause()・stop()・jump(_:)・amp(_:) と position・解析の値は通さないときと同じに効く。書き出し (固定の時計) では鳴らさずに、同じ設定から同じ出口の値を取れる。
代償が 3 つある。エフェクトの出口は中央なので、左右のある曲もモノラルになる。通した SoundFile は合成した音と同じ節の上限 (64) を 1 つ使い (スケッチから外すと返る)、埋まっていれば通さず 1 度だけ知らせる。標本化率がラックの 48000 Hz (書き出しでも同じ) と違う曲は、通すとラックの率へ読み直される (高い率の曲は、折り返しを抑える低域通過を掛けて下げる)。通した曲の鳴らし始め・止めは、合成した音と同じに約 5 ミリ秒かけて立ち上がる・消える。
鳴っている最中に通す瞬間だけは、直に鳴っていた音とラックの節が別の時計で進むので、つなぎ目で音が一瞬だけ細る (鳴らし始める前に通せば出ない)。書き出しで、標本化率が 48000 Hz でないファイルを、fps が 48000 を割り切れない (144 fps など) ときに通すと、position は通さないときと最大 1 標本ずれる。また、1 フレームに数千の音の変更を頼むなどで操作の列が詰まったときは、操作が落ちずに頼んだ順で載り、SoundFile の play() などの位置を動かす操作は、位置と一緒に詰まりが解けるまで控える。
修正
mokume bundle が、--out の直下にある <name>.app を、前に mokume が作ったものだけ作り直すようになりました。
これまでは中身を確かめずに消していたため、--out ~/Applications のように共有の置き場を指すと、同じ名前の無関係な .app (ただのフォルダでも) が中身ごと消えました。いまは Info.plist の mokume の印か、この版より前に束ねたものの識別子の一致で確かめ、確かめられなければビルドの前に場所を示して止まります (要らなければ手で消してください)。識別子を変えた作り直しも、印があるので通ります。シンボリックリンクは (先が mokume の出力でも) 辿らずに断り、前の出力は --out の中の一時の名前へ退けてから消すので、消す途中で止まっても <name>.app に半端なものは残りません。
回した線・図形の輪郭と、円・楕円の縁の濃さが、画素ごとに面積と一致するようになりました。
縁 1 本の濃さは縁から幅 1 の線形の渡しで出していたので、45° に回した既定の太さ 1 の線は置く位置で光の量が 0.92〜1.12 倍に、太さ 0.3 の回した輪郭は 0.80〜1.17 倍に振れていました。いまはどの置き方でも面積の ±3% に収まり、「1 画素より細い線は太さに比例した濃さで出る」という strokeWeight(_:) の説明どおりになります。細い線・塗りの枝との境目 (太さ 1 付近) で斜めの線が跳ぶこともなくなりました (既定の丸い端の線も、strokeDash の短い片も同じです)。回した形と円・楕円・弧の縁の画素が最大で 1 割ほど動きます (軸に沿った rect・線の絵は 1 ビットも変わりません)。
軸に沿った rect でも、曲がった部分 (rect(x, y, w, h, r) の角丸の角・strokeJoin(.round) の輪郭の外縁・.bevel の削ぎ) の縁は面積で数えるので、その画素が動きます (辺と尖った角の画素は動きません)。例外として、point() と、丸い端 (strokeCap(.round)) の線の端は、回しても、これまでどおりの縁の渡しのままです。曲線の縁ですが、既定の線を含む描画の費用を増やさないことと、回すかどうかで濃さが切り替わらないことを取りました (回した線の辺は面積で数えます)。
細い枝 (1 画素より細い線・塗り) との境目は、縁の形で測り方が違います。直線の縁 (rect・線) は、画素の正方形を縁の法線へ射影した幅 (45° で √2) で測るので、回した 1 画素前後の線が跳びません。曲線の縁 (円・楕円・扇・点・角丸の rect) は、向きによらない幅 (これまでどおり) で測るので、円や角丸の rect を回しても光の量は変わりません。細い枝は、丸い端をカプセルとして数えます (短い片の破線が端で多く出ていた分がなくなります)。
代償として、回した形と円・楕円・弧の縁の計算が増えます。1920×1080 で、回した面を覆う塗り 200 枚の GPU 時間が +25%、直径 700 の円 200 個 (塗り + 輪郭) が +41%、斜めの細い線 2,000 本が +14%、小さな円 4,000 個が +20% (0.053 → 0.064 ms) ほどです。軸に沿った rect・線だけの描画の費用は変わりません。基本図形のパイプラインが 18 本から 36 本に増えるので、起動の組み立ても増えます — Metal のコンパイルのキャッシュが無いとき (版を上げた直後の最初の起動) は 1.7 秒から 3.2 秒に、キャッシュがあるときは 10.8 ms から 12.1 ms になります。
mokume watch で、保存を続けて 2 度重ねたときに、前の世代の乗り換えを次の世代のものと読み違えなくなった (#2982)。
共有面の目録 (.mokume/viewport/surface.json) に、置いた子のプロセス番号 pid を任意の欄で足した (schemaVersion は 1 のまま)。道具の窓は、乗り換えの合図と、絵を受け取れない知らせに目録の番号を添え、見張りは待っている世代のものだけを受ける。これまでは、次の世代を待っている間に前の世代が 1 枚焼くと、その合図で待ちが下りて前の世代が畳まれ、次の世代が倒れても絵を受け取れなくても見張りは何もせず、窓には畳まれた世代の絵が凍っていた。
番号を書かない古いライブラリの子や、読めない目録は、これまでどおり「待っている世代のもの」と読む。
見張りが道具の窓へ前の世代を手放したと伝えるときも、手放す世代を子の番号で指すようになった。乗り換えを待つ子が 1 枚書いてすぐ死に、消えた知らせが窓の描き直しより先に来ても、窓は死んだ子のカーソルの頼みを読まない。
beginShape(.quads) の凹んだ 4 点を半透明の線で描くと、向かい合う辺の重なりを 2 回混ぜて濃くなっていたのを直しました。
同じ 4 点を quad() で描くと 1 回だけ混ざるので、描き方によって線の濃さが違っていました。いまは .quads の 4 点も quad() と同じ規則で、辺が交わらなければ線全体を 1 回だけ、向かい合う辺が交わる (砂時計の) 4 点は交点で 2 回混ぜます。1 つの beginShape(.quads) に並べた四角どうしは別々の輪郭なので、重なれば 2 回混ざります。beginShape(.polygon) は変わりません。
UDP の送り手 (OSC など) が、捨てた繋ぎから遅れて届いた失敗や待ちの知らせと、捨てた繋ぎの誤り無しの完了を読まなくなりました。
受け手が戻って送れたと読んだ後に、同じ送れない知らせが重ねて出る・待ちを知らせている最中に拒否へ読み替わって戻るのが遅れる・捨てた繋ぎの完了で待ちの知らせが早く戻る、といった形を、念のため塞ぐ守りです (実際に起きたことは確かめていません)。いまの繋ぎの失敗と待ちは、これまでどおり 1 度だけ知らせます。
SoundFile を detach(_:) で外して attach(_:) で足し直しても、もう一度鳴るようになった。
これまでは足し直した後が無音のままで、出力の機材は開けているのに No audio output could be opened と知らせていた (#3108)。
外している間は、鳴らす分の標本 (左右などチャンネルごとの標本) を持たない (解析用のモノラルの標本は持ち続ける)。足し直して鳴らし直すときに、読み込んだファイルからその分をもう 1 度読む。この読み込みは draw() を止める同期のものなので、draw() の中で起きれば 1 度だけ知らせる。エフェクトへ通すだけなら読み直さない。ファイルが消えた・別のものに替わった (標本の数・チャンネル数・標本化率が違う) などで読み直せなければ、鳴らさずに実時間で位置を進め、読めなかった事情とともに 1 度だけ知らせる。書き出し (mokume render) は鳴らさないので、位置は変わらない。
mokume watch の道具の窓が...
v0.33.0
新機能
外のパッケージの入り口・部品も、作者の時計の秒を読めるようにした (authorTime を公開した)。
時刻を指定した観測の枚では、供給の間の time が観測の秒になるので、再生位置や起点のような状態を time で進めると、観測が済んだ後も指定の秒が状態に残る。authorTime は観測の枚でも作者の時計の秒で、観測でない枚では time と同じ値なので、動画のような入り口は、状態を authorTime で進め、映すコマだけを time で決められる。time の値と振る舞いは変えていない。time の説明にも、観測の秒を読むのは draw() と、その直前の入り口の供給の間だと書いた。
3 次のベジェ曲線を 1 行で引く bezier() と、曲線の上の点・向きを返す bezierPoint()・bezierTangent() を追加した。
曲線に沿って物を動かすとき、3 次式を自分で展開しなくてよくなる (bezierPoint(60, 260, 640, 900, t) が t の位置の x、atan2(bezierTangent(420, 60, 600, 160, t), bezierTangent(60, 260, 640, 900, t)) が進む向き)。
名前と引数の並びは Processing・p5.js と同じで、bezier(x1, y1, x2, y2, x3, y3, x4, y4) は始点・制御点 2 つ・終点の順に渡す。bezier() は beginShape()・vertex()・bezierVertex()・endShape() を順に呼んだのと同じ絵になる。bezierPoint()・bezierTangent() は Sketch の外からも呼べる軸ごとの関数で、t が 0 なら始点、1 なら終点がちょうど返り、0…1 の外は締めずに曲線を延ばす。数でない値が混じったときは絵へ NaN を通さず、1 度だけ注意を言う。
入力の要求の綴りの誤りを、応答の warnings で名乗るようになった。
これまでは {"type":"mouseDown","x":1,"y":2,"buton":1} の buton が黙って捨てられ、左クリックとして通っていた (#2843)。いまは同じように受け取ったうえで、events[0] の位置・知らない名前・読む鍵の一覧を warnings に載せる。要求の最上位の余りの鍵 (evnts など) も同じで、events が欠けて読めない要求でも名乗る。要求は失敗させず、出来事も ignored へ回さない。
知らない鍵の無い応答は、これまでと同じ形のまま (warnings の鍵ごと書かない)。schemaVersion は 1 のままである。出来事のスキーマには additionalProperties: false を足したので、スキーマで要求を検める書き手は、知らない鍵を送る前に弾ける。
JSON から文字・整数・真偽・配列を取り出せるようになり、最上位が配列の JSON も読める (#2466)。
JSONObject に getString・getInt・getBoolean・getJSONArray・hasKey・keys を足し、新しい型 JSONArray は for-in で回せる (要素は JSONObject。count で数え、数・文字・真偽は getFloat(i)・getString(i)・getInt(i)・getBoolean(i) で読む)。読む口は loadJSONArray・requestJSONArray (ファイルも URL も)、通信で届いた文字を読む parseJSONObject・parseJSONArray。公開のデータの多くが持つ配列を、Foundation に降りずに読める。
let quakes = try loadJSONObject("https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson")
for f in quakes.getJSONArray("features") {
let c = f.getJSONObject("geometry").getJSONArray("coordinates")
let p = f.getJSONObject("properties")
circle(map(c.getFloat(0), -180, 180, 0, width), map(c.getFloat(1), 90, -90, 0, height), p.getFloat("mag") * 4)
if p.getFloat("mag") > 5 { text(p.getString("place"), 20, 20) }
}欠けた値・種類の違う値は落ちない。 無いキーや違う種類の値は、空の値 (getString は ""、getInt は 0、getBoolean は false、getJSONArray は空の配列) を返し、キーごとに 1 度だけ理由を知らせる (表の getString と同じ)。getInt は丸めない — 2 と 2.0 は 2、2.5 と Int に収まらない数は 0 を返して知らせる。keys は辞書の順ではなく文字の順に並ぶので、同じ JSON から毎回同じ絵ができる。
最上位が配列の JSON を loadJSONObject に渡したときの失敗 (malformed) は、理由が loadJSONArray を案内するようになった。
XML は、要素の中の文字 getContent()・属性の文字 getString(_:)・最初の子 getChild(_:) を読めるようになった (RSS の item.getChild("title").getContent())。無い子や属性は空の値を返して 1 度だけ知らせる。
エージェントの窓口 (mokume mcp) が、繋がった時点の挨拶 (initialize) の instructions で、立てた場所とそこがスケッチかどうかを名乗るようになった。
Package.swift の無い場所で起こした窓口は、その旨と mokume mcp <スケッチのディレクトリ> で起こし直す手を、最初の observe が答えるのを待たずに伝える。スケッチの場所で立てた窓口は、場所を言う 1 行だけを名乗る。serverInfo と protocolVersion は変わらない。
loadModel が、読み込んだ OBJ・STL に壊れた行や面があれば、そのファイルにつき 1 度だけ診断で知らせるようになりました。
読めない v・vn・vt・f の行、OBJ の仕様に無い語の行 (綴りの誤りの vv 1 2 3 など)、STL で読み飛ばした面が対象で、何を・何回・最初はどの行かを並べます (SVG の読み込みと同じ形)。これまでは、壊れた行も mtllib や usemtl のような意図して読まない行も同じ skippedLines の数に混ざり、知らせが出るのは面が 1 つも無いときだけでした。mtllib・usemtl・o・g・s など意図して読まない行だけなら出ません。Model.skippedLines の意味と値は変わりません。
OSC のフェーダーやセンサーの連続値を、宛名から最新の 1 つで読める osc.latest("/1/fader1") を追加しました。
osc.latest("/1/fader1")?.float(0) のように引け、毎フレーム列を回して変数に預ける定型が要りません。draw() の前に更新され、何も届かないフレームは前の値のままです。ワイルドカード (/1/*) は受けず、宛名は文字どおりに引きます。osc.messages は今までどおり、届いたものを全部、届いた順に渡します。
message.int(0) が、整数の値の小数 (1.0) も読むようになりました。TouchOSC のボタンや TouchDesigner は、整数のつもりの値も既定で小数で送るため、これまでは数えられませんでした。0.5 や NaN・無限・Int に収まらない値は、丸めずに今までどおり nil です。
送るだけの createOSC(send: ("127.0.0.1", 7000)) も追加しました。受けるポートを開かないので、TouchDesigner へ値を送るだけのスケッチが、使っていないポートを選ばずに済みます。
3 色以上の段から色を引く paletteLerp(stops, amount) を追加した (#2424)。
区切りは (色, 位置) の並びで、amount が位置の間にあれば両隣の色を lerpColor で混ぜる。熱さの段や地形の色を t から引くのに、区間を探して lerpColor を書き分けなくてよい。
let sky: [(LinearRGBA, Float)] = [
(color(20, 30, 80), 0), (color(200, 60, 60), 0.6), (color(250, 220, 120), 1),
]
for y in 0..<Int(height) {
fill(paletteLerp(sky, Float(y) / height))
rect(0, y, width, 1)
}区切りの規則は勾配 (Gradient) と同じ (位置の小さい順に安定に並べ直す・手前は最初の色・先は最後の色のまま伸びる)。amount は lerpColor と同じく 0…1 に締める。混ぜる空間は lerpColor と同じ線形で、p5.js の paletteLerp (今の colorMode() の空間で混ぜる) と中間の色が違う — 黒と白の真ん中は red() で約 187.5 になる。区切りが空なら透明な色、位置が数でない区切りは捨て、amount が数でなければ最初の色を返して、1 度だけ知らせる (落ちない)。
画素の面 pixels に、全画素を 1 つの関数で書き換える pixels.update { x, y, color in … } を追加した。
閉包は位置と元の色を受けて、書く色を返す。書いてよいかの判定と GPU の決着を入口で 1 回しか行わないので、pixels[x, y] の 2 重ループで同じことを書くより速い。意味は添字と同じで、置いてよい区間の外と形の組み立ての中では、閉包を呼ばずに 1 度注意して何もしない。
Image にも pixels を追加した。Canvas.pixels と同じ Pixels を返すので、カメラの絵を camera.image.pixels.update { … } で書き換えられる (Image は参照型なので、書き換えは同じ 1 枚を変える。取っておくなら先に copy() する)。
SketchRuntime(sketch:gpu:clock:persistsParams:) に persistsParams を足した (#1832)。
false で組むと、合わせたつまみの値の保存 (.mokume/state/params.json) を読みも書きもせず、@Param は宣言した既定値で動く。既定は true で、これまでどおり保存から戻し、変わった値を書く。
撮り直せば同じ絵になることを名乗る書き出し (同じ番号のフレームを描く参照スケッチの --render・説明文の例を撮る example-shots) は、撮った人の手元にだけ在る保存で絵が変わらないよう、persistsParams: false で組むようにした。自分で書く書き出しのループも、同じ絵を撮りたいときは false で組める。
線を破線で引く strokeDash(length, gap) を追加した。
長さと間隔は画素で、strokeDash(4, 4) の後に引いた線と図形の輪郭 (line()・rect()・ellipse()・arc()・triangle()・quad()・beginShape() の開いた線と閉じた輪郭) は、長さ 4・間隔 4 の破線になる。noStrokeDash() で実線へ戻す。
端の形 (strokeCap) は破線の 1 つずつに付き、模様は折れ目と図形の始点をまたいで続く。長さを 0 にして丸い端で引くと点線になる。塗りは破線にならず、輪郭を引かない図形と同じに残る。pushStyle() / popStyle() で他の線のスタイルと同じく戻り、createShape() の中で指定した破線は形に焼き付く。負の値・数でない値・無限・両方 0 の指定は、実線で引いて 1 度だけ知らせる。
奥行きを持つ線 (line(x, y, z, …) と奥行きのある頂点の形) は破線にならず、実線のまま。格子の補助線を実線と破線で描き分ける参照スケッチ dash-and-grid を足した。
Release の tarball を展開した mokume が、自分の版を 0.30.0 (release e48a07f) のように名乗るようになりました。
これまでは Homebrew の置き場でしか版が分からず、tarball から入れたものは --version も MCP の serverInfo.version も local build (日時) で、doctor は依存の版との食い違いを名指しできませんでした。版と commit は tarball に同梱の mokume.release から読むので、展開した場所を別のディレクトリへ移しても変わりません。Homebrew は同じ tarball を入れるため、0.30.0 (Homebrew) のまま名乗ります。手元でビルドしたものは今までどおり local build で、同梱のファイルが無い・壊れているときも同じです。
修正
loadShape(_:) が、大文字で書いた preserveAspectRatio (XMINYMID meet など) を、読めない値として知らせ、書いていないときと同じ中央寄せで写すようになった。
SVG の属性の値は大文字と小文字を区別するので、xMinYMid 以外の綴りはブラウザでも読めない値になる。これまでは小文字にそろえて読み、左寄せで描いていた。defer・none・meet・slice の綴りも同じ。fill="RED" などの CSS の値や、単位・transform の関数名の大文字は、これまでどおり読める。
mokume bundle が、開き方の紙を署名より前に書くようになりました。
これまでは紙が書けない回 (--out に同じ名前のディレクトリがあるなど) に、署名済みの <name>.app が残ったまま 1 で終わっていました。いまは署名の段に進む前に同じ失敗で止まります。あわせて README に、名前の検めを通っても組み立ての後で止まりうる形 (長さ以外で APFS が断る名前・パスが 1024 バイトを越える深い --out・exFAT / FAT32 の置き場) を書きました。
loadShape(_:) が、読まずに黙って消していた CSS の幾何の性質と変換を、捨てたものとして知らせるようになった。
rect の width・circle の r・path の d などを style 属性や <style> で書いた SVG、CSS の transform・rotate などを書いた SVG、<svg> 要素の transform 属性を書いた SVG は、これまでは何も知らせずに書いた値で描かれなかった。いまは CSS property "width" や <svg transform="rotate(45)"> の形で知らせる。描き方は変わらない (属性の値で描き、変換は掛けない)。
loadShape(_:) が、CSS の全体キーワード (initial・unset・revert・revert-layer) を、inherit と同じく勝った宣言として読むようになった。
これまでは fill: initial のようなキーワードを読めない値として知らせて捨て、下に書いた値 (fill="red" など) で描いていた。いまは CSS のとおり、initial は性質の初めの値 (fill なら黒・opacity なら 1)、unset は受け継ぐ性質なら親の値・そうでなければ初めの値になり、revert・revert-layer は unset と同じに読む。clip-path: initial などの描けない効果は、効果なしとして知らせない。根の <svg> の width: inherit は auto と同じに読み、形の長さの属性 (<rect x="unset">) は知らせて描かないのをやめて 0 や auto と読む。
detach(_:) で出口を外すと、外す直前に描き終えた最後の 1 枚が、その出口へ渡らないまま閉じていた。
出口へは描き終えた絵が 1 枚遅れて渡るためで、40 フレーム目の draw() で外すと、フレームの番号を控える出口は 1 から 38 で終わり、39 を一度も受け取らなかった。外す出口にだけ、描き終えた絵を渡してから閉じるようにした (40 フレーム目で外すと 1 から 39 を受け取る)。ほかの出口は今までどおり次のフレームの終わりで同じ絵を受け取り、二重にも欠けもしない。連番を書き出す出口でも、最後の 1 枚が欠けなくなった。
指定した書体が覆えず別の書体から引いた字 (太字を持たない欧文の書体に混ぜたハングル・タイ文字など) が、頼んだ太さ・傾きの面で描けないときの警告が、引いた書体の家族に頼んだ面が無いことと、その字を頼んだ面で持つ書体の例 (textFont() に渡せる名前。環境から引き、見つかったときだけ) を名乗るようになりました。
mokume が面を引き損ねたのか、家族が持たないのかを読み分けられます。字の描き方は変わりません (細い面のまま)。
エージェントの窓口 (mokume mcp) の道具が、引数の読み方を 2 つ揃えた。
省略できる欄の null は省いたのと同じに読むようになり、observe の time・count・every に null を渡しても道具の誤りにならない (型の違う値 "3"・true は今までどおり断る)。反対に、道具が知らない名前の引数は、黙って捨てずに道具の誤りで断る。observe に {"scal": 0.5} と綴りを違えて渡すと、いままでは黙って等倍で撮っていたが、知らない名前 (scal)...
v0.32.0
破壊的変更
資材の名前を探す口は、候補の場所の親のフォルダに入れず在るかを確かめられないとき、「見つからない」と言って資源の宣言を確かめるよう勧めるのをやめ、そこで探すのを止めて、入れなかった場所と OS の事情を名乗るようになりました (#2838)。
以前は、assets に入る権利を外す (chmod 000 assets) と、その下に置いたファイルが在っても AssetFailure.notFound (「資源の宣言を確かめよ」) になり、置いたファイルを探させる的外れな助言が出ていました。入れない親の下では、在るファイルと無いファイルの区別が付きません。
公開の AssetFailure に unreachable(path:location:reason:) を足しました。assetURL(_:) が投げます。location には確かめられなかった場所、reason には OS が名乗った事情 (Permission denied・symlink が巡る・宛先の無い symlink) が入ります。文面は "assets/notes.txt" cannot be reached at "/Users/me/sketch/assets/notes.txt": cannot tell whether it exists (Permission denied) の形で、「見つかった」とは言いません。loadTable・loadStrings などの文字のファイルを読む口と、画像・モデル・断片・音・動画を読む口は、それぞれの型の unopenable(path:location:reason:) で名乗ります (形は変えず、reason が cannot tell whether it exists (…) で始まります)。本当に無いときは、今までどおり notFound と探した場所の一覧です。
入れない候補に当たったら、後ろの候補に本物があっても採りません。 入れない場所は在るかもしれない場所で、黙って飛ばすと原因に辿り着けないためです。宛先の無い symlink も、無いものとは数えず、辿り着けないものとして名乗ります (以前は無いものとして次の候補へ進んでいました)。途中の段にあるもの (assets が外したドライブへの symlink など) も同じです。包みの置き場を数えられない (読む権利が無い) ときも、どこにも無かったときに「見つからない」とは言わず、数えられなかった置き場を名乗ります。作業ディレクトリは候補の先頭なので、そこに入れないと、後ろの束の資源に本物があっても採らずに名乗ります。
移行: AssetFailure を switch で網羅しているコードは、枝が足りないとコンパイル時に指摘されます。case .unreachable(let path, let location, let reason): の枝を足してください。直す先を示すなら、渡した名前 (path) ではなく確かめられなかった場所 (location) を使います。notFound の枝で「資源の宣言を確かめよ」と案内していたなら、入れない場所の権利を直すという案内は新しい枝へ移します。失敗をまとめて扱っている (catch で受けて description を出す、try? で受ける、など) なら、コードの変更は要りません。
コンパイラが気付かないずれ: 画像・モデル・断片・文字のファイル・音・動画を読む口は、以前 notFound を投げていた状況 (入れない親の下) で、unopenable を投げるようになります。catch ImageFailure.notFound { 既定の絵を使う } のように notFound だけを受けて既定へ落としている書き方は、入れない場合に既定へ落ちず、上へ抜けます。入れない場合も既定へ落としたいなら unopenable も受けてください (catch ImageFailure.notFound, ImageFailure.unopenable { … })。unopenable の reason が cannot tell whether it exists ( で始まるかで、在るかを確かめられない場合を見分けられます。
公開の面のベクトルを、SIMD2<Float> / SIMD3<Float> から自前の Vector2 / Vector3 に揃えました (#2617)。
Vector3 はこの版で新しく足しました (Vector2 は先に入っています)。数は Float・Double・Int のどれでも渡せ (Vector3(column * 40, 150, 0))、[200, 150, 500] のような配列リテラルからも作れ、+・-・数との * / / ができます。中身は SIMD のままで、SIMD との出入り口は .simd と Vector3(simd) です。
移した先は次のとおりです。
| 口 | これまで | これから |
|---|---|---|
Camera の eye・center・up と init(eye:center:up:projection:) |
SIMD3<Float> |
Vector3 |
Orbit の center・eye・up と init(center:…) |
SIMD3<Float> |
Vector3 |
Placement.rotation と init(…rotation:…) |
SIMD3<Float> |
Vector3 |
Transform.apply(x:y:) / apply(x:y:z:) の戻り値 |
SIMD2<Float> / SIMD3<Float> |
Vector2 / Vector3 |
spacePosition(screenX:screenY:depth:) の戻り値 |
SIMD3<Float> |
Vector3 |
Model.size・Model.center |
SIMD3<Float> |
Vector3 |
TextContour.points と init(points:isHole:) |
[SIMD2<Float>] |
[Vector2] |
Gamepad.leftStick・GamepadInput.leftStick |
SIMD2<Float> |
Vector2 |
ParamValue.vector2・.vector3 の関連値 |
SIMD2<Float> / SIMD3<Float> |
Vector2 / Vector3 |
@Param に書ける組 |
SIMD2<Float> / SIMD3<Float> |
Vector2 / Vector3 |
移行: コンパイルが型の食い違いを指すので、その行の値を Vector3(…) / Vector2(…) で包むか、読むほうを .simd で SIMD に戻してください。camera.eye = SIMD3(0, 0, 500) は camera.eye = Vector3(0, 0, 500) に、Placement(rotation: SIMD3(0, angle, 0)) は Placement(rotation: Vector3(0, angle, 0)) に、@Param var drift: SIMD2<Float> = SIMD2(0, 0) は @Param var drift: Vector2 = Vector2(0, 0) になります。SIMD の関数に渡すときは length(model.size.simd) のように .simd を付けます。@Param の保存値 (.mokume/state/params.json) の形は変わらず、これまでに保存した組の値は、そのまま Vector2 / Vector3 の @Param に読み戻されます。
Transform の積む口と jumpTime(_:) が、Int や Double の変数をそのまま受けるようになりました (#2525)。
対象は translate・rotate(by:)・rotateX(by:)・rotateY(by:)・rotateZ(by:)・scale・shearX(by:)・shearY(by:) と jumpTime(_:) です。これまでは Float しか受けず、let d = 2.0 を t.translate(x: d, y: d) や jumpTime(s) へ渡すには Float(d) と書き直す必要がありました。いまは Transform.apply や描く口と同じく、そのまま渡せます。受け口の型を広げただけで、計算は Float のままです。
var t = Transform.identity
let d = 2.0
t.translate(x: d, y: d) // Float(d) と書かなくてよくなりました
t.rotate(by: d)
jumpTime(1.5) // 変数でも、リテラルでも受け方を描く口に揃えたので、描く口と同じ 2 つの影響がこれらの口にも出ます。
1. .pi や .nan のような書き方は、型を名乗ります。 渡す先が型を決めなくなったので、.pi だけでは Float と Double のどちらか決まらず、コンパイルが通らなくなります。
t.rotate(by: .pi / 2) // 通りません
t.rotate(by: Float.pi / 2) // こう書きます
t.scale(x: .nan, y: 2) // 通りません — Float.nan と書きます2. 整数だけで書いた割り算は、整数のまま計算されます。 こちらはコンパイルが通ってしまうので、値が黙って変わります。
t.rotate(by: 1 / 2) // 0 を渡したことになります (これまでは 0.5)
t.rotate(by: 1 / 2.0) // 0.5 が渡ります
jumpTime(90 / 60) // 1 秒へ飛びます (これまでは 1.5 秒)移行: これらの口へ .pi・.nan・.infinity などを渡している所は、Float.pi のように型を付けて綴ってください (HALF_PI など、型の決まった定数はそのまま渡せます)。引数に整数だけの割り算を書いている所は、片方に小数点を付けてください (1 / 2 → 1 / 2.0)。width や time のように面が返した数が式に入っていれば、全体が Float として計算されるのでそのままで構いません。
Transform.apply(x:y:)・apply(x:y:z:) の戻り値を Vector2・Vector3 へ移す変更と同じ版に出ます。Transform まわりの直しを 1 回で済ませられます。
LinearRGBA.display(…)・.linear(…) は据え置きです。0–1 の目盛りで、0–255 の数を n / 255 と直して渡す書き方が自然に出る口なので、広げると上の 2 のように n / 255 が黙って 0 になるためです。音とカメラの口も、この変更の対象に入れていません。
新機能
入れた道具から、API リファレンスの例を開いて始められるようになりました。
mokume examples が同梱した例を一覧に出し、mokume new my-shape --from triangle がその例を @main を持つ 1 本のスケッチにして新しいパッケージを作ります (そのまま mokume run で窓が開きます)。
例の引き方は、リファレンスの見出しと同じ綴りです (fill(_:_:_:_:)・Circle.draw())。1 本に決まるなら triangle のような短い名前でも引けます。説明文に例が複数ある口 (circle・rect・arc など) と overload が複数ある口は、一覧が circle(_:_:_:)#2 のように番号つきで 1 本ずつ並べ、その綴りで選べます。括弧を含む綴りはシェルでは引用符で包みます。名前だけでは 1 本に決まらないときは候補を並べて、知らない名前は一覧を案内して止まります。どちらも何も作りません。
例は道具に同梱してあり、ネットワークは要りません。入れた版の説明文から作ったもので、mokume --version の版と揃っています。assets/・data/ の資材を読む例は、写せる実体が無いので一覧に含めていません。
fill(.linearGradient(…)) と fill(.radialGradient(…)) で、円・矩形・字などを勾配で塗れるようになりました。
区切りは (色, 位置) をそのまま並べて書き (組み立てた [(LinearRGBA, Float)] を渡す形もあります)、線形の勾配は from から to へ、放射の勾配は center から radius まで、位置 0 から 1 で色が変わります。例えば fill(.linearGradient((color(40, 30, 90), 0), (color(250, 140, 80), 1), from: [0, 0], to: [0, height])); rect(0, 0, width, height) は、紫から橙へ縦に変わる空になります。色は lerpColor(_:_:_:) と同じ線形の光の量で混ざります。
勾配は塗りだけに効き、線 (stroke) の色は変わりません。pushStyle() と popStyle() で積み降ろされ、createShape { } の中で塗った勾配は形に焼き付いて、置き場所で回すと一緒に回ります。shader() を付けると自分の断片が勝ちます。値型 (Circle・Rect) と Placement の fill は色のままで、勾配は描く状態に置いて、fill が nil の値を描きます。勾配の座標は fill(_:) を呼んだときの座標系で読みます。円錐の勾配と線の勾配はまだありません。
書体のファイルを読んで使えるようになり、文字の太さを 100〜900 で動かせるようになりました (#2445)。
loadFont(_:) が .ttf・.otf・.ttc・.otc を読んで Font を返し、textFont(_:) に渡すとその書体で描きます。textWeight(_:) は太さを数で頼む口で、整数や Double の変数をそのまま渡せます。
func setup() { display = try? loadFont("assets/fonts/Display.otf") } // var display: Font?
func draw() {
if let display { textFont(display) }
textWeight(500 + 400 * sin(time * 2))
text("mokume", 40, 120)
}作品に書体を同梱できます。 これまで書体を指す口は、この Mac に入っている書体の名前を受ける textFont("名前") だけで、配った先の Mac に同じ書体があるとは限りませんでした。loadFont が探す場所と順は loadImage と同じです (assetURL)。読んだ書体は環境へ登録しないので、同じ名前の書体が入っていても textFont("名前") の結果は変わらず、他のアプリにも見えません。同じファイルを更新されないまま読み直すと等しい値になり、焼き場の字形も使い回します。textWidth・textBounds・textOutline も読んだ書体で測ります。
失敗は型のついた FontFailure を投げます。無いパスは notFound (探した場所つき)、ディレクトリや読む権利の無いファイルは unopenable (解けた場所と OS の事情つき)、書体でないファイルは unreadable です。loadFont は走っている面を使わないので、走っていない所でも読めます。draw() の中で呼ぶと、ほかの同期の読み込みと同じく、口の名前を添えて 1 度だけ知らせます (setup() で 1 度読んでください)。
太さは書体が持つ面で決まります。 既定の書体と、読んだ可変書体は wght 軸をそのまま動かすので、textWeight(300) と textWeight(800) で字の幅も形も変わります (既定の書体の 40 画素の M の送り幅は、約 32.9 と約 35.2 です)。可変でない書体は、持つ面のうち最も近い太さの面で描きます (Helvetica は軽い面・標準・太字の 3 通り)。頼んだ太さから 100 を超えて離れた面しか無いときは、使った面を名乗って 1 度だけ警告します。100 未満・900 超は端へ丸めて、数でない値は知らせて前の太さのまま残します。太さは整数に丸めて焼き場の鍵に入るので、500.2 と 500.4 は同じ字形を使い、500 と 501 は別に焼きます。毎フレーム太さを動かすと、大きさを動かすのと同じく焼き直しが続きます (焼き場の頁が埋まれば頁を替えるので、字は欠けません)。
textStyle() と textWeight() は、後から呼んだほうが勝ちます。textStyle(.bold) の後の textWeight(300) は 300 で、textWeight(300) の後の textStyle(.bold) は太字で描きます。textStyle(.italic) の後の textWeight(300) は、傾きを残して 300 です。
公開の型の名前は Font です。SwiftUI を import するファイルでは mokume.Font と書けば解けます (Image・Shape と同じ扱い)。書体の名前の一覧の口と、待たずに読む requestFont はまだありません。
mokume render --out loop.gif と --out loop.mp4 で、貼ったり送ったりする形へ直接書き出せるようになりました (#2440)。
これまでは .mov (ProRes 4444) か連番しか書けず、授業の提出・SNS・README へ貼るたびに外の変換ツール (ffmpeg など) が要りました。mokume render --fps 25 --seconds 4 --out loop.gif と打つと、100 枚・各枚の遅延 4/100 秒・繰り返し無限の GIF が、窓を開かずにできます。4 秒で 1 周する作品なら継ぎ目なしのループです。.mp4 は H.264 の MP4 で、枚数と刻みは .mov と同じく --fps と --seconds のとおりです。外の道具は要りません (MP4 は AVFoundation、GIF は ImageIO で書きます)。
配る形は、同じ引数から書いても画素が一致するとは約束しません (減色や符号化は OS のもので、版で変わりえます)。書き終わりにもそう表示します。元にする形は今までどおり .mov か連番で、そちらの水準 3 は変わりません。GIF は 256 色で、色は sRGB に直して書き、透けた画素はアルファ 128 以上を不透明・それ未満を透明にします (半透明は書けません)。MP4 は透けを書かず、色は sRGB に直して書きます。
GIF の遅延は 1/100 秒の整数倍なので、--fps は 100 を割り切る値 (100・50・25・20・10 など) だけを受け付けます。--fps 60 のように割り切れない値は、ビルドの前に使い方の誤りとして止まり、使える近い値 (50・100) を言います。黙って遅延を丸めることも、...
v0.31.0
新機能
-
textOutline(_:_:_:spacing:)で、文字の輪郭の点を等間隔に取れるようになりました。spacingに画素で間隔を渡すと、周ごとに周の長さを均等に割る点が返るので、IやTの縦画の途中にも点が並び、粒が字の形に並ぶ絵を書けます。spacingを省いたtextOutline(_:_:_:)は、これまでの折れ目の点のままです。spacingが 0 以下・数でない値のときは、1 度だけ知らせて折れ目の点を返します。p5.js のtextToPoints/textToContoursのsampleFactorは書体の単位に対する比ですが、spacingは画素です。
修正
-
扇 (
arc) の輪郭が、太さに比べて扇が小さい・細いと、帯が欠けていたのを直した。noFill()でstrokeWeight(20)のarc(0, 0, 100, 6, -HALF_PI, HALF_PI)は帯が長軸に沿って丸ごと抜け、strokeWeight(10.4)のarc(0, 0, 20, 20, 0, PI)は帯の内側が 2 割ほど欠けて背景が透けていた。扇の内側に残る穴が潰れているか 1 画素より細いときは、穴の面積を画素の中で数え直すようにしたので、帯が欠けない。細長い楕円の扇は、細長い楕円の輪郭と同じく、近似ではなく真の距離で輪郭を数える。穴が太く中心の角が尖っていない円の扇の絵は変わらない (中心の角が 60° 未満の扇は、尖りの先の画素だけがわずかに変わる)。 -
楕円の扇 (
arc) の輪郭が、弧の端の近くで帯の外へにじんだり、欠けたりしていたのを直した。noFill()でstrokeWeight(22)のarc(0, 0, 80, 20, 0.3, 2.9)は、扇の外側に輪郭の色が最大 67% にじんでいた。既定の太さ 1 の扇でも、半径比 2:1 ほどの楕円では、弧の端の近くの帯の縁が最大 15〜25% 外れていた。円の扇の絵は変わらない。 -
動画の書き出し中に符号化器が転んだとき、転んだ瞬間に処理中だった 1 枚が、符号化器の理由ではなく「画素の器を借りられない」と報告されることがあったのを直しました。
状態を確かめてから画素の器を取るまでの間に符号化器が転ぶと、器が取れずに理由が落ちていました。器を取れなかった後にもう一度状態を読み、転んでいればその枚も符号化器の理由 (ディスクが埋まった、など) を名乗ります。
また、画素の器の先頭の番地を取れなかった枚が、何も書かれないまま録画に入っていたのを直しました。その枚は書き損じとして名乗られ、動画には入りません。
-
textFont()で指定した書体が、実行中に環境から外れて引けなくなったとき、別の書体で黙って描かず、警告を 1 度出すようになりました。書体の名前を確かめるのは
textFont()を呼んだ時だけで、書体を実際に作るのは、その後に初めて字を描く時と、大きさ・太さ・傾きを新しい値にした時です。そのあいだに Font Book などで書体を外すと、CoreText は別の書体 (Helvetica など) を黙って返し、指定が効かないのに効いたように見えていました。いまは書体を作る時にも名前を確かめ、引けなければ標準エラーに「"Futura"が引けず、"Helvetica"で描く」旨を 1 度だけ出します。指定は変えず、描く絵も今までと同じです。textFont()に無い名前を渡したときの警告とは別に数えます。書体を作るたびに名前を 1 度確かめます (1 回 1〜11 µs ほど)。同じ大きさ・太さ・傾きの字を描くあいだは控えを使うので、控えに当たる字では確かめは増えません。 -
Vector2の長さ・normalized()・limited(to:)が、成分はFloatに収まるのに 2 乗だけが溢れる大きな値 (Vector2(3e20, 4e20)など) で、infinity・(0, 0)・nanを返していたのを直した。Circleのintersects/containsとRect.intersects(Circle)も、半径の 2 乗が溢れる大きい円と、0 に潰れる小さい円で、離れていてもtrueを返していたのを、距離と半径の比較に直した。普通の大きさの値の結果は変わらない。
-
mokume watchで保存して作り直した版が、共有面を用意できずに自分の窓を開いたとき、前の版が止まらずに並んで走り続けていたのを直した。見張りは、窓へ倒れた子から区画
viewportに置かれる目印を見て、前の版を畳む。絵と入力は子の窓へ移る (見張りの窓には最後の絵が残る)。見張りは端末とプレビューでそのことを言う。 -
同じ場所で
mokume watchを 2 本起こすと、2 本目は何にも触れずに断って終わるようになった。いままでは 2 本目が 1 本目の区画を作り直し、1 本目の窓が 2 本目のスケッチを映したり、2 本目を止めたあとに 1 本目が自分の窓を開いて前の版が走り続けたりした。見張りは場所の中の錠 (
.mokume/.watch-lock) を持ち、見張りがSIGKILLなどで後始末なしに消えても錠はカーネルが外すので、残った場所ではそのまま見張り始められる。断るときは先客の pid を名乗る。同じ場所で別々に見張りたいときはMOKUME_WORK_DIRを分ける。
性能
-
合成音と音声ファイルの再生の解析 (
level・spectrumなど) が、値を読んだときだけ走るようになり、解析のたびに変換を作り直さなくなった。合成音を 50 個鳴らす作品の、音の解析にかかる時間が 1 フレーム 0.87 ms から、値を読まなければ 0.10 ms、全部読んでも 0.27 ms になる。止めた後も毎フレーム解析が続いていた音も、読まなければ解析しない。読んだ値は今までと同じ (フレームの頭の窓を解いたもの) で変わらない。
-
createGraphics()が、作りたての描き場所をもう一度塗り直さず、GPU の仕事が全部終わるのも待たなくなりました。作った描き場所は、これまでどおり透明な黒で始まります。層を
setup()で多数作るスケッチや、フレームの途中で層を作るスケッチで、作るたびに乗っていた余計な塗りと待ちが無くなります。 -
組み込みの効果 (
invert・monochrome・vignette・adjustなど) が、効果の種類ごとに特化した軽いシェーダで走るようになりました。4K で 4 段重ねた絵で、1 フレームの GPU の待ちが約 0.15 ms 下がります。絵は 1 ビットも変わりません。代わりに、種類を初めて使うフレームで、その種類のシェーダを組む時間(数十 ms)だけ止まることがあります。
-
図形の種類や塗りが次々に変わる絵で、列を積む CPU 時間が半分以下になりました。
1024×768 に 4 画素の矩形を 20,000 個、塗りだけ・輪郭だけを交互に置く絵で、描き切りを積む時間が約 21 ms から約 8 ms に下がります。前の列と同じ設定は設定し直さず、基本図形の列は読まない値を書かなくなりました。絵は変わりません。
-
スケッチを動かしている間の CPU の起床が、表示のリフレッシュだけに減りました。
60 fps のスケッチで 1 秒あたり 120 回だった起床が 60 回です。止まりを拾うための予備のタイマーが、表示のリフレッシュが来ている間は眠ったままになったためで、画面が眠ったときに自分で進める動きは変わりません。あわせて
mokume watchの道具の窓は、差し出し元の面を毎リフレッシュ引き直さず、握ったまま読むようになりました。 -
mokume watchの下で、録りを閉じている間や外からのpause()の間に入力が溜まった後の再開が、速くなった。1 フレームに 1 万行の入力が届いても、取り込みに 66 ms かかっていたのが 21 ms ほどで済む (行ごとに残りを写していたのをやめた)。入力が上限 (4096 件) を越えて溢れ続けるときの 1 万件の取り込みも、9.6 ms から 0.4 ms になった。届く入力・順序・捨てる件は変わらない。
-
線の角や端を組み立てる処理から、点ごとの配列の確保と要素ごとの実行時の隔離検査を除いた。
1 万本の線分の骨組みを作る時間は 3.9 ms から 1.6 ms になり、細い線の角 1 万個の片を集める時間は 3.0 ms から 1.7 ms になる。フレーム全体では差が計測の揺れに収まる小さな改善で、絵は変わらない。
-
beginContourで穴を多数開けた形の、三角形に分ける前の穴のつなぎが、穴の数が増えても急に遅くならなくなりました。穴を外周へつなぐ橋の相手を、近い順に調べて橋を架けられる最初の点を選び、橋を遮る辺は辺の囲みの箱で絞って調べます。穴を 30×30 に並べた形 (穴 900 個) で、つなぎに掛かる時間が約 1.4 秒から約 11 ミリ秒になりました。選ぶ橋の相手は変わらないので、分け方も絵も変わりません。
-
pixels[x, y]とget()/set()で画素を 1 つずつ読み書きするループが、画素ごとに GPU の決着を頼み直さなくなり、およそ半分の時間で回るようになりました。全画素を二重ループで書き換える書き方(
pixels[x, y] = pixels[x, y])は、1280×720 でこれまでの約 0.5 倍の時間です。読み書きの意味(書いてよい区間、効果が二重に掛からないこと、形の組み立ての中で書かないこと)は変わりません。 -
CPU が混んだ機械での、動画と連番の書き出しの詰まりを減らした。
符号化の仕事が、待つ側のフレームの処理より低い優先度で走っていたのを揃えた。1080p の ProRes を全コアに負荷を掛けた条件で書き出すと、毎秒 24 枚ほどだったのが 70 枚前後になる (空いた機械では変わらない)。あわせて、読み戻しの毎フレームの 0 埋めを省いた。書き出すファイルの中身は変わらない。
-
arc()の扇形が、画素ごとに角度の cos と sin を求めなくなりました。扇形の始まりと終わりの角の向きは扇形ごとに一定なので、頂点の段で 1 度だけ求めて受け渡します。絵は変わりません。1920×1080 に直径 80 の扇形を 1 万個重ねた場面で、GPU の仕事の時間が塗りだけで約 13%、塗りと輪郭で約 9% 減りました。
-
同じシェーダー・効果・計算を、値を変えずに何度も使う場面が速くなった。
値の宣言が多いシェーダーを 256 枚の切り抜き区画へ当てると、列を閉じるたびに全宣言を並べ直していて、宣言 64 個で 1 フレームに約 5 ms かかっていた。値を変えたときだけ詰め直す形にして、同じ場面は約 0.2 ms になった。絵と API は変わらない。
-
createGraphics()で描き場所を動的に作るスケッチが、面を作るたびに払っていた固定の費用がなくなりました。組み込みの図形を描くシェーダのライブラリとパイプラインを、面ごとに組み直さず同じ GPU の中で分け合うためです。同じ GPU で 12 面ずつ作った中央値は、64×64 の面で 2.71 ms から 0.11 ms、
createGraphics(64, 64)で 3.26 ms から 0.41 ms、1080p の面で 3.06 ms から 0.48 ms です (Apple M3 Max)。最初の面を作る費用と、面を描く費用は変わりません。 -
動かしていない描き場所の画素を読むとき、別の描き場所 (
createGraphicsで作った面など) を描いていても、全面を読み戻し直さなくなりました。1080p の面を読む時間が、別の面の描画の後で 0.65 ms から 0.47 ms に縮みます。静止した面の画素を毎フレーム読みながら別の面を動かす作品で効きます。読んだ画素は変わらず、その面へ描いたり画素を書いたりしたときは、これまでどおり次の読みに出ます。
-
noLoop()で止めた絵や、外から止めた絵が、止まっている間は窓にもmokume watchの窓にも出し直されなくなりました。これまでは、絵が変わらなくても表示のリフレッシュのたびに全面のパスを投入して画面へ出し続けていました。止まっている間に
set()で画素を書いたとき、捕まえやカーソルを隠す頼みや明るさの設定を変えたとき、窓の大きさを変えたときは、これまでどおり 1 回だけ出ます。走っているスケッチは何も変わりません。止まっている間は、道具に出る速さと時刻の表示も止まります。 -
textBounds()が絵文字を測るために CPU で描いた絵を、同じ大きさのtext()が焼くときにそのまま使うようになりました。textSize()を毎フレーム動かしながら絵文字をtextBounds()で測ってtext()で描くスケッチで、1 フレームに 2 回あった絵文字の CPU の描画が 1 回になります (リリースビルドで 1000px は 12.2 ms → 7.1 ms、3000px は 114 ms → 69 ms)。返る枠と描かれる絵は変わりません。
この版の絵
描画が変わった PR が 4 本ある。
v0.30.0
破壊的変更
-
画像・モデル・断片・音・動画を読む口は、名前の指す先がディレクトリだったときと、読む権利の無いファイルだったときに、それぞれの失敗の型に新しく足した
unopenable(path:location:reason:)で投げるようになりました (#2358)。足したのは
ImageFailure・ModelFailure・ShaderFailure・AudioFailure・MovieFailureの 5 つで、形は文字のファイルのDataFailure.unopenableと同じです。locationには名前が解けた場所、reasonには OS が名乗った事情 (Is a directory・Permission denied) が入り、文面は"photo.png" was found at "/Users/me/sketch/photo.png" but cannot be opened: Is a directoryの形です (名前が解けた場所と同じならat …は省きます)。これまでは、中身の形式や破損の失敗に畳んでいました。口ごとに次のとおりです。
口 これまで これから loadImage・requestImageImageFailure.undecodable(「形式を確かめよ」)ImageFailure.unopenableloadModel・requestModel(OBJ・STL)ModelFailure.unreadable(「壊れていないか確かめよ」)ModelFailure.unopenableloadShader・loadEffect・loadComputationShaderFailure.notFound(見つかっているのに「見つからない・資源の宣言を確かめよ」)ShaderFailure.unopenableloadSound・createAudioIn(file:)AudioFailure.unreadable(「壊れている・形式」)AudioFailure.unopenablecreateVideoMovieFailure.unreadable(「壊れている・形式」)MovieFailure.unopenable中身の壊れたファイル (正しい拡張子で中身が違うもの) は、今までどおり
undecodable/unreadableで投げます。ただし断片 (loadShader・loadEffect・loadComputation) は、開けたが UTF-8 の文字として読めない中身 (Shift_JIS で保存した.metalなど) も、これまではShaderFailure.notFound(「見つからない・資源の宣言を確かめよ」) で投げていました。新しく足したShaderFailure.unreadable(path:)で投げ、DataFailure.unreadableと同じく「UTF-8 の文字として読めない。UTF-8 で保存し直せ」と名乗ります。保存を拾って断片を読み直したときに読めなくなった場合も、failureが場所だけでなく、開けない事情か UTF-8 でないことを名乗ります。assetURL(_:)の振る舞いは変わらず、在ればディレクトリも返します。そのため、作業ディレクトリに同梱の資材と同じ名前のフォルダがあると、本物より先にそれが解けてunopenableになります。locationを見れば、どのフォルダが隠しているかが分かります。移行: 5 つの型のどれかを
switchで網羅しているコードは、枝が足りないとコンパイル時に指摘されます。case .unopenable(let path, let location, let reason):の枝 (ShaderFailureにはcase .unreadable(let path):も) を足してください。直す先を示すなら、渡した名前 (path) ではなく解けた場所 (location) を使います。ディレクトリや読む権利の無いファイルを.undecodable・.unreadable・ShaderFailure.notFoundの枝で受けていたなら、その扱いを新しい枝へ移します。UTF-8 でない断片をShaderFailure.notFoundの枝で受けていたなら、ShaderFailure.unreadableの枝へ移します。失敗をまとめて扱っている (catchで受けてdescriptionを出す、try?で受ける、など) なら、コードの変更は要りません。 -
Image.save(_:)の失敗を運ぶために、ImageFailureにunwritable(path:reason:)の case を足しました (#2449)。行き先の綴りと、書けなかった事情 (置き場が無い・書く権利が無いなど) を名乗ります。
saveTableなどが投げるDataFailure.unwritable(path:reason:)と同じ形です。これまでの口 (
loadImage・createImage・createVideoなど) は、この case を投げません。ImageFailureをswitchで全 case 数えているスケッチは、コンパイル時にunwritableの枝が足りないと指摘されます。移行: 枝を足してください。
loadImageなどの失敗だけを扱うswitchなら、case .unwritable: breakのように何もしない枝で足りますし、default:を置いていれば直す必要はありません。 -
orbitControl()の右ドラッグが、回す操作から注視点をずらす平行移動に変わった。⌥ を押したままの左ドラッグも平行移動になる (#2461)。
手本の p5.js と同じ割り当てで、被写体は指に付いて動き、注視点の奥行きにある点は指にぴったり付いてくる。回すのは今までどおり左ドラッグ (中ボタンなど、右以外のボタンも) で、スクロールの寄りも変わらない。
移行: 右ドラッグで回していたなら、左ドラッグで回す。トラックパッドでも、⌥ を押さずに引きずれば今までどおり回る。
あわせて、読み込んだモデルに合わせて視点を収める
Orbit.fitting(_ model: Model)を足した。setup()でorbit = .fitting(model)と書けば、起動直後からモデル全体が見える。収めるのは既定の縦の画角なので、横長と正方形の面では収まり、縦長の面では左右が切れうる。 -
mokume renderが渡す書き出しの頼み (環境変数MOKUME_RENDER) をスケッチが読めない・書き出せないとき、スケッチは窓を開いて 0 で終わらず、新しいRenderFailure.unreadableRenderRequest(value:)を投げて起動を断るようにしました (#2481)。@mainのスケッチは何もせずに、窓を開かず理由を標準エラーに出して 1 で終わります。これまでは、読めない値を窓の経路へ倒していたため、
mokume renderは何も書かれていないのに書けたと読んでいました。値が無いスケッチ (ふだんのrunなど) は、これまでどおり窓で走ります。移行:
SketchApplication(sketch:gpu:)を自分で呼んでRenderFailureをswitchで網羅している場合は、コンパイル時にunreadableRenderRequestの枝が足りないと指摘されます。枝を足してください (文面は\(failure)で人向けに出せます)。環境にMOKUME_RENDERを手で置いていて、窓で走らせたいスケッチは、その変数を外してください。
新機能
-
createCaptureにmirrored: trueを足し、鏡に映したように左右が入れ替わるカメラの絵を、scale(-1, 1)を書かずに受け取れるようにした。記録した絵を流す
createCapture(frames:mirrored:)にも同じ引数があり、機材の無い場所で鏡像を確かめられる。既定はfalseで、これまでのスケッチの絵は変わらない。p5.js のcreateCapture(VIDEO, {flipped: true})に当たるが、Apple の API ではflippedが上下の反転を指すので、mirroredと名乗る。captureDevices()が返す各機材は、出せる形式 (幅・高さ・フレームレートの範囲) をformatsで返す (新しい型CaptureFormat)。大きさを当てずっぽうに頼まずに、機材が出せる大きさから選べる。既定の 640×480 は変えていない — 縦横比の違うカメラ (Mac の内蔵カメラは 16:9) では真ん中が切り取られるので、切り取らずに受けたいときはformatsから縦横比の合う大きさを選ぶ。CaptureDeviceの等しさとハッシュは、これまでどおりidとnameだけで決まる。 -
Image.copy()で絵を写して取っておけるようになり、Image.save(_:)で絵を PNG ファイルにできるようになった。毎フレーム同じ 1 枚に上書きされるカメラの
imageも、キーを押した時点の絵をcopy()で撮って並べ、try? shot.save("out/shot-1.png")でファイルに残せる。copy()は元と画素も面も共有しないので、写した後でどちらを書き換えても他方は変わらない。save(_:)は呼んだその場で書き (スケッチのsave(_:)はフレームの外で書く)、行き先の探し方と出力の変換はスケッチのsave(_:)と同じで、透けた画素は透けたまま残る。書けない行き先では、行き先と事情を名乗るImageFailure.unwritable(path:reason:)を投げる。 -
立体の空間に線と点を置ける 6 つの数の
line(x1, y1, z1, x2, y2, z2)・3 つの数のpoint(x, y, z)と、床の格子と 3 本の軸を出すdebugMode()/noDebugMode()を追加した (#2462)。lineとpointは数の個数で意味が変わる。4 つ・2 つの数で書けば今までどおり平面の図形で視点を通らず、6 つ・3 つの数で書くと奥行きを持ち、camera()やorbitControl()の視点から見た位置に描かれる。描き方はbeginShape(.lines)/beginShape(.points)にvertex(x, y, z)を並べたのと同じである。Canvasにも同じ形がある。debugMode()は、面の中心・奥行き 0 を通る水平の面に格子 (1 辺が面の短いほうの辺、10 分割) を出し、そこから x が赤・y が緑・z が青の軸を伸ばす。緑の軸が画面の下へ伸びるので、縦軸が下向きであることが読める。シーンの記述なのでdraw()の中で毎フレーム書く。p5.js のようにsetup()で 1 度書くと、警告して無視される。参照スケッチgrid-and-axesが、見下ろした格子の上で粒の速度を 3 次元の線で描く例である。 -
loadTable()・requestTable()・loadStrings()・requestStrings()が、名前がhttp://かhttps://で始まれば URL から読むようになりました (#2470)。「ウェブに公開」したスプレッドシートの CSV の URL をそのまま渡せば、値を書き換えるだけで作品が変わります。JSON と XML の口と同じ決まりで、URL でなければこれまでどおりファイルとして探します。
URL の本文も、ファイルと同じ決まり (UTF-8・先頭の BOM を読み飛ばす・CSV の引用符と改行) で読みます。2xx 以外の状態や繋がらないときは
DataFailure.unreachableを投げます。同期版は、届くまで返らず、待つのは呼んでから最長 60 秒です。setup()の中で 1 度読むためのもので、フレームを止めずに読むのはrequestTable()/requestStrings()です。 -
外のサーバへ繋ぎに行く口を足しました (#2470)。
createTCPClient("127.0.0.1", 7000)は TCP で、createWebSocketClient("wss://relay.example/room")は WebSocket (ws://もwss://も) で繋ぎます。サーバから届いたものはmessagesから読み、TCPClient.write(_:)/WebSocketClient.send(_:)で書きます。手元の制御盤や TouchDesigner の TCP/IP DAT、展示室の中継サーバに繋いで、値を受けたり合図を送ったりできます。Processing のClientに当たります。サーバが居なくても、居なくなっても作れます。繋げない間の
stateは.unavailable、繋がると.running、繋がっていたのが切れると.disconnectedで、切れても繋げなくても 1 秒ごとに繋ぎ直し続けます (返事をしない相手は 5 秒で諦めて繋ぎ直します)。間隔は固定で、変える口はありません。繋がっていない間の書き込みは、1 度だけ診断に出して捨てます。相手なしで確かめるときは、記録した列を
createTCPClient(messages:)/createWebSocketClient(messages:)で流せます (createTCPServer(messages:)と同じ形です)。走っていない所 (初期化子の中など) から呼ぶと、繋ぐ口はNetworkFailure.notRunning(call:)を投げ、記録を流す口は 1 度だけ警告して何も届かないものを返します。createWebSocketClientにws://かwss://で読めない綴りを渡すとNetworkFailure.invalidHostを投げます。 -
乱数の口を Processing・p5.js の名前で 3 つ足した。
randomGaussian()は平均 0・標準偏差 1 の正規分布の値で、randomGaussian(mean, sd)は平均と標準偏差を決められる (中心へ寄せて散らす:circle(width / 2 + randomGaussian() * 120, height / 2, 8))。random(palette)は配列から 1 つ選び、空の配列ならnilを返す。shuffle(cards)は並べ替えた新しい配列を返し、渡した配列は変えない (結果を捨てると警告が出る)。どれも
random()と同じ列から引くので、randomSeed()を決めれば何度走らせても同じ値・同じ並びになる。Swift のrandomElement()・shuffled()は種に従わず、走らせるたびに変わる。SketchPartを名乗った自分の型の中からも呼べる。 -
SoundFileに、いま鳴らしている位置position(秒)・長さduration(秒)・その秒へ飛ぶjump(_:)を足した。名前は Processing Sound に倣い、単位と振る舞いは
Movieのtime・duration・jump(_:)に揃えた。曲の位置で場面を切り替える (if song.position < 62 { … })・サビから確かめる (song?.jump(62)) が、鳴らし始めた時刻を覚えて引く自作の時計なしに書ける。jump(_:)は呼んだ直後からpositionが飛び先を返し、鳴っている間は次のフレームからその位置の音が鳴って解析の値も移る。止めている間は位置だけを動かし、次のplay()がそこから鳴らす。0 より前と長さより後は端へ寄せ、数でない値は位置を変えずに 1 度だけ知らせる。書き出し (固定の時計) では位置がフレームの数で決まるので、同じフレームはいつも同じ曲の位置で描かれる。 -
表を自分で作って書き出せるようになった。
Table(columns: ["frame", "x", "y"])で見出しだけの表を作り、addRow([Float(frameCount), mouseX, mouseY])で数の行を足して、saveTableで CSV に書く (見出しありでloadTableすると同じ表に戻る)。列の数と合わないaddRowは行を足さずに 1 度だけ知らせる。文字のセルは足した行へsetStringで書く。文字列を 1 行ずつ書く
saveStrings(lines, "out/notes.txt")も足した。UTF-8 (BOM なし)・LF で書き、loadStringsで読み直すと同じ並びに戻る。引数はsaveTableと同じく書く値が先・置き場が後で、Processing のsaveStrings(filename, data)とは逆 (p5.js と同じ)。改行を含む要素は読み直すと行が分かれ、最初の要素の頭の U+FEFF は BOM として落ちるので、そのときは中身を変えずに書いて 1 度だけ知らせる。
修正
-
つまみの保存や応答など、
.mokume/に JSON を置く処理が、置く先に同じ名前のディレクトリがあると、そのディレクトリを中身ごと消してファイルに置き換えていた不具合を直した。いまは置き換えずに失敗し、「Is a directory」と名乗る (各処理の警告の出し方は変わらない)。
同じ根の兄弟も直した: 動画の書き出し先 (
recordの.movのパス) がディレクトリを指していると、それを中身ごと消していたのを、消さずに「Is a directory」で失敗するようにした。撮り始める前の観測の掃除も、目録や絵の名前をディレクトリが使っていれば、消さずに残す。 -
マイクと合成音の解析で、読み取り中の標本が上書きされるデータ競合を修正した。
描画側が長く止まっても音声側を待たせず、一つの公開済みの窓を対応する時刻とともに読み取る。
-
mokume bundleが最後に案内する確かめ方の 1 行が、アプリを閉じるまで.build<...
















