6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JS初心者が名取さなが喋るatomパッケージを作ってみた話

Posted at

注意

この記事はjavascriptのガチ初心者が書いてます。間違ってること多々あると思うので良かったら教えてください:pray:
怒られたりしたら消す場合があります。

つくったもの

おはようございナース:eggplant:
atomで名取さなちゃんが喋ってくれるプラグインを作りました!
さなちゃんコーディングするところ見てて...
機能とかは↓
https://atom.io/packages/natorisana-voice

よかったら使ってみてね!

使用者の感想

  • 18歳高専生
    プログラミング中に名取さなちゃんの声が聞けて開発効率が劇的に向上しました!!
    保存時に「あい~」と喋るので、聞くために保存をこまめに行うようになりました!!!

きっかけ

ひまのあさん(Twitter: @h1manoa)が作成した素晴らしすぎるvimプラグインを見つけて感動したんですがvimが使えないことに気づいて、atomプラグインを作ることにしました。

技術的(?)な話

環境

  • Windows10
  • Git v2.15
  • atom v1.28.1

atomのパッケージ開発方法

他に詳しく説明している記事やサイトがいっぱいあったから軽い説明だけ

パッケージを生成する

[Package] → [Package Generator] でパッケージを生成する。
TestPackageと入力するとtest-packageという名前のフォルダがC:\Users\ユーザ名\github\に生成され、
C:\Users\ユーザ名\.atom\packagesジャンクションが作られる。

C:\Users\ユーザ名\github\apm develop フォルダ名C:\Users\ユーザ名\.atom\dev\packagesジャンクションが作られ、開発パッケージとして追加される。

ここで、C:\Users\ユーザ名\.atom\packagesのジャンクションを削除しておくと、パッケージを公開した後に公開済みのものをインストールすると、

  • 通常起動
    公開したパッケージを読み込む

  • 開発モード(atom -d
    開発中(編集中)のパッケージを読み込む

ということが可能になる。

生成されるファイルの軽い説明

image.png

  • keymaps / test-package.json
    ショートカットキーとか設定するファイル
  • lib / test-package.js
    プログラムのメインファイル
  • lib / test-package-view.js
    プログラムのUI部分を記述するファイル
  • menus / test-package.json
    上の[package]クリックしたら出るやつのところの項目を設定するファイル(語彙力)
  • spec/
    テスト用のファイルらしいけどやったことないからよくわからん
  • styles/test-package.less
    スタイル設定用ファイル
  • package.json
    パッケージの名前、メインファイル、バージョン、依存関係などを記述するファイル
  • .gitignore
    gitの管理対象外にするファイルを記述するファイル

使わないやつは消しても問題ないのでspecフォルダは削除した()

※詳しくは**atomのFlight Manual**を見ような

プログラム

今回はUIを使わないからtest-package.jstest-package-view.jsのモジュール(?)を使っている部分を消した。

test-package.js

'use babel';

import { CompositeDisposable } from 'atom';

export default {
  subscriptions: null,

  activate(state) {
    this.subscriptions = new CompositeDisposable();

    // toggleコマンドを登録する
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'test-package:toggle': () => this.toggle()
    }));
  },

  deactivate() {
    this.subscriptions.dispose();
  },

  serialize() {
  },

  toggle() {
  }

};

初期処理はactivateに記述する。その他は適当に関数つくったり。

設定画面に項目を追加したい場合は

  config:{
    '音量':{
      'type':'integer',
      'default':100,
      'minimum':0,
      'maximum':1000,
      'description':'[%] (1~1000)',
      'order':1
    },
    'てねっ':{
        ....
    },
    ....
  }

みたいにすると追加される。

実行方法

ctrl + shift + Pでコマンドパレットを開き、'パッケージ名:toggle'で実行する。
image.png
今後、ファイルを編集したときは実行する前に、
ctrl + shift + F5 (または ctrl + shift + Pでコマンドパレットを開きWindow:Reloadを実行) で更新を反映してから実行する。
これめっちゃimportant

デバッグ方法

ctrl + shift + IでChromeの開発者ツールと同じやつ出てくるからそこでコンソールとか見ような:muscle:
(ちなみに同じelectron使ってるdiscordとかでもctrl + shift + Iすると出てくる)
image.png

atomパッケージの公開方法

package.jsonのdescription,repositoryが設定されていることを確認する。
versionは自分で変更しないこと。(最初は0.0.0のまま)
ここでREADME.mdも作ろうね :heart:

githubでリポジトリを作り、ファイルをプッシュする。

atom.ioにgithubでログインできるようにしておく。(?)

※パッケージ名で検索してみたり、https://atom.io/packages/パッケージ名にアクセスしてみたりしてパッケージ名が被ってないことを確認しておく


ここから先はWindowsでやるとちゃんと指定してるに、could not read Username for 'https://github.com': No errorっていうエラーが出るからLinuxでやった:sweat:


package.jsonと同じ階層でapm loginを実行するとatom.ioのマイページに飛ぶので、
トークンをコピーしてコマンドラインに貼る

apm publish {major|minor|patch}を実行し、githubにインストールすると:star:公開される:star:

  • major バージョンが 1.0.0 増える
  • minor バージョンが 0.1.0 増える
  • patch バージョンが 0.0.1 増える

公開に失敗した時の対処(エラーが出たりgithubログイン出来なかったときとか)

1.問題のコミットを消す
Prepare 1.0.0 releaseみたいなコミットが出来てるからgit reset --hard 1つ前のコミットのハッシュ値で戻す

2.タグを消す
git tagで問題のタグ名を見つけてgit tag -d タグ名

atomAPIで使ったやつの説明

Atom API Referenceに全部載ってるから詳しくはそっち見てね

atom.packages.getPackageDirPaths

パッケージのあるディレクトリのパスが配列で取得できる。
開発者モードだと最初の要素が.atom\devで、通常時だと最初の要素が.atom\packagesなので
atom.packages.getPackageDirPaths()[0]
とすると、両方の場合に対応できる

atom.config.get

atom.config.get('パッケージ名.変数名')
設定で指定されている値を取得する
例:atom.config.get('natorisana-voice.てねっ')

atom.keymaps.onDidMatchBinding

event.binding.commandで実行されたショートカットキー名を取得出来る
ショートカットキー名はctrl + .で有効になるKey Binding Resolverでわかる
image.png


atom.keymaps.onDidMatchBinding((event)=>{
      switch (event.binding.command) {
        case 'editor:newline':
          //改行
          if(atom.config.get('natorisana-voice.てねっ')){
            this.play(this.vTene);
          }
          break;
        
        case 'core:confirm':
          //確定
          if(atom.config.get('natorisana-voice.はいはい')){
            this.play(this.vHihi);
          }
          break;
          
        default:
          
      }
    });

atom.workspace.observeTextEditors → editor.getBuffer

テキストエディタからバッファを取得する

atom.workspace.observeTextEditors(editor => {
      let buffer = editor.getBuffer();
                   ...
}

onDidChange

変更されたときに呼び出される

//カウントダウン
      buffer.onDidChange((event)=>{
        if(atom.config.get('natorisana-voice.カウントダウン')){
          let text = buffer.getTextInRange(event.newRange);
          if(text.length == 1){
            for(let i=0;i<10;++i){
              if(text == i.toString()){
                this.play(this.vCountDown[i]);
                break;
              }
            }
          }
        }  
      });

onDidStopChanging

変更やめたときに呼び出される

//うんうん
      buffer.onDidStopChanging((event)=>{
        if(atom.config.get('natorisana-voice.うんうん')){
          if(event.changes.length){
            let text = event.changes[0].newText;
            if(text.indexOf('\n')==-1 && text.indexOf('\r')==-1){
              this.play(this.vUnun);
            }
          }
        }
      });

onDidSave

セーブのときに呼び出される。
onDidMatchBindingだとctrl + sのときだけしか実行されないから、他の保存方法でも実行されるようにこっちで実装した。

      buffer.onDidSave((event)=>{
        if(atom.config.get('natorisana-voice.あい~')){
          this.play(this.vAi);
        }
      });

atom.workspace.getActiveTextEditor → editor.insertText

アクティブなエディタを取得し、テキストを挿入する
インデントされている場所に挿入すると、2行目以降がちゃんとインデントされないからオプションは要修正

  insert(){
    let editor = atom.workspace.getActiveTextEditor()
    editor.insertText(this.sa_na,{select:true,autoIndentNewline:true});
  }

WebAudioAPIでローカルファイル再生する

(なんとなくでしか理解できてないので間違ってるかも)

AudioContextを作成する


  context:null,
  
  ....
  //ボイス入れるための変数
  vStart:null,
  vAi:null,
  vHihi:null,
  vCheer:null,
  vTene:null,
  vUnun:null,
  vCountDown:[],

  activate(state) {

    ....
    
    this.context = new window.AudioContext();
    this.loadVoice();
    this.eventSet();
  },

  deactivate() {
    this.context.dispose();
  },

ファイルを取得する

atom.packages.getPackageDirPathsを使ってパス指定したらローカルファイルを取得できた。
context.decodeAudioDataarraybufferaudiobufferに変換できる。
C言語脳なのでgetFileに変数の参照渡せばいいのではって思ったけど、型が違うってエラーで怒られたから代入の関数を渡すようにした。(ワタシJSヨクワカンナイ)


getFile(filename,callback){
    let path = atom.packages.getPackageDirPaths()[0]+'/natorisana-voice/voice/';
    let request = new XMLHttpRequest();
    request.open('GET',path+filename+'.mp3',true);
    request.responseType = 'arraybuffer';
    request.send();
    request.onload = ()=>{
      let res = request.response;
      this.context.decodeAudioData(res,(buf)=>{
        callback(buf);
      });
    };
  },

  loadVoice(){
    //おはようございナース
    this.getFile('oha',(buf)=>{this.vStart = buf;
      if(atom.config.get('natorisana-voice.おはようございナース!')){
        this.play(this.vStart);
      }
    });
    
    //あい~
    this.getFile('ai~',(buf)=>{this.vAi = buf;});
    
    //はいはい
    this.getFile('hihi',(buf)=>{this.vHihi = buf;});
    
    //応援
    this.getFile('ganbare',(buf)=>{this.vCheer = buf;});
    
    //てねっ
    this.getFile('tene', (buf)=>{this.vTene = buf;});
    
    //うんうん
    this.getFile('unun',(buf)=>{this.vUnun = buf;});
    
    //カウントダウン
    for(let i=0;i<10;i++){
      this.getFile('countdown/'+i,(buf)=>{this.vCountDown[i] = buf;});
    }
    
  },

再生する

contextは一度だけ作るだけだが、sourceは再生のたびに作る。

sourceを音量のgainに接続して、再生先のdestinationに接続する。
フィルターみたいにいろいろ接続していくらしい。

startで再生

  play(buffer){
    
    let source = this.context.createBufferSource();
    source.buffer = buffer;
    
    let gain = this.context.createGain();
    gain.gain.value = atom.config.get('natorisana-voice.音量')*0.01;
    
    gain.connect(this.context.destination);
    source.connect(gain);
    
    source.start(0);
  },

最後に

僕の誕生日名取さなちゃんと一緒なんですよ(めっちゃうれしい)

6
4
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?