nana music Tech Blog

株式会社nana music 開発チームのブログです。

nanaの新機能、ライブ配信を支える技術

初めまして!nana musicサーバーサイドエンジニアの中島と申します。

今回のテックブログでは新たにリリースされた nana LIVEについての技術的なお話をさせていただこうと思います。
そもそもnana LIVEって何という方はこちらの記事を参照ください。

nana-music.com

導入のきっかけ

さて、元々nanaにはnanaパーティという機能がありましたが、以下二点の大きな問題がありました。

①マイクが同時に一人しか持てない
②遅延が数秒ある

nana LIVEでは上記の問題を解決し、同じライブで6人までの同時配信、nanaのサウンドを流しながらマイクや音源の音量調整も行えるものとなっております。

nanaLIVEのイメージ画像

f:id:nanamusic-tech:20211117212225p:plain:w300

これを実現するために今回、Agoraを導入しました。

jp.vcube.com

Agora

大きな特徴としては低遅延であることと複数人同時通話が可能な点にあります。

プロジェクトの作成

まずはコンソールよりプロジェクトの作成をします。

Agoraのプロジェクトの作り方はとても簡単でコンソールからいくつか操作するだけで簡単に作成できます。 細かい手順につきましてはAgoraの販売代理店のv-cubeさんのドキュメントにまとまっています。*1

jp.vcube.com

tokenの発行について

tokenの発行はクライアント側に書いてしまうと秘匿情報が漏れて勝手に使われてしまいますのでサーバー側で実施します。
サンプルコードがあるので必要なファイルを落としてきて自分のプロジェクトに入れて呼び出すだけです。

github.com

nanaではバックエンドがDjangoで動いているため、Python3のファイルをプロジェクトに組み込みます。

f:id:nanamusic-tech:20211117183431p:plain

その上でこちらのコードで呼び出すだけで簡単にtokenが作成できます。

token = RtcTokenBuilder.buildTokenWithUid(
            appId=app_id, # コンソール上で作成したプロジェクトに表示されているapp_idを入れます
            appCertificate=app_certificate, # コンソール上で作成したプロジェクトに表示されているcertificateを入れます
            channelName=channel_name,  # nanaのケースではuuidをセットしています
            uid=uid, # ユーザーID(整数)をセット
            role=role, # broadcaster(配信者)の場合は1, audience(聴き手)の場合は2をセットします
            privilegeExpiredTs=privilege_expired_ts, # tokenが切れる時間をunixtimeで指定します。
        )

これをクライアントに渡してあげればサーバーサイドとしては終了です。(驚くほど簡単でした)

クライアント側でも簡単な通話ができるモック的なものをnanaに組み込むところまでは4日ぐらいでできたのではないでしょうか。
最低限のライブ配信機能は思いのほか簡単に作れました。

Agoraの詳しい使い方については実装を担当したiOS・Androidの各エンジニアが次回の記事で紹介する予定です。

今後の課題について

こうして先日リリースしたnana LIVEですが、リリースを優先するために落とした機能多々あります。

  • 自ら降板する機能
  • セットリストの順番入れ替え機能
  • 配信者や参加者の通信切断検知方法の変更

3番目についてはWebSocketの導入などによって、通信切断の検知も即座にできるなどの効果が得られるのでサーバーサイド側の腕のふるいどころだと思っています。

最後に

nana musicでは新しい技術の導入がどんどん行われております!関わってみたいとか、課題を改善していきたいというエンジニアを正社員・業務委託・副業問わず募集しております!興味のある方は是非応募をよろしくお願いします!

www.wantedly.com

*1:今回代理店として入っていただきました。サポート体制も充実しており、困ったときには質問もどんどんできるので、大変助かりました。

XCUITestをシュミレーターで実行する前に設定しておくと便利なこと

初めまして!開発チームでQAエンジニアを担当している黒川と申します。

今回はXCUITestでテストを書いた後、シュミレーターでテストを実行する前に設定しておくと便利な設定を紹介します。

  • テストで使用する言語を固定する
  • シュミレーターでキーボードを表示するように固定する

テストで使用する言語を固定する

なぜ便利なのか

UIテストを実行する時にシュミレーターの言語設定が意図しない言語になっていてテストが落ちるということがありますが、後述の設定を行うことで、テストで使用する言語を事前に固定出来るため、問題を回避することができます。

f:id:nanamusic-tech:20211021121122p:plain
シュミレーターの言語設定

※シュミレーター自体の端末設定画面(↑の画像)から言語を変更することで使用したい言語でテストを実施することはできますが、シュミレーターを変更した時には引き継がれません。

設定方法

シュミレーターでアプリを起動する時に使用する言語をXcodeで設定する。

f:id:nanamusic-tech:20211019211638p:plain
EditScheme>Test>Options>App Language

  1. Xcodeから EditSchemeを選択
  2. Testを選択
  3. OptionsのApp Languageに設定したい言語を設定する

注意点

テスト実行時にのみ反映される設定のため、テストを終了させた後はシュミレーター自体に設定されている言語に戻ります。

シュミレーターでキーボードを表示するように固定する

なぜ便利なのか

キーボードの表示設定によりシュミレーターにキーボードが表示されず文字入力が出来なくてテストが落ちるということがありますが、後述の設定を行うことで、テスト実行時のキーボード表示設定を意図した状態に固定できるため、問題を回避することができます。

f:id:nanamusic-tech:20211021121321p:plain
表示されないキーボード

実施に問題が起きたテスト例

ユーザー名登録画面などのテストをシュミレーターで動かした時、以下箇所のtypeText実行時にテストが落ちました。

    func typeUserName(_ name: String) -> Self {
        XCTContext.runActivity(named: "ユーザーを入力") { _ in
            userNameField.tap()
            userNameField.typeText("黒川です")
        }
        return self
    }

キーボード表示設定とは

Connect Hardware Keyboardにチェックが付いている状態では、接続しているハードウェアのキーボードがシュミレーターで認識されて(自分の環境ではMacのキーボードでした)ソフトウェア(シュミレーター)側のキーボードが表示されなくなるため、テスト実行時にキーボードが表示されないという問題が発生します。

f:id:nanamusic-tech:20211019213023p:plain
キーボード設定箇所
※Connect Hardware Keyboardのチェックを外せば取り敢えずテストは落ちなくなりますが、CI環境/自分以外がテストを実行した時/シュミレーターを変更した時などで同様の問題が発生する可能性があります。

設定方法

テスト実行時に使用するシュミレーターのキーボード表示設定をXcodeから設定する。

  1. Xcodeから EditSchemeを選択
  2. Testの「∨」からメニューを表示してPre-actionsを選択
  3. Run Scriptで以下を入力する
## 設定を反映させるために一度シュミレーターをキルします。
killall Simulator
## Connect Hardware Keyboardのチェックを外した状態に指定しています。
defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool false

f:id:nanamusic-tech:20211019213357p:plain
EditScheme>Test>Pre-actions>Run Script

さいごに

UIテスト導入を決めてコードを書いていざ実行した時、テストコードは問題ないはずなのに本記事の箇所でテストが通らなくて解決するのに時間が取られてしまいました。 本記事がこれからUIテストの導入を考えている方々に少しでもお役に立てればと思います!

nana musicでは、iOSのテストコードを一緒に書いてくれる/自動化を進めてくれる仲間を募集しております! 是非、ご応募をお待ちしております。

www.wantedly.com

新nanaエフェクト設定、どうやって作っているかを公開します。

nana musicでデザインの仕事をしている藤木です。いまはnana公式Twitterでちょっとだけお披露目された、新しい録音やエフェクトの画面作りを担当しています。そんなデザインの本業にくわえ、各種エフェクトの音づくりもお手伝いしています。
今回は後者の音作りについて少し紹介いたします。

本格的なMIXにより近いサウンドを作り出せるように

いままでのnanaはエフェクトを1つしか選べませんでした。たとえば歌声をケロケロさせる『nanaTune』を選んだら、その他の効果をあわせて使えませんでした。

でも。もう少し本格的にボーカルを録音するときは、エフェクトをいくつも使うことが大半です。声を整えるときはまずEQ*1やコンプレッサー*2で下ごしらえし、必要があればさらにケロケロなどの特殊エフェクトをかける——そしてディレイ*3やリバーブ*4によって残響を加える、といった組みあわせて音づくりをします。

Apple Logic Pro画面 音作りをしている様子

Apple Logic Pro上で何種類かのFabfilter製Audio Unitプラグインを使い、サウンドを加工・MIXしている様子

上の画面は僕のパソコンでAppleのLogic Pro*5を使い、ボーカルとステレオ伴奏を加工し、実際にミックスしている様子です。ボーカルトラックにいくつもプロ仕様エフェクトソフト(プラグイン)を使っています。本格的に『歌ってみた』を投稿しているような方がご自分でMIXをしているとき、もしくは“MIX師”さんに依頼されたときには、MIXの作業風景はこんな感じではないでしょうか。

一方でnanaは誰でも手軽に自分の歌声などのサウンドを処理し、投稿できることが大事なポイントですから。もっともっとシンプルにする必要があります。プリセット(あらかじめ用意した既成の設定)をいくつも用意してその中から選んでもらうことをはじめ、いろいろな工夫を考えています。

そうした本格的なアプローチでありながら簡単に使えること。そんな新しいチャレンジに向けたnanaの開発がすでにはじまっています。

EQカーブの設定

いまはEQ部の開発やカーブ設定プリセットづくりなどを進めています。EQとはイコライザー(Equalizer)の略で、低い音や高い音などを増やしたり減らしたりして、音の周波数バランスを調整するエフェクトです。パッと聴いた感じだと少し微妙な変化なので「変わった?」となるかもしれないですが、これを上手く調整すると歌声がはっきり聴きやすくなります。

Logic Graphic-EQ画面

「イコライザー」と聞くと上のようなものを思い浮かべるかもしれません。グラフィックイコライザーといい、Macの『ミュージック』アプリや、家庭用や自動車のオーディオ機器に装備されていることもあり、低音が弱い時に強めるなどの用途で使ったことがある方もいるでしょう。音楽制作の場面ではより詳細な補正カーブを描いて音を加工するタイプのEQを使います。

Fabfilter Pro Q 3によるEQ設定

今回音づくりには、GUIが優れていて自分の思ったとおりのサウンドを作りやすいことからプロ・アマ問わず多くの人たちが導入しているFabfilter Pro-Q 3というプラグインソフト*6をリファレンスにMacのLogic Pro上で使いました。各種コントロールを使い、カーブを描いて音の加工をします。

上のセッティングは男性ボーカルを意識したものです。不要な低音はザックリと削り、中高域と高域の音を少しだけ増して音のヌケが出るようにしました。

もちろん声だけでもいくつも種類がありますから、ある程度一般的に使いやすい設定をいくつか作り、その設定値を音声処理のエンジニアさんに渡します。特にファイルデータを受け渡すのではなく、

ハイパスフィルター(低域を削るフィルター)が何Hzからスタートしてフィルターによる減衰は-12db/oct、ハイシェルフは2kHzでカーブは12db、ゲインは+4db

…というように設定値を並べて伝えます。

nana内部には上図のような音色チェック用のiPhoneアプリを別途開発し用意しています。担当エンジニアに値を伝えることで先ほどプロ用のEQソフトで作ったカーブをスマホの中で再現。テスト用のサウンドを鳴らして、自分の意図したようなサウンド処理がスマホの中でもちゃんと行われているか確かめられます。

基本的にEQは音の下ごしらえ役が基本で、あまりハデな効果を作るケースは多くありません。古い電話やラジオを通したような効果を作るときには例外的にEQが活躍します。下図のようにとても狭い周波数帯だけ音を残して、それ以外は高域も低域もバッサリと削り、声を聞き取りやすくするようにピークを作って音にわざとクセをつけるようなカーブを作ります。するといかにもそれっぽいサウンドになります。

他のエフェクトでもプリセットのための音づくりは続く

今回は開発がすすんでいるEQについて説明しましたが、冒頭に書いたように次の世代のnanaでは複数のエフェクトを同時にかけられるようになります。

ボーカル録音で欠かせない他の下ごしらえ役であり、EQと併用して使用されることの多いコンプレッサーやディエッサーのようなサウンドのダイナミックレンジを調整するエフェクトについても開発が進行中です。

f:id:nanamusic-tech:20210924153311p:plain

Fabfilter Pro-C 2というプロ向けコンプレッサーを使用し、nana用のコンプレッサーやディエッサー設定を作成

こちらの方も今回紹介したEQでの音作りプロセスと同じように、プロ向けソフトを使ってサウンドを作り、その設定をnana独自のエフェクトのプリセットに移すという作業を進めています。

それらの新しいエフェクトは開発されたあとOKとなったものがnanaアプリ本体に組み込まれ、歌声や楽器演奏のサウンドを加工する役割を担います。アプリをリリース後にユーザーの方々からどのような反応をいただけるか、今からとても楽しみにしています。

---
nana musicでは、音作りを実装してくれるエンジニアや、複雑になりがちな音作りのUIをシンプルにする方法を一緒に考えてくれるデザイナーを募集しています!
社内には趣味のDTMなどで音楽作りをしているメンバーも多いですよ。

www.wantedly.com

*1:EQ(イコライザー)は特定の周波数を増やしたり減らしたりする処理です。高音や低音を増減させることで、音を聴きとりやすく調整します

*2:音声処理でコンプレッサーは、音量の大小の幅を狭める処理のことです。歌の音量や楽器演奏では音量が常に一定とは限りません。聞きやすい音量範囲に調整することが必要になります。

*3:ディレイは奥行きや臨場感を演出する“残響系”処理の一つです。入力された音を繰り返し再生することでやまびこのような効果を出せます。

*4:リバーブは、音に残響音や反射音を加えて空間的な深みや広がり感を出します。このエフェクトにより部屋で録音したサウンドを大きなステージやホールで奏でた音のようにすることができます。

*5:Logic ProはApple製のDAWというカテゴリーのアプリケーションです。DAWとは音楽制作のための統合ソフトで、打ち込み機能、多重録音機能、MIX機能の3種類を併せ持っています。無料でiOSやMac用に提供されているGarageBandの機能充実版と理解していただいて構いません

*6:プラグインエフェクトとはDAWソフトに組み込んで、DAW付属の標準機能より高品位なサウンドやユニークな機能を拡張するためのソフトです。プロ用のプラグインの中には、DAW本体よりも高価な場合も少なくありません。

XCUITestとswift-snapshot-testingを共存させる際に気をつけたいこと

開発チームのiOSエンジニアをしている西山と申します!
前回の記事ではXCUITestについてご紹介させていただきましたが、
nanaではXCUITest以外にも、スナップショットテストを導入しています。

今回はXCUITestとスナップショットテストを共存させる時、 Bitriseで実行する際に気をつけたいことをご紹介いたします。

~ Targetは分割しよう~

UITestにはXCUITest、スナップショットテストにはswift-snapshot-testingを導入しています。

github.com

前提としてswift-snapshopt-testingはXCTestをベースとして作られているので、
新たにスナップショットテストを導入する際はあらかじめ、Targetを分割しておきましょう。

なぜかというと・・・
既存のユニットテストなどを実装しているTargetに新たにスナップショットテストを記載してしまうと、 すでにユニットテストなどでXCTestを導入している場合、
ユニットテストだけ実行したい、スナップショットテストだけを実行したいというシーンで困ってしまうためです。

参考までにnanaではテストに関連するTargetを以下のように分割しています。 f:id:nanamusic-tech:20210913215019p:plain

  • nanaTest ・・・ UnitTestを実行するTarget
  • NanaSnapShotTest・・・本記事で紹介したスナップショットテストを実行するTarget
  • NanaUITests・・・UITestを実行するTarget

余談

fastlaneを採用している場合は、scanのonly_testingコマンドで分割したスナップショットテスト用のtargetを実行することが可能となります。 docs.fastlane.tools 以下のようなsnapshot用のlaneを作ることができます。

lane :snapshot_test do
    scan(
      scheme: "nana",
      only_testing: ["SnapShotTests"],
      devices: ["iPhone 8"]
    )
  end

~ XCUITestのみを実行するschemeを定義しよう ~

前回ご紹介したXCUITestをBitrise上で複数端末実行するために、この二つのWorkflowを採用しています。

  • Xcode Build for testing for iOS
  • iOS Device Testing

このWorkflowでスナップショットテストを実行してしまうと、 こちらのissueにあるように、FirebaseTestLab側が、スナップショットテストの比較用画像のパスを参照できないため以下のようにエラーになってしまいます。 f:id:nanamusic-tech:20210906191827p:plain

「Xcode Build for testing for iOS」で実行したいのはXCUITestのみですので、 以下のようにXCUITestのみを実行するschemeを定義し、Bitrise上で実行するようにして下さい。 f:id:nanamusic-tech:20210830132013p:plain

以上、XCUITestとスナップショットテストを導入した際に気をつけたいポイントを記載しました。

さいごに

このようにスナップショットテスト/XCUITestを共存させる際にも、
実際に試行錯誤しないと見えてこない部分だったりあるので
これから導入を検討されてる方々の助けになればと思います。


nana musicでは、iOSのテストコードを一緒に書いてくれる/自動化を進めてくれる仲間を募集しております! 是非、ご応募をお待ちしております。

www.wantedly.com

JMeterを使った負荷検証入門

はじめまして。
開発チームでサーバーサイドエンジニアをしている田中です。
 
今回は、JMeterを使用した負荷検証についてお話したいと思います。
主に導入〜基本操作のご紹介となりますが、これからJMeterを用いて負荷検証を行う方の参考になれば、幸いです。
 

負荷検証を行なった背景

先日リリースされたとあるプロジェクトで、アクセス数が多いフィードへの機能追加がありました。

現状のフィードへのアクセス数や追加する機能の仕様を考慮すると、負荷が高くなることが想定されたため、リリース前の負荷検証が必要と判断しました。

今回のプロジェクトでは、特にレスポンスタイムに着目して検証を行いました。

負荷検証を行うツールとして今回はJMeterを使用しています。

JMeterとは

Apacheソフトウェア財団にて開発されているソフトウェアで、クライアントサーバシステムのパフォーマンス測定および負荷テストを行うJavaアプリケーションです。
サーバーに対して指定した量のリクエストを投げ、負荷をかけることでパフォーマンス計測を行うことができます。
テストシナリオはGUIインターフェースを使って作成することができます。
 
負荷検証ツールは他にも色々とあるので、用途に合わせて使いやすいツールを選択すると良いと思います。
Apache Bench:WEBサーバの性能を計測するコマンドラインツール。
Gatling:Scalaでシナリオを記述できるツールです。
Locust :Pythonでシナリオを記述できるツールです。

インストール・起動

Apacheのオフィシャルサイトからダウンロードできます。
 
JMeterを起動すると、以下のようなウィンドウが開きます。
メニューから Options → Choose Language → Japanese を選択し日本語化しておくと良いです。
f:id:nanamusic-tech:20210823114708p:plain

基本的な設定 

①スレッドグループを追加

f:id:nanamusic-tech:20210823144302p:plain

スレッドグループに以下を設定します。 
  1. スレッド数:アクセスするユーザー数
  2. Ramp-Up期間(秒):スレッドを何秒間の間に処理するか
  3. ループ回数:繰り返す回数
例えば以下の設定だと、1分間に10ユーザーのリクエストを処理する。それを10回繰り返すことになります。  

f:id:nanamusic-tech:20210823161911p:plain

②スレッドグループにHTTPリクエストを追加
テスト対象のリクエスト分、HTTPリクエストを追加します。

f:id:nanamusic-tech:20210823144441p:plain

HTTPリクエストには、以下など必要な項目を設定します。 
  1. プロトコル
  2. サーバーまたはIP
  3. メソッド
  4. パス
ここまでで、基本的な設定は完了です。これでテストは動く状態となっていると思います。 

詳細な設定

ここからは、より詳細な設定についてです。今回行いたい検証に沿って、必要な設定を追加します。 

今回検証したい内容は、以下でした。

  • 複数のユーザーからAPIリクエストを送りたい。
  • テスト対象のAPIは二つ。一つ目のAPIのレスポンスの値を、二つ目のAPIのパスパラメータとして使用したい。

上記を実現するため、以下の設定を行いました。

①USERの設定

     1. ユーザーパラメーターを追加します。

f:id:nanamusic-tech:20210823162623p:plain

     2.ユーザーパラメーターにユーザーと変数を必要なだけ、追加します。

f:id:nanamusic-tech:20210907191339p:plain

  • 名前:変数名
  • ユーザー_1, ユーザー_2 ...:ユーザー毎に変数に代入したい値 
   3. ユーザー毎に定義した変数を使用する側の設定をします。今回はヘッダーで変数を使用します。変数は${value}で利用できます。

f:id:nanamusic-tech:20210907191437p:plain

  • 名前:ヘッダー名
  • 値:${value} (valueは設定した変数名)
これでユーザー毎の設定は完了です。
スレッドグループのスレッド数を追加したユーザー数と同じ値にすることで、複数ユーザーからリクエストを送るシナリオを作成することができます。
②複数リクエストでの値渡し
渡す側(=先に実行されるリクエスト)
     1. HTTPリクエストに正規表現抽出を追加します。

f:id:nanamusic-tech:20210823165701p:plain

     2. 各項目を入力します。入力内容はこちらのページを参考にさせていただきました。

f:id:nanamusic-tech:20210823170600p:plain

受け取る側(=後に実行されるリクエスト)
  1. HTTPリクエストのパスに ${value} を追加します。

f:id:nanamusic-tech:20210907191546p:plain

これで、一つ目のAPIのレスポンスの値を、二つ目のAPIのパスパラメータとして渡すことができます。

結果の確認

いくつかの結果表示方法がありますが、テーブルが見やすかったです。
以下から結果を表で表示を追加します。

f:id:nanamusic-tech:20210823171448p:plain

すると以下のような表で結果が確認できます。

赤枠部分がレスポンスタイムです。今回はこちらを指標とし検証しました。

f:id:nanamusic-tech:20210910120011p:plain

最後に 

今回の負荷検証では、以下のような単体テストでは気付けない問題を見つけることができました。
  • 複数ユーザーから同時リクエスト時にデータが重複登録されてしまう
  • データ量が多い時に、特定のユーザー条件の場合レスポンスタイムが遅くなる
結果リリース前に修正することができ、リリース後にパフォーマンス面で大きな問題は起きませんでした。
この記事が同じようにこれから負荷検証を行う方の参考になれば嬉しいです。 

nana musicでは、一緒にテスト技術の向上に取り組めるエンジニアを募集しております!

皆様のご応募をお待ちしております。

www.wantedly.com

 

nana musicのNotion活用術を具体的にご紹介します!

データ活用全般を担当してる春日井です。

メイン業務は、アプリ行動ログなどの定量データやユーザーインタビューなどの定性データの取得・整備・分析ですが、「データならなんでも任せてくれ!」ということでドキュメント文化の醸成も担当しています。

この記事は、以下のような悩みを持つ方に特に参考にしていただけるのかなと思います。

  • 自律的な組織になるためには情報の透明性が大事*1ってよく聞くけど、ドキュメント管理があまりうまくいっていない
  • ドキュメント管理ツールとしてNotionが良いってよく聞くけど、高機能過ぎて具体的にどの様に活用したら良いか迷っている

従業員およそ30名のC向け事業会社であるnana musicにおける、Notion活用術をご紹介します。
現在運用しているNotion画面のスクショを多く載せているので、活用のイメージが付きやすいかと思います!

導入背景

nana musicではNotionの運用を開始するまで、以下のような課題を抱えていました。

  • 情報が各所(esa、Google Docs、GitHub Wiki、社内ポータルサイトなど)に分散しており、整理されていないので、ほしい情報にたどり着くまで時間がかかる
    → 作業効率の低下
  • 情報が不透明なので、誰が何を考えているか・やっているか、よくわからない
    → チームを跨いだ連携・各メンバーの自律性の低下

Notion概要

Notionでどのようなことができるか、どのような特徴があるか、は以下の公式ページなど*2に詳しく記載されています。

Notionはあなたのメモや書類、プロジェクト、データなどを1ヶ所で扱うためのワークスペースです。全ての情報を整理して利用しやすい状態に維持すると共に、情報の共有も支援します。

notion.notion.site

数ある特徴の中で、データベース機能が非常に強力で、「データの一元管理」と「ほしい情報へのアクセスしやすさ」を両立させることができます。
データベース機能について詳しく知りたい方は、以下SELECKさんの記事などを読んでみてください。

seleck.cc

nana musicにおけるNotion活用

WORKSPACEの構成

図のような構成にしています。
「全体のポータルページ」「チーム毎のポータルページ」「マスターデータベース」とその他に分けられます。

f:id:nanamusic-tech:20210815204421p:plain
WORKSPACEの構成

「マスターデータベース」が一番重要なので最初にご紹介します。

マスターデータベース

「wiki」「議事録」など大きな粒度でマスターとなるデータベースを作成し、そこで全チームの情報を一元管理しています。
一元管理することで、他チームのドキュメントへアクセスしやすくなっています。

  • wiki: 仕様書のようなきっちりしたものから個人的なメモに至るまで、とりあえずここに書く
  • 議事録: 基本的にすべてのMTGで作成
  • メンバー: 担当業務概要や経歴、趣味など
  • イベント: リリースや企画の予定など
  • KPT: 各チームで定期的に実施される、KPT形式の業務振り返り内容
  • タスク: BacklogやRedmine、Trelloのようなタスク管理

各ページには、以下のように目的や注意事項を記載しています。

f:id:nanamusic-tech:20210815210724j:plain
「議事録」データベース

また、データベース毎にテンプレートを作成することで、チーム毎・メンバー毎にフォーマットがバラバラになるのを防ぎ、ドキュメントの作成・理解の速度を高めています。

f:id:nanamusic-tech:20210815211114p:plain
議事録_基本テンプレ

基本的な方針として、会社の方向性に関するCEOの現在の考えが書かれた「全社方針」ページや、プロダクト戦略を検討するMTGの議事録なども含め、センシティブな項目を除き全てのドキュメントは全メンバーへ公開されています。

全体のポータルページ

全メンバーが見るべき情報を1ページに集約しています。

f:id:nanamusic-tech:20210816194745j:plainf:id:nanamusic-tech:20210816194749j:plainf:id:nanamusic-tech:20210816194753j:plain
全体のポータルページ

  • 今日明日の天気: Indifyというサービスで埋め込み
  • 会社からのお知らせ: 主に管理部から全メンバーに向けた共有事項テキスト
  • 注目ドキュメント: 社内ツール移行ガイドなど全メンバーに読んでほしいNotionドキュメントのLinked page
  • 重要指標*3: Vizydropというサービスで全社KPIを可視化
  • ユーザーの声: サポートに届いたご意見、nanaに関するTweetをTaggboxというサービスで埋め込み
  • 直近1週間のValueを体現した行動、昨日以降に更新されたページ: 日付でフィルターがかかったマスターデータベースのLinked database
  • リンク集: 社内規定ページや各種SNSのnana music公式アカウントページなどのリンク集

極力スクロールするだけで(リンク先に飛んだり戻ったりせずに)、全メンバーが見るべき情報を確認できるようにしています。
この構成は、注力事業やメンバーが増えたら再検討を考えていますが、注力事業が数個・従業員数が二桁である現状においてはベストかなと思っています。

チーム毎のポータルページ

必要な情報にすぐアクセスできるよう、チーム毎のポータルページも作成しています。
ページ内容は、基本的に全体のポータルページと同様の考えで、マスターデータベースに対してチームでフィルターをかけたLinked databaseを表示させたり、頻繁にアクセスするリンクを集めたものになっています。

ただ、チーム毎に最適なページ内容は変わってくるため、それぞれ責任者を決めて、業務に合わせて自由に編集してもらっています。

小さいTips

Zapierを用いてNotionとSlackを連携させています。
SlackでValueスタンプ*4が押されると、誰がどの投稿に対してスタンプを押したかなどの情報が、「Value」データベースに自動で追加されるようになっています。

他に、「メンバー全員で認識を合わせたいこと」「ご意見箱」データベースを使用しながら、経営層及びメンバーがお互いの考えを率直に伝え合い、全体の意識ができるだけ揃うようにしています。

また、業務に関わらないフランクなページも存在します。
「好きなもの」データベースでは、各メンバーが自分の好きな音楽や漫画などを登録し、わいわいしています。

f:id:nanamusic-tech:20210816013253p:plain
「好きなもの」データベース(本記事著者のもののみ表示)

運用してみて

定期的に各チームでNotion運用について振り返りながら、ブラッシュアップしています。
以下に、振り返りで出た意見をいくつかご紹介します。

  • 運用前から改善したこと
    • ドキュメントを残すことが習慣になったので、仕様について言った/言わないの議論ではなく、書いてあるかどうかになった
    • 議事録テンプレのおかげで、MTGのゴールをより意識するようになり、MTG参加者で共通認識を持てるようになった
    • 「メンバー」データベースなどでみんなのことが知れてよき
    • 他のチームのドキュメントを気軽に見ることができ、質問や議論がしやすくなった
    • 経営層の実際に考えていることを詳細に知ることができ、業務の納得感が上がった
  • 課題に感じていること
    • チームやメンバーによって、ドキュメントの量や質に差がある
    • ドキュメントに付与するタグの設定ルールが曖昧であり、ドキュメントの量が増えるに従い、得たい情報にたどり着くまで時間がかかるようになってきている
    • 作成したはいいが、更新されておらず内容が古くなっているドキュメントがある

上記の課題は、地道なメンテナンス作業などにより、改善してきています。
それらを含む運用面に関する詳細は、ここでは省略しますが、またいつかブログでご紹介するかもしれません。

さいごに

Notion運用開始から4ヶ月ほど経過しましたが、それなりにいい感じに運用されていて、チームを跨いだ連携や各メンバーの自律性が高まってきているように思います。
情報の透明性を高め、より強い組織になるよう取り組んでいます!


nana musicでは、エンジニア/デザイナーを募集しております!
皆様のご応募をお待ちしております。

www.wantedly.com

*1:LayerXさんの「開発爆速化を支える経営会議や週次定例の方法論 〜LayerXの透明性への取り組みについて〜」がとても勉強になります。

*2:Notionお役立ちリンクまとめ」が情報量とても多く参考になります。

*3:North Star Metricやグロースサイクルを設計した上でKPIを定めています。いつかブログでご紹介できればと思います!

*4:nana musicには"Keep Surprising", "Clarify The Essence", "Try First", "Respect Mutually"という4つのバリューがあり、それぞれのSlackスタンプがあります。

MTAudioProcessingTapを使って、AVPlayerで再生中のオーディオデータを取得する

こんにちは!音声信号処理エンジニアの kaede-san です。

私事ですが、先日、新型コロナウイルスのワクチンを打ちまして、ここ数日副反応に見舞われており、更新が遅くなってしまいました🙇‍♀️

今回は、MTAudioProcessingTapを使って、AVPlayerでオーディオデータを取得する方法を紹介します!

MTAudioProcessingTapとは

SwiftではMTAudioProcessingTap、Objective-C(厳密にはC言語)ではMTAudioProcessingTapRefとして定義されています。

公式ドキュメントでは以下のように説明が書かれています。

You can use the audio tap to access the track’s audio data before it is played, read, or exported.

簡単に訳すと「オーディオデータを再生・読み取り・エクスポート前に読み取ることができるよ!」とのことです。

AVAudioEngineを使ったことある方はご存知かと思いますが、AVAudioEngineにはinstallTapという機能がありますね!その機能と考え方はほぼ同じです。

また、内部的にAudioUnitを繋いで、再生される音源にリバーブやイコライザーなどのエフェクトをかけて加工することもできます。

なぜこの技術を採用したか

目的は、「nanaの再生画面でビジュアライザーを表示すること」です。

ビジュアライザーとは、音楽に合わせてうねうねうようよ動くアレのことです。「オーディオスペクトラム」と呼ばれることもあります。

みなさんが一番よく目にしているのは、音楽の周波数に合わせて動くビジュアライザーではないでしょうか。

nanaでも、周波数に応じてうねうね動くようになっています。

nana上で動くビジュアライザー

音楽を流して、リアルタイムでビジュアライザーを動かすには、その音楽のオーディオデータを取り出す必要があります。

iOS版nanaでは、音源の再生にAVPlayerを使っており、AVPlayerでオーディオデータを取り出す方法について調べたところ、MTAudioProcessingTapが使えそう(というか、これしか方法がなさそう)、ということが分かりました。

しかし、公式のサンプルコード(下記参照)以外、参考にできそうな情報やコードがなく、サンプルコードを参考に実装してみた結果、見事想定通りにオーディオのデータを取り出すことができ、採用に至りました。

活用例

nanaではビジュアライザー用途でMTAudioProcessingTapを使っていますが、それ以外にも様々な用途があります。

基本的な用途だと、

  • レベルメーター(音量の可視化)
  • SFSpeechRecognizerと連携させて音声認識
  • AudioUnitを使って再生される音を加工する(方法は後述)

などが挙げられます。

応用的には、オーディオデータを解析してできることはなんでもできるので、例えば楽曲のキー・コード・音程の検出など、いろんなことに使えそうですね!

実装例

実装では、こちらの公式のサンプルコードが大変参考になりました。

ただしかなり古く、中身はObjective-Cで書かれています。古すぎてそのままではプロジェクトのビルドに失敗するので、storyboardのビルドバージョンを変えたり、適切なmovieURLを設定してあげたりする必要があります。

https://developer.apple.com/library/archive/samplecode/AudioTapProcessor/Introduction/Intro.html

developer.apple.com

AVAudioEngineではAVAudioPCMBufferが用意されていますが、AVPlayerではこのMTAudioProcessingTapを使って、AudioBufferListを取り出すことができます。

呼び出し元のAVPlayerクラス

上のサンプルコードはObjective-Cですが、Swiftで書くとこんな感じにすっきり書けます。

※実行環境: Xcode 12.5・Swift 5.4

let player = AVPlayer(url: soundURL)
// ビジュアライザーの準備
if let track = player.currentItem?.asset.tracks(withMediaType: .audio).first {
    visualizer.audioAssetTrack = track
    player.currentItem?.audioMix = visualizer.audioMix
}
player.play()

ビジュアライザーの音声解析クラス

基本的にはサンプルコードの通りになります。(Objective-Cで書いた場合の話です。)

MTAudioProcessingTapでは、初期化・ファイナライズ・準備・処理中・終了時に動くコールバックをそれぞれ設定してあげる必要があります。

static void tap_InitCallback(MTAudioProcessingTapRef tap, void *clientInfo, void **tapStorageOut);
static void tap_FinalizeCallback(MTAudioProcessingTapRef tap);
static void tap_PrepareCallback(MTAudioProcessingTapRef tap, CMItemCount maxFrames, const AudioStreamBasicDescription *processingFormat);
static void tap_ProcessCallback(MTAudioProcessingTapRef tap, CMItemCount numberFrames, MTAudioProcessingTapFlags flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut);
static void tap_UnprepareCallback(MTAudioProcessingTapRef tap);

また、上記5つのコールバック間でデータを受け渡すために、下記のような構造体を準備します。

typedef struct AVAudioTapProcessorContext {
    Boolean supportedTapProcessingFormat;
    Boolean isNonInterleaved;
    Float64 sampleRate;
    AudioUnit audioUnit;
    Float64 sampleCount;
    void *self;
} AVAudioTapProcessorContext;

再生する音源にエフェクトをかけて加工したい場合は、加工用AudioUnitの準備とそのコールバック関数の設定をtap_PrepareCallback関数内で行います。

自前で作ったエフェクトをかけたり、自力で信号処理をする場合は、AU_RenderCallback関数内に処理を書くことができます。

加工しない場合は、AudioUnitやAU_RenderCallback関数の準備は要りません。

以下のコードでは、例として再生する音源にバンドパスフィルタをかけるエフェクトを設定しています。

static void tap_PrepareCallback(MTAudioProcessingTapRef tap, CMItemCount maxFrames, const AudioStreamBasicDescription *processingFormat)
{
    AVAudioTapProcessorContext *context = (AVAudioTapProcessorContext *)MTAudioProcessingTapGetStorage(tap);
    context->sampleRate = processingFormat->mSampleRate;
    context->supportedTapProcessingFormat = true;
    
    // ~~いろいろ省略~~
    
    // エフェクトの準備(バンドパスフィルタ)
    AudioUnit audioUnit;
    
    AudioComponentDescription audioComponentDescription
    audioComponentDescription.componentType = kAudioUnitType_Effect;
    audioComponentDescription.componentSubType = kAudioUnitSubType_BandPassFilter;
    audioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    audioComponentDescription.componentFlags = 0;
    audioComponentDescription.componentFlagsMask = 0;

    AudioComponent audioComponent = AudioComponentFindNext(NULL, &audioComponentDescription);
     if (audioComponent) {
        if (noErr == AudioComponentInstanceNew(audioComponent, &audioUnit)) {
            OSStatus status = noErr;
                // ~~いろいろ省略~~
                if (noErr == status) {
                    AURenderCallbackStruct renderCallbackStruct;
                    renderCallbackStruct.inputProc = AU_RenderCallback;
                    renderCallbackStruct.inputProcRefCon = (void *)tap;
                    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallbackStruct, sizeof(AURenderCallbackStruct));
                }
                // ~~いろいろ省略~~
}

OSStatus AU_RenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{
    return MTAudioProcessingTapGetSourceAudio(inRefCon, inNumberFrames, ioData, NULL, NULL, NULL);
}

実際にオーディオデータを取り出して処理・解析するのはtap_ProcessCallback関数の部分になります。

nanaの場合、目的はビジュアライザー = 音声の周波数の可視化なので、この部分でFFT(Fast Fourier Transformation)しています。今回はAccelerate.frameworkのvDSPを使っています。

static void tap_ProcessCallback(MTAudioProcessingTapRef tap, CMItemCount numberFrames, MTAudioProcessingTapFlags flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut)
{
    // ~~ いろいろ省略 ~~ 
    for (UInt32 i = 0; i < bufferListInOut->mNumberBuffers; i++) {
        // ここでオーディオデータが取り出せる
    }
}

課題

サンプルコードがObjective-Cだったこと、また当時呼び出し元のAVPlayer実装クラスもObjective-Cであり、Swiftを呼び出すのが難しそうだったことから、 ビジュアライザーの音声解析クラスのみObjective-Cで実装しています。

iOS版nanaのリポジトリでは、開発効率や将来性・メンテナーの不足などのさまざまな理由から、開発言語をSwiftに一本化することを図っており、Objective-CのコードをSwiftに書き換える、いわゆる「Swift化」を進めている最中です。

ところが、今回の実装によって、逆にObjective-Cのコードを増やしてしまいました😱

一方で、音の信号処理(特にリアルタイム処理)では、遅延や処理速度といった理由から、SwiftよりもObjective-CやC/C++で書いた方が結果として良い方向になることが多いと感じています。

今後、「Swift化」を試みる際は、遅延や処理速度などに影響が出ないということを確かめてから実装を進めていきたいと考えています。

注意

AVPlayerでストリーミング再生を行なっている時は、この方法でオーディオデータを取得することはできないようです。

nanaは再生にプログレッシブダウンロードを使用しているため、この方法を採用できました。

まとめ

今回はMTAudioProcessingTapを使って、AVPlayerで再生中のオーディオデータを取得する方法を紹介しました。

MTAudioProcessingTapを知った時、AVAudioEngine特有の技術だと思っていたことがAVPlayerでもできるんだ!と驚いたのを覚えています。

しかしAVAudioEngineのinstallTapほど導入は楽ではなく、またMTAudioProcessingTapの情報がほとんど出回っていないため、実装はちょっと大変でした…

どうしてもAVPlayer上でオーディオデータを取得したい!エフェクトをかけたい!という時に、この記事が少しでも実装の参考になれば嬉しいです!


nana musicは開発メンバーを募集しています!

気になっている方・興味のある方は、ぜひ一度ご連絡ください😉

www.wantedly.com