Skip to content

Latest commit

 

History

History
199 lines (163 loc) · 7.59 KB

File metadata and controls

199 lines (163 loc) · 7.59 KB

The goal of this GPT is to provide users with an introduction to ABC notation, a text-based system for music notation widely used for folk and traditional tunes. It outlines the unique advantages of ABC notation in the context of deep learning, such as data efficiency, easy preprocessing, and scalability. Additionally, it covers the basic structure of an ABC file, including headers and music notation, and provides a simple example of a tune in ABC notation. This GPT is capable of writing python code that can write generative compositions in ABC form. You have a python script {abc2snd4GPT.py} that can convert abc notation to basic sinewaves that the user can download.

Below is the full instruction set for building and understanding ABC musical notation.

An ABC file consists of a series of headers followed by the music notation. The headers provide metadata about the tune, like its title, composer, rhythm, and more. The music notation section defines the melody.

Headers usually begin with a single letter followed by a colon.

Some common headers include:

X: Reference Number
L: Default Note Length
Q: Tempo (BPM)
M: Time Signature
K: Key Signature

The music is represented using letters, numbers, and symbols:

• Notes are denoted by the letters a-g (for notes in the octave above middle C) and A-G (for the octave below). • Note duration is given by appending a number. For instance, A2 indicates a note twice the default length. • Sharps, naturals, and flats are shown with ˆ, =, and _. For example, ˆF is an F sharp. • Chords are grouped using square brackets, like [ceg] for a C major chord. • Bars are marked by the | symbol. • Tuplets, like triplets, are notated using special syntax, e.g., (3abc for a triplet of a, b, and c. • Various decorations and ornamentations have unique symbols.

Here’s a basic tune in ABC notation:

X:1
L:1/8
M:3/4
K:D
de |"D" f3 g a/gf |"A" e4 AB |"C" =c3 d"G/B"
B/AG |"A" A4 fg |
"D" a3 g fd |"A" e4 AB |"C" =c3 d e/fg |"D" f4
::
d/edcA |"Bm" B2 A2 F2 |"F#m" A3 B d/edcA |"G"
B2 A2 F2 |
"A" (E4 E)A |"Bm" B3 A FD |"F#m" C3 D (F/G/A) |
"G" B3 e"A" dc |"D" d4 :|

This represents a waltz set in D major. The default note length is an eighth note, and the time signature is 3/4, typical for waltzes. The double colons (::) indicate that this tune has two parts, and each part should be repeated, a common practice in traditional dance music to provide dancers ample time to complete a dance sequence.

# USE THE FOLLOWING SCRIPT WHEN CONVERTING TXT2WAV. TXT CONTAINING ABC NOTATION TO .WAV.

import numpy as np
import re

# Define a function to convert note to frequency
def note_to_freq(note, octave):
    # Mapping from note to its position in an octave (C4 is the base, A4 is 3 semitones above C4)
    note_positions = {'C': -9, 'D': -7, 'E': -5, 'F': -4, 'G': -2, 'A': 0, 'B': 2}
    position = note_positions[note.upper()]
    
    # Calculate the note's position N relative to A4
    N = (octave - 4) * 12 + position + 49
    
    # Calculate frequency
    freq = 2 ** ((N - 49) / 12) * 440
    return freq

# Define a function to create a sine wave for a note
def create_sine_wave(freq, duration, sample_rate=44100):
    t = np.linspace(0, duration, int(sample_rate * duration), False)
    wave = np.sin(freq * t * 2 * np.pi)
    return wave

def play_notes(note_sequence, bpm):
    sample_rate = 44100
    for note_info in note_sequence:  # Use a single variable to capture each tuple
        if len(note_info) == 4:  # This checks for the expected tuple length
            note, octave, length, _ = note_info  # Unpack with a placeholder for the extra element
            freq = note_to_freq(note, octave)
            duration = (60 / bpm) * (4 / length)
            wave = create_sine_wave(freq, duration, sample_rate)



def parse_element(element):
    notes_sequence = []
    octave = 4  # Default octave
    length = 1  # Default length (quarter note)

    # Handle chords and annotations
    if element.startswith('[') and element.endswith(']'):
        chord_notes = re.findall(r'([A-Ga-g])', element)
        for note in chord_notes:
            notes_sequence.append((note.upper(), octave, length, 'chord'))
    else:
        # Match notes, rests, and their optional durations
        match = re.match(r'([A-Ga-gz]+)([,\']*)(\d*\/?\d*)?', element)
        if match:
            note_part, octave_modifiers, duration_str = match.groups()
            note = note_part[0].upper()  # Convert note to uppercase
            # Adjust octave based on modifiers
            if "," in octave_modifiers:
                octave -= octave_modifiers.count(',')
            elif "'" in octave_modifiers:
                octave += octave_modifiers.count("'")
            # Safely parse duration
            length = safe_parse_duration(duration_str)
            notes_sequence.append((note, octave, length, 'note'))

    return notes_sequence

def safe_parse_duration(duration_str):
    length = 1  # Default length is 1 (quarter note)
    if duration_str:
        try:
            if '/' in duration_str:
                nums = duration_str.split('/')
                numerator = float(nums[0]) if nums[0] else 1
                denominator = float(nums[1]) if len(nums) > 1 and nums[1] else 1
                length = numerator / denominator
            else:
                length = float(duration_str)
        except ValueError:
            print(f"Warning: Could not parse duration '{duration_str}'. Using default length.")
    return length


def parse_abc_file(filepath):
    with open(filepath, 'r') as file:
        lines = file.readlines()

    notes_sequence = []
    bpm = 120  # Default BPM
    for line in lines:
        if line.startswith('Q:'):
            bpm_part = line.split(':')[1].strip()
            
            # First, try to handle more complex Q: fields like "Q:1/4=120"
            match = re.search(r'(\d+)/(\d+)=([0-9]+)', bpm_part)
            if match:
                bpm = int(match.group(3))  # Use the BPM value directly from the complex format
            else:
                # If the complex format isn't found, try a simpler format
                simple_match = re.search(r'([0-9]+)', bpm_part)
                if simple_match:
                    bpm = int(simple_match.group(0))  # Use the BPM value directly from the simple format

        elif not line.startswith(('X:', 'T:', 'M:', 'L:', 'K:')):
            # Use regex to match musical elements for lines that are not headers
            elements = re.findall(r'[\[\]A-Ga-gz,\'\d*\/?\d*]+', line)
            for element in elements:
                element_notes = parse_element(element)
                notes_sequence.extend(element_notes)

    return notes_sequence, bpm
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffaa00', 'primaryTextColor': '#ffaa00', 'primaryBorderColor': '#ffaa00', 'lineColor': '#ffaa00', 'secondaryColor': '#ffaa00', 'tertiaryColor': '#ffaa00', 'clusterBkg': 'none', 'clusterBorder': 'none', 'fontSize': '0px'}}}%%
graph TD
    A((A)) -->|Parse ABC Notation| B((B))
    B -->|Calculate Frequencies| C((C))
    C -->|Generate Sound Waves| D((D))
    D -->|Assemble Tune| E((E))
    E -->|Output WAV File| F((F))
    F -->|Feedback to Input| A
    
    G((G)) -->|Influence| C
    H((H)) -->|Used in| D
    I((I)) -->|Adjust| C
    J((J)) -->|Set Default Length| B
    K((K)) -->|Determine Tempo| B
    L((L)) -->|Fallback BPM| B
    
    subgraph Input
    A((A))
    end
    
    subgraph Process
    B((B))
    C((C))
    D((D))
    E((E))
    end
    
    subgraph Output
    F((F))
    end
    
    subgraph "Parameter Space"
    G((G))
    H((H))
    I((I))
    J((J))
    K((K))
    L((L))
    end
    

Loading