うつろぐ

文章

ポエム、独自性に憑りつかれた不安

非常にしんどくなったので整理のために書きます。自分のことしか書いてないのでバイアスが強いです。そんなに読まないでください。軽く読んでもいいよ。

「どういう研究がしたいか」という問い、敗北

きっかけ。研究指導が近づいてきたので「どういう研究がしたいか」について向き合わなければならない時は絶対に来るんだけど、面談とかで機会が立派に与えられるんだけど、見事に機会を逃したというか、逃れたというか。この「どういう研究がしたいか」に対して「この質問答えにくいな~」と思いながら、というか、思っていたから、適当に済ませてしまった。学科で2年間過ごしてきてなんとなく「これどうにかなんないの?」と思い続けてきたことを、何も詰めずに疑問や欲求の形のまま「研究したいこと」として書いた。結果ぼんやりしすぎてて何も伝わらないし、そもそも自分でもどこに本質があるのか分かっていないものは説明できない。

「詰めること」の不足

今回失敗した部分について、「準備不足」で済めばいいんだけど、同じようなことを創作活動とかでもやっているな~と思ってしまってから一気に滅入ってしまった。圧倒的に「詰めること」が足りない。言語やAPIで遊んでおしまい。作曲途中の曲を投げておしまい。プロトタイプ作っておしまい。思えば音ゲーでさえ詰めてない。「詰める」という概念から逃げすぎ。幼少期から積み上げてきたものが、最近更に加速した気がする。そもそも詰める工程って楽しい筈では?なんでこんなにも苦しんでるの?次のようなこと?

能力の特化と物事の追求

僕がこの学科に入ってから感じたこととして、「もはや1つの能力だけでオラオラ言っていても生き残れない」というのがあって、それが「一つの事柄に時間をかけすぎるのは馬鹿」という意識に繋がってしまったのだと思うんだけど、これが本当によくなかった。昔から勉強もスポーツも、ゲームでさえ「上には上がいる」の意識が強すぎて突き詰めることから逃れ続けてきた自分にとって、この誤った意識の獲得はそれを正当化してしまう恐ろしいものだった可能性がある。能力の特化と物事の追求は違う。まず次元というか、軸が違う。それがわからないのは幼児。

独自性に憑りつかれた不安

独自性単体を世に投げているのは世の養分

僕が学科に入ってから大事にしていることの一つにインプットがある。独自性はインプットからしか生まれない。それは正しい。そして、僕は独自性のレベルでのアウトプットもしている。これもいい。問題は、この独自性のレベルでのアウトプットで終わっていること。完全に自己満足に浸っている。勿論独自性単体では研究もアートも自分で成し遂げられない。その独自性単体を見て他の誰かが勝手にそれを成し遂げていく。独自性単体を世に投げているのは世の養分。研究ができないのはまずいのは勿論、そんなんだから食っていける自信が湧き出てこないし、就職も出来ないという気持ちになってくる。

Twitterは大人の娯楽

ここで言う「大人」は職がある人、とくに自分にとって楽な方法で金が稼げている人のことを指す。
Twitterはまさに「独自性単体を世に投げる」ことに長けたツールである。大量のインプットをTLから得て、思ったことを独自性単体のレベルでアウトプットして、ついでにそれによって自分の中に文脈が形成されていく。僕がTwitterに依存したのはやっぱりこの「独自性単体を世に投げ」て自己満足に浸る環境が整っていたからだと言えるし、その姿勢がTwitterによって更に加速してしまったとも言える。それによって就職が不安になる一方だから、職を得てからやったほうがいいな、というだけの話。

独自性を褒められると嬉しいのは中二病だから

これは僕個人の問題。そのとおり。他の項目もまあまあ自分のことしか言ってないけど。

就職の難易度が分からない

散々不安だと言っていた就職について。
「就職は求道なのでは?」と思い始めている。大学で如何に頑張ってスキル、突き詰める力などの種々の「自分にとって楽な方法で金を稼ぐ道具」を獲得して、そこからどのような自己PRを行えばその道具が適切に使えて、自分にとって楽な方法で金が稼げる状態に"""至る"""かという求道なのでは?と思い始めている。
で、そこに"""至る"""のはどれほど大変なことなのかわからない。内定が来た時点で"""至る"""ことが確定するわけではないので余計にわからない。就職ってなんだよ。

人生の苦しみは「やりたいことが無くて苦しい」と「やりたいことが出来なくて苦しい」の二択?

あの、最後に独自性と全く関係ない疑問なんですけど、実は今まで前提にしてきたことなんですけど、
やりたいことが無い人ってこの世に存在するんですか?
自分が色々やりたすぎるだけなのかもしれないけど、やりたいことが無い人がいるとして、その精神状態が全くわからない。なんか、「ストレス耐性が高い人は好奇心が低くてやりたいこと無いかもしれない」とか思ったりするんだけど、もしかして「ストレス耐性が高いけど、やりたいことが無くて苦しい人」と「ストレス耐性が低くて体調や精神を壊してやりたいことが出来なくて苦しい人」のどちらかなのでは?しらない

FMS生に向けた「ネットワーク理論」履修上のことについて

学科向けの記事です。
FMSB2で僕が履修したNDの「ネットワーク理論」について。
(この記事の内容は2017年度の「ネットワーク理論」に基づいたものです。来年度は担当教員、評価、講義の進行が異なる場合があります。)

基本情報

ネットワーク理論はNDの選択必修の2年次科目(2016年入学者用カリキュラム時点)で、MSとFMSの生徒は他学科履修科目として履修することができる。他学科履修科目なので他学部履修科目のような窓口の問い合わせは必要なく、履修登録画面から選択することができる。評価は授業中の演習30%、平常点10%(謎、たぶん出席)、期末考査60%。2単位。

科目の内容

ざっくり。
グラフ理論についての基礎(隣接行列によるグラフの表現など)をさらっとやって、そこから様々な有名な問題についてグラフ理論による解析の例を学ぶ。

まずグラフっていうのはこういうやつ。
f:id:Distorted_Unchi:20180131175115p:plain
点を要素、辺を関係として要素と要素の関係性を表現できるやつ。駅と駅を線路で繋ぐネットワークを表してもいいし、辺を矢印にして(有向グラフ)動物の捕食関係やワークフローを表してもいい。

でここからグラフ理論を活用した簡単な例を一部紹介すると、
f:id:Distorted_Unchi:20180131175152p:plainf:id:Distorted_Unchi:20180131175249p:plain
左のグラフは一筆書きできるけど右のグラフは何をどうやっても一筆書きできない。あと左のグラフは「一筆書きでかつ始点が終点と一致する」みたいなことができない。こういう内容が定理で証明できたり、
f:id:Distorted_Unchi:20180131175830p:plain
A~E駅を鉄道で繋ぐ際に線路整備コスト(各辺の数字)が最小になる繋ぎ方を求めるアルゴリズムがわかったりする。
他にも4つの立方体の問題(急性精神病問題)とか6人の会合(参考:ラムゼーの定理と6人の問題 | 高校数学の美しい物語)とか最大流問題とか色々やったけど今回は図が簡単に書けるやつだけ図を出しました。デジタルな図書くの意外としんどい。
6人の会合については一般化したラムゼーの定理には触れなかったので、そこまで踏み入ってくれればもっと面白かったのになあと思う。授業の傾向としては全体的に、色々なグラフ理論に関する問題に浅めに扱う感じ。

講義

(2017年度の「ネットワーク理論」に基づいたもの)
基本的に出席はない。が、抜き打ちで一回だけ出席取ってたり、宿題(演習課題)のプリントを講義の終わりに配ってたりしたし、講義資料のPDF配布がなくプリントなので基本的に出席しないと(特にアウェイなMSやFMSの人は)困ることが多い。講義はプリントの穴埋め形式で進むし進行もゆっくりめなのであんまり理解に困ることはない。説明もけっこう分かりやすい。教科書は全く使わなかったので来年も担当が同じ人なら買わなくていいけど、内容を深めるための参考書としては良質。
科目の内容の関係上、前提となる数学的スキルは殆どない。基本的な行列の演算や、高校レベルの数列、場合の数に関しての理解ができれば十分。
講義中に演習問題を解くけど、それを提出したりはしない。授業外課題に関しては、2回だけ宿題(演習課題)があったが、あまり負担は大きくない。

テスト

(2017年度)
この辺りの分野はいくらでも応用問題が作れるのでテストもそんな感じなのではとビクビクしていたが、意外にも講義で扱った問題そのままの出題が殆どだった。講義プリントを全て真面目に復習していれば確実に解けるものだった。とはいえ扱った内容の量自体が相当なものなので復習にはそれなりに時間がかかるんだけど。

単位

授業外の負担がかなり少なめなので、授業を受けて復習をそれなりにやってテストに臨めば楽な部類に入ると思う。ただ、当然のことかもしれないけど、内容に興味がないと講義とかテスト対策とかに対してモチベが出なさそうな科目だな~と思った。

所見

「この分野に関して興味があるけどグラフ理論真面目に学んだこと全くない」という人にはおすすめの科目。少し独学してる、みたいな人には講義内容は退屈になると思う。講義内容を踏まえて自分で更に発展させたい、みたいなことは比較的やりやすいと思う。また、グラフに関する問題を解決するアルゴリズムがけっこう出てくるので、FMSにありがちな「それをコンピューターにやらせるぞ~」みたいな動機も生まれやすいので、アルゴリズム実装オタクにもおすすめ。

Processingとffmpegで簡易BGA作成 その2(BPMに同期したモーション、fps指定書き出し)

前回 - Processingとffmpegで簡易BGA作成 その1(レンダリングとプレビューを分けるまで) - うつろぐの続きになります。前回の記事をまだ読んでいない方は是非そちらからお願いします。
 

前回は取り敢えずProcessingとffmpegで音声つきの動画を書き出すところと、レンダリングとプレビューを分けるところをやってきました。
これに今回BPMに同期したモーションの作成やプレビュー、fpsを指定した書き出し(レンダリング)を実現すれば、一通りProcessingでBGAを作成する基盤が完成することになります。
 

前回までのコードの構造を改めて見てみる

前回の最終的なコードは以下のようなものでした。

import ddf.minim.*;
//Minimのsignal等を使っていないのでこの一行だけでよかった

boolean render=true;
//フレーム書き出しと動画への変換を行うかどうか。
//falseにするとプレビューのみを行う

int frameSum=1000;

float size=0;

Minim minim;
AudioPlayer player;

void setup() {
  size(600, 600);
  minim=new Minim(this);
  player=minim.loadFile("music.wav");

  if (render) {
    //以前のフレームデータを消去
    launch(sketchPath()+"\\clearFrames");
    
    //フレーム書き出し
    for (int i=0; i<frameSum; i++) {
      display(i);
      if (i%100==0) {
        println("Now I am dumping frame"+i);
      }
      save("frames/"+nf(i, 4)+".png");
    }
    
    //動画に変換
    launch(sketchPath()+"\\render");
  }
  player.play();
}

void draw() {
  display(frameCount-1);

  fill(0);
  noStroke();
  rect(0, 0, 100, 100);
  fill(-1);
  text(frameCount-1, 50, 50);
}

void stop() {
  player.close();
  minim.stop();
  super.stop();
}

//--------------------------------------

void display(int t) {
  background(0);
  size=400+300*sin(radians(t*10));

  fill(255, 0, 0);
  noStroke();
  ellipse(width/2.0, height/2.0, size, size);
}

構造として、このコードではsetup内でフレームの書き出し、draw内でプレビューを行っており、両方で同じ描画を行うためにdisplay()でタイミングを引数にとって描画内容を返しています。今回はこのdisplay()にBPMに沿ったタイミングを渡してあげることで、BPMに同期したモーションを実現します。

更に、これを現実時間に依存したタイミングを用いると、プレビューもBPMに同期したものになります。前回はProcessingの処理速度に依存したタイミング(frameCount)を用いてプレビューを描画していたのでプレビューが速くなったり遅くなったりしてしまいましたが、現実時間に依存したタイミング(millis()など)を用いることによって、常に一定の速さのモーションになるわけです。

BPM同期の機構をつくる

現実時間に依存したタイミングを用いたBPM同期の機構をつくっていきます。

時間管理をしてくれるclassをつくる

Processingではmillis()で「プログラム起動からの経過ミリ秒」が取得できるので、これを用いてストップウォッチのようなclassを作り、時間管理をさせます。

class MGTime {
  int pMillis, runMillis;
  boolean working;

  MGTime() {
    pMillis=millis();
    runMillis=0;
    working=false;
  }

  void timeStep() {
    if (working) {
      runMillis+=millis()-pMillis;
      pMillis=millis();
    }
  }

  void stop() {
    working=false;
  }
  
  void kill(){
    working=false;
    runMillis=0;
  }

  void start() {
    working=true;
    pMillis=millis();
  }

  int time_normal() {
    return runMillis;
  }
}

start()した後にdraw等のループ内でtimeStep()し、time_normal()でストップウォッチの現在のミリ秒を返します。stop()で一時停止、kill()で停止してリセットします。

BPM換算の機能を追加する

time_normal()で返るミリ秒は、1000ミリ秒(1秒)がBPM60の四分音符と同じ長さになります。よってこの時点でBPM60の楽曲に同期したモーションは簡単に作れますね。ここから発展させて、各BPMでの四分音符を1000としたものをtime_synched()という関数で返すことで、任意のBPMに同期したモーションを作成することが容易になります。
ちなみにtime_synched()の内部のtoSynchedで、ミリ秒を指定したBPMでの換算数値を返しています。

(これがMGTimeクラスの最終版になります)

class MGTime {
  int pMillis, runMillis, BPM;
  boolean working;

  MGTime() {
    pMillis=millis();
    runMillis=0;
    BPM=60;
    working=false;
  }

  void setBPM(int _BPM) {
    BPM=_BPM;
  }

  void timeStep() {
    if (working) {
      runMillis+=millis()-pMillis;
      pMillis=millis();
    }
  }

  void stop() {
    working=false;
  }
  
  void kill(){
    working=false;
    runMillis=0;
  }

  void start() {
    working=true;
    pMillis=millis();
  }

  int time_normal() {
    return runMillis;
  }

  float time_synched() {
    return this.toSynched(runMillis);
  }
  
  float toNormal(float t){
    return t/(BPM/60.0);
  }
  
  float toSynched(float t){
    return t*(BPM/60.0);
  }
}

setBPMでBPMを指定し、time_synched()でBPM換算した数値が返ります。

このclassを用いたBPM同期モーションの例

使用例です。

import ddf.minim.*;

Minim minim;
AudioPlayer player;
MGTime mgt;

void setup() {
  size(600, 600);
  minim=new Minim(this);
  player=minim.loadFile("music.wav");

  mgt=new MGTime();
  mgt.setBPM(170);

  mgt.start();
  player.play();
}

void draw() {
  mgt.timeStep();
  display((int)mgt.time_synched());
}

void stop() {
  player.close();
  minim.stop();
  super.stop();
}

//--------------------------------------

void display(int t) {
  background(0);

  int turn=1000;
  float size=400*(pow(turn-(t%turn), 2)/pow(turn, 2));

  noStroke();
  fill(255, 0, 0);
  ellipse(width/2.0, height/2.0, size, size);
}


こんな感じになります。
www.youtube.com

fpsを指定して書き出す

前述のストップウォッチのclass(MGTime)にtoSynchedという関数を用意していました。これがfps指定書き出しの際にも使えます。レンダリングfpsに応じた一定の間隔で書き出すことになります。まずは1000.0/fpsでその間隔をミリ秒で算出し、それをBPM換算すると、そのBPMでの書き出し間隔が算出されます。

float interval=mgt.toSynched(1000.0/fps);

for (int i=0; i<frameSum; i++) {
  display((int)(i*interval));
  save("frames/"+nf(i, 4)+".png");
}

サンプル

先ほどのMGTimeクラス最終版を利用した場合のサンプルです。

import ddf.minim.*;

boolean render=true;
//フレーム書き出しと動画への変換を行うかどうか。
//falseにするとプレビューのみを行う

int frameSum=1000;
int fps=60;

Minim minim;
AudioPlayer player;
MGTime mgt;

void setup() {
  size(600, 600);
  minim=new Minim(this);
  player=minim.loadFile("music.wav");

  mgt=new MGTime();
  mgt.setBPM(170);

  if (render) {
    //以前のフレームデータを消去
    launch(sketchPath()+"\\clearFrames");

    //フレーム書き出し
    float interval=mgt.toSynched(1000.0/fps);

    for (int i=0; i<frameSum; i++) {
      display((int)(i*interval));
      if (i%100==0) {
        println("Now I am dumping frame"+i);
      }
      save("frames/"+nf(i, 4)+".png");
    }

    //動画に変換。このbatは命令数が多いのでlaunch()から呼ぶと動作が不安定なので手動でbatを実行する必要があるかも
    launch(sketchPath()+"\\render");
  }
  mgt.start();
  player.play();
}

void draw() {
  mgt.timeStep();
  display((int)mgt.time_synched());
}

void stop() {
  player.close();
  minim.stop();
  super.stop();
}

//--------------------------------------

void display(int t) {
  background(0);

  int turn=1000;
  float size=400*(pow(turn-(t%turn), 2)/pow(turn, 2));

  noStroke();
  fill(255, 0, 0);
  ellipse(width/2.0, height/2.0, size, size);
}

displayでの描画内容は「このclassを用いたBPM同期モーションの例」のものと同じです。ここまで出来たらあとはProcessingでいかにかっこいい描画が出来るかに懸かっています。がんばり太郎

Processingとffmpegで簡易BGA作成 その1(レンダリングとプレビューを分けるまで)

総コン Advent Calendar 2017 - Adventarの19日目の記事です。
 

先日M→Fes 2017 : ATNDを見に行ってきました。詳細は省きますが最高になりました。そして、衝動的な映像作品作成欲求に駆られました。しかし、僕はAdobeに課金をしていないので、これまで散々お世話になってきたProcessingを使って映像作品を作ろう!となりました。
 

今回は取り敢えずProcessingとffmpegで音声つきの動画を書き出すところと、そこから発展させて「音楽に同期した動画を作成する」環境にしていく為の第一段階として、レンダリングとプレビューを分けるところをやっていきます。

ProcessingとMinimのインストール、ffmpegのインストールとパスを通すところまではやっている前提で書いていきます。

 

 

取り敢えず動画ができろ

取り敢えずProcessingとffmpegで音声つきの動画を書き出します。
 

フレームの書き出し(Processing)

Processingで現在のフレームを書き出す方法は、色々なサイトにある通り
save("hoge.png");
を書くだけです。簡単ですね。

以下のコードを実行すると、アニメーション描画100フレーム分をpng画像としてframesフォルダに書き出したのち、描画が完了して黒画面になります。

int frameSum=100; //書き出すフレーム数

float size=0; //描画内の円のサイズ

void setup() {
  size(600, 600);
}

void draw() {
  if (frameCount<=frameSum) {
    background(-1);
    size=400+300*sin(radians(frameCount*10));

    fill(255, 0, 0);
    noStroke();
    ellipse(width/2.0, height/2.0, size, size);

    save("frames/"+nf(frameCount, 4)+".png");
  } else {
    background(0);
  }
}

こんな感じに出力されます。
f:id:Distorted_Unchi:20171219220237p:plain

動画に変換(ffmpeg

実は、書き出したフレームをmp4にするツールがProcessingに最初から搭載されている(ツール→ムービーメーカー)のですが、使ってみたところ何故かうまく動作しなかったので、書き出したフレームをmp4の形式にする為にffmpegを叩きます。
ffmpegで連番画像から動画生成 / 動画から連番画像を生成 ~コマ落ちを防ぐには~ - Qiita

ffmpegで動画と音声を結合する方法 - 何気に大変
を参考に、フレーム(連番画像:0001.png,0002.png,...)と音声から動画に変換するコマンドを1行にすると以下のようになります。

ffmpeg -framerate 40 -i frames\\%4d.png -i music.wav -vcodec libx264 -pix_fmt yuv420p -r 60 out.mp4

ここで「%4d」は0001,0002...のような4桁の連番整数の指定を示します。「%3d」だと001,002...のようになります。また、batファイルに書く際は「%%4d」となるようです。

結果としてこんな感じの動画が出ます。長さは音声ファイルに合わせられているのが分かるかと思います。

www.youtube.com


レンダリングとプレビュー(描画)を分ける

分けます。

なぜ

なぜそんなことをするのか。

Processingでプレビューがしたい

音声と同期した動きの確認を、いちいち動画を書き出してからやっているのでは面倒なので、Processingで大まかにプレビューがしたい。

フレーム書き出しとプレビューは分けたい

先ほどのコードで描画しながらフレームを書き出してみて分かったかもしれませんが、フレーム書き出しと描画を同時に行うとめちゃくちゃ重いです。プレビューどころではありません。

構造

大まかな流れは以下になります。
(setup()で以前のフレームデータを消去)→(setup()でフレーム書き出し)→(setup()でffmpegにより動画に変換)→draw()でプレビュー

実装

以下のコードを実行すると、最初にフレーム書き出しや動画への変換が行われ、それが完了すると音声と共にプレビューが始まります。
PBGA_step1.pde

import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
import ddf.minim.spi.*;
import ddf.minim.ugens.*;

boolean render=true;
//フレーム書き出しと動画への変換を行うかどうか。
//falseにするとプレビューのみを行う

int frameSum=1000;

float size=0;

Minim minim;
AudioPlayer player;

void setup() {
  size(600, 600);
  minim=new Minim(this);
  player=minim.loadFile("music.wav");

  if (render) {
    //以前のフレームデータを消去
    launch(sketchPath()+"\\clearFrames");
    
    //フレーム書き出し
    for (int i=0; i<frameSum; i++) {
      display(i);
      if (i%100==0) {
        println("Now I am dumping frame"+i);
      }
      save("frames/"+nf(i, 4)+".png");
    }
    
    //動画に変換。このbatは命令数が多いのでlaunch()から呼ぶと動作が不安定、手動でbatを実行する必要があるかも
    launch(sketchPath()+"\\render");
  }
  player.play();
}

void draw() {
  display(frameCount-1);

  fill(0);
  noStroke();
  rect(0, 0, 100, 100);
  fill(-1);
  text(frameCount-1, 50, 50);
}

void stop() {
  player.close();
  minim.stop();
  super.stop();
}

//--------------------------------------

void display(int t) {
  background(0);
  size=400+300*sin(radians(t*10));

  fill(255, 0, 0);
  noStroke();
  ellipse(width/2.0, height/2.0, size, size);
}

以下は上のコードに必要なbatファイル。pdeと同じフォルダにおく。

clearFrames.bat(フレーム書き出しの前に、以前のフレームデータを消去)

del frames\\*.png

render.bat(動画に変換)

del out.mp4
ffmpeg -framerate 40 -i frames\\%%4d.png -i music.wav -vcodec libx264 -pix_fmt yuv420p -r 60 out.mp4


動作している様子です。
www.youtube.com


次やること

今の状態では、FPSBPMを考慮して音楽に同期する映像を作る環境が整っていないため、次回はそれをやっていきます。
それではまた次回!

「Slackbotの独自性って何」ってなった人が作ったクソSlackbot

総コン Advent Calendar 2017 - Adventar の12日目の記事です。

大学の研究室のゼミで「PythonでSlackbotを作ろう」みたいな流れになったのでそこで作ったSlackbotについて記事を書きます。

 

 

概念的な話

実装以前の「何を作るか?」みたいな話です。

Slackbotの独自性って何

今思えばこんなにこれについて悩む必要なかったというか、Slackbotの開発を始めるまで「Slackbotが動く」ということ自体が楽しい、みたいなことが想定できていなかったんだけど、当時「Slackbotに出来て他のbot(LINE@、Twitterbotなど)に出来ないことってなんだ???」ということについてはだいぶ悩んでました。

特にSlackは閉じたツールなので、「みんなに使ってもらう」という点についてはとても弱いのです。なのでその閉じた中で何が出来たら嬉しいかをずっと考えてました。

 

公開とか、機能を試してもらうとか

「面白対話botを作ってみんなに使ってもらう」みたいなことだとやっぱりLINE@一強な感じもします。対話の実装はSlackbot並みに簡単っぽいし、公開もTwitterとかにリンクやらを投げるだけだし。一方「一定間隔で何かをするbot」だとTwitterbotでしょうね。

Slackはやはり閉じたツールなので一般公開には向きません。当時はこのことばっかり気になってたのですが、よく考えてみたらSlackにも「使ってもらう」観点でのメリットがありました。

 

Slackbotはチームの一員として発言することになるので、「チームに仲間が一人加わった感」が出ます。これは当時全く意識出来てなかった点です。研究室のSlackに元々いるSlackbotが動いている様子を見ている感じだと、面白い奇抜な機能よりも、AIっぽさを優先するとこの「チームに仲間が一人加わった感」が強くなるのだと思います。

 

Slackの印象

で当時は「一般公開に向かない」ということしか考えてなかったのでここで詰まりそうになったのですが、Slackの特徴として「PCアプリにもスマホアプリにも通知が飛ぶ」というのを意識し始めていていました。これはLINEにも当てはまるのですが、僕の中のSlackに対する印象として

「LINEと比較してPCとスマホのどちらでもよく起動する」

というのがありました。LINEぜんぜん開きませんね。起動するのにエネルギーが要ります。というか僕のLINEに来るインフォメーションが今の所成人式の同窓会関連と心理学(大学の講義)のグループワーク関連しかないのが問題なのですが。


あと、制作物の方向性の決め手になった出来事が一つあって、なんと先日僕はゼミの日程が変わったことを完全に忘れていて結果的にゼミをブッチしてしまった。これはたぶんTrelloの通知が標準でオフになっているのが悪い。間違いない。これがきっかけで

「Slackにある重要事項を通知させるのに別のアプリが必要なのは馬鹿らしい」

という感じになってきました。

結果何になったか

上のようなことを経た結果、

「メモしたチーム内の重要事項を通知してくれる君」

になりました。:tada: 
 

実装

実装(実装)

よくあるサービス未満の何かをつくった

http://iida.nkmr.io/*Proj/Hatena_melTest/index.php

取り敢えず「メモしたチーム内の重要事項を通知してくれる君」の「メモ」の部分をPHPで作りました。これはSlackに紐づいてるものではなく動作サンプルです。CSS全く書いてないのはゆるして

諸々の操作は適当にやることにして、重要な部分だけ。

f:id:Distorted_Unchi:20171212011455p:plain

このようなメモを作ると、通知時刻の2017/12/12 01:15になると
 
f:id:Distorted_Unchi:20171212011820p:plain

このようなリプライが届く、という想定です。ユーザー名をhereにしてあげると全員に通知出来るし、ユーザー名で特定ユーザーに通知出来る。

 

これに追随したSlackbot

紐づいたSlackチームのチャンネルにリプライ通知を送信する、というようなSlackbotをPythonで作成しました。

機能として、

・通知時刻になったらリプライ通知送信(1分ごと)
コマンドライン的なメモの作成(create:ユーザーID,タイトル,説明,通知時刻)
・指定ユーザーページ表示(userpage:user)
・ホーム表示(home)
 
があります。後半二つは若干おまけっぽい。

だいたいのソースコードはこちら。サンプルなので一部アドレスやパスワードは変えてあります。

# -*- coding: utf-8 -*-

from slackbot.bot import Bot
from slackbot.bot import respond_to
from slackbot.bot import listen_to
from slacker import Slacker
import slackbot_settings

from datetime import datetime
import threading

import json
import requests
import re

#------------------------------------------

slack=Slacker(slackbot_settings.API_TOKEN)

#------------------------------------------

def timer_minute():
    resJson=requests.get('http://iida.nkmr.io/*Proj/Hatena_melTest/dumperAPI.php?username=xxxxxx&password=xxxxxx').json()
    for i in range(len(resJson)):
        if format(resJson[i]['due']) == datetime.now().strftime('%Y/%m/%d %H:%M'):
            slack.chat.post_message(
                "melbot_test",
                '@'+format(resJson[i]['author'])+': 「'+format(resJson[i]['title'])+'」の通知時刻になりました: http://iida.nkmr.io/*Proj/Hatena_melTest/edit_form.php?id='+format(resJson[i]['id']),
                as_user=True,
                link_names=True
            )

    timerM=threading.Timer(60.0,timer_minute)
    timerM.start()


#------------------------------------------

@respond_to(r'(create:.*,.*,.*,.*)')
def request_func(message,something):
    pattern=r'create:(.*),(.*),(.*),(.*)'
    searchOB=re.search(pattern,something)
    if searchOB:
        print(searchOB.group(1))
        print(searchOB.group(2))
        print(searchOB.group(3))
        print(searchOB.group(4))
    requests.post(
        'http://iida.nkmr.io/*Proj/Hatena_melTest/submitterAPI.php',
        {
            'author':searchOB.group(1),
            'title':searchOB.group(2),
            'body':searchOB.group(3),
            'due':searchOB.group(4),
            'mode':'new',
            'username':'xxxxxx',
            'password':'xxxxxx'
        }
    )
    message.reply('requestしたよ')

@respond_to(r'(userpage:.*)')
def userpage_func(message,something):
    pattern=r'userpage:(.*)'
    searchOB=re.search(pattern,something)
    if searchOB:
        print(searchOB.group(1))
        message.reply('http://iida.nkmr.io/*Proj/Hatena_melTest/userpage.php?author='+searchOB.group(1))

@respond_to(r'home')
def home_func(message):
    message.reply('http://iida.nkmr.io/*Proj/Hatena_melTest')

#------------------------------------------

def main():
    timerM=threading.Timer(60.0,timer_minute)
    timerM.start()
    bot = Bot()
    bot.run()

if __name__ == "__main__":
   main()

 

最初のブロックはライブラリのインポート。

次のブロックではSlackerの設定をしています。

次のブロックでは、「通知時刻になったらリプライ通知送信」の部分を実装しています。threadingを用いて1分ごとに全てのメモの時刻(due)を参照して、分までが現在時刻と一致したらSlackerのchat.post_messageでリプライを送っています(chat.post_ephemeralが何故か動かなかったので代用)。
chat.post_ephemeralをchat.post_messageで代用する際、link_namesをTrueにしておかないと、「@user」などの部分がハイライトされず、平文になってしまい通知も届かないので注意。こうなる。↓
f:id:Distorted_Unchi:20171212014155p:plain

その次のブロックでは、リプライに対する応答を書いています。@respond_toの引数には正規表現が使えます。また、@respond_toの引数の1部分を()で括るとその値が関数の第二引数somethingに格納されます。
somethingは一つしか取れないようで、例えばrequest_funcでやっているように複数に分けて取りたいときは一度全体を()で括ってsomethingに格納したのち、reライブラリのsearchで分けて格納し直す運びになります。

最後のブロックはテンプレですね

ドメインAPIもつくった

 気付いた方もいるかもしれませんが、今回データの参照や追加の際にpythonからmysqlを叩くのではなく、PHPで自ドメインAPI(もどき?)を作成してrequestsで叩いています。というのも、研究室のサーバーにmysql関連のpythonライブラリが入っておらず、教授に依頼してインストールしていただいても良かったのですが、「APIとかそういうの全然作ったことないな」と思ったので何となくやりました。

データ追加の方はinsert実行するだけなのでロクなこと書いてないので省略して、データをjsonで吐き出す方の解説を簡単にしたいと思います。

<?php
function raw_json_encode($input) {

  return preg_replace_callback(
    '/\\\\u([0-9a-zA-Z]{4})/',
    function ($matches) {
      return mb_convert_encoding(pack('H*',$matches[1]),'UTF-8','UTF-16');
    },
    json_encode($input)
  );

}

header('Access-Control-Allow-Origin:http://iida.nkmr.io');
header("Content-Type: application/json; charset=utf-8");

if($_GET['username']==='xxxxxx' && $_GET['password']==='xxxxxx'){
  $sql="
  select * from table;
  ";

  $mysqli=new mysqli("localhost", "username", "password");
  $mysqli->set_charset("utf8");
  $mysqli->select_db("hoge_db");
  $results=$mysqli->query($sql);

  $data=array();

  while($line=$results->fetch_array(MYSQLI_ASSOC)){
    $data[]=array(
      id=>$line['id'],
      author=>$line['author'],
      title=>$line['title'],
      body=>$line['body'],
      due=>$line['due']
    );
  }

  echo raw_json_encode($data);
  $mysqli->close();
}else{
  header("Location: error.php");
  exit;
}
?>

研究室のサーバーのphpのバージョンがゲロ古いのでPDOではなくmysqliを使っています。

ポイントは2つで、まず1つはheader("Content-Type: application/json; charset=utf-8");です。これで出力結果のフォーマットをjsonに指定しています。これは
PHPを用いて3行でAPIを作る!! | Life Tips
を参考にさせて頂きました

もう1つはjson_encodeで日本語が文字コードに変換されてしまうので、
json_encodeで全角文字が文字化けする - PHPプロ!Q&A掲示板
のraw_json_encodeという関数を利用させていただきました。これによるとPHP5.4以降ならjson_encodeにオプションが適用できるらしいですが、研究室のサーバーのphpのバーzyありがとうございました。

こんな感じに出力される
f:id:Distorted_Unchi:20171212021620p:plain

おしまい。

参考文献
PythonでSlackbotを作る(3) – ビットログ
初心者に捧げる対話システムの作り方 - Qiita
PHPを用いて3行でAPIを作る!! | Life Tips
json_encodeで全角文字が文字化けする - PHPプロ!Q&A掲示板

とにかく自分が作った曲で音ゲーがしたい人の話

総コン Advent Calendar 2017 - Adventar の5日目の記事です。

 

今回は僕の音楽班での主な活動内容についての記事になります。僕とDTM音ゲーについてです。

 

 

今までのことについて

若干P班っぽい内容も入ります。

B1の初めに作った「めっちゃ守る」

僕がB1(大学1年生)の頃の初めのほうに「EP演習」という科目があって、HSPで好きなものを作って発表会をする、といういきなりエキサイティングなイベントがあったのですが、当時からまあまあ太鼓の達人とかやっていて音ゲーが好きだった僕は「ここでいきなり音ゲーを作りたい!」となって、オリジナルの音ゲーを作りました。詳細は

音ゲー×タワーディフェンス「めっちゃ守る(体験版)」について - うつろぐ

に丸投げします。

この時は曲は無料音源を使用する形になっていて、当時から総コン音楽班に所属していたため(曲はまだ1曲も作っていなかった)、やはり「全部自分で作ってやりたい!」という気持ちになりました。

 

音ゲーに向けた楽曲というモチベーション

音楽班での主な活動として、僕は「何らかの音ゲーに収録されている楽曲の作成」を主にやってきました。

soundcloud.com

きいて

より具体的に言うと、「長さが2分前後で、何らかの世界観を持っている曲」を意識して作ってきました。

 

長さについて、これは僕個人の感覚なのかもしれないですが、曲の展開を詰め込む上で音ゲーの2~3分がちょうど間延びしにくい長さだなあと思います。音ゲーの楽曲だと大体

イントロ(サビに類したメロディであることも多い)→Aメロ→(Bメロ)→ブレイク→盛り上がり→サビ→ラストやアウトロ

という流れが多く、やっぱり丁度良く展開が詰まっているなあと思います。

 

世界観については、音ゲーの楽曲にはだいたい

・意味深なタイトル

・ジャケット画像

が付いていて、これが楽曲自体が聞き手に想起させるものを強めたり変えたりします。僕は作曲をしているときに「楽曲単体で深い世界観を出すの難しすぎる~~~」と思ったりするので、このことを逆手に取ってタイトルとジャケットから先に作って、そのタイトルとジャケットが想起させる世界観を深める楽曲を作ったりもしています。

因みにこの「LZH」という曲はジャケットから先に作って、そこからタイトル→曲と後から作りました

soundcloud.com

ジャケットはProcessing(正確に言うとOpenProcessing)で作成しました。

www.openprocessing.org

 

Dynamix公募

自分が作った曲で音ゲーをする方法は実は3つもあります。

・自作曲を自作音ゲーで遊ぶ

・自作曲の創作譜面をする

・自作曲を音ゲーの公募に出して受かる

この中の3つ目を実践した所、落ちました。なんか公募のレベルが前回より上がってるとかだったので当然と言えば当然。

この曲。

soundcloud.com

Dynamixというゲームの公募に出したのですが、実は僕はこのゲームをあまりプレイしたことがなく、あまり「この音ゲーに合う曲」みたいなのが考えられていなかったのがまずいかな~と思います。

取り敢えず当時意識したこととしては

・僕が音ゲーコアを出しても受からないので変な曲を作る

・リズムに特徴を出す

辺りでした。

 

音ゲーの創作譜面

 既存のAC音ゲーには大体有志によるシミュレータや譜面創作ツールが出てたりします。

太鼓の達人:太鼓さん次郎

ZMY's Place 太鼓さん次郎 使い方① ダウンロード

SOUND VOLTEX:K-Shoot MANIA

K-Shoot MANIA (ケーシューマニア)

CROSS×BEATS:

・X Beats Player

XBP Portal Site – クロスビーツシリーズの譜面シミュレータ「X Beats Player」のポータルサイト

・PxB Editor

クロスビーツ風ゲームシミュレーター PxB Editor作った - 游星通信

などなど。

因みにCROSS×BEATSの創作譜面に関しては個人的にはPxB Editorの方が機能が充実していて好きです。どうでもいいけど「PxB Editor」なのか「PxB Map Editor」なのかが謎。

音ゲーをしていると、譜面がきっかけで曲が好きになったりすることもあるのですが、譜面を作っていて曲が好きになったりすることもあります。また、好きな曲に綺麗な譜面が付けれられるととても気持ちいいです。

 

遂に自分の曲で創作譜面した話

先ほど紹介したLZHで、PxB Editorを用いて創作譜面しました。

www.youtube.com

作曲のときにDAWで自分が置いた音が分かっていて、どの音を叩かせたいかが分かっている状態で譜面を作っていたので、全てを支配している状態になりました。なんか神にでもなった気分。

 

音ゲー自作したい

最初に紹介した自作音ゲーのプロトタイプ「めっちゃ守る」は、実は音ゲータワーディフェンスを掛け合わせたようなゲームなのですが、音ゲーの部分は既存の音ゲーと丸被りしているので、新しいルールの音ゲー音ゲーはルールが同じであればすべて同じなので)を作りたいと思うわけですが、どうもPCで操作する音ゲーだと限界がありそう。そこで、「スマホ音ゲーを作りたい!」となるのですが、これは当時も今も若干ハードルと学習コストの高いことなので、なかなか手が出せていません...

そもそも、スマホでゲームを作ろうとするとAPIやバージョンの互換性とかを色々と考慮しないといけないっぽくて、それを知った時に「この世で最もめんどくさいな...」と思ってしまった。だからもういっそゲーム機の開発がしたくなって、「Nintendo Switch開発を進めた方が良いのでは!?!?!?」とか言い出したんだけど、どう考えても敷居高い。

 

簡易プロトタイプ

取り敢えずスマホで軽く動いてくれ~という思いで、OpenProcessingで、僕の脳内で絶賛稼働中の音ゲー「Lotustep Tone」のプロトタイプを作ってみました。これ詳しく説明してると日が暮れるので取り敢えずフィーリングでプレイしてくれ。

www.openprocessing.org

(マルチタップに対応してないのでうんこ)

 

今後のことについて

目標。

譜面から曲を作る

創作譜面をしていると時々「こういう配置したい!」と思うことがあるので、もう譜面から曲作ってみたら面白いんじゃないかなと思ったのでやってみたい。

 

ちゃんと音ゲー作る

ちゃんとスマホ開発する。

 

BOFに参加する

これが最も重要。BMS自体プレイしたことなくて毎年曲と映像しか見てないけど、「自分の曲」を「音ゲーの形式」で「色々な人に見てもらえる」機会なので、絶対にBOFに曲を出したい。

 

いじょう。

SQliteでカラム定義変更(カラム名変更、属性変更など)を行う最短ルート暫定版

大学の講義でSQLite使い始めたんですけど、けっこうたのしいですね。今まで研究室のサーバーにあるMySQLを使ってたんですが、あれみたいにくそでかい管理機構があってそこで色々するんじゃなくて、DBがファイル単位でさらっと管理できるのがたのしいです。ウケます。利便性というよりは感覚的な問題。

SQLiteはまあそんな感じで敷居が低くて、更に色々な言語や環境から扱えるのでステキです。なんかProcessingからも操作できるらしい。ほえ~。

ただ、他のDBでは標準で実装されてる機能がSQLiteには平気でなかったりします。今回のカラム定義変更がその一つです。今回はこれをなんとかしてやります。

 

大まかな流れ

適当な名前で新しいカラム定義を持ったtableを作る(1コマンド)

→元のtableのデータを全部同じ並びで新tableにぶち込む(1コマンド)

→元のtableを消す(1コマンド)

→新tableの名前を元のtableの名前にする(1コマンド)

 

やってみよう

今回この画像のようなtableを使います。

f:id:Distorted_Unchi:20171129234443p:plain

このtableについて、

・idをPRIMARY KEY&AUTOINCREMENTにしたい

・nameをusernameにしたい

という想定でやっていきます

 

適当な名前で新しいカラム定義を持ったtableを作る

先程の要件を満たしたtableを作って、tempとか脳が死んだような名前をつけます。

f:id:Distorted_Unchi:20171129235008j:plain

カラム数が多いtableに対して変更が1つとかだと本当にしんどいです。逆に、複数の変更をまとめて一遍にできるのでそのようなときには楽な方法になります。

 

元のtableのデータを全部同じ並びで新tableにぶち込む

ここが一番知名度の低い操作。insertの引数(?)にselect節をとります。

f:id:Distorted_Unchi:20171129235442j:plain

この構文は

insert into [table](ca1,ca2,ca3) select cb1,cb2,cb3 from [table];

のようにすることもできます。ここでcaとcbの数は同じでなければなりません。

 

元のtableを消す

なんで画像で説明してんだってくらい簡単な操作。

f:id:Distorted_Unchi:20171130000123j:plain

間違えてtempや他のtableをdropしないように注意...

あと消す前にtempにちゃんとデータが移ってるか1回確認したほうがいいかも。

 

新tableの名前を元のtableの名前にする

カラムのrenameは1コマンドでできない癖にtableのrenameは1コマンドでできるのでやります。

f:id:Distorted_Unchi:20171130000407j:plain

これでカラム定義の変更が完了しました。

 

結果

f:id:Distorted_Unchi:20171130001344j:plain

できた。いえ~~~。

 

まとめ

なんかグダグダでしたが、取り敢えず

1.新しいカラム定義を持ったtableをcreateする

2.insert into [table] select * from [table]; などでデータを全部同じ並びでぶっこむ

3.元のtableをdropする

4.alter table [table] rename to 名前で元のtableと同じ名前にする

こんな感じです。しつこいけど2から3に行く前に中身が移ってるか確認したほうがいいかも。以上。

画像が見にくいのは反省ですね...近いうちに何とかします