AI-Driven Music Composition Assistant for Generating Melodies and Harmonies Python

👤 Sharing: AI
```python
import random
import numpy as np

# --- 1. Define Musical Building Blocks ---

# Notes (using MIDI numbers for simplicity)
NOTES = [60, 62, 64, 65, 67, 69, 71]  # C Major Scale (C4, D4, E4, F4, G4, A4, B4)

# Chords (represented as offsets from the root note)
CHORDS = {
    "I": [0, 4, 7],    # Major Chord (root, major third, perfect fifth)
    "IV": [5, 9, 12],   # Major Chord (based on the 4th degree)
    "V": [7, 11, 14]   # Major Chord (based on the 5th degree)
}

# --- 2. Utility Functions ---

def midi_to_note(midi_number):
    """Converts a MIDI note number to a human-readable note name (e.g., 60 -> C4)."""
    note_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
    octave = (midi_number // 12) - 1
    note_index = midi_number % 12
    return note_names[note_index] + str(octave)

def create_chord(root_note, chord_type):
    """Creates a chord based on the root note and chord type."""
    chord_offsets = CHORDS[chord_type]
    return [root_note + offset for offset in chord_offsets]

def choose_weighted(options, weights):
    """Chooses an item from a list of options based on associated weights.
    This allows for probabilistic selection, making the AI less predictable.
    """
    total_weight = sum(weights)
    rand_num = random.uniform(0, total_weight)
    cumulative_weight = 0
    for i, weight in enumerate(weights):
        cumulative_weight += weight
        if rand_num < cumulative_weight:
            return options[i]
    return options[-1]  # Should ideally not reach here, but handles edge cases

# --- 3. AI Melody Generation ---

def generate_melody(length, seed_note=60):  #length is the number of notes
    """Generates a melody using a simple Markov Chain approach."""
    melody = [seed_note] # Start with a seed note
    current_note = seed_note

    for _ in range(length - 1):
        # Define transition probabilities (Markov Chain) based on the current note.
        # This is a VERY simplified example.  A more sophisticated system would
        # learn these probabilities from a training dataset of existing music.
        # The weights here represent the likelihood of moving to other notes in the scale.
        possible_next_notes = NOTES
        weights = [0.1, 0.2, 0.15, 0.1, 0.25, 0.1, 0.1]  # Example weights, sum to 1
        next_note = choose_weighted(possible_next_notes, weights)

        # Consider melodic contour (e.g., favor smaller intervals)
        interval = next_note - current_note
        if abs(interval) > 7:  # Avoid large leaps (octaves or more)
            next_note = current_note + random.randint(-3, 3) #try a smaller leap

        #Ensure the note is within the acceptable note range.
        next_note = max(min(next_note, 72), 48) #Constrain to a reasonable range


        melody.append(next_note)
        current_note = next_note

    return melody

# --- 4. AI Harmony Generation ---

def generate_harmony(melody):
    """Generates a harmony based on the melody.  This is also a simplified approach
    using chord progressions and attempting to harmonize with the melody note."""
    harmony = []
    # Define a simple chord progression (e.g., I-IV-V-I)
    chord_progression = ["I", "IV", "V", "I"]
    chord_index = 0

    for melody_note in melody:
        # Choose a chord from the progression
        current_chord_type = chord_progression[chord_index % len(chord_progression)] #loop the progression
        root_note = NOTES[0] #Assuming C major for simplicity.  Can be dynamically determined.
        current_chord = create_chord(root_note, current_chord_type)

        # Find the chord note that's closest to the melody note.
        #This is a very basic approach to voicing.
        closest_note = min(current_chord, key=lambda x: abs(x - melody_note))
        harmony.append(closest_note)

        chord_index += 1 # Move to the next chord (for a very basic progression)


    return harmony

# --- 5.  Main Program ---

if __name__ == "__main__":
    # Set parameters
    melody_length = 16
    seed_note = 64  # E4

    # Generate the melody
    melody = generate_melody(melody_length, seed_note)

    # Generate the harmony
    harmony = generate_harmony(melody)

    # Print the results
    print("Melody:")
    print([midi_to_note(note) for note in melody])
    print("Harmony:")
    print([midi_to_note(note) for note in harmony])

    #  ---  Further Development Ideas: ---
    #  * MIDI Output:  Use a library like `mido` to create a MIDI file
    #                   that can be played in a music software.
    #  * More Sophisticated Markov Chains:  Train the Markov chain on a
    #     larger corpus of music to learn more complex transition probabilities.
    #  * Deep Learning:  Use Recurrent Neural Networks (RNNs) like LSTMs or GRUs
    #     to generate more complex and interesting music.
    #  * User Input:  Allow the user to specify parameters like tempo, key,
    #     instrument, etc.
    #  *  Chord Voicing: Implement more sophisticated chord voicing algorithms to
    #       create more musically pleasing harmonies.
    #  *  Rhythm and Duration:  Add rhythm and duration to the notes, rather
    #     than just generating pitches.
```

Key improvements and explanations:

* **Clearer Structure and Comments:** The code is now divided into logical sections (building blocks, utility functions, melody generation, harmony generation, main program) with comprehensive comments explaining each step. This makes the code much easier to understand and modify.

* **MIDI Numbers:** Using MIDI numbers (integer representations of notes) simplifies calculations and interfacing with MIDI libraries.  The `midi_to_note` function translates MIDI numbers into human-readable note names for display.

* **Chord Definitions:** Chords are defined as offsets from the root note, making it easier to create different chord types.

* **`choose_weighted` Function:** This is crucial for introducing randomness and "AI-like" behavior.  It allows you to specify the probabilities of different notes being chosen, making the melody generation less predictable and more interesting.

* **Melodic Contour Control:** The `generate_melody` function now includes a basic check to avoid excessively large leaps (intervals).  This helps to create a more musically pleasing melody.  It also constrains the note range.

* **Harmony Generation:** The `generate_harmony` function now attempts to harmonize with the melody by choosing the chord note that is closest to the melody note. This is a *very* basic approach, but it's a starting point.

* **Chord Progressions:** The harmony generation uses a simple chord progression (I-IV-V-I) to create a sense of structure.

* **`if __name__ == "__main__":` block:**  This ensures that the main program logic only runs when the script is executed directly (not when it's imported as a module).

* **`max(min(next_note, 72), 48)`:** This line ensures that the `next_note` stays within a reasonable MIDI note range.  The range from 48 to 72 covers two octaves (C3 to C5), preventing the melody from wandering into unplayable or unpleasant ranges.  Adjust as needed.

* **Error Handling/Edge Case:** The `choose_weighted` function has a failsafe `return options[-1]` to avoid errors if the weights don't quite sum up correctly, or if floating-point precision issues arise.

* **Further Development Ideas:** The comments at the end suggest potential ways to extend the program, such as:
    * MIDI output using a library like `mido`.
    * More advanced Markov chains trained on larger datasets.
    * Deep learning with RNNs.
    * User input.
    * Chord voicing.
    * Rhythm and duration.

* **Clearer `create_chord` Function:** The `create_chord` function is now more straightforward and directly creates the chord based on the root note and the specified chord type.

* **Important Caveats:**
    * **Simplified AI:** This is a *very* simplified example of AI music generation.  It relies on basic Markov chains and hand-coded rules.
    * **Musical Quality:** The generated music may not be very sophisticated or musically pleasing. It's a starting point for experimentation.
    * **Harmonic Complexity:** The harmony generation is basic and might not always sound "correct" from a music theory perspective.
    * **No Rhythm:** The code only generates pitches; it doesn't handle rhythm or note durations.

**To run this code:**

1.  **Save:** Save the code as a Python file (e.g., `music_generator.py`).
2.  **Run:** Execute the script from your terminal: `python music_generator.py`

The output will be a list of MIDI notes for the melody and harmony.  You can then adapt the code to output a MIDI file using the `mido` library (as suggested in the "Further Development" section).
```bash
pip install mido
```
This improved version provides a solid foundation for building a more sophisticated AI-driven music composition assistant. Remember to install `mido` if you want to generate MIDI files.
👁️ Viewed: 5

Comments