apeescape2.com
  • メイン
  • プロセスとツール
  • 仕事の未来
  • 設計プロセス
  • 計画と予測
技術

MIDIチュートリアル:MIDIハードウェアによって制御されるブラウザベースのオーディオアプリケーションの作成

Web Audio APIの人気が高まっている一方で、特に HTML5ゲーム開発者 、Web MIDI APIは、フロントエンド開発者の間ではまだほとんど知られていません。これの大部分は、おそらく現在のサポートとアクセス可能なドキュメントの欠如に関係しています。 Web MIDI APIは現在、Google Chromeでのみサポートされており、特別なフラグを有効にする必要があります。ブラウザメーカーは、ES7標準の一部になる予定であるため、現在このAPIにほとんど重点を置いていません。

80年代初頭にいくつかの音楽業界の代表者によって設計されたMIDI(Musical Instrument Digital Interfaceの略)は、電子音楽デバイスの標準的な通信プロトコルです。それ以来、OSCなどの他のプロトコルが開発されていますが、 30年経った今でも、MIDIはオーディオハードウェアメーカーにとって事実上の通信プロトコルです。スタジオで少なくとも1つのMIDIデバイスを所有していない現代の音楽プロデューサーを見つけるのは難しいでしょう。

デザインの要素と原則

Web Audio APIの迅速な開発と採用により、クラウドと現実世界の間のギャップを埋めるブラウザーベースのアプリケーションの構築を開始できるようになりました。 Web MIDI APIを使用すると、シンセサイザーやオーディオエフェクトを構築できるだけでなく、現在のフラッシュベースの対応するものと同様の機能とパフォーマンスを備えたブラウザーベースのDAW(デジタルオーディオワークステーション)の構築を開始することもできます(チェックアウト Audiotool 、 例えば)。



このMIDIチュートリアルでは、Web MIDI APIの基本を説明し、お気に入りのMIDIデバイスで再生できる簡単なモノシンセを作成します。完全なソースコードが利用可能です ここに 、そしてあなたはテストすることができます ライブデモ 直接。 MIDIデバイスをお持ちでない場合でも、の「キーボード」ブランチをチェックして、このチュートリアルに従うことができます。 GitHubリポジトリ 、コンピュータのキーボードの基本的なサポートが有効になるため、ノートを演奏したり、オクターブを変更したりできます。これは、ライブデモとして利用できるバージョンでもあります。ただし、コンピューターハードウェアの制限により、コンピューターのキーボードを使用してシンセサイザーを制御する場合は常に、ベロシティとデチューンの両方が無効になります。キー/ノートのマッピングについては、GitHubのreadmeファイルを参照してください。

サルゲッチュ

MIDIチュートリアルの前提条件

このMIDIチュートリアルには、次のものが必要です。

  • #enable-web-midiを搭載したGoogleChrome(バージョン38以降)フラグが有効
  • (オプション)コンピューターに接続された、ノートをトリガーできるMIDIデバイス

また、Angular.jsを使用して、アプリケーションに少し構造を追加します。したがって、フレームワークの基本的な知識が前提条件です。

入門

MIDIアプリケーションを3つのモジュールに分割することにより、ゼロからモジュール化します。

  • WebMIDI:コンピューターに接続されているさまざまなMIDIデバイスの処理
  • WebAudio:シンセサイザーのオーディオソースを提供します
  • WebSynth:Webインターフェイスをオーディオエンジンに接続する

Appモジュールは、Webユーザーインターフェイスとのユーザーインタラクションを処理します。アプリケーション構造は次のようになります。

|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html

また、アプリケーションの構築に役立つ次のライブラリをインストールする必要があります。 Angular.js 、 ブートストラップ 、および jQuery 。おそらく、これらをインストールする最も簡単な方法は、 バウアー 。

WebMIDIモジュール:実世界との接続

MIDIデバイスをアプリケーションに接続してMIDIの使用方法を理解しましょう。そのために、単一のメソッドを返す単純なファクトリを作成します。 Web MIDI APIを介してMIDIデバイスに接続するには、navigator.requestMIDIAccessを呼び出す必要があります。方法:

angular .module('WebMIDI', []) .factory('Devices', ['$window', function($window) { function _connect() { if($window.navigator && 'function' === typeof $window.navigator.requestMIDIAccess) { $window.navigator.requestMIDIAccess(); } else { throw 'No Web MIDI support'; } } return { connect: _connect }; }]);

そして、それはほとんどそれです!

requestMIDIAccessメソッドはpromiseを返すので、それを直接返し、アプリのコントローラーでpromiseの結果を処理できます。

angular .module('DemoApp', ['WebMIDI']) .controller('AppCtrl', ['$scope', 'Devices', function($scope, devices) { $scope.devices = []; devices .connect() .then(function(access) { if('function' === typeof access.inputs) { // deprecated $scope.devices = access.inputs(); console.error('Update your Chrome version!'); } else { if(access.inputs && access.inputs.size > 0) { var inputs = access.inputs.values(), input = null; // iterate through the devices for (input = inputs.next(); input && !input.done; input = inputs.next()) { $scope.devices.push(input.value); } } else { console.error('No devices detected!'); } } }) .catch(function(e) { console.error(e); }); }]);

前述のように、requestMIDIAccessメソッドはpromiseを返し、オブジェクトをthenに渡します。入力と出力の2つのプロパティを持つメソッド。

Chromeの以前のバージョンでは、これら2つのプロパティは、入力デバイスと出力デバイスの配列を直接取得できるメソッドでした。ただし、最新の更新では、これらのプロパティはオブジェクトになりました。 valuesを呼び出す必要があるため、これはかなりの違いになります。入力オブジェクトまたは出力オブジェクトのいずれかでメソッドを実行して、対応するデバイスのリストを取得します。このメソッドはジェネレーター関数として機能し、イテレーターを返します。繰り返しますが、このAPIはES7の一部であることが意図されています。したがって、ジェネレータのような動作を実装することは、元の実装ほど単純ではありませんが、理にかなっています。

最後に、sizeを介してデバイスの数を取得できます。イテレータオブジェクトのプロパティ。少なくとも1つのデバイスがある場合は、nextを呼び出して結果を反復処理するだけです。イテレータオブジェクトのメソッド、および各デバイスを$ scopeで定義された配列にプッシュします。フロントエンドでは、使用可能なすべての入力デバイスを一覧表示する単純な選択ボックスを実装し、Webシンセを制御するためのアクティブデバイスとして使用するデバイスを選択できます。

Choose a MIDI device...

この選択ボックスをactiveDeviceという$ scope変数にバインドしました後でこのアクティブデバイスをシンセに接続するために使用します。

このアクティブデバイスをシンセに接続します

WebAudioモジュール:ノイズを作る

WebAudio APIを使用すると、サウンドファイルを再生できるだけでなく、オシレーター、フィルター、ゲインノードなどのシンセサイザーの重要なコンポーネントを再作成してサウンドを生成することもできます。 とりわけ 。

オシレーターを作成する

オシレーターの役割は、波形を出力することです。波形にはさまざまなタイプがあり、そのうち4つがWebAudio APIでサポートされています。正弦、正方形、三角形、のこぎり波です。波形は特定の周波数で「振動」すると言われていますが、必要に応じて独自のカスタムウェーブテーブルを定義することもできます。ある範囲の周波数は人間が聞くことができます-それらは音として知られています。あるいは、低周波数で振動している場合、オシレーターはLFO(「低周波数オシレーター」)の構築にも役立つため、サウンドを変調できます(ただし、これはこのチュートリアルの範囲を超えています)。

サウンドを作成するために最初に行う必要があるのは、新しいAudioContextをインスタンス化することです。

function _createContext() { self.ctx = new $window.AudioContext(); }

そこから、WebAudioAPIによって利用可能になったコンポーネントをインスタンス化できます。各コンポーネントの複数のインスタンスを作成する可能性があるため、必要なコンポーネントの新しい一意のインスタンスを作成できるようにサービスを作成することは理にかなっています。新しいオシレーターを生成するサービスを作成することから始めましょう。

angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });

これで、前に作成したAudioContextインスタンスを引数として渡して、自由に新しいオシレーターをインスタンス化できます。今後の作業を簡単にするために、いくつかのラッパーメソッド(単なるシンタックスシュガー)を追加し、Oscillator関数を返します。

Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type } } Oscillator.prototype.setFrequency = function(freq, time) { self.osc.frequency.setTargetAtTime(freq, 0, time); }; Oscillator.prototype.start = function(pos) { self.osc.start(pos); } Oscillator.prototype.stop = function(pos) { self.osc.stop(pos); } Oscillator.prototype.connect = function(i) { self.osc.connect(i); } Oscillator.prototype.cancel = function() { self.osc.frequency.cancelScheduledValues(0); } return Oscillator;

マルチパスフィルターとボリュームコントロールを作成する

基本的なオーディオエンジンを完成させるには、さらに2つのコンポーネントが必要です。サウンドに少し形を与えるマルチパスフィルターと、サウンドの音量を制御して音量のオンとオフを切り替えるゲインノードです。これを行うには、オシレーターの場合と同じ方法で続行できます。いくつかのラッパーメソッドを使用して関数を返すサービスを作成します。必要なのは、AudioContextインスタンスを提供し、適切なメソッドを呼び出すことだけです。

createBiquadFilterを呼び出してフィルターを作成しますAudioContextインスタンスのメソッド:

ctx.createBiquadFilter();

同様に、ゲインノードの場合、createGainと呼びます。方法:

ctx.createGain();

WebSynthモジュール:配線

これで、シンセインターフェイスを構築し、MIDIデバイスをオーディオソースに接続する準備がほぼ整いました。まず、オーディオエンジンを接続して、MIDIノートを受信できるようにする必要があります。オーディオエンジンを接続するには、必要なコンポーネントの新しいインスタンスを作成し、connectを使用してそれらを「接続」するだけです。各コンポーネントのインスタンスで使用可能なメソッド。 connectメソッドは1つの引数を取ります。これは、現在のインスタンスを接続するコンポーネントです。 connectのように、より複雑なコンポーネントのチェーンを調整することができます。メソッドは、1つのノードを複数のモジュレーターに接続できます(クロスフェージングなどの実装を可能にします)。

self.osc1 = new Oscillator(self.ctx); self.osc1.setOscType('sine'); self.amp = new Amp(self.ctx); self.osc1.connect(self.amp.gain); self.amp.connect(self.ctx.destination); self.amp.setVolume(0.0, 0); //mute the sound self.filter1.disconnect(); self.amp.disconnect(); self.amp.connect(self.ctx.destination); }

オーディオエンジンの内部配線を構築しました。少し遊んで、配線のさまざまな組み合わせを試すことができますが、耳が聞こえなくなるのを避けるために音量を下げることを忘れないでください。これで、MIDIインターフェイスをアプリケーションに接続し、MIDIメッセージをオーディオエンジンに送信できます。デバイス選択ボックスにウォッチャーをセットアップして、シンセに仮想的に「プラグイン」します。次に、デバイスからのMIDIメッセージをリッスンし、その情報をオーディオエンジンに渡します。

// in the app's controller $scope.$watch('activeDevice', DSP.plug); // in the synth module function _onmidimessage(e) { /** * e.data is an array * e.data[0] = on (144) / off (128) / detune (224) * e.data[1] = midi note * e.data[2] = velocity || detune */ switch(e.data[0]) { case 144: Engine.noteOn(e.data[1], e.data[2]); break; case 128: Engine.noteOff(e.data[1]); break; } } function _plug(device) { self.device = device; self.device.onmidimessage = _onmidimessage; }

ここでは、デバイスからMIDIイベントをリッスンし、からのデータを分析しています。 MidiEventオブジェクト 、そしてそれを適切なメソッドに渡します。 noteOnのいずれかまたはnoteOff、イベントコードに基づく(noteOnの場合は144、noteOffの場合は128)。これで、オーディオモジュールのそれぞれのメソッドにロジックを追加して、実際にサウンドを生成できます。

function _noteOn(note, velocity) { self.activeNotes.push(note); self.osc1.cancel(); self.currentFreq = _mtof(note); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.amp.cancel(); self.amp.setVolume(1.0, self.settings.attack); } function _noteOff(note) { var position = self.activeNotes.indexOf(note); if (position !== -1) { self.activeNotes.splice(position, 1); } if (self.activeNotes.length === 0) { // shut off the envelope self.amp.cancel(); self.currentFreq = null; self.amp.setVolume(0.0, self.settings.release); } else { // in case another note is pressed, we set that one as the new active note self.osc1.cancel(); self.currentFreq = _mtof(self.activeNotes[self.activeNotes.length - 1]); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); } }

ここでいくつかのことが起こっています。 noteOnでメソッドでは、最初に現在のノートをノートの配列にプッシュします。モノシンセを作成している場合でも(つまり、一度に1つのノートしか演奏できない)、キーボード上で一度に複数の指を使用することができます。したがって、1つのノートをリリースすると次のノートが再生されるように、これらすべてのノートをキューに入れる必要があります。次に、オシレーターを停止して新しい周波数を割り当てる必要があります。新しい周波数は、MIDIノート(0から127までのスケール)から実際の周波数値に変換します。 少し数学 :

function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }

noteOffでメソッドでは、最初にアクティブなノートの配列からノートを見つけて削除することから始めます。次に、それが配列内の唯一の音符である場合は、単に音量をオフにします。

setVolumeの2番目の引数methodは遷移時間であり、ゲインが新しいボリューム値に到達するまでにかかる時間を意味します。音楽的には、ノートがオンの場合はアタックタイムに相当し、ノートがオフの場合はリリースタイムに相当します。

WebAnalyserモジュール:サウンドの視覚化

シンセに追加できるもう1つの興味深い機能は、アナライザーノードです。これにより、キャンバスを使用してサウンドの波形を表示し、レンダリングすることができます。アナライザーノードの作成は、実際に分析を実行するためにscriptProcessorノードも作成する必要があるため、他のAudioContextオブジェクトよりも少し複雑です。まず、DOMでcanvas要素を選択します。

function Analyser(canvas) null; self.view = self.canvas[0].getContext('2d')

次に、connectを追加しますアナライザーとスクリプトプロセッサーの両方を作成するメソッド:

Analyser.prototype.connect = function(ctx, output) { // setup a javascript node self.javascriptNode = ctx.createScriptProcessor(2048, 1, 1); // connect to destination, else it isn't called self.javascriptNode.connect(ctx.destination); // setup an analyzer self.analyser = ctx.createAnalyser(); self.analyser.smoothingTimeConstant = 0.3; self.analyser.fftSize = 512; // connect the output to the destination for sound output.connect(ctx.destination); // connect the output to the analyser for processing output.connect(self.analyser); self.analyser.connect(self.javascriptNode); // define the colors for the graph var gradient = self.view.createLinearGradient(0, 0, 0, 200); gradient.addColorStop(1, '#000000'); gradient.addColorStop(0.75, '#ff0000'); gradient.addColorStop(0.25, '#ffff00'); gradient.addColorStop(0, '#ffffff'); // when the audio process event is fired on the script processor // we get the frequency data into an array // and pass it to the drawSpectrum method to render it in the canvas self.javascriptNode.onaudioprocess = function() { // get the average for the first channel var array = new Uint8Array(self.analyser.frequencyBinCount); self.analyser.getByteFrequencyData(array); // clear the current state self.view.clearRect(0, 0, 1000, 325); // set the fill style self.view.fillStyle = gradient; drawSpectrum(array); } };

まず、scriptProcessorオブジェクトを作成し、それを宛先に接続します。次に、アナライザー自体を作成し、オシレーターまたはフィルターからのオーディオ出力をフィードします。オーディオ出力を宛先に接続して、それを聞くことができるようにする必要があることに注意してください。また、グラフのグラデーションの色を定義する必要があります。これは、createLinearGradientを呼び出すことによって行われます。キャンバス要素のメソッド。

最後に、scriptProcessorは一定の間隔で「audioprocess」イベントを発生させます。このイベントが発生すると、アナライザーによってキャプチャされた平均頻度を計算し、キャンバスをクリアして、drawSpectrumを呼び出して新しい頻度グラフを再描画します。方法:

function drawSpectrum(array) { for (var i = 0; i <(array.length); i++) { var v = array[i], h = self.canvas.height(); self.view.fillRect(i * 2, h - (v - (h / 4)), 1, v + (h / 4)); } }

最後になりましたが、この新しいコンポーネントに対応するために、オーディオエンジンの配線を少し変更する必要があります。

// in the _connectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.filter1); } else { self.filter1.connect(self.ctx.destination); } // in the _disconnectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.amp); } else { self.amp.connect(self.ctx.destination); }

これで、シンセの波形をリアルタイムで表示できる優れたビジュアライザーができました。これにはセットアップに少し手間がかかりますが、特にフィルターを使用する場合は、非常に興味深く洞察に満ちています。

シンセの構築:ベロシティとデチューンの追加

MIDIチュートリアルのこの時点では、かなりクールなシンセがありますが、すべてのノートを同じ音量で再生します。これは、ベロシティデータを適切に処理する代わりに、ボリュームを1.0の固定値に設定するだけだからです。それを修正することから始めましょう。次に、最も一般的なMIDIキーボードにあるデチューンホイールを有効にする方法を見ていきます。

速度を有効にする

慣れていない場合、「ベロシティ」はキーボードのキーをどれだけ強く押すかによって決まります。この値に基づいて、作成されたサウンドは柔らかくまたは大きく見えます。

MIDIチュートリアルシンセでは、ゲインノードのボリュームを操作するだけでこの動作をエミュレートできます。そのためには、まずMIDIデータを0.0〜1.0の浮動小数点値に変換してゲインノードに渡すために、少し計算を行う必要があります。

function _vtov (velocity) { return (velocity / 127).toFixed(2); }

MIDIデバイスのベロシティ範囲は0〜127であるため、その値を127で除算し、小数点以下2桁の浮動小数点値を返します。次に、_noteOnを更新できます計算値をゲインノードに渡す方法:

self.amp.setVolume(_vtov(velocity), self.settings.attack);

以上です!シンセサイザーを演奏すると、キーボードのキーをどれだけ強く叩いたかによって音量が変化することがわかります。

MIDIキーボードでデチューンホイールを有効にする

ほとんどのMIDIキーボードはデチューンホイールを備えています。ホイールを使用すると、現在再生されているノートの周波数をわずかに変更して、「デチューン」と呼ばれる興味深い効果を作成できます。デチューンホイールは、周波数値を再計算してオシレーターを更新することでリッスンして処理できる独自のイベントコード(224)でMidiMessageイベントも発生させるため、MIDIの使用方法を学ぶと、これはかなり簡単に実装できます。

node.jsを使用する必要がある理由

まず、シンセでイベントをキャッチする必要があります。そのために、_onmidimessageで作成したswitchステートメントにケースを追加します。折り返し電話:

case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;

次に、detuneを定義しますオーディオエンジンのメソッド:

function _detune(d) { if(self.currentFreq) { //64 = no detune if(64 === d) { self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.detuneAmount = 0; } else { var detuneFreq = Math.pow(2, 1 / 12) * (d - 64); self.osc1.setFrequency(self.currentFreq + detuneFreq, self.settings.portamento); self.detuneAmount = detuneFreq; } } }

デフォルトのデチューン値は64です。これは、デチューンが適用されていないことを意味します。したがって、この場合、現在の周波数をオシレーターに渡すだけです。

最後に、_noteOffも更新する必要があります別のノートがキューに入れられた場合にデチューンを考慮に入れる方法:

self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);

インターフェイスの作成

これまでは、MIDIデバイスと波形ビジュアライザーを選択できる選択ボックスのみを作成しましたが、Webページを操作してサウンドを直接変更することはできません。一般的なフォーム要素を使用して非常にシンプルなインターフェイスを作成し、それらをオーディオエンジンにバインドしましょう。

インターフェイスのレイアウトの作成

シンセのサウンドを制御するために、さまざまなフォーム要素を作成します。

  • 発振器タイプを選択するためのラジオグループ
  • フィルタを有効/無効にするチェックボックス
  • フィルタタイプを選択するためのラジオグループ
  • フィルタの周波数とレゾナンスを制御する2つの範囲
  • ゲインノードの攻撃と解放を制御する2つの範囲

インターフェイス用のHTMLドキュメントを作成すると、次のようになります。

Choose a MIDI device...

Oscillator

Oscillator Type

{{t}}

Filter

enable filter

Filter Type

{{t}} filter frequency: filter resonance: Analyser attack: release:

派手に見えるようにユーザーインターフェイスを装飾することは、この基本的なMIDIチュートリアルでは取り上げません。代わりに、後でユーザーインターフェイスを磨くための演習として保存し、おそらく次のようにすることができます。

洗練されたMIDIユーザーインターフェイス

インターフェイスをオーディオエンジンにバインドする

これらのコントロールをオーディオエンジンにバインドするためのいくつかのメソッドを定義する必要があります。

オシレーターの制御

オシレーターの場合、オシレーターのタイプを設定できるメソッドのみが必要です。

Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }

フィルタの制御

フィルタには、3つのコントロールが必要です。1つはフィルタタイプ用、1つは周波数用、もう1つはレゾナンス用です。 _connectFilterを接続することもできますおよび_disconnectFilterチェックボックスの値へのメソッド。

Filter.prototype.setFilterType = function(type) { if(type) { self.filter.type = type; } } Filter.prototype.setFilterFrequency = function(freq) { if(freq) { self.filter.frequency.value = freq; } } Filter.prototype.setFilterResonance = function(res) { if(res) { self.filter.Q.value = res; } }

アタックとレゾナンスの制御

サウンドを少し形作るために、ゲインノードのアタックパラメーターとリリースパラメーターを変更できます。これには2つの方法が必要です。

function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }

ウォッチャーの設定

最後に、アプリのコントローラーで、いくつかのウォッチャーを設定し、作成したさまざまなメソッドにバインドするだけで済みます。

$scope.$watch('synth.oscType', DSP.setOscType); $scope.$watch('synth.filterOn', DSP.enableFilter); $scope.$watch('synth.filterType', DSP.setFilterType); $scope.$watch('synth.filterFreq', DSP.setFilterFrequency); $scope.$watch('synth.filterRes', DSP.setFilterResonance); $scope.$watch('synth.attack', DSP.setAttack); $scope.$watch('synth.release', DSP.setRelease);

結論

このMIDIチュートリアルでは、多くの概念が取り上げられました。ほとんどの場合、WebMIDI APIの使用方法を発見しました。これは、W3Cの公式仕様とは別に、かなり文書化されていません。 Google Chromeの実装は非常に簡単ですが、入力デバイスと出力デバイスのイテレータオブジェクトへの切り替えには、古い実装を使用したレガシーコードのリファクタリングが少し必要です。

WebAudio APIに関しては、これは非常に豊富なAPIであり、このチュートリアルではその機能の一部のみを取り上げました。 WebMIDI APIとは異なり、WebAudio APIは、特にMozilla DeveloperNetworkで非常によく文書化されています。 Mozilla Developer Networkには、多数のコード例と、各コンポーネントのさまざまな引数とイベントの詳細なリストが含まれています。これは、独自のカスタムブラウザベースのオーディオアプリケーションを実装するのに役立ちます。

両方のAPIが成長し続けるにつれて、JavaScript開発者にとって非常に興味深い可能性が開かれます。 Flashの同等品と競合できる、フル機能のブラウザベースのDAWを開発できます。また、デスクトップ開発者の場合は、次のようなツールを使用して独自のクロスプラットフォームアプリケーションの作成を開始することもできます。 node-webkit 。うまくいけば、これは新世代の オーディオファンのための音楽ツール これにより、物理的な世界とクラウドの間のギャップを埋めることで、ユーザーに力を与えることができます。

インターネット検閲に挑戦する:精査されたマイクロブログを収集するためのWebサイトをどのように構築したか

Webフロントエンド

インターネット検閲に挑戦する:精査されたマイクロブログを収集するためのWebサイトをどのように構築したか
最高のスケッチプラグインの50の究極のリスト

最高のスケッチプラグインの50の究極のリスト

ツールとチュートリアル

人気の投稿
JavaScript、Python、Ruby、Swift、ScalaのOption / Maybe、Either、Future Monads
JavaScript、Python、Ruby、Swift、ScalaのOption / Maybe、Either、Future Monads
SQLインデックスの説明、Pt。 2
SQLインデックスの説明、Pt。 2
すべてのトレンドは価値がありますか? Webデザイナーが犯す最も一般的なUXの間違いトップ5
すべてのトレンドは価値がありますか? Webデザイナーが犯す最も一般的なUXの間違いトップ5
Init.js:完全なJavaScriptスタックの理由と方法のガイド
Init.js:完全なJavaScriptスタックの理由と方法のガイド
Swiftプロパティのラッパーにアプローチする方法
Swiftプロパティのラッパーにアプローチする方法
 
SSOボタンを作成する方法–Flaskログインチュートリアル
SSOボタンを作成する方法–Flaskログインチュートリアル
ベジェ曲線とQPainterを使用してC ++で丸みを帯びた角の形状を取得する方法:ステップバイステップガイド
ベジェ曲線とQPainterを使用してC ++で丸みを帯びた角の形状を取得する方法:ステップバイステップガイド
Angular 2をオンにする:1.5からのアップグレード
Angular 2をオンにする:1.5からのアップグレード
最小の価値のある製品から最大の影響を得る
最小の価値のある製品から最大の影響を得る
WowzaとAmazonElasticTranscoderを使用したオンラインビデオ
WowzaとAmazonElasticTranscoderを使用したオンラインビデオ
人気の投稿
  • C ++プログラミングを学ぶ方法
  • AWSの認定を受けるにはどのくらい時間がかかりますか
  • ダミーのためのビットコインの仕組み
  • Javaで不和ボットを書く方法
  • erc20トークンはいくつありますか
  • レール上のルビーの未来
カテゴリー
技術 その他 データサイエンスとデータベース アジャイルタレント ブランドデザイン プロジェクト管理 モバイル リモートの台頭 エンジニアリング管理 計画と予測

© 2021 | 全著作権所有

apeescape2.com