0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オレオレライブラリ、スニペットAdvent Calendar 2024

Day 1

poormidi.js とか pomidi.mjs とか呼んでるオレオレ Web MIDI API ラッパー

Last updated at Posted at 2024-12-03

このシリーズは 下書きなしに Qiitaに直接殴り書きしてますw

Web MIDI API の雑なラッパーです。大昔にQiitaでも紹介しました。

じゃ、いいじゃん。という話なんですが、手元で使ってるVer.と随分乖離したな。ということで、一応メモ書きで残しとこうと。

  • 当時はMeteorパッケージとか、斜め上のパッケージングした形跡がありだが、更新もしてない
  • 手元では ES6 Module Ver.しか使ってない

というわけで、github更新しろよ。という話なんですが、微妙に毎年アプリ作る都度無計画に機能追加してたりするので、どれがなんだか状態。

2024年版を貼っときます。

.js
// https://github.com/tadfmac/poormidi
// CCby4.0 D.F.Mac.TripArts Music.
// 
// pomidi.js (Very Poor) Web MIDI API Wrapper
// For Google Chrome Only :D

// 2015.02.23 Change for P&P on Chrome Canary !!!! (W.I.P. and experimental now)
// 2015.06.07 P&P feature has now supported.
// 2015.08.08 add sendCtlChange()
// 2015.08.08 On sendNoteOn(), sendNoteOff() and sendCtlChange(), 
//            arguments are now able to set midi-channel,
//            and also these can be omitted.
//            See also comments in this code, for details.
// 2017.09.30 name change to `pomidi` is nowrenew to promise style.
// 2022.11.09 add device selection feature.
// 2023.09.22 ES6 export
// 2024.11.20 setHandler() Changed to not execute handle Event until setHandler is called.

const DEB = false;

class handler{
  constructor(){
    this.device = null;
    this.func = null;
    if(DEB) console.log("new handler()");
  }
  setDevice(device){
    if(DEB) console.log("handler.setDevice("+device+")");
    this.device = device;
  }
  handler(e){
//    console.log("handler.hander() dev:"+this.device+" e:"+e);
    if(this.handler != null){
      this.func(e,this.device);
    }
  }
  setHandler(func){
    if(DEB) console.log("handler.setHandler()");
    this.func = func.bind(this);
  }
}

class pomidi{
  constructor() {
    this.midi = null;
    this.inputs = [];
    this.outputs = [];
    this.timer = null;
    this.onChangeEvent = null;
    this.onMidiEvent = null;
    this.devices = {};
  }
  init(options){
    if(DEB) console.log("pomidi.init()");
    return new Promise((resolve)=>{
      try{
        navigator.requestMIDIAccess(options).then((access)=>{
          this.success(access);
          if(DEB) console.log("MIDI init OK");
          resolve(this); 
        },(err)=>{
          if(DEB) console.log("MIDI is not supported!");
          resolve(null);
        });
      }catch(e){
        if(DEB) console.log("MIDI is not supported!");
        resolve(null);
      }
    });    
  }
  _setHandleEvent(){
    if(DEB) console.log("poormidi._setHandleEvent()");
    if(this.onMidiEvent == null){
      return;
    }
    for(var cnt=0;cnt<this.inputs.length;cnt++){
      if(this.inputs[cnt].name in this.devices){
        let hdr = new handler();
        hdr.setDevice(this.inputs[cnt].name);
        hdr.setHandler(this.onMidiEvent);
        this.inputs[cnt].hdr = hdr;
        this.inputs[cnt].onmidimessage = hdr.handler.bind(hdr);
      }else{
        this.inputs[cnt].hdr = null;
        this.inputs[cnt].onmidimessage = null;
      }
    }
  }
  setHandler(func, devices){
    if(DEB) console.log("poormidi.setHandler() devices="+devices);
    if(this.midi == null){
      console.log("poormidi.setHandler() NG! MIDI is not supported!");
      return;
    }
    this.onMidiEvent = func;
    if(devices != undefined){
      if(Array.isArray(devices)){
        for(let cnt=0;cnt<devices.length;cnt++){
          this.devices[devices[cnt]] = devices[cnt];
        }
      }else{
        this.devices[devices] = devices;
      }
    }else{
      this.devices = {};
    }
    this._setHandleEvent();
  }
  setOnChange(func){
    if(DEB) console.log("poormidi.setOnChange()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.setOnChange() NG! MIDI is not supported!");
      return;
    }
    this.onChangeEvent = func;
  }
  sendNoteOn(){
    if(DEB) console.log("poormidi.sendNoteOn()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.sendNoteOn() NG! MIDI is not supported!");
      return;
    }
    let note = 0;
    let velocity = 100;
    let channel = 0;
    let devices = null;
    let sendCnt = 0;
    if(arguments.length == 1){
      // midi.sendNoteOn(note);
      note = arguments[0];
    }else if(arguments.length == 2){
      // midi.sendNoteOn(note,velocity);
      note = arguments[0];
      velocity = arguments[1];
    }else if(arguments.length == 3){
      // midi.sendNoteOn(channel,note,velocity);
      channel = arguments[0] & 0x0f;
      note = arguments[1];
      velocity = arguments[2];
    }else if(arguments.length == 4){
      // midi.sendNoteOn(channel,note,velocity,devices);
      channel = arguments[0] & 0x0f;
      note = arguments[1];
      velocity = arguments[2];
      devices = arguments[3]; // array
    }else{
      if(DEB) console.log("poormidi.sendNoteOn:parameter error!!");
      return 0;
    }  
    if(this.outputs.length > 0){
      for(let cnt=0;cnt<this.outputs.length;cnt++){
        if(DEB) console.log("poormidi.sendNoteOn() output to :"+this.outputs[cnt].name);
        let send = false;
        if(devices == null){
          send = true;
        }else{
          for(let cnt2=0;cnt2<devices.length;cnt2++){
            if(devices[cnt2] == this.outputs[cnt].name){
              send = true;
              break;             
            }
          }
        }
        if(send){
          sendCnt ++;
          this.outputs[cnt].send([0x90|channel,note&0x7f,velocity&0x7f]);
        }
      }
    }
    return sendCnt;
  }
  sendNoteOff(){
    if(DEB) console.log("poormidi.sendNoteOff()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.sendNoteOff() NG! MIDI is not supported!");
      return;
    }
    let note = 0;
    let channel = 0;
    let devices = null;
    let sendCnt = 0;
    if(arguments.length == 1){
      // midi.sendNoteOff(note);
      note = arguments[0];
    }else if(arguments.length == 2){
      // midi.sendNoteOff(channel,note);
      channel = arguments[0] & 0x0f;
      note = arguments[1];
    }else if(arguments.length == 3){
      // midi.sendNoteOff(channel,note,devices);
      channel = arguments[0] & 0x0f;
      note = arguments[1];
      devices = arguments[2]; // array
    }else{
      if(DEB) console.log("poormidi.sendNoteOff:parameter error!!");
      return 0;
    }  
    if(this.outputs.length > 0){
      for(let cnt=0;cnt<this.outputs.length;cnt++){
        if(DEB) console.log("poormidi.sendNoteOff() output to :"+this.outputs[cnt].name);
        let send = false;
        if(devices == null){
          send = true;
        }else{
          for(let cnt2=0;cnt2<devices.length;cnt2++){
            if(devices[cnt2] == this.outputs[cnt].name){
              send = true;
              break;             
            }
          }
        }
        if(send){
          sendCnt ++;
          this.outputs[cnt].send([0x80|channel,note,0]);
        }
      }
    }
    return sendCnt;
  }
  sendCtlChange(){
    if(DEB) console.log("poormidi.sendCtlChange()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.sendCtlChange() NG! MIDI is not supported!");
      return;
    }
    let channel = 0;
    let number = 0;
    let value = 0;
    let devices = null;
    let sendCnt = 0;
    if(arguments.length == 2){
      // midi.sendCtlChange(number,value);
      number = arguments[0];
      value = arguments[1];
    }else if(arguments.length == 3){
      // midi.sendCtlChange(channel,number,value);
      channel = arguments[0] & 0x0f;
      number = arguments[1];
      value = arguments[2];
    }else if(arguments.length == 4){
      // midi.sendNoteOn(channel,number,value,devices);
      channel = arguments[0] & 0x0f;
      number = arguments[1];
      value = arguments[2];
      devices = arguments[3]; // array
    }else{
      if(DEB) console.log("poormidi.sendCtlChange:parameter error!!");
      return;
    }
    if(this.outputs.length > 0){
      for(let cnt=0;cnt<this.outputs.length;cnt++){
        let send = false;
        if(devices == null){
          send = true;
        }else{
          for(let cnt2=0;cnt2<devices.length;cnt2++){
            if(devices[cnt2] == this.outputs[cnt].name){
              send = true;
              break;             
            }
          }
        }
        if(send){
          sendCnt ++;
          if(DEB) console.log("poormidi.sendCtlChange() output to :"+this.outputs[cnt].name);
          this.outputs[cnt].send([0xB0|channel,number&0x7f,value&0x7f]);
        }
      }
    }
    return sendCnt;
  }
  sendNoteOnAt(){
    if(DEB) console.log("poormidi.sendNoteOnAt()");
    if(this.midi == null){
      console.log("poormidi.sendNoteOnAt() NG! MIDI is not supported!");
      return;
    }
    let note = 0;
    let velocity = 100;
    let channel = 0;
    let devices = null;
    let sendCnt = 0;
    let at = null;
    if(arguments.length == 2){
      // midi.sendNoteOn(note);
      at = arguments[0];
      note = arguments[1];
    }else if(arguments.length == 3){
      // midi.sendNoteOn(note,velocity);
      at = arguments[0];
      note = arguments[1];
      velocity = arguments[2];
    }else if(arguments.length == 4){
      // midi.sendNoteOn(channel,note,velocity);
      at = arguments[0];
      channel = arguments[1] & 0x0f;
      note = arguments[2];
      velocity = arguments[3];
    }else if(arguments.length == 5){
      // midi.sendNoteOn(channel,note,velocity,devices);
      at = arguments[0];
      channel = arguments[1] & 0x0f;
      note = arguments[2];
      velocity = arguments[3];
      devices = arguments[4]; // array
    }else{
      if(DEB) console.log("poormidi.sendNoteOnAt:parameter error!!");
      return 0;
    }  
    if(this.outputs.length > 0){
      for(let cnt=0;cnt<this.outputs.length;cnt++){
        if(DEB) console.log("poormidi.sendNoteOn() output to :"+this.outputs[cnt].name);
        let send = false;
        if(devices == null){
          send = true;
        }else{
          for(let cnt2=0;cnt2<devices.length;cnt2++){
            if(devices[cnt2] == this.outputs[cnt].name){
              send = true;
              break;             
            }
          }
        }
        if(send){
          sendCnt ++;
          this.outputs[cnt].send([0x90|channel,note&0x7f,velocity&0x7f],at);
        }
      }
    }
    return sendCnt;
  }

  onStateChange(){
    if(DEB) console.log("poormidi.onStateChange()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.onStateChange() NG! MIDI is not supported!");
      return;
    }
    if(this.timer != null){
      clearTimeout(this.timer);
    }
    this.timer = setTimeout(()=>{
      this.refreshPorts();
      this.timer = null;
      if(this.onChangeEvent != null){
        if(DEB) console.log(">>>>>>>>>>> onChange expired")
        this.onChangeEvent();
      }
    },300);
  }
  refreshPorts(){
    if(DEB) console.log("poormidi.refreshPorts()");
    if(this.midi == null){
      if(DEB) console.log("poormidi.refreshPorts() NG! MIDI is not supported!");
      return;
    }
    this.inputs = [];
    this.outputs = [];

    // inputs
    let it = this.midi.inputs.values();
    for(let o = it.next(); !o.done; o = it.next()){
      this.inputs.push(o.value);
      console.log("input port: name:"+o.value.name+", id:"+o.value.id+", oem:"+o.value.manufacturer+",type:"+o.value.type);
    }
    if(DEB) console.log("poormidi.refreshPorts() inputs: "+this.inputs.length);

    this._setHandleEvent();

    // outputs
    let ot = this.midi.outputs.values();
    for(let o = ot.next(); !o.done; o = ot.next()){
      this.outputs.push(o.value);
      console.log("output port: name:"+o.value.name+", id:"+o.value.id+", oem:"+o.value.manufacturer+",type:"+o.value.type);
    }
    console.log("poormidi.refreshPorts() outputs: "+this.outputs.length);
  }
  success(access){
    if(DEB) console.log("poormidi.success()");
    this.midi = access;
    this.refreshPorts();
    this.midi.onstatechange = ()=>{this.onStateChange()};
  }
  wait(ms){
    return new Promise((resolve)=>{setTimeout(resolve,ms);});
  }
}

export default pomidi;

使い方

導入

.js
import MIDI from "./pomidi.mjs";

初期化

.js
let midi = new MIDI();
midi = await midi.init();

なんでオブジェクト上書きしてんの?
という謎コードですが、init() で初期化に失敗するとnull返す仕様なので、objectが nullならその後のエラー処理もやりやすいでしょ?というオレオレ仕様!気持ち悪いけど慣れたw

note on 送信

.js
midi.sendNoteOn(1);

パラメータを省略しまくれる仕様は健在で、ノートナンバー 1 をブロードキャストします。

note off とか cc とかもまー同様。

.js
midi.sendNoteOn(1,1,100,[deviceName]);
  • 第1パラメータ:送信先 MIDI CH
  • 第2パラメータ:ノートナンバー
  • 第3パラメータ:ベロシティ
  • 第4パラメータ:送信対象デバイスリスト

送信先指定しようとすると、いきなりパラメータ省略できなくなります。

MIDIイベント受信

.js
midi.setHandler(func);

MIDIイベントが来た時に呼び出されるコールバック関数を指定します。
上記だと、全てのデバイスのMIDIイベントに反応します。

.js
midi.setHandler(func,[device1,device2]);

第2パラメータで受信対象のMIDIデバイス名を指定できます。

まとめ

相変わらず雑なラッパーですが、いまだによく使います。
まー使いたい人がいたら使ってみてください。適当にカスタマイズ推奨。

直接編集、めっちゃ重くない!? Qiita

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?