RSS Twitter Facebook


« 2018年11月 | 2019年01月のアーカイブ |

2019/01/15

[Web Audio API] AudioWorklet を 1 ファイルで書く方法



AudioWorklet は Web Audio API でユーザーが自分独自のカスタムノードを作成できるようにするための仕組みで、プログラムのメインスレッドで走らせる AudioWorkletNode と音声処理スレッドで実行する AudioWorkletProcessor と呼ばれる部分で構成されます。

基本的な書き方としては AudioWorkletNode 側と AudioWorkletProcessor 側を別ファイルの ".js" で書くというスタイルが標準的で、これは別に悪い事ではないのですが前身となった ScriptProcessor が単に関数を定義するだけで使えたのに比べると、ちょっと試しに軽く使いたい時に、別ファイルを準備するのがちょっと面倒くさいと感じてしまいます (大規模なプログラムを書くつもりなら分けておいた方が良いと思うんですが)。

という事で、1ファイルでメインプログラムとプロセッサをまとめて書く方法を幾つか試してみます。


今回ベースにするコード

まずはベースとして前回の記事 ([WebAudio API] AudioWorklet の使い方) で書いた AudioWorklet のオーバードライブを使用します。

プロセッサ側のコードは次のようになっています。

overdrive-proc.js

class OverDrive extends AudioWorkletProcessor {
    static get parameterDescriptors () {
        return [{
            name: 'drive',
            defaultValue: 0,
            minValue: 0,
            maxValue: 1,
            automationRate: "k-rate"
        }];
    }
    process (inputs, outputs, parameters) {
        let input = inputs[0];
        let output = outputs[0];
        let drv = Math.pow(0.05,Math.abs(parameters.drive[0]));
        for (let channel = 0; channel < output.length; ++channel) {
            for (let i = 0; i < output[channel].length; ++i) {
                var d=input[channel][i];
                if(d<0)
                    output[channel][i]=-Math.pow(-d,drv);
                else
                    output[channel][i]=Math.pow(d,drv);
            }
        }
        return true;
    }
}
registerProcessor("OverDrive", OverDrive);

プロセッサのコードは "OverDrive" クラスを定義して、registerProcessor() で名前を付けて登録する、という処理になっています。 このプロセッサを使うメインプログラムとして取り合えず簡単なものを書いたものが次のコードです。


<!doctype html>
<html lang="en">
<body>
<audio id="soundsrc" src="./loop.wav" controls loop></audio><br/>
Drive : <input id="drive" type="range" min="0" max="1" step="0.01" value="0"/><br/>
<script>
window.onload = (async ()=>{
    audioctx = new AudioContext();
    await audioctx.audioWorklet.addModule('overdrive-proc.js');
    const src = new MediaElementAudioSourceNode(
        audioctx,{mediaElement:document.getElementById('soundsrc')}
    );
    const overdrive = new AudioWorkletNode(audioctx, 'OverDrive');
    const paramDrive = overdrive.parameters.get('drive');
    src.connect(overdrive).connect(audioctx.destination);

    document.getElementById("drive").addEventListener("input",(ev)=>{
        audioctx.resume();
        paramDrive.value=ev.target.value;
    })
});
</script>
</body>
</html>

このコードのテストページ


音源を <audio> タグでドキュメント内に置いて MediaElementAudioSourceNode で読み込む方法で、オーバードライブを経由して鳴らします。ちなみに window.onload で初期化をしていますが、プライバシーポリシー制限でユーザー操作が無い状態で作った AudioContext は "suspend" 状態ですので、スライダーを触ったら resume() するという事をやっています。

プロセッサのファイル "overdrive-proc.js" を読み込んでいるのが 9 行目、audioctx.audioWorklet.addModule() です。


取り合えずプロセッサ側のコードを文字列として埋め込んでみる

まずはファイル "overdrive-proc.js" の内容をそのまま文字列に入れておいて DataURI 経由でアクセスするという方法です。38 行目の処理、
await audioctx.audioWorklet.addModule('data:text/javascript,'+encodeURI(overdriveproc));
がそれです。'data:text/javascript,' に続けてプロセッサの文字列をエスケープしてくっ付ける事で、URL としてアクセスできるようにしています。

単純に文字列としてコードを入れるのは、昔の JavaScript だと文字列が改行で途切れないように全ての行末に「\」を付ける必要があったりしてあまり実用的ではなかったのですが、ES6 でテンプレート文字列が使えるようになってからは、コード全体を 「`」で囲むだけですのでこれでもまあいけなくはないです。

このコードのテストページ

<!doctype html>
<html lang="en">
<body>
<audio id="soundsrc" src="./loop.wav" controls loop></audio><br/>
Drive : <input id="drive" type="range" min="0" max="1" step="0.01" value="0"/><br/>
<script>
const overdriveproc =`
class OverDrive extends AudioWorkletProcessor {
    static get parameterDescriptors () {
        return [{
            name: 'drive',
            defaultValue: 0,
            minValue: 0,
            maxValue: 1,
            automationRate: "k-rate"
        }];
    }
    process (inputs, outputs, parameters) {
        let input = inputs[0];
        let output = outputs[0];
        let drv = Math.pow(0.05,Math.abs(parameters.drive[0]));
        for (let channel = 0; channel < output.length; ++channel) {
            for (let i = 0; i < output[channel].length; ++i) {
                var d=input[channel][i];
                if(d<0)
                    output[channel][i]=-Math.pow(-d,drv);
                else
                    output[channel][i]=Math.pow(d,drv);
            }
        }
        return true;
    }
}
registerProcessor("OverDrive", OverDrive);
`;
window.onload = (async ()=>{
    audioctx = new AudioContext();
    await audioctx.audioWorklet.addModule('data:text/javascript,'+encodeURI(overdriveproc));
    const src = new MediaElementAudioSourceNode(
        audioctx,{mediaElement:document.getElementById('soundsrc')}
    );
    const overdrive = new AudioWorkletNode(audioctx, 'OverDrive');
    const paramDrive = overdrive.parameters.get('drive');
    src.connect(overdrive).connect(audioctx.destination);

    document.getElementById("drive").addEventListener("input",(ev)=>{
        audioctx.resume();
        paramDrive.value=ev.target.value;
    })
});
</script>
</body>
</html>

特殊なタイプのスクリプトとして HTML 内に埋め込む方法

他のやり方としては HTML 内に特殊なスクリプトとして埋め込む方法があります。スクリプトのタイプをそのまま実行される JavaScript と解釈されないように特殊な名前で定義します。

こういうやり方は WebGL の GLSL シェーダーを HTML 内に書く時などにも使われていて 'x-shader/x-vertex' とか 'x-shader/x-fragment' などとする事が多いようです。'x-' を付けるのは標準のタイプではない、という意味になります。 ここでは、AudioWorklet 用の JavaScript なので、
<script type="x-audioworklet/javascript">
としました。中身は document.getElementById('id').innerHTML で取ってこれますので処理としては文字列とそれほど違いはないですね。

ここの表示ではプロセッサのコードが一応 JavaScript としてシンタックスハイライトが効いているので文字列の場合よりかなり読みやすいですが、これはエディタによると思います。VScode では未知のスクリプト扱いで駄目でした。

このコードのテストページ

<!doctype html>
<html lang="en">
<body>
<audio id="soundsrc" src="./loop.wav" controls loop></audio><br/>
Drive : <input id="drive" type="range" min="0" max="1" step="0.01" value="0"/><br/>

<script id="overdriveproc" type="x-audioworklet/javascript">
class OverDrive extends AudioWorkletProcessor {
    static get parameterDescriptors () {
        return [{
            name: 'drive',
            defaultValue: 0,
            minValue: 0,
            maxValue: 1,
            automationRate: "k-rate"
        }];
    }
    process (inputs, outputs, parameters) {
        let input = inputs[0];
        let output = outputs[0];
        let drv = Math.pow(0.05,Math.abs(parameters.drive[0]));
        for (let channel = 0; channel < output.length; ++channel) {
            for (let i = 0; i < output[channel].length; ++i) {
                var d=input[channel][i];
                if(d<0)
                    output[channel][i]=-Math.pow(-d,drv);
                else
                    output[channel][i]=Math.pow(d,drv);
            }
        }
        return true;
    }
}
registerProcessor("OverDrive", OverDrive);
</script>

<script>
window.onload = (async ()=>{
    audioctx = new AudioContext();
    await audioctx.audioWorklet.addModule(
        'data:text/javascript,'
        + encodeURI(document.getElementById('overdriveproc').innerHTML)
    );
    const src = new MediaElementAudioSourceNode(
        audioctx,{mediaElement:document.getElementById('soundsrc')}
    );
    const overdrive = new AudioWorkletNode(audioctx, 'OverDrive');
    const paramDrive = overdrive.parameters.get('drive');
    src.connect(overdrive).connect(audioctx.destination);

    document.getElementById("drive").addEventListener("input",(ev)=>{
        audioctx.resume();
        paramDrive.value=ev.target.value;
    })
});
</script>
</body>
</html>

普通の JavaScript のクラスとして宣言してしまう方法

前の 'x-audioworklet' あたりの方法がまあ妥当かと思いつつ、最後にもうひとつトリッキーなやりかたで、普通に JavaScript のクラスとして書いてしまう方法もなくはないです。

もちろんそのままプロセッサのコードを メインのコード内に書いても動かないので少し細工します。

まずプロセッサ用のクラスは 'AudioWorkletProcessor' クラスを継承して作成しますが、この AudioWorkletProcessor はメインスレッド側には存在しないため、そもそもエラーになるのでダミーを宣言しておきます。
class AudioWorkletProcessor{}
それから次の 'addAudioWorklet()' がメインスレッド側のクラスをプロセッサとして登録するための関数です。やっている事は宣言されているクラスオブジェクトから 'toString()' で文字列化して、後は文字列として埋め込む方法と違いはありません。

ただし引数として取るのはクラスオブジェクトそのものですので、プロセッサ登録のための 'registerProcessor()' は自前で追加しています。また登録される名前はクラス名そのものになります。ここではクラス 'OverDrive' で宣言しているのでノードを作成する時も
new AudioWorkletNode(context, 'OverDrive')
となります。

addAudioWorklet()の戻り値は 'addModule()' と同じく Promise ですので必要なら await で待つなりしてください。

このコードのテストページ

<!doctype html>
<html lang="en">
<body>
<audio id="soundsrc" src="./loop.wav" controls loop></audio><br/>
Drive : <input id="drive" type="range" min="0" max="1" step="0.01" value="0"/><br/>

<script>
// Register Class as an AudioWorklet Processor
class AudioWorkletProcessor{}
function addAudioWorklet(context, proc){
    var f=`data:text/javascript,${encodeURI(proc.toString())}; registerProcessor("${proc.name}",${proc.name})`;
    return context.audioWorklet.addModule(f);
}

// AudioWorklet Processor Class
class OverDrive extends AudioWorkletProcessor {
    static get parameterDescriptors () {
        return [{
            name: 'drive',
            defaultValue: 0,
            minValue: 0,
            maxValue: 1,
            automationRate: "k-rate"
        }];
    }
    process (inputs, outputs, parameters) {
        let input = inputs[0];
        let output = outputs[0];
        let drv = Math.pow(0.05,Math.abs(parameters.drive[0]));
        for (let channel = 0; channel < output.length; ++channel) {
            for (let i = 0; i < output[channel].length; ++i) {
                var d=input[channel][i];
                if(d<0)
                    output[channel][i]=-Math.pow(-d,drv);
                else
                    output[channel][i]=Math.pow(d,drv);
            }
        }
        return true;
    }
}

// Main Program
window.onload = (async ()=>{
    audioctx = new AudioContext();
    await addAudioWorklet(audioctx, OverDrive);
    const src = new MediaElementAudioSourceNode(
        audioctx,{mediaElement:document.getElementById('soundsrc')}
    );
    const overdrive = new AudioWorkletNode(audioctx, 'OverDrive');
    const paramDrive = overdrive.parameters.get('drive');
    src.connect(overdrive).connect(audioctx.destination);

    document.getElementById("drive").addEventListener("input",(ev)=>{
        audioctx.resume();
        paramDrive.value=ev.target.value;
    })
});
</script>
</body>
</html>

これならエディタで確実にシンタックスハイライトが効くというのが良い所です。
メインプログラムのスコープ内にクラスオブジェクトとして存在してしまっているのが無駄と言えば無駄ですが、許せる範囲でしょうか。

posted by g200kg : 12:20 AM : PermaLink

2019/01/14

[WebAudio API] AudioWorklet の使い方



さて、2019 年になって ScriptProcessor の後継とされる AudioWorklet が Chrome で動き始めてからもう一年くらい経ちましたかね。まだまだガリガリ使われているという感じでもないのは、以前の ScriptProcessor に比べると構造が複雑になってちょっととっつきにくくなったからでしょうか。少しハードルが上がった感はありますね。

それに現状は Web Audio API の仕様とブラウザの実装がまだ少し乖離している所が残っているので気を付けないとハマるかも知れません。

とは言っても ScriptProcessor がいつまであるかもわからないし、そろそろちゃんと使わないとですね。

という事でまずはサンプルを書いてみました。

AudioWorklet (OverDrive)


たいしたものではないですが、これはオーバードライブエフェクトを AudioWorklet で実装したものです。[Play]を押すと音楽が流れて [Drive] のツマミでかかり具合を調整できます。


ソースコード overdrive-proc.js


class OverDrive extends AudioWorkletProcessor {
    static get parameterDescriptors () {
        return [{
            name: 'drive',
            defaultValue: 0,
            minValue: 0,
            maxValue: 1,
            automationRate: "k-rate"
        }];
    }
    process (inputs, outputs, parameters) {
        let input = inputs[0];
        let output = outputs[0];
        let drv = Math.pow(0.05,Math.abs(parameters.drive[0]));
        for (let channel = 0; channel < output.length; ++channel) {
            for (let i = 0; i < output[channel].length; ++i) {
                var d=input[channel][i];
                if(d<0)
                    output[channel][i]=-Math.pow(-d,drv);
                else
                    output[channel][i]=Math.pow(d,drv);
            }
        }
        return true;
    }
}
registerProcessor("OverDrive", OverDrive);


AudioWorklet では、音声処理を実行するプロセッサはメインのプログラムとは別ファイルで用意するのが基本です。

この 'overdrive-proc.js' がプロセッサ部分になりますが、AudioWorkletProcessor クラスから派生した class を定義し、registerProcessor() で登録します。プロセッサはメインプログラムとは別のオーディオ処理専用のスレッドで実行され、変数を受け渡したりというメインプログラムの世界とは直接的なやりとりはできません。

ここで定義したクラス内の process() 関数が実際の処理を行います。引数の inputs が音声信号の入力の配列、outputs が音声信号の出力の配列、parameters はこのノードが持っているパラメータの値を表します。OverDrive のようなエフェクター的なノードの場合はデフォルト状態の入力が 1 本、出力が 1 本で良いです(ステレオ音声等のマルチチャンネルも入力の数としては 1 本です)。

なので inputs[0] および outputs[0] が処理すべき入出力になります。入出力はブロック (128 サンプル) 毎に渡されますので、各チャンネルの各サンプルを入力から取得して処理を行い出力に渡しています。

ちなみにオーバードライブ処理というと一般的には tanh 関数なんかが良く使われるのですが、ここで行っているのは

\(output = Math.pow(input, 定数)\)

という処理を正負対称にくっつけたものを使っています。ここで定数を 1.0 ~ 0.05、つまり実数乗根とすると 1.0 で歪み無し、小さくなる程すぐに 1.0 に漸近するカーブになるので歪みが大きくなります。更にパラメータの 0.0 ~ 1.0 に対して冪乗の定数は

\(Math.pow(0.05, パラメータ)\)

で 1.0 ~ 0.05 の範囲にマップしています。入力に対する出力カーブは次のようになります。
まあこのあたりは好みもあるので適当ですが。

後は process() の最後が return true になっている所に注目です。とりあえず今は process() は true を返す、という事で良いですがこれはまた後述。

それから、static get parameterDescriptors() いうメソッドがありますが、これはこの OverDrive ノードが持っているパラメータを表しています。Oscillator ノードの frequency や Gainノードの gain のようなものです。

ここではdrive という 0 ~ 1 の範囲のパラメータを一つだけ持っていて animationRate が "k-rate" である事が宣言されています。

animationRate は "k-rate" と "a-rate" があり、"a-rate" はパラメータが 1 回の process() の呼び出し内でサンプル単位で変化し(する可能性があり)、"k-rate" はブロック単位なので process() 呼び出し内では必ず固定値になります。"a-rate" だともう少しコード量が増えてしまうし、"k-rate" より処理が重くなります。


ソースコード メインプログラム (html)

さて、プロセッサは登録しましたがこれを使う側です。
async function Init(){
    audioctx = new AudioContext();
    buffer = await LoadSample(audioctx, "./loop.wav");
    await audioctx.audioWorklet.addModule("overdrive-proc.js");
    overdrive = new AudioWorkletNode(audioctx,"OverDrive");
    overdrive.drive = overdrive.parameters.get("drive");
    vol = new GainNode(audioctx,{gain:0.5});
    analyser = new AnalyserNode(audioctx);
    src = new AudioBufferSourceNode(audioctx, {buffer:buffer, loop:true});

    src.connect(overdrive).connect(vol).connect(analyser).connect(audioctx.destination);

    //....
}
初期化の部分だけですが、async/await で書いています。
  • AudioContext の作成
  • 音源のロード
の次に
  • addModule
で、さっき作った "overdrive-proc.js" を追加していますが、この API は Promise を返しますので await で終了を待っています。これでメインプログラム側で OverDrive が使えるようになります。 そして
  • overdrive = new AudioWorkletNode(audioctx,"OverDrive");
が実際に AudioWorklet のノードを作成している部分です。これにより普通のノードとして使用できる AudioWorklet ノードが作成され、自動的にユーザーからは見えないオーディオ処理スレッド側では対応する overdrive-proc.js の OverDrive 処理のインスタンスが生成されます。

次の
  • overdrive.drive = overdrive.parameters.get("drive");
は、OverDrive ノードが持っているパラメータにアクセスしやすくするために overdrive ノードのプロパティにくっつけている処理です。これで、GainNode.gain と同様に OverDrive.drive という形式でアクセスできます。これについてはまた後述。

最後の行で、ノードの接続は

src(BufferSource音源) => overdrive(AudioWorklet) => vol(メインボリューム用Gain) => analyser(波形表示用) => destination

と普通のノードと同様に connect() で接続しています。後は音源用のデータを fetch で読んだり、ツマミをいじった時に オーバードライブのパラメータを変更したり、出てきた音の波形を Canvas で描いたりしていますけど、今までの WebAudio のプログラムと違う所は無いと思います。


気を付けないといけない事

今はまだ仕様と実装が完全に一致していない部分があったりするので幾つか注意点があります。
  • プロセッサの process() の戻り値は、ノードのライフタイムを制御します。

    仕様上は false を返した場合、ノードに信号が入力されている間だけ動作を続け、接続が無くなると自動的にノードが破棄される、という事になっています。これはその内、実装に反映されるのではないかと思いますが、今は false を返すとノードが止まってしまうので必ず true を返して動かしっぱなしにする必要があります。

  • 作成した AudioWorkletNode のパラメータにアクセスするには
    overdrive.parameters.get("drive")
    のように parameters.get() を経由する必要があります。例えば OscillatorNode.frequency や DelayNode.detayTime のようには行かない所が、まあ事情はあると思いますがちょっと残念ではありますね。
    このサンプルではせめて、という事でメインプログラム側でノードを作った後、
    overdrive.drive = overdrive.parameters.get("drive")
    とやって overdrive.drive でアクセスできるようにしています。これがプロセッサ側のコードでできれば良いんですけどね。

    アクセスした先のオブジェクトは普通の AudioParam ですので、.value に値を代入したり、setValueAtTime() などのオートメーション関数を使ったり、他のノードの出力を接続して変調を掛けたりという事は通常通りできます。

  • この例では使っていないのですが、AudioWorklet のノードのパラメータに作成と同時に初期値を渡したい時、少し書式が違います。

    例えば GainNode を作成する時、
    new GainNode(audioctx, {gain:0.5})
    とやって作成時に gain を 0.5 に設定するなんて事ができますが、AudioWorklet ノードの場合、例えば OverDrive の drive を 0.5 に設定するなら、
    new AudioWorkletNode(audioctx,"OverDrive",{drive:0.5});
    ではなくて、
    new AudioWorkletNode(audioctx,"OverDrive",{parameterData:{drive:0.5}});
    のようになります。これが一応仕様上定められた書き方ですが、仕様書内の EXAMPLE コードでも書き方が違っている部分があったりするので混乱しやすい所です。

  • AudioWorklet だけの問題ではないですが、去年導入された Privacy Policy の影響で WebAudio で音を出すには最初にユーザーの操作が無くてはいけません。AudioWorklet は動き始める前に AudioContext を使って必要なモジュールの登録処理等が必要ですので、処理の順序を考えるのが少し面倒になった感があります。まあちゃんと考えれば良いのですけど。

  • その内修正は入ると思いますが、基本的に Web Audio API の仕様書内にある EXAMPLE はまだちゃんと整備されていないのでそのままでは動かないものが多いです。

    今のところ実際に動作するコードのサンプルとして一番信頼できるのは、Google の WebAudio 開発者である @hoch さんが ChromeLabs で公開している Audio Worklet のサンプルページ です。

という事で、まだ少し気を付けつつ、ではありますけど AudioWorklet もそろそろガシガシ使えるフェーズに入りつつあるんじゃないですかね。

posted by g200kg : 3:40 AM : PermaLink

2019/01/09

Frizing でひっかかりやすい所


IoT とか Make 界隈で良く使われている Frizing という回路図エディタがあります。

基本的に初心者向けであまり大規模なものには向かないのですが、「ブレッドボード図」が簡単に描けるというのが特色で、ちょっとした工作の解説用の図を作る時なんかに重宝されています。

多分ブレッドボード図を描くために使っている人が大多数なのではないかと思いますが、機能としてはブレッドボード図、普通の回路図、基板のアートワーク、ガーバーの出力、おまけにソフトウェアのコードまでを一括管理しようというなかなか遠大な目標を持つツールです。

まあブレッドボード図が必要ないのなら Eagle なり KiCad なりを使った方が良いとは思いますが。

現在バージョンは 0.9.3b のベータ版で少しバギーな部分を残したまま既に2年半くらいアップデートが無いので今後の開発がどうなるかは少し気になる所ではあります。

frizing.org

それで、この Frizing で在り合わせの部品でブレッドボード図を描くだけなら、なかなか快適ではあるのですが、それ以上の事、独自の部品を登録したり、回路図を描いたりという事をやろうとすると色々問題点が噴出して一気にハードルが上がるんですよね。去年の年末あたりから割合ちゃんと触る機会があって、かなり問題を回避するコツを掴んできましたのでポイントだけ紹介します。


そもそも描きたい図はどれなのか


ブレッドボード図と回路図を描くのであれば、回路図=>ブレッドボード図の順序で書いた方が効率が良いです。

ブレッドボード図と回路図は一応並行で進める事もできるのですが、片方で描いた配線が反対側では仮接続のような状態になりますので、それが邪魔で回路図が書きにくい事があります。


作った部品のピンが勝手にショートしてしまう


新しい部品を登録する時、似た部品を基にして画像だけを差し替えて作る、という手法が良く紹介されているのですが、例えば GND や電源が複数本あって内部で繋がっているというような部品を基にしてしまうとその内部接続をそのまま引き継いでしまい、部品エディタの操作で修復する方法が無さそうです(見つけられないだけ?)。

例えば PIC16F883 の場合 PIN 8 と 19 がどちらも GND で内部で繋がっています。これの画像だけを差し替えて新しい部品を作っても PIN 8 に配線しようとすると 19 にもつながってしまいます。

PIN 8 につないだのに PIN 19 にも繋がっている。

PIN 数多めの部品は複数電源やGNDが複数出ている事が多いのでベースにするのは小さめのIC等で後から部品エディタで PIN 数を増やした方が良さそうです。


フォントの大きさがおかしくなる


これは良く知られた問題ですが、ブレッドボード図でも回路図でも、Inkscape で 0.1 インチグリッドに載せて SVG で描けば差し替え用の画像が描けますが、フォントの大きさが狂ってしまいます。

Inkscape が出力する SVG ファイル中のフォントサイズ指定が 'px' になっているのが Frizing で解釈できないのが原因のようなので、SVG をエディタで開いて font-size の 'px' 指定を消せばなおります。それも面倒ならすべてのオブジェクトをパスに変換してしまえば避けられます。どちらが良いかは一長一短。


回路図を描く時、ジャンクションが変なところについてしまう


特にブレッドボード図を先に書いて回路図エディタで仮接続を頼りに線を引いていると下の図のように部品の足の所にジャンクション(大き目の黒丸)が付いてしまいがちです。

これはトポロジー的には下の図のようになっていて部品間ワイヤーが2本以上集まっている所にジャンクションが付くという仕様のせいです。

下の図のように枝分かれさせたい場所にあらかじめ折れ曲がり点を作っておき、部品の足からその点に向かってワイヤーを引くという手順を取ると回避できます。なおこの時、ブレッドボード側の仮配線を一度消さないと、仮配線が邪魔で部品の足から出るワイヤーを選択できない事があります。

部品を登録する時にピン割り付けが面倒くさい

既存部品の画像を差し替えて新しい部品を作る時、各ピンがSVG画像のどこに対応するかを毎回指定するのが面倒くさいのですが、これは SVG 内のピンに相当するオブジェクトのプロパティで ID に "connector0pin"、"connector1pin" などの名前を付けておくと自動的に割り付けられます。部品エディタで各端子を表す ID 名 "connectorN" に "pin" を付けた名前です。わかりにくいですね。

なお、この機能はある程度認知されているようですが、うまく動作しないという報告もそれなりにあります。実際にやってみると、今のところおかしな挙動には遭遇していないのですが。

基板パターン図用の画像が差し替えできない

基板パターン用に使う SVG 画像は表面、裏面に配置するパッドをそれぞれまとめて "copper0"、"copper1" という名前を付けたレイヤーに入れておく必要があります。

更にこれは "プレーンSVG" 形式で保存した場合の話で、デフォルトの "Inkscape SVG" 形式で保存する場合は、各パッドをレイヤーではなく"グループ" でまとめてグループの ID に "copper0"、"copper1" という名前を付けると大丈夫なようです。

以上 Frizing で遭遇した問題の workaround 的ノウハウでした。

そもそもこのツール、ブレッドボード図を描くというニッチな所に特化している感じではありますが、コードエディタが統合されていたり、曲がりなりにもオートルーターが搭載されていたり、ガーバーが出力できたり、目指している所はなかなか凄いです。

今後に期待したいと思います。

posted by g200kg : 2:35 AM : PermaLink

2019/01/06

google ウェブサイト翻訳ツール


google が提供している翻訳系のサービスの1つなのですが、「ウェブサイト翻訳ツール」というのに久しぶりにアクセスしたら↓のような結果になって、どうやらサービスが終了の方向に向かっているようです。

google ウェブサイト翻訳ツール

この「ウェブサイト翻訳ツール」は良く使われているいわゆる「google 翻訳」ではなくて、サイト作成者がウェブページに仕込んでおくとページに英語やその他言語への切り替え機能を勝手に付けてくれるという無料サービスで、私も使っていたんですけどね。

下の画面の赤で囲ったのが言語切り替え用のウィジェットです。

で、これ、翻訳のエンジンは google 翻訳と同じか思いきや何か違うんです。何年か前、機械学習を使って google 翻訳の精度が飛躍的に向上した! というニュースが流れて、これでもう日本語で書いてウェブサイト翻訳ツールを入れておけば全て OK になるのか、と思ったらなんか違う。

左がgoogle翻訳で右がウェブサイト翻訳ツールです。全体的に google 翻訳の方がこなれていて、どうやら機械学習の恩恵はウェブサイト翻訳ツールには導入されなかったようです。

google翻訳でページ丸ごと翻訳するリンクとかを貼って似たような事はできなくもないのですが、それはそれで iframe の中に対応できないとか色々あって、結局待っていればウェブサイト翻訳ツールの翻訳精度が上がるのではないかと思っていたのですがね。残念ながらこういう事になったようです。

今後の方向として真っ当なやり方としては Google Cloud Translation を導入するというくらいなのかな。

まあインストール済みの翻訳ツールはそのまま動くという事なのでしばらく様子見ですけど。
---
最初のお断りのメッセージには「ネイティブで翻訳をサポートしているブラウザを使用されることをおすすめします」なんて書かれていますが、それは Chrome の事ですよね、というのはともかく、そういうブラウザ側で翻訳するのが普通になったりするんですかね? それはそれで楽な世界で良いなとは思うのですが。

posted by g200kg : 7:20 PM : PermaLink

« 2018年11月 | 2019年01月のアーカイブ |


g200kg