特定のスタイルに基づいて音楽を生成する

この投稿では、状況依存文法を使用して特定のスタイルで音楽を生成する非常に簡単な方法についてお話したいと思います。




はじめに



MIDI形式のファイルを使用します。 はい、habrayuzer、私はあなたとすすり泣きますが、メロディーの楽器、音符、および持続時間がすでに最初に綴られているのはこの形式です。 しかし、 MP3から(少なくとも些細な方法で)それらを取得することはできませんが、私は…



しかし、絶望しないでください。インターネットには高品質のMIDIファイルがたくさんあります。 HamieNET.comサービスをお勧めできます。 MIDIファイルをダウンロードできるだけでなく、MP3に変換されたメロディを一度にマージしたり、MIDIから受信したXMLファイルをマージしたり、ファイルに含まれるトラックを表示したり、1つずつ聞くことができます。 MIDIをMP3に変換するためのオンラインサービスもあります。 確かに、お金を払わなければ、1日に1つのメロディしか変換できません。



作業実演



これはオリジナルのNightwishの曲です-Ever Dream:

リンク ミラー

そして、これはアルゴリズムの結果として起こったことです:

リンク ミラー

何も変更せずにプログラムを再起動するだけで、メロディは少し異なります。

リンク ミラー



もちろん、最初のバージョンではジャムが時々聞こえ、2番目のバージョンではすべてがよりスムーズになります。



アルゴリズムの説明



このアルゴリズムは、元のメロディーから状況依存文法を構築し、その後、指定された初期シーケンスからこの文法に基づいて新しいメロディーを生成することに基づいています。



メロディを作成するには、いくつかのルールが必要です。たとえば、現在の一連のノートの後にどのノートを配置できるかなどです。 さらに、既存のメロディーに基づいて新しいメロディーを構築することは、これらのルールを自分で発明する必要はなく、与えられたメロディーのためにそれらを取得するだけです。



これらのルールはすべて文法を形成します。 ルールの左部分と右部分の両方を、 終端文字と非終端文字のコンテキストで囲むことができるため、コンテキスト依存です。 今では、これが何であるか、どのような恐ろしい言葉なのかがはっきりしていない可能性があります。 しかし、例を見てみましょう。すべてがすぐに配置されます。



通常の行ABCDEFGIKFHLEFJを使用します。 そして、例えば、記号Fで始まる文法を構築し始めます(一般に、これは記号ごとに行わなければなりません)。



突然シンボルFに出会った場合にどの文字を入れるかを指示するルールを作成する必要があります。これは次のように書かれています。F-> something 。 ご覧のとおり、このようなルールを作成することはできません。次の文字を単独で決定することはできないためです。Fの後にG、H、またはJのいずれかがあります。 Fを囲む文字。Fの前に1文字入力します。EFとKFを取得します。 ここでの文字Fのコンテキストは、文字EとKです。コンテキストを1文字だけ拡張したので、この文法を構築する方法は、コンテキストを動的に拡張する方法と呼ばれます。



ルールを作成できるかどうか見てみましょう。 KFがHになった後、他のオプションはありません。 最初の最後のルールを得ました:KF-> H(「KF Hを生成 」と読みます)。 ここで、たとえば、行の終わりでKFに出会って、行を1文字拡張する必要がある場合、Hを安全に記述できます。



しかし、まだ問題があります。EFの後にGまたはJのいずれかが存在する可能性があります。したがって、コンテキストをもう1文字、DEFおよびLEFだけ拡張する必要があります。 そして最後に、最終ルールを取得しました。

KF-> H

DEF-> G

LEF-> J



これらは文字Fのルールであり、そのコンテキストに応じて、3つのルールのいずれかを選択します。



新しい行を生成するプロセスは次のとおりです。たとえば、ADEFなどの初期シーケンスが指定されます。 最後から手紙を取り始めます。 F-そのような左側のルールはありません。コンテキストを展開します-EF、再びいいえ、展開します-DEF、そのようなルールがあり、Gを入れ、ADEFGを取得します。 私たちは最初からやり直します:文字Gなどを受け取ります 必要な回数だけ。



文法をツリー形式で表すと便利です。 文字Fの場合、ツリーは次のようになります。



ノードにはルールの左側の部分があり、ノードの横にはルールの右側の部分-可能な製品が書き込まれます。 ツリーの左側の数字は、ノードがどのコンテキストレベルにあるかを示しています。



今、次の問題に直面しています:与えられたルールに厳密に従って新しいシーケンスを生成する場合元の文字列(メロディ)を正確に取得し、 新しいものを作成する必要があります。 したがって、場合によっては、最終規則に到達してはならず、中間規則を取る必要があります。 これは、時々、会ったときに次のシーケンスを言うことを意味します:ADEF、コンテキストを最終ルールDEF-> Gに拡張しませんが、Fで停止し、ノードで任意の製品(H、G、J)を選択するためにランダムまたは他の方法を使用しますツリーのF。 またはEFに専念し、製品GまたはJを選択します。 ツリー内のコンテキストレベルとこのレベルの製品をランダムに選択します。



それでは、各文字は文字ではなくコードであるが、MIDIファイルに適用される場合、これは一連のイベント(ノートのオン/オフの切り替え、チャンネルの変更など)であると言う方が正確です。アルゴリズムはすでにメロディの準備ができています。



MIDIについてもう少し



MIDIをXMLに変換できるオンラインサービスに出会ったので、それを使用する方がはるかに便利だと思いました。 また、もう1つ実行する必要があります。 MIDIファイルにはいくつかの形式があります。 フォーマット0には1つのトラックが含まれ、フォーマット1および2には複数のトラックが含まれます。 ほとんどの場合、MIDIは1の形式で提供され、各楽器には独自のトラックがあります。ギターは1つのトラックで演奏し、バイオリンは他のトラックで演奏します。 これにより、文法の構築に多少の困難が追加されます。 他のトラックで何が起こっているかを追跡する必要があります。 したがって、すべてのインストゥルメントがヒープ上の同じトラック上にあるように、フォーマット1をフォーマット0に変換するだけです。その後、XMLに転送します。 これらのツールはすべてここから入手できます



最後に、このファイルのようなものを取得します。

<? xml version ="1.0" encoding ="ISO-8859-1" ? > <br> <! DOCTYPE MIDIFile PUBLIC <br> "-//Recordare//DTD MusicXML 0.9 MIDI//EN" <br> "http://www.musicxml.org/dtds/midixml.dtd" > <br> < MIDIFile > <br> < Format > 0 </ Format > <br> < TrackCount > 1 </ TrackCount > <br> < TicksPerBeat > 384 </ TicksPerBeat > <br> < TimestampType > Absolute </ TimestampType > <br> < Track Number ="0" > <br> < Event > <br> < Absolute > 0 </ Absolute > <br> < ControlChange Channel ="2" Control ="91" Value ="46" /> <br> </ Event > <br> < Event > <br> < Absolute > 0 </ Absolute > <br> < ProgramChange Channel ="2" Number ="49" /> <br> </ Event > <br> < Event > <br> < Absolute > 0 </ Absolute > <br> < ControlChange Channel ="2" Control ="0" Value ="0" /> <br> </ Event > <br>...<br> < Event > <br> < Absolute > 24908 </ Absolute > <br> < NoteOff Channel ="11" Note ="41" Velocity ="127" /> <br> </ Event > <br> < Event > <br> < Absolute > 24912 </ Absolute > <br> < NoteOn Channel ="11" Note ="41" Velocity ="127" /> <br> </ Event > <br> < Event > <br> < Absolute > 24956 </ Absolute > <br> < NoteOff Channel ="11" Note ="41" Velocity ="127" /> <br> </ Event > <br> < Event > <br> < Absolute > 24960 </ Absolute > <br> < NoteOn Channel ="11" Note ="41" Velocity ="127" /> <br> </ Event > <br>...<br> </ Track > <br> </ MIDIFile > <br><br> * This source code was highlighted with Source Code Highlighter .







ここでは、メロディ384のペースを見ることができます。最初に、各楽器のチャンネル設定が設定されていますが、NoteOnおよびNoteOff-ノートのオン/オフを切り替えるイベントに興味があります。 これらには、ノートが再生されるチャンネル、ノート自体の番号、およびその速度が含まれます。 各イベントの絶対時間も表示されます。 絶対時間と相対時間でXMLを生成できます。 絶対時間-トラックの最初からの経過時間、相対-最後のイベントからの経過時間。 絶対を取る方がより便利です その上で、同じ瞬間に発生したイベントを簡単にグループ化できます。 これらのグループを「コード」と呼びます。



shKodim



「コード」クラスについて説明しましょう。

public class Chord<br> {<br> // "" <br> public int Delay { get ; set ; }<br> // <br> public List < string > Events { get ; set ; }<br><br> public Chord()<br> {<br> Events = new List < string >();<br> }<br> } <br><br> * This source code was highlighted with Source Code Highlighter .







同時に、「コード」を比較するためのクラスを作成します。

public class ChordComparer : IEqualityComparer<Chord><br> {<br> public bool Equals(Chord x, Chord y)<br> {<br> if (x.Delay != y.Delay)<br> return false ;<br> if (x.Events.Count != y.Events.Count)<br> return false ;<br> foreach ( var ev in x.Events)<br> if (!y.Events.Contains(ev))<br> return false ;<br> foreach ( var ev in y.Events)<br> if (!x.Events.Contains(ev))<br> return false ;<br> return true ;<br> }<br><br> public int GetHashCode(Chord obj)<br> {<br> int hash = obj.Delay.GetHashCode();<br> obj.Events.ForEach(ev => hash += ev.GetHashCode());<br> return hash;<br> }<br> } <br><br> * This source code was highlighted with Source Code Highlighter .







そして、コードシーケンスを比較するためのクラス:

public class ListComparer : IEqualityComparer< List <Chord>><br> {<br> private ChordComparer cc = new ChordComparer();<br><br> public bool Equals( List <Chord> x, List <Chord> y)<br> {<br> if (x.Count != y.Count)<br> return false ;<br> for ( int i = 0; i < x.Count; i++)<br> if (!cc.Equals(x[i], y[i]))<br> return false ;<br> return true ;<br> }<br><br> public int GetHashCode( List <Chord> obj)<br> {<br> int hash = 0;<br> obj.ForEach(ch => ch.Events.ForEach(ev => hash += ev.GetHashCode()));<br> obj.ForEach(ch => hash += ch.Delay.GetHashCode());<br> return hash;<br> }<br> } <br><br> * This source code was highlighted with Source Code Highlighter .







ツリーノードクラス:

// , <br> public class Node<br> {<br> // - <br> public List <Chord> Value { get ; set ; }<br> // -, - , - , null <br> public List <KeyValuePair<Chord, Node>> Nodes { get ; set ; }<br><br> // <br> public Chord GetProduction( List <Chord> seq)<br> {<br> // , <br> if (Nodes.Count == 1 && Nodes[0].Value == null )<br> {<br> return Nodes.First().Key;<br> }<br> else <br> {<br> // <br> List <Node> path = new List <Node>();<br> ListComparer lc = new ListComparer();<br> path.Add( this );<br> // "", , <br> // <br> if (seq.Count == 1)<br> {<br> if (Nodes.Count != 0)<br> {<br> return Nodes[Helper.rand.Next(Nodes.Count)].Key;<br> }<br> // , , "", <br> // <br> else <br> {<br> int t = Helper.rand.Next(-10, 11);<br> int index = 0, i;<br> for (i = 0; i < Helper.listchords.Count; i++)<br> if ((index = Helper.listchords[i].IndexOf(seq.Last())) != -1) break ;<br> return Helper.listchords[i][ Math .Max(0, Math .Min(index + t, Helper.listchords[i].Count - 2))];<br> }<br> }<br> else <br> {<br> // <br> List <Chord> extseq = seq.GetRange(seq.Count - 2, 2);<br> // <br> List <Node> nodes = Nodes<br> .Where(node => node.Value != null && lc.Equals(node.Value.Value, extseq))<br> .Select(node => node.Value)<br> .Distinct()<br> .ToList();<br> // , "" <br> if (nodes.Count == 0)<br> {<br> if (Nodes.Count != 0)<br> {<br> return Nodes[Helper.rand.Next(Nodes.Count)].Key;<br> }<br> else <br> {<br> int t = Helper.rand.Next(-10, 11);<br> int index=0, i;<br> for (i = 0; i < Helper.listchords.Count; i++)<br> if ((index = Helper.listchords[i].IndexOf(seq.Last())) != -1) break ;<br> return Helper.listchords[i][ Math .Max(0, Math .Min(index + t, Helper.listchords[i].Count - 2))];<br> }<br> }<br> // <br> else <br> {<br> // <br> Node rule = nodes.First();<br> // , <br> rule.GetProduction(seq, ref path, 3);<br> // 60% <br> if (Helper.rand.NextDouble() <= 0.6)<br> {<br> return path.Last().Nodes[0].Key;<br> }<br> // , <br> // <br> else <br> {<br> Node p = path[Helper.rand.Next(path.Count)];<br> return p.Nodes[Helper.rand.Next(p.Nodes.Count)].Key;<br> }<br> }<br> }<br> }<br> }<br><br> // , c - <br> private void GetProduction( List <Chord> seq, ref List <Node> path, int c)<br> {<br> // <br> path.Add( this );<br> // , <br> }<br> } <br><br> * This source code was highlighted with Source Code Highlighter .







プロジェクト全体をここでマージできますミラー )。



ここで重要な役割は、ランダムに配置する方法です。 最初は、最後のノードの数に等しい期待値でポアソン分布でランダムに作成しましたが、メロディーは非常に乱雑だったため、より単純なオプションを残しました。



記事はこの記事を読んだ後に書かれました。




All Articles