musicLineアプリ開発日記

作曲を誰でも楽しく簡単に♪

描画の高速化(OpenGL VBO)

今回は音符を描画するために使用しているライブラリOpenGLの高速化の話。

描画の高速化(コマ落ち解消)



はじめに

musicLineでは、コミュニティでユーザーが投稿した曲を再生できるようになっており、再生している曲のイメージを可視化できるソングビジュアライゼーションという機能があります。

ソングビジュアライゼーション


Twitter#createbymusiclineタグから引っ張ってきたツイートからソングビジュアライゼーションのテーマを紹介します。
(すみません勝手に使ってます。)

ピアノロール
スペクトラム
バブル
サークル
波形

そして、やばい曲の到来

すみません。やばいという表現は少し失礼ですね。
音符をこれでもかと敷き詰めた物凄い曲を再生する時、ソングビジュアライゼーションの機能の動作がとても重くなる状態です。

音符を敷き詰めた物凄い曲
https://3musicline.com/community/149961 (アプリリンク)


通常の曲であれば、30FPS(1秒間に30フレーム描画)程度で動作しますが、この曲で音符が物凄くあるところは3FPSになります。
3FPSだと1秒間に3フレームしか描画しないので、とてもカクカク動きます。(最低でも10FPSは欲しいところ。。。)

音符が多くなるとカクカクした動きに


今回はソングビジュアライゼーションのテーマの中でもピアノロールを高速化ができそうだったのでしてみました。



描画の高速化

OpenGLでは様々な工程がありますが、その処理をGPUで行うことになります。

OpenGLでの描画の流れについての参考ページ


そのため、アプリ側のCPUから描画する頂点情報(位置、色等)をGPUに送ることになるのですが、この転送時間をいかに減らせるかが高速化の1つのポイントとなります。


VBOへ頂点情報を一気に送る

VBO(Vertex Buffer Object)はGPU側の領域で頂点情報を格納しておく場所です。


現在は1フレーム分の描画に必要な頂点情報収集して、毎回配列でGPUへ転送しています。

毎フレームで1フレーム分の頂点情報を転送


しかし、毎フレームで情報収集とGPU転送の時間が掛かるため、非効率です。特にピアノロールのような画面が左から右へ移り変わるような単純なアニメーションであれば、予め数フレーム分の頂点情報を一気に送ることが有効です。数フレーム分の頂点情報をストックしておいて、あとは毎フレームでスクロール位置のみGPUへ転送すれば、描画するフレーム部分をGPU側で計算できます。

数フレーム分の頂点情報を一気に転送
描画部分をGPU側で計算


つまり、毎フレームCPU側で計算した頂点情報を転送するのではなく、数フレーム分の頂点情報を一気に送ってGPU側で描画部分を計算することで、転送回数を削減することができます。


IBOで重複する頂点情報を省略

IBO(Index Buffer Object)はGPU側の領域で頂点インデックス情報を格納しておく場所です。


例えば音符1つを描画するためには、三角形ポリゴンを2枚使います。頂点が各々3点なので合計6点となります。

三角形ポリゴン2枚で6頂点

しかし、2点は共有する頂点なので2点分の頂点情報が重複します。
通常は頂点配列を転送すると、前から3点ずつをグルーピングして三角形ポリゴンを描画しますが、三角形ポリゴンの頂点を指定することができます。
三角形ポリゴンの構成する頂点インデックス(頂点配列の前からの番号)を指定することで、重複する情報を省略して三角形ポリゴンを描画することができます。

頂点インデックスの使用

重複した無駄な情報を省くことで転送データ量が減り、高速化に繋がります。



実装

AndroidOpenGLのVBOとIBOを使用して、効率良く描画をしてみます。

参考ページ

処理の流れは

  1. バッファを作成
  2. 頂点情報をバッファへ転送
  3. 頂点情報をLocationにバインド
  4. 頂点インデックスを指定して描画

となります。


1. バッファを作成

val bufferIds: IntArray // VBO, IBO の確保領域のIDリスト

bufferIds = IntArray(6).also {
    GLES20.glGenBuffers(6, it, 0)
}

この例では、6個のバッファ(VBO x 5 + IBO)を確保しています。

VBO

  • 座標
  • UV位置
  • 角丸幅
  • 音符のX範囲

IBO

  • 頂点インデックス


ちなみに、使用後必要がなくなったバッファは削除することでメモリを節約します。

GLES20.glDeleteBuffers(6, bufferIds, 0)


2. 頂点情報をバッファへ転送

val vertexBuffer: FloatBuffer = ... // 音符の4頂点座標を集めてFloatBufferに変換する

val floatByte = 4  // floatは4byte
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[0]) // バッファIDの指定

vertexBuffer.position(0) // バッファのポインターを先頭へ
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexBuffer.capacity() * floatByte, vertexBuffer, GLES20.GL_STATIC_DRAW) // 頂点を転送

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) // バッファIDの解除

この例では、頂点座標を転送しています。
glBindBufferの第一引数にGLES20.GL_ARRAY_BUFFERと指定することで、VBOへ転送します。
glBindBufferの第二引数に確保したバッファIDを指定します。


また、頂点インデックスはGLES20.GL_ELEMENT_ARRAY_BUFFERと指定して、IBOへ転送します。

val indexBuffer: IntBuffer = ... // 頂点インデックスを計算してIntBufferに変換する

val intByte = 4
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferIds[5]) // バッファIDの指定

indexBuffer.position(0)
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * intByte, indexBuffer, GLES20.GL_STATIC_DRAW) // 頂点インデックスを転送

GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0) // バッファIDの解除


3. 頂点情報をLocationにバインド

GLES20.glEnableVertexAttribArray(noteVtPosLoc) // Location有効
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[0]) // バッファIDの指定

GLES20.glVertexAttribPointer(noteVtPosLoc, 2, GLES20.GL_FLOAT, false, 0, 0) // 頂点情報をLocationにバインド

GLES20.glDisableVertexAttribArray(noteVtPosLoc) // Location解除
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) // バッファIDの解除

2. と同様にglBindBufferでバッファIDを指定し、glVertexAttribPointerでShaderで使用する変数のLocationにバインドします。


4. 頂点インデックスを指定して描画

var faceIndexesBufferCount = // 頂点インデックスの数

GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferIds[5]) // バッファIDの指定

GLES20.glDrawElements(GLES20.GL_TRIANGLES, faceIndexesBufferCount, GLES20.GL_UNSIGNED_INT, 0) // 描画

GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0) // バッファIDの解除

2. と同様にglBindBufferでバッファIDを指定し、glDrawElementsの第四引数を0に設定することで頂点インデックスを指定して描画します。



全コードを表示

class MidiNotesShader() {

    // region Property

    // Location
    private val noteVtPosLoc: Int // 頂点位置
    private val noteVtUvLoc: Int // UV位置
    private val noteColorLoc: Int // 頂点色
    private val noteRoundWidthLoc: Int // 角丸幅
    private val noteRangeLoc: Int // 音符のX範囲
    private val scrollLoc: Int  // スクロール
    private val barPosLoc: Int  // 再生バー位置
    // endregion

    private val bufferIds: IntArray // VBO, IBO の確保領域のIDリスト

    private var vertexBuffer: FloatBuffer = toFloatBufferf(listOf())
    private var uvBuffer: FloatBuffer = toFloatBufferf(listOf())
    private var colorBuffer: FloatBuffer = toFloatBufferf(listOf())
    private var roundWidthBuffer: FloatBuffer = toFloatBufferf(listOf())
    private var rangeBuffer: FloatBuffer = toFloatBufferf(listOf())

    private var faceIndexes = listOf<Int>()
    private var faceIndexesBufferCount = 0

    private val shaderProgramId: Int = GLES20.glCreateProgram()
    private val program = Program()
    private var vertexShader: Int? = null
    private var fragmentShader: Int? = null
    // endregion

    // region Initializer
    init {
        loadProgram()

        // VBO, IBO のバッファ領域確保
        bufferIds = IntArray(6).also {
            GLES20.glGenBuffers(6, it, 0)
        }

        // 頂点情報
        noteVtPosLoc = GLES20.glGetAttribLocation(shaderProgramId, program.vtPosAttr)
        noteColorLoc = GLES20.glGetAttribLocation(shaderProgramId, program.colorAttr)
        noteRoundWidthLoc = GLES20.glGetAttribLocation(shaderProgramId, program.roundWithAttr)
        noteRangeLoc = GLES20.glGetAttribLocation(shaderProgramId, program.rangeAttr)
        noteVtUvLoc = GLES20.glGetAttribLocation(shaderProgramId, program.vtUvAttr)

        // Uniform
        scrollLoc = GLES20.glGetUniformLocation(shaderProgramId, program.scrollUni)
        barPosLoc = GLES20.glGetUniformLocation(shaderProgramId, program.barPosUni)
    }

    fun dispose() {
        GLES20.glDeleteBuffers(6, bufferIds, 0)
        deleteProgram()
    }
    // endregion

    // region Method

    // 数フレーム分必要な音符の情報を計算する
    fun calcBufferData(frameCount: Int) {

        // 数フレーム分の音符を取得する
        val screenNotes = getNotesInScreen(frameCount)

        // 音符の頂点を集める
        vertexBuffer = screenNotes.getNotePointsBuffer()

        // 音符のUV座標を集める
        uvBuffer = screenNotes.getUVsBuffer()

        // 音符の色を集める
        colorBuffer = screenNotes.getColorsBuffer()

        // 音符の角丸の幅を集める
        roundWidthBuffer = screenNotes.getRoundWidthsBuffer()

        // 音符のX範囲を集める
        rangeBuffer = screenNotes.getNoteRangesBuffer()

        // 頂点インデックスを計算する
        val indexes = (0 until screenNotes.size * 4).groupBy { it / 4 }.values.flatMap { (a, b, c, d) ->
            listOf(a, b, c, d, c, b)
        }
        faceIndexes = indexes
    }

    // BufferDataをVBO, IBOへ転送する
    fun sendBufferData() {

        val floatByte = 4

        // 音符の頂点を転送
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[0])
        vertexBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexBuffer.capacity() * floatByte, vertexBuffer, GLES20.GL_STATIC_DRAW)

        // 音符のUV座標を転送
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[1])
        uvBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, uvBuffer.capacity() * floatByte, uvBuffer, GLES20.GL_STATIC_DRAW)

        // 音符の色を転送
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[2])
        colorBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, colorBuffer.capacity() * floatByte, colorBuffer, GLES20.GL_STATIC_DRAW)

        // 音符の角丸の幅を転送
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[3])
        roundWidthBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, roundWidthBuffer.capacity() * floatByte, roundWidthBuffer, GLES20.GL_STATIC_DRAW)

        // 音符のX範囲を転送
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[4])
        rangeBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, rangeBuffer.capacity() * floatByte, rangeBuffer, GLES20.GL_STATIC_DRAW)

        // 頂点インデックスを転送
        val indexBuffer = toIntBuffers(faceIndexes)
        val intByte = 4
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferIds[5])
        indexBuffer.position(0)
        GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * intByte, indexBuffer, GLES20.GL_STATIC_DRAW)
        faceIndexesBufferCount = faceIndexes.size

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0)
    }

    fun draw() {

        // GLSL設定
        GLES20.glUseProgram(shaderProgramId)

        // 機能有効
        GLES20.glEnable(GLES20.GL_BLEND)
        GLES20.glDisable(GLES20.GL_DEPTH_TEST)

        // 機能設定
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_DST_COLOR)

        // 変数有効
        GLES20.glEnableVertexAttribArray(noteVtPosLoc)
        GLES20.glEnableVertexAttribArray(noteVtUvLoc)
        GLES20.glEnableVertexAttribArray(noteColorLoc)
        GLES20.glEnableVertexAttribArray(noteRoundWidthLoc)
        GLES20.glEnableVertexAttribArray(noteRangeLoc)

        // 変数設定
        val crossTime = 6_000 // 6秒で画面を横切る
        val playBarPosition = 0.25 // 0(左端) ~ 1(右端)
        val barPos = playBarPosition * 2f - 1f // -1 ~ 1
        GLES20.glUniform1f(scrollLoc, time / crossTime * 2f - barPos)

        GLES20.glUniform1f(barPosLoc, barPos)

        // 音符の頂点をnoteVtPosLocにバインド
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[0])
        GLES20.glVertexAttribPointer(noteVtPosLoc, 2, GLES20.GL_FLOAT, false, 0, 0)

        // 音符のUVをnoteVtUvLocにバインド
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[1])
        GLES20.glVertexAttribPointer(noteVtUvLoc, 2, GLES20.GL_FLOAT, false, 0, 0)

        // 音符の色をnoteColorLocにバインド
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[2])
        GLES20.glVertexAttribPointer(noteColorLoc, 4, GLES20.GL_FLOAT, false, 0, 0)

        // 音符の角丸幅をnoteRoundWidthLocにバインド
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[3])
        GLES20.glVertexAttribPointer(noteRoundWidthLoc, 1, GLES20.GL_FLOAT, false, 0, 0)

        // 音符のX範囲をnoteRangeLocにバインド
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferIds[4])
        GLES20.glVertexAttribPointer(noteRangeLoc, 2, GLES20.GL_FLOAT, false, 0, 0)

        // 頂点インデックスをバインド
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferIds[5])
        // 描画
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, faceIndexesBufferCount, GLES20.GL_UNSIGNED_INT, 0)


        // 変数解除
        GLES20.glDisableVertexAttribArray(noteVtPosLoc)
        GLES20.glDisableVertexAttribArray(noteVtUvLoc)
        GLES20.glDisableVertexAttribArray(noteColorLoc)
        GLES20.glDisableVertexAttribArray(noteRoundWidthLoc)
        GLES20.glDisableVertexAttribArray(noteRangeLoc)

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0)

        // 機能解除
        GLES20.glDisable(GLES20.GL_BLEND)
    }



    // Shaderプログラムを読み込む
    private fun loadProgram() {
        val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, program.vertexCode).also {
            vertexShader = it
        }
        val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, program.fragmentCode).also {
            fragmentShader = it
        }

        GLES20.glAttachShader(shaderProgramId, vertexShader)
        GLES20.glAttachShader(shaderProgramId, fragmentShader)
        GLES20.glLinkProgram(shaderProgramId)
    }

    // Shaderプログラムを削除する
    private fun deleteProgram() {
        GLES20.glDeleteProgram(shaderProgramId)
        vertexShader?.let { GLES20.glDetachShader(shaderProgramId, it) }
        fragmentShader?.let { GLES20.glDetachShader(shaderProgramId, it) }

        GLES20.glDeleteShader(GLES20.GL_VERTEX_SHADER)
        GLES20.glDeleteShader(GLES20.GL_FRAGMENT_SHADER)
    }

    // endregion

    // region InnerClass
    class Program {

        // region Property

        // uniform
        val scrollUni = "u_scroll"
        val barPosUni = "u_barPos"

        // attribute
        val vtPosAttr = "v_pos"
        val colorAttr = "v_color"
        val roundWithAttr = "v_round_width"
        val vtUvAttr = "v_uv"
        val rangeAttr = "v_range"

        // varying
        private val colorVary = "f_color"
        private val roundWithVary = "f_round"
        private val vtUvVary = "f_uv"
        private val bright = "f_bright"
        // endregion

        // region Code
        // uniform: プリミティブごとの情報(描画呼び出し全体で一定)
        // attribute: 頂点毎の情報(通常:位置、法線、色、UVなど)
        // varying: フラグメント(ピクセル)ごとの情報。頂点間で値が補完される(Vertexの入力→Fragmentで補間された出力: 1頂点=>多ピクセル)
        val vertexCode = """
            attribute vec2 $vtPosAttr;
            attribute vec4 $colorAttr;
            attribute float $roundWithAttr;
            attribute vec2 $vtUvAttr;
            attribute vec2 $rangeAttr;
            
            varying vec4 $colorVary;
            varying vec2 $vtUvVary;
            varying float $roundWithVary;
            varying float $bright;
            
            uniform float $scrollUni;
            uniform float $barPosUni;
            
            void main() {
                gl_Position = vec4($vtPosAttr - vec2($scrollUni, 0.), 0.0, 1.0);
                
                $colorVary = $colorAttr;
                $roundWithVary = $roundWithAttr;
                $vtUvVary = $vtUvAttr;
                
                float start = $rangeAttr.x;
                float end = $rangeAttr.y;
                // noteの左がスクロールバーの左の時(鳴り終わり)
                float barPos = $barPosUni + $scrollUni;
                if (end < barPos) {
                    float lengthTime = end - start; // endからの経過時間
                    float power = clamp((1.3 - lengthTime) / 1.3, 0., 1.); //1~0 4秒で0
                    float endBright = power * 0.7;

                    float elapsedScroll = barPos - end; // endからの経過時間
                    float decay = clamp(elapsedScroll / 0.2, 0., 1.); //0=1 0.5秒で1 減衰
                    
                    $bright = clamp(endBright - decay, 0., 1.);
                 } else if(barPos < start) {
                    // まだ鳴ってない
                    $bright = 0.;
                 } else {
                    // 鳴っている
                    float elapsedScroll = barPos - start;
                    float power = clamp((1.3 - elapsedScroll) / 1.3, 0., 1.); //1~0 4秒で0
                    $bright = power * 0.7;
                 }
            }
        """.trimIndent()

        val fragmentCode = """
            precision mediump float;
            varying vec4 $colorVary;
            varying float $roundWithVary;
            varying vec2 $vtUvVary;
            varying float $bright;
            
            void main() {

                vec2 pos = $vtUvVary-vec2(0.5);
                float s = -0.5 + $roundWithVary;
                float e = 0.5 - $roundWithVary;
                
                // 丸角にする
                float dis0 = length(vec2((pos.x - s) * (0.5 / $roundWithVary), pos.y));
                float dis1 = length(vec2((pos.x - e) * (0.5 / $roundWithVary), pos.y));
                if((0.5 < dis0 && pos.x < s) || (0.5 < dis1 && e < pos.x)) discard;
                      
                gl_FragColor = vec4(mix($colorVary.rgb, vec3(1.), $bright), $colorVary.a);
            }
        """.trimIndent()

        // endregion
    }
    // endregion
}



おわりに

今回はVBOとIBOを用いてOpenGLの高速化を行いました。

改善後の動作

ご覧のように音符が物凄く増えても30FPSを維持し、スムーズな動作になりました。
ピアノロール以外のテーマでも高速化できそうであれば行うかもしれません。

ちなみに作曲画面で物凄く音符がある時は、ソロボタンや画面を拡大して音符の描画量を減らすことで遅延を少なく画面移動できます。
それでも、音符が物凄く多いと少し編集するだけでも動作が重いと思いますが。。。(すみません)

とにかく作者の執念を感じられる作品でした。



そると。さんインタビュー

mL民のインタビュー企画第4回です。
※mL民:作曲アプリmusicLineユーザーのこと
(「」タグで他も見てね☆)

今回はそると。さんを紹介します。
DMでのやり取りをインタビュー形式にまとめました。

  


スー

そるとさん、よろしくお願いします。

それではまず、ユーザーネームの由来はなんですか?


そると。

ユーザーネームは完全に名字から来てます!!
リアルでもそるととか塩とか言われてるのでそのまま名前にした感じです🧂


スー

なるほど。
そうだったんですね!

では、ひとこと自己紹介をお願いします。


そると。

そるとと申しますー!めちゃくちゃ切ないピアノの曲を聴きながら泣ける風景を見ることが趣味というか、好きです👍
とにかく感傷に浸るのがとても好きで、日常の景色でさえも永遠と眺めてられますw


スー

そういえば、そるとさんが応募したコンテストテーマ「めちゃくちゃ切なくて感傷に浸れる曲」もそのテーマでしたね!

日常の景色を見て感傷に浸るか〜
なかなかいい感性をもってますね!
musicLineのアイコンも風景が綺麗で印象に残ってます♪

そるとさんから見たら日常の景色さえもこんな感じに輝いているのかな〜♪


影響を受けたアーティストはいますか?


そると。

多分皆さんが知らない電子音楽のアーティストばっかりになってしまうんですが、一応挙げさせていただきます!

Namiceさん、F-777さん、Hinkikさん、DJVIさん、MDKさんなどです…!!
特にNamiceさんにはかなり影響を受けました、正直このアーティストに出会えてなかったら今の僕はなかったと思います


スー

ほんと。知らないアーティストばっかりだあ。


ダラーン

いや、言い方。

もっと違う言い方あるでしょ。もう


スー

(やっぱりダラーンの声が聞こえるような?)

でもそるとさんがどんなアーティストに影響を受けたか興味ありますね〜♪

ポチポチポチっと検索…!

雰囲気がいいですね~
調べてみるとフィンランドの方なんですねー!


そると。

日本で影響を受けた方だったらOrangestarさんです…!!


スー

こちらも素敵なボカロPの方ですね!
なんだかそるとさんのルーツがわかった気がします☆

さて、そんなそるとさんですが
作曲に興味を持ったきっかけはなんでしょう?


そると。

なんか、ふと作ってみたいなーと思ったんですよね、なんでなのかは分からないですが…w

そこで「作曲アプリ」と検索して出てきたのがmusiclineでした!
実はかなりの古参でアイコン変わる前からやってます💪


スー

アイコン変わる前というと2018年くらいか。。


そんな前から!?


確か鯖頭さんの後くらいに出てきたと思ってました~

でもよく考えればコミュニティに曲を投稿するときには既に使い込んでますもんね♪
みんな想像以上に昔から使ってくれてるんだな☆

古くからmusicLineを選んでもらってありがとうございます!!!


でも昔を知られてるってなんだかちょっと恥ずかしい。


(昔のアイコンは忘れていいんだよ~)


次に、作曲する際に心がけていることはありますか?


そると。

最近は音のバランスを意識してますー ベースとコードとメロディ、ドラムとFXがしっかり心地よく聴こえる場所を頑張って探してるつもりです、出来てるかは分かりませんが()


スー

バランス大切ですよね!
最近殿堂入りされた「亡失にガーベラを。」もドラムがいい感じに聴けて、バランスが良かった気がします!

作曲初心者の方にアドバイスはありますか?


そると。

たくさん曲を聴きましょう!!
どんな曲でも必ずなにか吸収できるものがあるので、自分の理想に近い音楽を探して、聴いて、学習するのがいいと思います👍


スー

たくさん曲を聴くのが大事なんですね!

そういえば、みんなのインタビューで僕の知らないアーティストをいっぱい知っていてすごいです。

色々な曲を意識して聴くことが作曲に繋がっているんだと思います!


では次はXで募集した質問で、今から50歳位までのざっくりとした人生設計を教えて欲しいです!


そると。

うーん、こういうのあんまり考えたことなかったので結構難しい…w
とりあえず23歳くらいまでには就職して、働きながら音楽を作って、
30歳までには結婚して、40歳になる前に子供を作って50歳くらいからはもう健康に過ごしていたいです…………


スー

理想的〜!
普通が1番良かったりするんですよね〜

では、最後にみなさんに聴いてもら。。


ダラーン

なんか忘れてない?


スー

!?



きょろきょろ




きょろきょろ







はっ!!

やっぱりダラーン見てたんじゃん!
前から声が聞こえる気がしてたんだよなー


ダラーン

そりゃスーくん一人だと心配だからね~♪
それより今回もスーくんのアイコン描いてもらったでしょー


スー

あ、いっけない☆

そうだそうだー
今回も素敵なアイコンを描いてくれました!

満天の星空が素敵ですね♪
見上げてる感じが良い
光がキラキラだ~

風景がメインのアイコンがそるとさんっぽいですね!
ありがとうございます!!



では気を取り直して、最後にみなさんに聴いてもらいたい自分のオリジナル曲はありますか?


そると。

MLだったら特に「Trust in ××」と「夜明けと街灯」を聞いてほしいです、結構自分でも好きなので…!!!!


Trust in ××

https://3musicline.com/community/69068 (アプリリンク)


夜明けと街灯

https://3musicline.com/community/54334 (アプリリンク)


あと図々しいですがYouTubeの方も良ければ来てください……()


スー

もう♪しょーがないな


そろそろペンダント変えようと思ってたんだよね〜

これをここに付けて~




キラーーーーーーーーーン


ダラーン

。。なにが?

キラーーーーーーーーーンじゃないよ。


スー

いや、YouTubeのそるとさんの宣伝になるかなと思って。。


ダラーン

うん。ありがとう☆

じゃあ僕からも紹介します!
昔から作曲活動を頑張ってるそるとさんです。

いい曲ばかりなのでYouTubeにも訪れてみてはどうでしょう~♪

そると。/salt. - YouTube


スー

それそれ〜
musicLineに限らず楽しい作曲ライフ&ボカロPとしての活動も応援しています~♪


ダラーン

まったくー
スーくんには任せてられないなあ~
次から僕も参加しようかな。。


スー

今回はそると。さんのインタビューでした。

質問に丁寧にお答えいただき、ありがとうございます!

描いていただいた僕の絵は1週間ほど公式アカウントのアイコンとして使わせて頂きます!


次へ




ずるゐさんインタビュー

mL民のインタビュー企画第3回です。
※mL民:作曲アプリmusicLineユーザーのこと
(「」タグで他も見てね☆)

今回はずるゐさんを紹介します。
DMでのやり取りをインタビュー形式にまとめました。

  



スー

ずるゐさん、よろしくおねがいします。

それではまず、ユーザーネームの由来はなんですか?


ずるゐ

読んで字の如く、性格が狡猾だから…

というのは冗談で、 言いにくいですがゴリゴリ本名から取ってます。
ゐ に関しては記憶がありません。
変換が面倒なので、い にして結構です。


musicLineのお友達からは、
ずるたんとか、スンギとか呼ばれてます。


スー

なんだ~
性格がずるいわけではないんですね。笑

でも、この「ゐ」が特徴的で名前覚えていました♪
ちなみにmusicLineのアイコンはこちらです。

こちらのほうが僕には馴染みがあります!
あとアイコンは両方ともjack=kさんが描いてくれたみたいです。

絵の上手い友達って貴重だなー

友達からはあだ名で呼ばれてるんですね~
じゃあ、僕もずるたんって呼ぼうかな☆


。。。


やっぱり、ずるいさんで!!


では、ひとこと自己紹介からお願いします。


ずるゐ

クラスでは所謂大人しい子でした。ずるゐと申します。
musicLineには3年ほどお世話になってます。
いつもありがとうございます。

趣味は音楽を聴くこと、作ること。
そして同じかそれ以上に、
ゲームが大好きです。常に何かしらやってます。


ここ最近やったタイトルだと
『パラノマサイト FILE23 本所七不思議』があります。

群像劇であり、オカルトホラーであり、
推理サスペンスであり、ヒューマンドラマであり、
色々な要素がうまくまとまった1本で…

…すみません。長くなってしまうので自重します。


スー

ぽちぽちぽちっと検索


これか!!

参照ページ パラノマサイト FILE 23 本所七不思議 公式サイト | SQUARE ENIX

なんか面白そうですね~

雰囲気が出ていて謎解きしたくなりますね☆

あと七不思議って言われると興味が。。




え?


ぎゃーーーーーーーーーーーーーー


でたーーーーーーー!!!!!!!





。。。



な、夏にしたいげ、ゲームですね==~


(僕はちょっと大丈夫かな。
怖いの苦手だし、できるか不安)



ではずるゐさんに色々と聞いていきます♪
影響を受けたアーティストはいますか?


ずるゐ

色々なところから少しずつ影響を受けているんだと思いますが。。

好きなアーティストさんで言うと、
フレデリックさん、ORESAMAさん、ワルキューレさん。
ツミキさん、youまんさん、r−906さんとか好きですね。

それから、ボカロは古いものから沢山聴いてます。
炉心融解や深海少女、アルビノなどが特に好きです。


スー

色々な方から影響を受けてらっしゃるんですね~

炉心融解や深海少女、アルビノ!!
僕も思い出深いものばかりです♪

作曲活動をしていて楽しいと思う時はどんなときですか?


ずるゐ

めちゃくちゃ当たり前のことですが、うまくいくと楽しいですね。
中でもコード進行を練るのとかやっぱり楽しいですよね。
使ったことない進行が組めると「よし!」って思います。

あと、自分は音楽理論がほとんど分からない所謂感覚派なので、端から見て"理論を分かってそうな曲"が作れると気持ちいいですね。


スー

おー仲間~同志だ!

ここだけの話

僕も音楽理論は全然わかんないんだよね~

なんだあ。ずるたんもあんましわかってないんだね~

このこの~

良い曲書く癖に~



仲間~♪ 仲間~♪


ダラーン

スーくんは勉強してないだけでしょ

サボってるだけのスーくんと同じにしたら失礼だよ


スー

グサッ
(なんだろ何かが心に刺さる音がした。)

まあ、ずるゐさんは多くの作曲経験から音楽理論が感覚で身についていて、素晴らしい曲ができるんだと思います。

サボっているだけの僕と同じにしてすみませんでした。


でも音楽理論がわからなくても良い感じに作曲できることが、musicLineの目標の一つでもあります。
今後もアップデート頑張ります☆


ちなみに、作曲する際に心がけていることはありますか?


ずるゐ

心がけている、とまでは言えないかもしれませんが、あるにはあります。
曲全体で見た時に、一部の展開や楽器のみ力が入りすぎていないか。粗っぽいところはないか、それによって曲全体の品位を落としてしまってはいないか。そういうところに対してすごく几帳面になってしまいます。しっくり来る言葉としては"整頓"ですかね。

関連するところに、楽器をなるべく少なくする というものもあります。シンプル・イズ・ベストみたいなものが、概念として好きなんです。


スー

へ~そうだったんですね!
その「整頓」を意識することでずるゐさんらしさが表現できているのかもしれないですね♪

では続いて、目標はありますか?


ずるゐ

目標はこれといって設定してないですね。
漠然としていて良いなら"バズりたい"ですね。
ちやほやされたいとか、そういう気持ちが強いので。

何気ない日常ツイートにさえ、
3RTくらいつくようになりたい…


スー

わかるその気持ち〜バズりたいですよね〜
やっぱり誰かに褒めてもらいたいし、すごいって言ってほしい。

ずるたんにもせっかくアイコンを描いてもらったので、お互いバズれるように頑張ろ~!

僕の凛々しい感じがでていますね♪
背景の輝いているようなグラデーションも良い

あとなんだか着ぐるみ着ているような線がある!?
(気にしすぎかな~)
僕は着ぐるみじゃないぞ☆

ありがとうございます!


では次はXで募集した質問ですが、
もし異世界転生したらそこはいったいどんな世界で、ずるゐさんはどんな人物として生きていきたいですか?


ずるゐ

急にぶっとんだ質問ですね…
基本的に面倒そうだし、
異世界転生なんてまっぴらごめんですね。


スー

急にぶっとんだ質問ですみません。。
まあそういわずに~


名前はちょっと言えないですが、

某陶芸家の方からずるゐさん指名での質問リクエストでして


ダラーン

これほぼ名前言ってるようなものだと思うけど!


スー

いいのいいの♪

きっと快くOKを出してくれるはず☆
いつもイベントを楽しくなるように考えて頂きありがとうございます!

なのでぶっ飛んでますが、教えてください~


ずるゐ

まあ、もししてしまうとしたら、
働かなくていい世界がいいなあ。
そしてそこではモテモテで、大金持ちで、
地位も名誉もこの手の中に…


スー

うーん

欲張りですね


でもでも僕も欲張りでした~
きっと誰もが共感できて、好感をもてるはず☆


では最後に、みなさんに聴いてもらいたい自分のオリジナル曲名を教えてください。


ずるゐ

折角なので3曲ほど選定しました。
猫の庭園、未だ夜は明けなゐ、Dropです。

猫の庭園 は初めてボカロ化した曲なので、
すごく思い入れがあります。
musicLineの方々には当時すごく反応をいただいて、
感謝しかありませんでした。


https://3musicline.com/community/81293 (アプリリンク)



未だ夜は明けなゐ は初めてのコラボで、
これまたすごく思い入れがありますね。
メンバーの間宮みよさん、夜湾さん、朱明さんとは
仲が良いので、またやりたいです。


https://3musicline.com/community/113313 (アプリリンク)



最後に Drop です。
これは単純に曲がめちゃくちゃ気に入ってます。かっこいい。


https://3musicline.com/community/123563 (アプリリンク)


スー

今回はずるゐさんのインタビューでした。

質問に丁寧にお答えいただき、ありがとうございます!

描いていただいた僕の絵は1週間ほど公式アカウントのアイコンとして使わせて頂きます!


次へ




鯖頭さんインタビュー

mL民のインタビュー企画第2回です。
※mL民:作曲アプリmusicLineユーザーのこと
(「」タグで他も見てね☆)

今回は鯖頭さんを紹介します。
DMでのやり取りをインタビュー形式にまとめました。

  



スー

鯖頭さん、よろしくお願いします。

それではまず、ひとこと自己紹介をお願いします。


鯖頭

かれこれMLで4年お世話になっている鯖頭と申します。
皆様いつも曲聴いてくれてありがとうございます……!


スー

もう4年になるんですね。
もっと前からコミュニティにいてるような気がしてます

とても長く使ってもらってありがとうございます!


鯖頭

作曲以外では観光地・秘境駅などを巡ったりする趣味があります。
主にインターネットにいます!


スー

秘境駅…!
良い趣味ですね!

ちなみに、おすすめの秘境駅はありますか?


鯖頭

岩手県の「有家(うげ)駅」は駅前に海が広がってて大変綺麗でしたのでそこがおすすめです!


スー

そうなんですね~


ぽちぽちぽちっと検索

これか!!

参照ページ 有家駅 | 海の見える駅

お~いいですね~
有家駅、いつか行ってみたいです♪



では次に、ユーザーネームの読み方、由来を聞いてもいいですか?


あ、読み方は

さ・ば・が・し・ら さん


ですよね☆


知ってますよ〜


鯖頭


鯖頭の呼び名は一応「さばあたま」としています


スー

ガーーーーーン


(なんか知ったかぶりしたみたいになったじゃん)


鯖頭

(フリック入力しやすいので)


スー

ガーーーーーン


(知らないよ。なに入力のしやすさで名前決めてんだよ)


鯖頭

由来はよく覚えていないんですが、すごく適当に決めた可能性が高いですね…


スー

適当なのーーーーー


(がしらがしらがしらがしらがしらがしらがしらがしら)



。。。


鯖頭

呼びやすい方で呼んでいただいて大丈夫です!


スー

良かったですー

さばがしらさん😊

では色々聞いていきますよー♪
影響を受けたアーティストはいますか?


鯖頭

枚挙にいとまがなくなっちゃうので全員は書けないんですが、

特に米津玄師さん、長谷川白紙さん、いよわさん、君島大空さん、youまんさん、煮ル果実さん、john(TOOBOE)さんや

ML内では鈴音メロディさん、白夜さん等の方々に影響を受けたと思われます…!


スー

たくさんいますね♪

鈴音メロディさん、白夜さんもいいですね〜
なんだかその頃が懐かしい☆

作曲に興味を持ったきっかけはなんですか?


鯖頭

中学生の頃ずっと米津玄師さんの曲を聴いてた時期があったんですが、ボカロ時代の曲を聴いた事がきっかけで作曲とかDTMの興味が湧いてきた感じです…!


スー

そうだったんですね!米津玄師さん、ボカロ界でも大人気ですよね。

好きな楽器とかありますか?


鯖頭

MLではハープ、オルゴール、ボウ、なかなか使う機会はないんですがシタールなどが好きです!
ML外だと調律狂わせたピアノの音とかが直近のマイブームです


スー

調律狂わせたピ・ア・ノ!?
(すごいマニアックだけど。。
MLでもピッチベンドを実装したら表現できるかな?)


。。。



(難しそうだし、ダラーンに頼も。。)


目標はありますか?


鯖頭

明確な目標は今の所ないです!
強いて言うならこのまま末永く活動していくことでしょうか…?


スー

続けるの大事ですよね~

musicLine内に限らず、これからの制作活動も応援しています!!


そういえば描いてくれたアイコンは、鯖頭さんのボカコレ予告動画をモチーフにデザインされてましたね☆


それがこちら

独特~ 。。。
鯖頭ワールドが全開してますね!!

でも、あえて黒フチを付けたり
風景をベースにして表現した素材感とか
現代アートのような独特な世界観


嫌いじゃないです☆

ありがとうございます!
ボカコレも頑張ってください~

鯖頭さんのボカコレエントリー曲


モチーフとなった鯖頭さんのキャラデザイン



そんな独特~な鯖頭さんですが、
作曲していて辛かったことはありますか?


鯖頭

作曲を始めて1年くらい経ったときにスランプが来てその時はだいぶ辛かったですね…
その時までいわゆる「丸の内サディスティック進行」に近いコード進行でしか曲を作れなかったので、思いついたものを表現する力が不足していたのが原因でした。


スー

そんな時期もあったんですね…
すごく天才肌だと思っていたので意外です!

鯖頭さんと言うとかなりハイペースで曲を作っている印象があるのですが、 作曲する際に心がけていることはありますか?


鯖頭

特に意識というほどでもないですが、自分の好みであるかどうかをできるかぎり最優先に作曲するようにしてます…!


スー

好みを最優先か~
それが作曲が早くなることに繋がるんですかね?


鯖頭

恐らく曲の原案が思いつきやすい性質があるんだと思いますが、これは様々なジャンルを割と好き嫌いしないで聴いてるのが原因だと思ってます…

あと理由はわからないですが風呂場で思いつく事が多いです


スー

なるほど。色々な曲を聴いてみて自分の好みがわかるって大切ですね!

好みがはっきりすることで、芯がブレずに結果的に作曲が早くなるってことかもしれないですね♪

お風呂で思いつく感じわかる〜
なんかリラックスして考えるのに丁度いい空間がお風呂なんじゃないかな♪


それでは最後にみなさんに聴いてもらいたい自分のオリジナル曲名を教えてください!


鯖頭

個人的には「鯨」を聴いてほしいです…!


https://3musicline.com/community/57014 (アプリリンク)


スー

うんうん

スーは「TAMAYA」もお勧めだな!!


ダラーン

ひえーーー
鯖頭さんのお勧め流したーー!!


スー

この曲は鯖頭さん初の殿堂入り曲でしたよね♪
(あれ?なんかダラーンがちらついた?)


昔からのmL民には馴染みがあるんじゃないかな~

この曲はどこか懐かしく、打上花火に感じた儚さを思い出させてくれる。夏の思い出を詰め込んだように美しく、打上花火のように散っていく気がして悲しい。。ずっと続けばいいのになあ。

それで当時、密かに応援していた僕は。。


ダラーン

いいから「鯨」を聴いてーー!!


スー

!?

あ、すみません。勝手に紹介しちゃった☆


鯖頭

大丈夫です!紹介ありがとうございます…!


https://3musicline.com/community/12437 (アプリリンク)


スー

「鯨」も聴きました!すごくいいですね。
色々な音の混ざり合いがあって深い曲です♪


今回は鯖頭さんのインタビューでした。

質問に丁寧にお答えいただき、ありがとうございます!

描いていただいた僕の絵は1週間ほど公式アカウントのアイコンとして使わせて頂きます!


次へ




jack=kさんインタビュー

musicLineのmL民インタビュー企画です。
今回はjack=kさんを紹介します。
DMでのやり取りをインタビュー形式にまとめました。

  



スー

インタビュー受けてくれてありがとうございます。

ではまず、じゃっくぅぅぅぅ

。。。


ユーザーネームの読み方を教えて頂いていいですか?


jack=k

読み方にこだわりはありませんが、最近はjack=kでじゃっくわかーと呼んで頂いております。


スー

じゃっくわかーさんだったんですね!

ちなみに、お名前の由来はなにかありますか?


jack=k

それといった由来は特になく昔から惰性で使い続けている名前です。


スー

そうなんですね〜



(!?

アイコンの顔になにか書いてある。。)




ワカメ。。





はっ(我に返る)

それでは次に、ひとこと自己紹介をお願いします!!


jack=k

名前を出してくださってありがとうございました、嬉しさ7割と自分程度の人間にこんなありがたい機会があって良いのかとガチガチです。

それといつも曲、絵、動画やTwitterを見て頂いたり、交流してくださっている方々本当にありがとうございます!

インターネットと創作が趣味の一般的なオタクです。よければ仲良くして下さい。 会話も得意ではないですが趣味です!!ぜひ!!!


スー

謙虚な感じがとても好印象!

趣味も多くてなんだか話も弾みそうですね♪

そうそう僕の絵も描いてくれました〜

急なお願いに応えてくれてありがとうございます!


しかもかわいい♪

僕の優しそうな雰囲気が全面に出ちゃってますね☆

あとメガホン抱えているところも良いぞ

PRを頑張っている僕の姿を見事に表現していますね!



(あとでダラーンに自慢しよっと♪)




それでは、色々とお聞きしていきたいと思います。

影響を受けたアーティストはいますか?


jack=k

主にsho_fishさん、silentroomさん、ハードコアタノシーの方々、sum41さんに影響を受けているような気がします。

musiclineをやってみたい!と思ったきっかけは鯖頭さんと夜湾さんの曲でした。

musiclineを始めてからは音の鳴らし方などの音作りで影響を受けているのは幼稚さん、曲として影響を受けたのは鯖頭さん、鈴音メロディさん、AYATORIさんです。


スー

色々な方の曲を聞かれているんですね〜

作曲自体に興味を持ったきっかけはあったんですか?


jack=k

覚えている限りこれといった出来事はなく、昔から曲と音を出す事が好きでした。
(ボウルとか机でやかましくしたり ペンドラムとかも好きでした)

そういえば暴れ回って音を出して回り、それを脳内で曲にする遊びをよくやっていたので、それが一番のきっかけだったのかもしれないです……


スー

暴れ回って、、、!?

す、すごいですね!

(あれ?
ちょっとぶっ飛んだ人なのかな。


そうそう

ペンドラムは、ドラムの練習にもなりますもんね。

絵も上手ですし、昔から制作活動が好きだったんですね〜


そんなちょっとぶっ飛んだ
じゃなくて謙虚な、じゃっくわかーさんですが、

なにか目標とかってありますか?


jack=k

考えたら目標ってないかも……。

自分が好きだな〜と思える曲をできる限り遠慮なく、容赦なく、妥協なく? 正直ただひたすら自己満足で好きの作品化をしているので、誰かに聴いていただけているだけで嬉しい!ありがとうございます!という気持ちでいます。


スー

誰かに聴いてもらってるのが嬉しいか~
(やっぱり謙虚じゃん☆)

目標とかではなく、好きを形にできることが素敵ですね!

では、作曲活動をしていて楽しいと思うのはどんな時ですか?


jack=k

やっぱり自分で好きだなと思う曲が作れたり、面白い音が作れたりした時は嬉しいです!

最近は矩形波ティンパニでプニプニした音ができたり、自分の曲をアレンジしたフレーズがすごく自分好みに出来たりした時が楽しかったです。


スー

プニプニした音!

良さそうですね〜☆

作曲する際に心がけていることはありますか?


jack=k

なんとなくですが、そこにある?音を音階って箱に当てはめてフレーズにしている!とか、この音があると気持ちいいのはここだなぁと感じたらそこに音を置く!という意識はぼんやりとあります。

意識している事かというと違うかもしれません……


スー

なんだかすごい。。

理論よりも感覚を大切にしているんですね。



(僕もそういう感覚を大切にしないと!)


jack=k

正直、自分に音楽知識や音楽的なセンスはないのですが、今できる技術やある知識は存分に使って出来る限り理想としている形を削り出すイメージで作曲しています。
(これは曲に限った事ではないかもしれません)

そのためにぼんやりと浮かんできたメロディや構成、歌詞や表現は何よりすぐにメモをして出来るだけ逃さないようにしています!

採れたてをお届けって感じ


スー

センスありますよ~

メモをしっかりとって逃さないようにしているんですね。

これからも「採れたてのメロディー」楽しみにしてます。


作曲初心者の方に向けてアドバイスはありますか?


jack=k

そんな人様に何か言えるほど大した人間じゃないんですが……!!
作品として作るなら出来るだけ楽しんで、できた子は全力で愛してあげましょうと言いたいです。

愛が1番ですし愛はあるだけ良いですからね、ホントに!!
愛でている内にそこから出てくる新しいアイデアもあったりなかったりすると思うので…多分……

でも自分もこんな事言えるほどちゃんとしてないです、なんなら万年初心者です。未だによくわかってないことのほうが多い!!助けてくれー!!


スー

だ、大丈夫ですか?笑

とにかく自分が生み出した曲は愛するべきってことが伝わりました!


やっぱりまずは自分が自分の曲を愛してあげないといけないですね。

誰にも愛してくれなくても、自分だけは愛してあげないと。


自分が一番良くわかっているはずなのに。。


愛さないなんて、可哀そう。。




ダラーン

スー君が取り乱してどおするんだい


スー

はっ(我に返る)

(なんかダラーンの冷たい目が頭をよぎったぞ)

すみません。ちょっと取り乱しましたが

最後に、みんなに聴いてほしい自分のオリジナル曲名を教えてください


jack=k

一番難しいかもしれない、全然選べない……!!
欲を言うなら全部聴いてほしいです、自分の曲が大好きで仕方ないので……(笑)

自分なんかが言うのもあれなのですが、おすすめしたい!強いて言うなら…!って言うのは「ground_breaKING(wiscaさんとの合作です。あちらのアカウントから出ています)」など他の方との合作曲、「Tempest of the abyss」、「Morpho menelaus」です!!


ground_breaKINGはドラム楽器のアップデート後すぐに開始した合作で、名前の通り当時は革新的な曲だったと自負しております。

https://3musicline.com/community/127867 (アプリリンク)

イントロや構成、ベースやキックはほぼwiscaさんのもので、サビのメロディやちょっとした上側の音は自分がやっています。
musiclineらしからぬ音がなっているんじゃないかな……と勝手に思っていますし、自分が誰よりもこの曲が好きですと自負しています。


Tempest of the abyssは自分が初めて殿堂入りさせて頂けるほどの身に余る評価を頂いた思い出深い曲です。


https://3musicline.com/community/63405 (アプリリンク)

一つ前の曲で出来たシンセブラス1で作ったリードシンセのようなものを活用しています。ずっと使ってみたかった音でずっと作りたかったフレーズが作れた感動は今も覚えていますし、今もお気に入りです。


Morpho menelausは上2つと違い特に思い出がある!というわけではないのですが、純粋に曲としてお気に入りです。


https://3musicline.com/community/123457 (アプリリンク)

実際に弾けるかどうかはピアノ経験者ではないので分からないですけれど。ブライトネスとピアノをレイヤーした響きのあるぽーんと伸びたサビのメロディと、サビ前で静かにピアノが泣いているのがとてもお気に入りです……!!
出来ることならここだけでも生音で聞いてみたいです!!頼む〜!!(笑)


スー

ありがとうございます。

おすすめして頂いた曲はmusicLineから探してYouTubeにアップさせて頂きました。

今回トップバッターのjack=kさんでした。

質問に丁寧にお答えいただき、ありがとうございます!

描いていただいた僕の絵は1週間ほど公式アカウントのアイコンとして使わせて頂きます!


次へ




リンク先のページ情報を取得する

今回はリンク先のページ情報(タイトル、詳細、サムネイル)を取得する話。

ページ情報の表示



はじめに

musicLineではコミュニティでユーザー同士が会話するためのチャット機能があります。以前までそのチャットにYouTubeのURLを載せるとアプリ内で動画を再生できるように実装していましたが、そのライブラリのサポートが終了して機能しなくなりました。


今後はWeb技術のiFrameを使うことでYouTubeの埋め込みを実現する方法もあるようですが、これを機会にURLをコメントに送信したときの挙動を再検討しました。

そもそも今まではYouTube以外のURLを送っても、リンク先ページの情報がわからなく、URLテキストのみを表示する仕様になってました。

URL先ページ情報なし


しかし、この仕様ではリンク先のページに飛ばないと内容がわからなく、会話の中でリンク先を送ることに躊躇してしまうことがあると思います。
そこでYouTubeに限らずURLをコメントで送ることで、リンク先ページの情報を取得して、表示できるように改良しました。



ページ情報の取得

OGP (Open Graph Protcol)

WebページはHTMLで書かれていますが、特にリンクをシェアした時に使用してほしい文書や画像はOGP (Open Graph Protcol)に基づいて設定されます。

↑このリンクバーもOGPを元に情報を取得しているはず

具体的には、Webから以下のようなHTMLドキュメントを取得するので、ogメタタグの要素を確認すると情報を取得できます。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta property="og:title" content="タイトル" />
        <meta property="og:image" content="画像のURL" />
        <meta property="og:description" content="ページの説明" />
        ...

    </head>
    <body>
        ...
    </body>
</html>
プロパティ 内容
og:title タイトル
og:image 画像のURL
og:description ページの説明


実装

下記のページを元にAndroid+Kotlinでリンク先のページ情報を取得するように実装しました。


基本的にはJsoupライブラリでHTMLのURLを指定するだけで、要素の内容を取得できます。

val doc = Jsoup.connect(url).get()
val headEls = doc.head().children()
for (v in headEls) {
    val prop = v.attr("property")
    val content = v.attr("content")
    println("プロパティ:$prop、内容:$content")
}


ただ、メインスレッドでJsoup.connect(url)にてWebからHTMLをダウンロードすると例外が発生するため、ダウンロードする際はサブスレッドで行います。
ViewModelで処理を行っている場合は、viewModelScopeを使用するといいでしょう。

viewModelScope.launch(Dispatchers.Main) {
    
    // サブスレッドでHTMLを取得
    val doc = withContext(Dispatchers.IO) {
        try {
            Jsoup.connect(url).get()
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }
    }
    ...
}


全コードを表示

viewModelScope.launch(Dispatchers.Main) {
    
    // サブスレッドでHTMLを取得
    val doc = withContext(Dispatchers.IO) {
        try {
            Jsoup.connect(url).get()
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }
    }
    doc?.let {
        val headEls = it.head().children()
        for (v in headEls) {
            val prop = v.attr("property")
            val content = v.attr("content")
            when (prop) {
                "og:title" -> {
                    // Webページのタイトル
                }
                "og:description" -> {
                    // Webページの説明
                }
                "og:image" -> {
                    // WebページのサムネイルURL
                }
            }
        }
    }
}




おわりに

今回の実装でリンク先のイメージや詳細がわかるようになりました。

リンク先の詳細を表示


ちなみにYouTubeやニコニコのURLは検出して、再生マークを付けてます。
YouTubeのアプリ内再生は非対応となりましたが、タップでYouTubeに飛べるため、アプリ内で再生しなくても良いかと思います。

動画URLの再生マーク



スーへPRアカウントを任せるよ

僕の名前はダラーン


作曲アプリの開発をしてはブログを更新しているよ


今日は僕の日記を公開します




2023/6/25 (日)

なんだかすごいコミュニティが盛り上がってるな


スー君に聞いたら昨日ML杯が開催されていたとの事


なんだかドヤ顔で答えるスーは満足げだった





年末のML紅白といい、毎回楽しそうな企画をしてくれるなあ



今に始まったことではないが、musicLineはmL民に支えられていると実感する
(mL民とは:誰が命名したのかmusicLineのユーザーの事を指すらしい。考えた人のセンス抜群だな)




当初は同じ詩で異なる曲を書く企画「ターニング」に驚いたものだ



僕のアプリ実装した作曲リレーとかよりも、全然流行ってたのを覚えている。。








まあとにかく建設的なユーザーに恵まれて、僕は嬉しい





ML作曲力向上委員会さんやハリボテさん以外にも


mL民が有志で行ってくれているイベントが沢山あるんだろうな
(拾い切れていないmL民の方々すみません)


こういう時、リツイートで拡散とか感謝コメントしたいんだけど


公式アカウントであんまり特定のユーザーのリツイートするのは良くないよなあ。。




スーは別にいいじゃんやりたいようにしたらって言うけど、



あまり頻度が多くても嫌がられるだろうし



重要な告知が流れてしまうし、やっぱりよくないような気がする




もっと気軽にいいねとかリツイートする用の別アカウントでも作ろうかな





2023/7/6 (木)

新しいSNSが発表された


今朝、スー君がSNSに疎いダラーンに教えてあげようと

Threadsを教えてくれた





流石に知ってるー


と思ったが、またしてもドヤ顔で答えるスーは満足そうだったので、心に留めた




そんなスー君は朝からあたふたしていた。

TwitterだけしかSNSやってないけど大丈夫かな?」

「みんなTwitterからThreadsに乗り換えるってツイートしてるぞ」

と思い出したようにSNSを巡回してはあわてていた。


いつも投稿は任せっきりなのに、たまに焦りだすスー君。



でも確かにそろそろSNSの活用を真剣に考えた方がいいかもなと僕も思った




公式のメインでは新機能や不具合について大切な告知をするとして



mL民と一緒にコミュニティを盛り上げる用の別アカウントがあってもいいね


メインの方だと躊躇してしまういいねやリツイートもしていきたい





あとmL民のユーザーにインタビューとかしてみたいんだな


アプリを利用してくれているmL民がどんな人物なのか興味ある


他のmL民を知ればきっとコミュニティも楽しくなるのではないかな





あとアンケートもしたいな


アプリに関することもそうだし、作曲で意識していることとか調査することで


作曲初心者のユーザーでも続けられるヒントがありそう





う~ん
でもiOSも進捗進めないといけないし、


Androidの新機能も実装したいし、


ブログも書きたい。。



SNSまでなかなか手が回らないなあ。








2023/7/10 (月)

新しく公式のアプリPRアカウントを作成することになった。



「mL民と一緒にコミュニティを盛り上げるアカウントいつ始める?」
とスー君が話を切り出した。



正直今の開発で手が回らなかったので、
musicLineのiOS版がある程度できてきたらかな?と曖昧に返事した


スー君ももうちょっと要領良く開発を進めてくれてもいいのよ?






「遅くないか」とスー


カチーン


こっちの気持ちも知らないでー


じゃあスーがやってちょうだいよ



と申し立てたところ





え、どしたん

そんなに言い過ぎてない、よ?




とにかく、なかなか手が回らなかったダラーンのケツを叩いてくれたスー君


結局PRアカウントはスー君に任せることになった。





スー曰く1ヶ月で一千人フォロワーを突破するという意気込みらしい



。。。


自信過剰なのか欠如しているのかよくわからない奴だな




でも意外とちゃんと考えていたようで驚いた
(というより内心スー君を馬鹿にしていたのは内緒☆)


さらにTwitterだけでなくInstagramやThreadsまで即座にアカウントを開設した


思い立った時の行動力はすごい




まあ気を張らずに頑張ってほしい


PRアカウントよろしく~




2023/7/14 (金)

アプリのPRアカウントをスーに任せたはいいけど



早速スー君はあたふたしはじめているなあ



どうやら本当に公式?と疑いの目が向けられているみたい。。




でもそれはそれで、面白いか^^




もっとスーが疑われてあたふたしているところを見たい気持ちもあるけど






SNSを任せたのは僕だし、流石にスーに悪いかな



しょーがない公式から告知するかあ








まあ明日でいいや☆








おわりに

今回は初の試みで日記を公開してみました。
公式アカウントを開設した経緯についての日記はいかがだったでしょうか。



このブログではモバイル開発に関する技術寄りな記事を投稿しますが、


アプリの新機能やiOSの進捗であったり、たまに面白おかしく雑談したりしますので、またよかったら見に来てください。



また、PRアカウントを公式からすぐに告知せず、疑念の目を向けさせてしまったユーザーの皆様にはすみません。


今後は公式PRアカウントの方でもSNSを活用してmusicLineを積極的に広めていきたいと思います。



あとスーはちょっと抜けたところがあるので、誤字脱字が目立つかもしれません。


ただmusicLineを広めたい意欲や、ユーザーの活動を後押ししたい想いは人一倍強いので、暖かく見守ってやってください。




開発チーム一同

よろしくお願いします。