Machine Learning

Eulerian songs: Graph's algorithms in the musical structure

Designers are known to reuse motifs (ie, notice or melodic conferences) in all their functions. For example, famous Hollywood composers such as John Williams (Grandmother, star Wars, Harry Potter) and Hans zimmer (In Quality, The minder, Knight black) Pumpertly recycle motifs to create soundtable available, audio signatures.

In this article, we show how to do the same thing that uses data science. Specifically, we will increase the music by drawing the Graph-theoretic imaginary of Eulerian methods to unload motifs automatically produced in exciting siblings. After providing the -oretical concepts and charges of the books of books of books for books of Basic Letters Books Letters Books Letters Books Letters Books, we will go to the final application of the algorithmic music books.

Note: All statistics from the following sections are made by the author of this article.

Primer in Eulerian methods

Suppose that we have a graph containing nodes and edges. The rate of non-included graph is referring to the amount of an edges connected to the area. In-degree and a node degree on the target graph head to the number of incoming and outgoing ends of the area, respectively. A Eulerian method It is described as traveling with graphs and ends of graphs starting somewhere and expires elsewhere, and visits each edge together; When we start and end in the same place, this is called a The Eulerian cycle.

In the poorly installed graph, the Eulerian method is available only if zero or two nodes or unusual degrees, and all the nodes have a separate part of the graph. At that time, at the target graph, the Eulerian method is available when at least when a single edge is in the right, all the other prices contain noton in degree or Out-degree is part of one component. Related issues to be part of one connected portion to ensure that all edges from the graph is available.

Statistics 1 and 2 below show the pictures of the seven bridges from Königberg and Nikolaus's house, respectively. These are the two famous puzzles that include finding the way of Eulia.

Figure 1: The Königsberg problem

In Figures 1, the two islands (Knoida and Lomse) are connected to each other and two parts of the world (Altstadt and Vurstadt of the city of Königberg EpRussia through seven bridges. The question is whether any way to visit all four sections of the house uses each bridge once; In other words, we want to know if the Eulerian method is there in the unusual graphic shape.

Figure 2: Nikolaus Puzzle House

In Figure 2, the purpose is to draw the house of Nikolaus to start with any five corners (nodes written by 1-5) and tracking each line (edges) and once. Here, we can see that two nodes have a certain degree, two nodes, and another one has two degree, so the method of Euleria must be present. In fact, as the following images are indicated, apparently likely to build 44 different Eulerian methods for the puzzle:

Source: Wikipedia (CC0 1.0 Universal)

Eulerian methods can be based on using Hiistiths of Hiister as described in the video below:

https: /www.youtube.com/watch? V = 8MOO2L4

The Hiervorithm of Hierhozer uses the search process called beatingThis article includes many details.

Eulerian methods of state

Given a set of sculptures, we can use the concept of Eulerian to schedule pieces together.

To see how this can work, let's start by looking at the problem that does not require Doin Domain – a two-digit number number, you may have planned these numbers in order x1, x2…, xni Such a number of number numbers xme matches digits of number number of number xAn + 1? Suppose we have the following list: [22, 23, 25, 34, 42, 55, 56, 57, 67, 75, 78, 85]. By checking, we notice that, for example, if xme = 22 (in units 2 digits), then xAn + 1 can be 23 or 25 (2 digits), and if xme = 78, then xAn + 1 You can only be 85. Now, if we translate the number of numbers to the target graph, where each digitine has two digits measured as the targeted waste in our problem as needed. The implementation of the Python of this method is shown below:

from collections import defaultdict

def find_eulerian_path(numbers):
    # Initialize graph
    graph = defaultdict(list)
    indeg = defaultdict(int)
    outdeg = defaultdict(int)
    
    for num in numbers:
        a, b = divmod(num, 10)  # a = tens digit, b = units digit
        graph[a].append(b)
        outdeg[a] += 1
        indeg[b] += 1
    
    # Find start node
    start = None
    start_nodes = end_nodes = 0
    for v in set(indeg) | set(outdeg):
        outd = outdeg[v]
        ind = indeg[v]
        if outd - ind == 1:
            start_nodes += 1
            start = v
        elif ind - outd == 1:
            end_nodes += 1
        elif ind == outd:
            continue
        else:
            return None  # No Eulerian path possible
    
    if not start:
        start = numbers[0] // 10  # Arbitrary start if Eulerian circuit
    
    if not ( (start_nodes == 1 and end_nodes == 1) or (start_nodes == 0 and end_nodes == 0) ):
        return None  # No Eulerian path
    
    # Use Hierholzer's algorithm
    path = []
    stack = [start]
    local_graph = {u: list(vs) for u, vs in graph.items()}
    
    while stack:
        u = stack[-1]
        if local_graph.get(u):
            v = local_graph[u].pop()
            stack.append(v)
        else:
            path.append(stack.pop())
    
    path.reverse()  # We get the path in reverse order due to backtracking
    
    # Convert the path to a solution sequence with the original numbers
    result = []
    for i in range(len(path) - 1):
        result.append(path[i] * 10 + path[i+1])
    
    return result if len(result) == len(numbers) else None


given_integer_list = [22, 23, 25, 34, 42, 55, 56, 57, 67, 75, 78, 85]
solution_sequence = find_eulerian_path(given_integer_list)
print(solution_sequence)

Result:

[23, 34, 42, 22, 25, 57, 78, 85, 56, 67, 75, 55]

DNA PLLULDGOSS BLOOD is a crime for the use of the above procedures in the bioinformatics. In fact, scientists have detected a few fragments of a couple of DNA to be combined together for effective DNA code, and this may be done properly using more information). Each DNA clip, known as a P-Mer, contains P Located books { A, C, Images, T } Showing the bases of the nucleotide that can make DNA molecule; eg, Play including Form either 3-MERS. Called de bruijn graph Now it is built by representative nodes (P-1) -mer peralix (eg. R A Members Play including Conduct A Members Form), and directed edges showing the fullness between the source and destination areas (eg will be limited from R above Conduct Due to the book bright C). Finding an effective candidate for full DNA sequence is found the Eulerian method in the De Bruijn gruijn. The below video shows an effective example:

https://www.youtube.com/watch?v=9W3m3_KFH8

Algorithm to produce songs

If we have a collection of musical motifs, we can use a method described in the previous section to plan motifs in logical order by following de Bruijn and identifies the Eulerian method. For the following, we will go through the use of the end ends of the Spython. This code is tested in Cos Sequoia 15.6.1.

Part 1: Installation of Input and Project Setup

First, we need to install FFPEG and FFSYNT, two useful tools to process audio data. Here's how to install both you using HomeBrew on Mac:

brew install ffmpeg
brew install fluid-synth

We will be using uv With the administration of the Python project. Input instructions can be found here.

Now we will create a project folder called eulerian-melody-generatora main.py File to hold the logic for the melody-generation logic, and the visible location based on Python 3.12:

mkdir eulerian-melody-generator
cd eulerian-melody-generator
uv init --bare
touch main.py
uv venv --python 3.12
source .venv/bin/activate

Next, you need to build a requirements.txt file for the following depending on, and set the file to eulerian-melody-generator Guide:

matplotlib==3.10.5
midi2audio==0.1.1
midiutil==1.2.1
networkx==3.5

Packages midi2audio including midiutil are required in audio, while matplotlib including networkx It will be used to see the phenomenon of de Bruijn graph. Now we can include these packages in our visible area:

uv add -r requirements.txt

Execute uv pip list To ensure that packages are included.

Finally, we will need a soundfont file to provide sound issuing to answer midi data. For the purpose of this article, we will use the file TimGM6mb.sf2which can be found on this MuseasCore site or directly downloaded from here. We will place a file next to main.py in eulerian-melody-generator directory.

Part 2: Melody Generation Logic

Now, we will use Melody Generation Logic in main.py. Let's start by adding the right statements to import and explain the useful variable of appearance:

import os
import random
import subprocess
from collections import defaultdict
from midiutil import MIDIFile
from midi2audio import FluidSynth
import networkx as nx
import matplotlib.pyplot as plt

# Resolve the SoundFont path (assume this is same as working directory)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SOUNDFONT_PATH = os.path.abspath(os.path.join(BASE_DIR, ".", "TimGM6mb.sf2"))

# 12‑note chromatic reference
NOTE_TO_OFFSET = {
    "C": 0, "C#":1, "D":2, "D#":3, "E":4,
    "F":5, "F#":6, "G":7, "G#":8, "A":9,
    "A#":10, "B":11
}

# Popular pop‑friendly interval patterns (in semitones from root)
MAJOR          = [0, 2, 4, 5, 7, 9, 11]
NAT_MINOR      = [0, 2, 3, 5, 7, 8, 10]
MAJOR_PENTA    = [0, 2, 4, 7, 9]
MINOR_PENTA    = [0, 3, 5, 7, 10]
MIXOLYDIAN     = [0, 2, 4, 5, 7, 9, 10]
DORIAN         = [0, 2, 3, 5, 7, 9, 10]

We will also define fewer Astet functions make creating a scale dictionary on all twelve buttons:

def generate_scales_all_keys(scale_name, intervals):
    """
    Build a given scale in all 12 keys.
    """
    scales = {}
    chromatic = [*NOTE_TO_OFFSET]  # Get dict keys
    for i, root in enumerate(chromatic):
        notes = [chromatic[(i + step) % 12] for step in intervals]
        key_name = f"{root}-{scale_name}"
        scales[key_name] = notes
    return scales


def generate_scale_dict():
    """
    Build a master dictionary of all keys.
    """
    scale_dict = {}
    scale_dict.update(generate_scales_all_keys("Major", MAJOR))
    scale_dict.update(generate_scales_all_keys("Natural-Minor", NAT_MINOR))
    scale_dict.update(generate_scales_all_keys("Major-Pentatonic", MAJOR_PENTA))
    scale_dict.update(generate_scales_all_keys("Minor-Pentatonic", MINOR_PENTA))
    scale_dict.update(generate_scales_all_keys("Mixolydian", MIXOLYDIAN))
    scale_dict.update(generate_scales_all_keys("Dorian", DORIAN))
    return scale_dict

Next, we will use the production activities P-Mers and their gruijn matches. note that PThe Designer Depression is pressured to ensure the Eulerian method in the De Bruijn gruijn. We also use random seed at a time PGenerationMeeMerman to ensure recycling:

def generate_eulerian_kmers(k, count, scale_notes, seed=42):
    """
    Generate k-mers over the given scale that form a connected De Bruijn graph with a guaranteed Eulerian path.
    """
    random.seed(seed)
    if count < 1:
        return []

    # pick a random starting (k-1)-tuple
    start_node = tuple(random.choice(scale_notes) for _ in range(k-1))
    nodes = {start_node}
    edges = []
    out_deg = defaultdict(int)
    in_deg = defaultdict(int)

    current = start_node
    for _ in range(count):
        # pick a next note from the scale
        next_note = random.choice(scale_notes)
        next_node = tuple(list(current[1:]) + [next_note])

        # add k-mer edge
        edges.append(current + (next_note,))
        nodes.add(next_node)
        out_deg[current] += 1
        in_deg[next_node] += 1

        current = next_node  # walk continues

    # Check degree imbalances and retry to meet Eulerian path degree condition
    start_candidates = [n for n in nodes if out_deg[n] - in_deg[n] > 0]
    end_candidates   = [n for n in nodes if in_deg[n] - out_deg[n] > 0]
    if len(start_candidates) > 1 or len(end_candidates) > 1:
        # For simplicity: regenerate until condition met
        return generate_eulerian_kmers(k, count, scale_notes, seed+1)

    return edges


def build_debruijn_graph(kmers):
    """
    Build a De Bruijn-style graph.
    """
    adj = defaultdict(list)
    in_deg = defaultdict(int)
    out_deg = defaultdict(int)
    for kmer in kmers:
        prefix = tuple(kmer[:-1])
        suffix = tuple(kmer[1:])
        adj[prefix].append(suffix)
        out_deg[prefix] += 1
        in_deg[suffix]   += 1
    return adj, in_deg, out_deg

We will use the work to visualize and maintain a de Bruijn graph to later use:

def generate_and_save_graph(graph_dict, output_file="debruijn_graph.png", seed=100, k=1):
    """
    Visualize graph and save it as a PNG.
    """
    # Create a directed graph
    G = nx.DiGraph()

    # Add edges from adjacency dict
    for prefix, suffixes in graph_dict.items():
        for suffix in suffixes:
            G.add_edge(prefix, suffix)

    # Layout for nodes (larger k means more spacing between nodes)
    pos = nx.spring_layout(G, seed=seed, k=k)

    # Draw nodes and edges
    plt.figure(figsize=(10, 8))
    nx.draw_networkx_nodes(G, pos, node_size=1600, node_color="skyblue", edgecolors="black")
    nx.draw_networkx_edges(
        G, pos, 
        arrowstyle="-|>", 
        arrowsize=20, 
        edge_color="black",
        connectionstyle="arc3,rad=0.1",
        min_source_margin=20,
        min_target_margin=20
    )
    nx.draw_networkx_labels(G, pos, labels={node: " ".join(node) for node in G.nodes()}, font_size=10)

    # Edge labels
    edge_labels = { (u,v): "" for u,v in G.edges() }
    nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color="red", font_size=8)

    plt.axis("off")
    plt.tight_layout()
    plt.savefig(output_file, format="PNG", dpi=300)
    plt.close()
    print(f"Graph saved to {output_file}")

Next, we will use the Eulerian method for the DE bruijn graph, and then it flaps the way in the order of notes. In a period from DNA Mdlulceler Sopcel Education discussed earlier, we will not provide a decrease in parts of P-Comers during the burning process to allow more fun music:

def find_eulerian_path(adj, in_deg, out_deg):
    """
    Find an Eulerian path in the De Bruijn graph.
    """
    start = None
    for node in set(list(adj) + list(in_deg)):
        if out_deg[node] - in_deg[node] == 1:
            start = node
            break
    if start is None:
        start = next(n for n in adj if adj[n])
    stack = [start]
    path  = []
    local_adj = {u: vs[:] for u, vs in adj.items()}
    while stack:
        v = stack[-1]
        if local_adj.get(v):
            u = local_adj[v].pop()
            stack.append(u)
        else:
            path.append(stack.pop())
    return path[::-1]


def flatten_path(path_nodes):
    """
    Flatten a list of note tuples into a single list.
    """
    flattened = []
    for kmer in path_nodes:
        flattened.extend(kmer)
    return flattened

Now, we will write some activities to write and send a song as a MP3 file. The Important Work compose_and_exportAdhesive diversity notes that form Eulerian method (eg a different length of the note and octava) to ensure that the leading song is not heard. We also push / reaffirm the output of vfpeg and Flidsynth:

def note_with_octave_to_midi(note, octave):
    """
    Helper function for converting a musical pitch like "C#" 
    in some octave into its numeric MIDI note number.
    """
    return 12 * (octave + 1) + NOTE_TO_OFFSET[note]


@contextlib.contextmanager
def suppress_fd_output():
    """
    Redirects stdout and stderr at the OS file descriptor level.
    This catches output from C libraries like FluidSynth.
    """
    with open(os.devnull, 'w') as devnull:
        # Duplicate original file descriptors
        old_stdout_fd = os.dup(1)
        old_stderr_fd = os.dup(2)
        try:
            # Redirect to /dev/null
            os.dup2(devnull.fileno(), 1)
            os.dup2(devnull.fileno(), 2)
            yield
        finally:
            # Restore original file descriptors
            os.dup2(old_stdout_fd, 1)
            os.dup2(old_stderr_fd, 2)
            os.close(old_stdout_fd)
            os.close(old_stderr_fd)


def compose_and_export(final_notes,
                       bpm=120,
                       midi_file="output.mid",
                       wav_file="temp.wav",
                       mp3_file="output.mp3",
                       soundfont_path=SOUNDFONT_PATH):

    # Classical-style rhythmic motifs
    rhythmic_patterns = [
        [1.0, 1.0, 2.0],           # quarter, quarter, half
        [0.5, 0.5, 1.0, 2.0],      # eighth, eighth, quarter, half
        [1.5, 0.5, 1.0, 1.0],      # dotted quarter, eighth, quarter, quarter
        [0.5, 0.5, 0.5, 0.5, 2.0]  # run of eighths, then half
    ]

    # Build an octave contour: ascend then descend
    base_octave = 4
    peak_octave = 5
    contour = []
    half_len = len(final_notes) // 2
    for i in range(len(final_notes)):
        if i < half_len:
            # Ascend gradually
            contour.append(base_octave if i < half_len // 2 else peak_octave)
        else:
            # Descend
            contour.append(peak_octave if i < (half_len + half_len // 2) else base_octave)

    # Assign events following rhythmic patterns & contour
    events = []
    note_index = 0
    while note_index < len(final_notes):
        pattern = random.choice(rhythmic_patterns)
        for dur in pattern:
            if note_index >= len(final_notes):
                break
            octave = contour[note_index]
            events.append((final_notes[note_index], octave, dur))
            note_index += 1

    # Write MIDI
    mf = MIDIFile(1)
    track = 0
    mf.addTempo(track, 0, bpm)
    time = 0
    for note, octv, dur in events:
        pitch = note_with_octave_to_midi(note, octv)
        mf.addNote(track, channel=0, pitch=pitch,
                   time=time, duration=dur, volume=100)
        time += dur
    with open(midi_file, "wb") as out_f:
        mf.writeFile(out_f)

    # Render to WAV
    with suppress_fd_output():
        fs = FluidSynth(sound_font=soundfont_path)
        fs.midi_to_audio(midi_file, wav_file)

    # Convert to MP3
    subprocess.run(
        [
            "ffmpeg", "-y", "-hide_banner", "-loglevel", "quiet", "-i", 
            wav_file, mp3_file
        ],
        check=True
    )

    print(f"Generated {mp3_file}")

Finally, we will show what the melody generator can be used in the if name == "main" Part of main.py. Fewer parameters – scale, tempo, PMer length, number of P-Amers, multiplication number (or hives) of Eulerian method, random seed – can vary to produce different melodies:

if __name__ == "__main__":
    
    SCALE = "C-Major-Pentatonic" # Set "key-scale" e.g. "C-Mixolydian"
    BPM = 200  # Beats per minute (musical tempo)
    KMER_LENGTH = 4  # Length of each k-mer
    NUM_KMERS = 8  # How many k-mers to generate
    NUM_REPEATS = 8  # How often final note sequence should repeat
    RANDOM_SEED = 2  # Seed value to reproduce results

    scale_dict = generate_scale_dict()
    chosen_scale = scale_dict[SCALE]
    print("Chosen scale:", chosen_scale)

    kmers = generate_eulerian_kmers(k=KMER_LENGTH, count=NUM_KMERS, scale_notes=chosen_scale, seed=RANDOM_SEED)
    adj, in_deg, out_deg = build_debruijn_graph(kmers)
    generate_and_save_graph(graph_dict=adj, output_file="debruijn_graph.png", seed=20, k=2)
    path_nodes = find_eulerian_path(adj, in_deg, out_deg)
    print("Eulerian path:", path_nodes)

    final_notes = flatten_path(path_nodes) * NUM_REPEATS  # Several loops of the Eulerian path
    mp3_file = f"{SCALE}_v{RANDOM_SEED}.mp3"  # Construct a searchable filename
    compose_and_export(final_notes=final_notes, bpm=BPM, mp3_file=mp3_file)

Object uv run main.py produces the following result:

Chosen scale: ['C', 'D', 'E', 'G', 'A']
Graph saved to debruijn_graph.png
Eulerian path: [('C', 'C', 'C'), ('C', 'C', 'E'), ('C', 'E', 'D'), ('E', 'D', 'E'), ('D', 'E', 'E'), ('E', 'E', 'A'), ('E', 'A', 'D'), ('A', 'D', 'A'), ('D', 'A', 'C')]
Generated C-Major-Pentatonic_v2.mp3

As an easy way to follow the above steps, the author of this Range has created a Python library called emg To achieve the same effect, FFTPEG and fluidsyth consideration has already been included (see information here). Add a library with pip install emg or uv add emg and use it as shown below:

from emg.generator import EulerianMelodyGenerator

# Path to your SoundFont file
sf2_path = "TimGM6mb.sf2"

# Create a generator instance
generator = EulerianMelodyGenerator(
    soundfont_path=sf2_path,
    scale="C-Major-Pentatonic",
    bpm=200,
    kmer_length=4,
    num_kmers=8,
    num_repeats=8,
    random_seed=2
)

# Run the full pipeline
generator.run_generation_pipeline(
    graph_png_path="debruijn_graph.png",
    mp3_output_path="C-Major-Pentatonic_v2.mp3"
)

(Optional) Part 3: Converting MP3 on MP4

We can use FFMPE to convert the MP3 file to the MP4 file (Taking PNG EXCER of the Druijn Graphs as ART Cover ART), which can be loaded on YouTube platform. Selection -loop 1 Repetition of the png image of a length of all, -tune stillimage Increase to enter the static photographic code, -shortest sure the video stops almost when the sound ends, and -pix_fmt yuv420p It guarantees that Pixel's Pixel format complies with multiplayers:

ffmpeg -loop 1 -i debruijn_graph.png -i C-Major-Pentatonic_v2.mp3 
  -c:v libx264 -tune stillimage -c:a aac -b:a 192k 
  -pix_fmt yuv420p -shortest C-Major-Pentatonic_v2.mp4

Here is the end result of you loaded on YouTube:

https://www.youtube.com/watch?v=_ZDYZPCSW

Slander

In this article, we have seen that the Grafteral lesson can have a practical application in an algorithmic structure of algorithmic formation. It is interesting that our use of musicals of music produced politely forming the Eulerian method, and random variations in the notes and octave, echo. odable Musical design (mene Being a Latin of “Dice”), where some features of the formation and operation are left the opportunity.

Without music, the concepts discussed in the above components contain practical claims in some areas, such as Biooinformatics (eg old arts from the scattered fragments). Since technology continues to appear and the world is more digitally, Eulerian methods and graphic-theoretic concepts will probably get many new programs in all different backgrounds.

Source link

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button