{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Juliaでオーディオプログラミング入門\n", "このNotebookは [Wanoグループ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/wano-group) の8日目の記事です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 0. はじめに\n", "~~〆切当日になってもネタが思い浮かばなかったので、~~ 今年も趣味のオーディオプログラミングの話をします。 \n", "ちなみに、去年は、[Faust Lang.とWeb Audio APIによるオーディオプログラミング入門](https://qiita.com/tdaiki-wano/items/06656a04a82963e22129)について取り扱いました。 \n", "今年は、Julia Lang.とJupyaterLabを組み合わせた、オーディオデータの取り扱い(生成・再生・加工・可視化)について触れていきます。 \n", "\n", "## 1. Julia Languageの紹介\n", "[Julia Language](https://julialang.org/)は2018年にVersion 1.0がリリースされた比較的新しい汎用プログラミング言語です。 \n", "[動的型付き言語にあるまじき実行速度を叩きだせる点](https://julialang.org/benchmarks/)が大きな魅力ですが、そのシンタックスも非常に魅力的な言語です。 \n", "ちょっとだけ、Juliaの魅力を紹介しましょう。以下のコードを見てください。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "r₀ = [1.0, 0.0] = [1.0, 0.0]\n", "r₁ = rot(0.5π) * r₀ = [6.123233995736766e-17, 1.0]\n" ] }, { "data": { "text/plain": [ "2-element Array{Float64,1}:\n", " 6.123233995736766e-17\n", " 1.0 " ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 回転行列\n", "function rot(θ::AbstractFloat)\n", " [\n", " cos(θ) -sin(θ)\n", " sin(θ) cos(θ)\n", " ]\n", "end\n", "\n", "# r₀(1, 0)を+90度回転させる\n", "@show r₀ = [1., 0.]\n", "@show r₁ = rot(0.5π) * r₀" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "たったこれだけのコードからでも、他のプログラミング言語にはないユニークかつ魅力的な特徴がいくつか見てとれます。\n", "* ベクトルや行列の記述が簡単(見た目もかなり直感的!)\n", "* 特別な記述なしに行列の四則演算ができる\n", "* 動的型付き言語でありながら、必要な部分で型システムの恩恵を受けられる\n", "* ASCII文字以外でも変数に使える\n", "* etc... \n", " \n", "同等の記述をここまでシンプルかつ直感的な形で表現できる汎用プログラミング言語は、なかなか無いのでないでしょうか。 \n", "また、見ての通りJupyter上で実行できるので、データの可視化周りのライブラリも充実しています。 \n", "例えば、グラフ描画用のライブラリ([Plots.jl](http://docs.juliaplots.org/latest/))を使えば、以下のようにさくっとNotebook上でグラフを描画できます。" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "-1.0\n", "\n", "\n", "-0.5\n", "\n", "\n", "0.0\n", "\n", "\n", "0.5\n", "\n", "\n", "1.0\n", "\n", "\n", "-1.0\n", "\n", "\n", "-0.5\n", "\n", "\n", "0.0\n", "\n", "\n", "0.5\n", "\n", "\n", "1.0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using Plots\n", "gr()\n", "\n", "θ = LinRange(0, 8π, 200)\n", "A = LinRange(0, 1, 200)\n", "r = hcat((A .* map(x -> rot(x) * r₀, θ))...)\n", "plot(r[1, :], r[2, :], st=:scatter, legend=false, xlim=(-1, 1), ylim=(-1, 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "楽しい!! ✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. Juliaで生成した波形データの音を鳴らす\n", "オーディオプログラミングの初手といえば、Hello, sineこと440 Hzのサイン波を鳴らすことでしょうか (※要出典) \n", "以下は、440 HzのSineを生成・再生するコードスニペットです。" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0.000\n", "\n", "\n", "0.002\n", "\n", "\n", "0.004\n", "\n", "\n", "0.006\n", "\n", "\n", "0.008\n", "\n", "\n", "-1.0\n", "\n", "\n", "-0.5\n", "\n", "\n", "0.0\n", "\n", "\n", "0.5\n", "\n", "\n", "1.0\n", "\n", "\n", "\n" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fs = 48000.0 # sampling rate\n", "f = 440.0 # 440 Hz\n", "sec = 10 # 10 sec分の長さのデータを生成 \n", "n = [i for i in 0:sec*fs]\n", "y = sin.(2π*f*n/fs)\n", "plot(n[1:400] / fs, y[1:400], legend=false)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[SampledSignals.jl](https://github.com/JuliaAudio/SampledSignals.jl)を使えばNotebook上で音を聞くことができます。" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "音量注意\n" ] }, { "data": { "text/html": [ "\n" ], "text/plain": [ "480001-frame, 1-channel SampleBuf{Float64, 1}\n", "10.000020833333334s sampled at 48000.0Hz\n", "▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using SampledSignals\n", "\n", "# mono\n", "println(\"音量注意\")\n", "SampleBuf(0.4y, fs)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ], "text/plain": [ "480001-frame, 2-channel SampleBuf{Float64, 2}\n", "10.000020833333334s sampled at 48000.0Hz\n", "▂▃▃▄▄▄▄▄▄▄▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇▇▇▇\n", "▇▇▇▇▇▇▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▄▄▄▄▄▄▄▃▃▂" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# stereo [波形データ * channel]のMatrixを渡す\n", "amplitude = collect(LinRange(0, 1.0, length(y)))\n", "Y = [amplitude .* y reverse(amplitude) .* y]\n", "SampleBuf(0.4Y, fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ただのデータ列だったものが音として認識できるとテンション上がりますね。 \n", "楽しい!! ✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. オーディオデータを読み込んで再生する\n", "[LibSndFile.jl](https://github.com/JuliaAudio/LibSndFile.jl)を使います" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ], "text/plain": [ "480000-frame, 2-channel SampleBuf{FixedPointNumbers.Fixed{Int16,15}, 2}\n", "10.0s sampled at 48000.0Hz\n", "▆▃▃▂▆▄▄▂▆▃▆▃▆▄▄▂▆▃▃▂▆▄▄▂▆▃▆▃▆▄▆▃▆▃▃▂▆▄▄▂▆▃▆▃▆▄▄▂▆▃▃▂▆▄▄▂▆▃▆▃▆▄▆▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\n", "▆▃▃▁▆▄▄▂▆▃▆▃▆▄▄▂▆▃▃▂▆▄▃▂▆▃▆▃▆▄▆▃▆▃▃▁▆▄▄▂▆▃▆▃▆▄▄▂▆▃▃▂▆▄▃▂▆▃▆▃▆▄▆▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using FileIO: load\n", "import LibSndFile\n", "\n", "audio = load(\"./AudioSample.wav\")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0.0\n", "\n", "\n", "2.5\n", "\n", "\n", "5.0\n", "\n", "\n", "7.5\n", "\n", "\n", "10.0\n", "\n", "\n", "-0.4\n", "\n", "\n", "-0.2\n", "\n", "\n", "0.0\n", "\n", "\n", "0.2\n", "\n", "\n", "0.4\n", "\n", "\n", "Left\n", "\n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0.0\n", "\n", "\n", "2.5\n", "\n", "\n", "5.0\n", "\n", "\n", "7.5\n", "\n", "\n", "10.0\n", "\n", "\n", "-0.4\n", "\n", "\n", "-0.2\n", "\n", "\n", "0.0\n", "\n", "\n", "0.2\n", "\n", "\n", "0.4\n", "\n", "\n", "Right\n", "\n", "\n", "\n" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wavedata = audio.data\n", "x = 0:size(wavedata)[1]-1\n", "left = plot(x/audio.samplerate, wavedata[:, 1], title=\"Left\", legend=false, ylims=(-0.4, 0.4))\n", "right = plot(x/audio.samplerate, wavedata[:, 2], title=\"Right\", legend=false, ylims=(-0.4, 0.4))\n", "plot(left, right, layout=(2,1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. 読み込んだオーディオデータを加工する\n", "せっかくなので、ちょっと信号処理っぽいこともしてみましょう。 \n", "読み込んだドラムループに[Moog等のシンセサイザーで利用されているラダーフィルタ](https://quod.lib.umich.edu/i/icmc/bbp2372.1996.123/1/--analyzing-the-moog-vcf-for-digital-implementation?rgn=full+text;view=image;q1=stilson)を適用してみます。" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ], "text/plain": [ "480000-frame, 2-channel SampleBuf{Float64, 2}\n", "10.0s sampled at 48000.0Hz\n", "▅▂▁▁▅▂▂▁▅▂▅▂▅▃▂▁▅▂▂▁▅▃▂▁▅▂▅▂▅▃▅▂▅▂▂▁▆▃▃▁▅▂▅▂▅▃▃▁▅▂▃▁▅▃▃▁▅▃▅▂▅▃▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\n", "▅▂▁▁▅▂▂▁▅▂▅▂▅▃▂▁▅▂▂▁▅▃▃▁▅▂▅▂▅▃▅▂▅▂▂▁▆▃▃▁▅▂▅▂▅▃▃▁▅▂▂▁▅▃▃▁▅▃▅▂▅▃▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mutable struct LadderLPF\n", " z::Float64\n", " x::Vector{Float64}\n", " y::Vector{Float64}\n", "end\n", "\n", "LadderLPF() = LadderLPF(0., zeros(Float64, 4), zeros(Float64, 4))\n", "\n", "function process(f::LadderLPF, x₀::Float64, fc, k, fs)\n", " ωc = 2π*fc\n", " a = (ωc + 1.3fs)\n", " b₀ = ωc/a\n", " b₁ = 0.3ωc/a\n", " a₁ = (0.3ωc - 1.3fs)/a\n", " \n", " x = x₀ - f.z\n", " for i=1:4\n", " y = b₀*x + b₁*f.x[i] - a₁*f.y[i]\n", " f.x[i] = x\n", " f.y[i] = y\n", " x = y\n", " end\n", "\n", " f.z = k*x\n", " \n", " return x\n", "end\n", "\n", "l = convert(Vector{Float64}, wavedata[:, 1])\n", "f = LadderLPF()\n", "k = 1.5\n", "fs = audio.samplerate\n", "fc = collect(LinRange(500.0, 20.0*10^3, length(l)))\n", "for i = 1:length(l)\n", " l[i] = process(f, l[i], fc[i], k, fs)\n", "end\n", "\n", "r = convert(Vector{Float64}, wavedata[:, 2])\n", "f = LadderLPF()\n", "for i = 1:length(fc)\n", " r[i] = process(f, r[i], fc[i], k, fs)\n", "end\n", "\n", "SampleBuf([l r], fs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. おわりに\n", "他にも、Juliaによるgifアニメーションの作成やオーディオ線形システムの特性評価についても書きたかったのですが、時間が無いのでこのあたりで終わります。 \n", "本稿で最小の労力で、豊かな表現を引き出せるJulia言語の魅力が少しでも伝われば幸いです。 " ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "終わりだよ~(o・∇・o) \n" ] } ], "source": [ "println(\"終わりだよ~(o・∇・o) \")" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.3.0", "language": "julia", "name": "julia-1.3" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.3.0" } }, "nbformat": 4, "nbformat_minor": 4 }