#はじめに
カオス(力学系)からチャンスオペレーション(偶然性の音楽)ができないか?という試みをSiv3Dで行っています。カオスを用いることで、単純なランダムと違って曲を数式で一意に定義可能で、初期値鋭敏性により偶然性の音楽を生成することができ、有界性による限られた範囲での動きを音階に当てはめやすいというメリットが考えられます。
#ロジスティック写像での例
ここではロジスティック写像のサンプルをご紹介します。
https://twitter.com/hat_a2c/status/1258944697159933952
#使い方
ロジスティック写像は[0,1]の範囲を離散的に移動します。その際、パラメータの値に応じて不動点に収束したり、周期的に振動したり、カオスになったり、ランダムになったりします。
その中でも、このパラメータを調整して3周期軌道にすることで3領域を行き来しながらも不規則にバーストする振る舞いを表現することができます。そこで、その3領域に3和音コードの音を割り当てています。
さらに今回は、4和音コードの関係となる2つの3和音コードを用意することで、4和音コードのアルペジオになるようにしてみました。(不協和音を避けるため基本的には同時発音しない条件としています)
発音についてもロジスティック写像の0.5を境にしてON,OFFを割り当てています。
参考:
https://sakkyoku.info/theory/chord-decoration-01/
https://ja.wikipedia.org/wiki/%E5%9B%9B%E5%92%8C%E9%9F%B3
https://note.com/kazmaki_416/n/nf6c4394d954f
#ソースコード
OpenSiv3D(0.4.3)を前提としています。素人コードなので冗長な部分があるかと思いますが、ご活用頂ければと思います。
# include <Siv3D.hpp>
void func(double n[10], double m)
{
n[9] = n[8];
n[8] = n[7];
n[7] = n[6];
n[6] = n[5];
n[5] = n[4];
n[4] = n[3];
n[3] = n[2];
n[2] = n[1];
n[1] = n[0];
n[0] = m;
}
LineString funcR(double a, Vec2 p, int d)
{
Vec2 l1 = Vec2(0.000 * d + p.x, p.y - a * 0.000 * (1 - 0.000) * d);
Vec2 l2 = Vec2(0.125 * d + p.x, p.y - a * 0.125 * (1 - 0.125) * d);
Vec2 l3 = Vec2(0.250 * d + p.x, p.y - a * 0.250 * (1 - 0.250) * d);
Vec2 l4 = Vec2(0.365 * d + p.x, p.y - a * 0.365 * (1 - 0.365) * d);
Vec2 l5 = Vec2(0.500 * d + p.x, p.y - a * 0.500 * (1 - 0.500) * d);
Vec2 l6 = Vec2(0.625 * d + p.x, p.y - a * 0.625 * (1 - 0.625) * d);
Vec2 l7 = Vec2(0.750 * d + p.x, p.y - a * 0.750 * (1 - 0.750) * d);
Vec2 l8 = Vec2(0.875 * d + p.x, p.y - a * 0.875 * (1 - 0.875) * d);
Vec2 l9 = Vec2(1.000 * d + p.x, p.y - a * 1.000 * (1 - 1.000) * d);
return LineString
{
l1,l2,l3,l4,l5,l6,l7,l8,l9
};
}
LineString funcLine(double n[10], Vec2 p , int d)
{
Vec2 l1 = Vec2(n[9] * d + p.x, p.y - n[8] * d);
Vec2 l2 = Vec2(n[8] * d + p.x, p.y - n[8] * d);
Vec2 l3 = Vec2(n[8] * d + p.x, p.y - n[7] * d);
Vec2 l4 = Vec2(n[7] * d + p.x, p.y - n[7] * d);
Vec2 l5 = Vec2(n[7] * d + p.x, p.y - n[6] * d);
Vec2 l6 = Vec2(n[6] * d + p.x, p.y - n[6] * d);
Vec2 l7 = Vec2(n[6] * d + p.x, p.y - n[5] * d);
Vec2 l8 = Vec2(n[5] * d + p.x, p.y - n[5] * d);
Vec2 l9 = Vec2(n[5] * d + p.x, p.y - n[4] * d);
Vec2 l10 = Vec2(n[4] * d + p.x, p.y - n[4] * d);
Vec2 l11 = Vec2(n[4] * d + p.x, p.y - n[3] * d);
Vec2 l12 = Vec2(n[3] * d + p.x, p.y - n[3] * d);
Vec2 l13 = Vec2(n[3] * d + p.x, p.y - n[2] * d);
Vec2 l14 = Vec2(n[2] * d + p.x, p.y - n[2] * d);
Vec2 l15 = Vec2(n[2] * d + p.x, p.y - n[1] * d);
Vec2 l16 = Vec2(n[1] * d + p.x, p.y - n[1] * d);
Vec2 l17 = Vec2(n[1] * d + p.x, p.y - n[0] * d);
Vec2 l18 = Vec2(n[0] * d + p.x, p.y - n[0] * d);
return LineString
{
l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,l17,l18
};
}
void Main()
{
//発音データ
const Audio pC(GMInstrument::Piano1, PianoKey::C3, 0.5s);
const Audio pE(GMInstrument::Piano1, PianoKey::E3, 0.5s);
const Audio pF(GMInstrument::Piano1, PianoKey::F3, 0.5s);
const Audio pG(GMInstrument::Piano1, PianoKey::G3, 0.5s);
//ロジスティック写像パラメータ
double r = 3.858;
//初期値
double xo = Random();
double yo = Random();
double mo = Random();
double no = Random();
double x = xo;
double y = yo;
double m = mo;
double n = no;
//表示用
const Font font(20);
const Font font2(15);
int d = 200;
Vec2 px = Vec2(100, 240);
Vec2 py = Vec2(100, 520);
Vec2 pm = Vec2(450, 240);
Vec2 pn = Vec2(450, 520);
double lx[10] = { xo,xo,xo,xo,xo,xo,xo,xo,xo,xo };
double ly[10] = { yo,yo,yo,yo,yo,yo,yo,yo,yo,yo };
double lm[10] = { mo,mo,mo,mo,mo,mo,mo,mo,mo,mo };
double ln[10] = { no,no,no,no,no,no,no,no,no,no };
//処理用
bool play = false; //再生用
uint64 timer = 0; //更新時間管理用
bool on = false; //発音判定用
int count = 0; //発音回数
int span = 120; //発音間隔[msec]
while (System::Update())
{
if (KeySpace.down()) //スペースキーで起動
{
play = true;
timer = Time::GetMicrosecSinceEpoch();
count = 1;
on = true;
}
if (play)
{
uint64 value = (Time::GetMicrosecSinceEpoch() - timer); //再生時間[usec]
uint64 next = span * 1000 * count; //発音時間間隔判定[usec]
if (on)
{
on = false;
}
else if (value >= next)
{
x = r * x * (1.0 - x);
y = r * y * (1.0 - y);
m = r * m * (1.0 - m);
n = r * n * (1.0 - n);
if ((y >= 0.5 && x < 1.0 / 3.0) || (n <= 0.5 && m < 1.0 / 3.0))
{
pG.playOneShot(0.5);
}
else if ((y >= 0.5 && x > 2.0 / 3.0) || (n <= 0.5 && m > 2.0 / 3.0))
{
pC.playOneShot(0.5);
}
else if ((y >= 0.5 && x >= 1.0 / 3.0 && x <= 2.0 / 3.0) && !(n <= 0.5 && m >= 1.0 / 3.0 && m <= 2.0 / 3.0))
{
pE.playOneShot(0.5);
}
else if (!(y >= 0.5 && x >= 1.0 / 3.0 && x <= 2.0 / 3.0) && (n <= 0.5 && m >= 1.0 / 3.0 && m <= 2.0 / 3.0))
{
pF.playOneShot(0.5);
}
else if (y >= 0.5 && x >= 1.0 / 3.0 && x <= 2.0 / 3.0 && n <= 0.5 && m >= 1.0 / 3.0 && m <= 2.0 / 3.0)
{
}
count += 1;
on = true;
func(lx, x);
func(ly, y);
func(lm, m);
func(ln, n);
}
}
if (1) //以下、表示用
{
font(U"X").drawBase(250, 20);
font2(U"n+1").drawBase(265, 20);
font(U" = rX").drawBase(290, 20);
font2(U"n").drawBase(340, 20);
font(U"(1-X").drawBase(350, 20);
font2(U"n").drawBase(395, 20);
font(U"); r= ", r).drawBase(405, 20);
font(U"G3").draw(px.x + 28, px.y + 10);
font(U"E3").draw(px.x + d * 0.5 - 6, px.y + 10);
font(U"C3").draw(px.x - 37 + d, px.y + 10);
font(U"初期値 = ", xo).draw(px.x - 72 + d * 0.5, px.y + 32);
font(U"OFF").draw(py.x + d * 0.25 - 6, py.y + 10);
font(U"ON").draw(py.x - d * 0.25 - 4 + d, py.y + 10);
font(U"初期値 = ", yo).draw(py.x - 72 + d * 0.5, py.y + 32);
font(U"G3").draw(pm.x + 28, pm.y + 10);
font(U"F3").draw(pm.x + d * 0.5 - 6, pm.y + 10);
font(U"C3").draw(pm.x - 37 + d, pm.y + 10);
font(U"初期値 = ", mo).draw(pm.x - 72 + d * 0.5, pm.y + 32);
font(U"ON").draw(pn.x + d * 0.25 - 6, pn.y + 10);
font(U"OFF").draw(pn.x - d * 0.25 - 4 + d, pn.y + 10);
font(U"初期値 = ", no).draw(pn.x - 72 + d * 0.5, pn.y + 32);
Line(px.x, px.y, px.x + d, px.y - d).draw(1, Palette::White);
Line(py.x, py.y, py.x + d, py.y - d).draw(1, Palette::White);
LineString line1 = funcR(r, px, d);
LineString line2 = funcR(r, py, d);
line1.drawCatmullRom(1, Palette::White);
line2.drawCatmullRom(1, Palette::White);
LineString lineX = funcLine(lx, px, d);
lineX.draw(1, Palette::Pink);
LineString lineY = funcLine(ly, py, d);
lineY.draw(1, Palette::Pink);
Line(px.x + d * 1.0 / 3.0, px.y, px.x + d * 1.0 / 3.0, px.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Line(px.x + d * 2.0 / 3.0, px.y, px.x + d * 2.0 / 3.0, px.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Line(py.x + d * 1.0 / 2.0, py.y, py.x + d * 1.0 / 2.0, py.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Triangle(px.x + d * xo, px.y, px.x + d * xo - 3, px.y + 7, px.x + d * xo + 3, px.y + 7).draw(Palette::Pink);
Triangle(py.x + d * yo, py.y, py.x + d * yo - 3, py.y + 7, py.x + d * yo + 3, py.y + 7).draw(Palette::Pink);
Line(pm.x, pm.y, pm.x + d, pm.y - d).draw(1, Palette::White);
Line(pn.x, pn.y, pn.x + d, pn.y - d).draw(1, Palette::White);
LineString line3 = funcR(r, pm, d);
LineString line4 = funcR(r, pn, d);
line3.drawCatmullRom(1, Palette::White);
line4.drawCatmullRom(1, Palette::White);
LineString lineM = funcLine(lm, pm, d);
lineM.draw(1, Palette::Skyblue);
LineString lineN = funcLine(ln, pn, d);
lineN.draw(1, Palette::Skyblue);
Line(pm.x + d * 1.0 / 3.0, pm.y, pm.x + d * 1.0 / 3.0, pm.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Line(pm.x + d * 2.0 / 3.0, pm.y, pm.x + d * 2.0 / 3.0, pm.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Line(pn.x + d * 1.0 / 2.0, pn.y, pn.x + d * 1.0 / 2.0, pn.y - d).draw(LineStyle::SquareDot, 1, Palette::White);
Triangle(pm.x + d * mo, pm.y, pm.x + d * mo - 3, pm.y + 7, pm.x + d * mo + 3, pm.y + 7).draw(Palette::Skyblue);
Triangle(pn.x + d * no, pn.y, pn.x + d * no - 3, pn.y + 7, pn.x + d * no + 3, pn.y + 7).draw(Palette::Skyblue);
}
}
}