뉴진스 슈퍼샤이 커버 - chatGPT 도움받아서 midi 노트 chord 생성하기

littleshy_youtube_full.wav

text2midi


처음에 짠 파이썬 코드에서 계속 변경하고 있는데 과정을 공유합니다

text를 입력받으면 해당 문자를 split 해서 하나의 midi note 숫자로 변환하여 출력하는 프로그램을 만들고 싶어 예를 들어 "love" 라고 치면 52 = 'l' 68 = 'o' 32 = 'v' 45 = 'e' 로 note를 주고 소문자는 velocity가 98 대문자는 velocity 가 118 이런 식으로 그리고 내가 화음으로 이뤄진 midi chord dataset을 가지고 있는데 이 데이터셋을 기반으로 해당 글자의 화음을 만들어줬으면 좋겠어 이 프로그램을 만들기 위한 알고리즘 혹은 딥러닝 적용 방법을 순차적으로 예시와 함께 나열해줘

이러한 프롬프트로 처음에 시작하였는데요.


우선 midi 파일의 구조에 대해서 말씀드리자면

    1  1  2    89 	Note	 	1	 	D3	 	 101	        0  0  0  104 
        1  1  2    97 	Note	 	1	 	A♯2	 	 101	        0  0  1  144 
        1  1  2  105 	Note	 	1	 	E2	 	 101	        0  0  1    64 
        1  1  2  113 	Note	 	1	 	E3	 	 101	        0  0  0  224 
        1  1  2  121 	Note	 	1	 	G2	 	 101	        0  0  0  144 
        1  1  4    17 	Note	 	1	 	D3	 	 101	        0  0  0  144 
        1  1  4    33 	Note	 	1	 	A♯2	 	 101	        0  0  1  208 
        1  1  4    49 	Note	 	1	 	E2	 	 101	        0  0  1  112 
        1  1  4    65 	Note	 	1	 	E3	 	 101	        0  0  1    16 
        1  1  4    81 	Note	 	1	 	G2	 	 101	        0  0  0  160 
        1  2  2    23 	Note	 	1	 	D3	 	 101	        0  0  0  174 

이런 식으로 구성이 되어 있는데요. 각 열에 대해 예상하는 feature 이름은

  1. Column 1: Identifier (ID) - This column may indicate some unique identifier for each note entry.

  2. Column 2: Channel - It might represent the MIDI channel used for the note.

  3. Column 3: Duration - This column could indicate the duration of the note in some musical unit (e.g., ticks).

  4. Column 4: Velocity - The velocity or strength with which the note is played.

  5. Column 5: Note Type - Indicates the type of the note (e.g., "Note").

  6. Column 6: Note Index - An index or reference number for the note.

  7. Column 7: Note Pitch - The musical pitch of the note (e.g., A♯2, D3, E2, etc.).

  8. Column 8: Instrument - It might represent the MIDI instrument or sound used for the note.

  9. Column 9: Parameter 1 - Some parameter associated with the note (e.g., 0, 1, 2).

  10. Column 10: Parameter 2 - Another parameter associated with the note (e.g., 0, 1, 2).

  11. Column 11: Parameter 3 - A third parameter associated with the note (e.g., 0, 1, 2).

  12. Column 12: Parameter 4 - A fourth parameter associated with the note (e.g., 16, 40, 104, etc.).


step

텍스트를 입력받아 각 문자를 MIDI 노트 숫자로 변환하고, 이를 기반으로 화음을 생성하는 프로그램을 만들고 싶으시다면, 다음과 같은 방법으로 구현할 수 있습니다.

1. 텍스트를 입력받아 각 문자를 분리합니다.

2. 각 문자를 MIDI 노트 숫자로 변환합니다. 이때, 소문자와 대문자에 따라 벨로시티(velocity) 값을 다르게 설정할 수 있습니다.

3. MIDI 코드 데이터셋을 분석하여 화음 패턴을 추출합니다.

4. 추출한 화음 패턴을 기반으로 각 문자에 해당하는 MIDI 노트 숫자로 화음을 생성합니다.



step2

MIDI 파일을 분석하여 화음 패턴을 추출하는 것은 가능합니다. 이 경우, MIDI 파일을 파싱하여 노트 정보를 추출하고, 이를 기반으로 화음 패턴을 분석할 수 있습니다.

MIDI 파일 파싱은 여러 가지 방법으로 할 수 있습니다. 예를 들어, Python에서는 `mido`와 같은 라이브러리를 사용하여 MIDI 파일을 파싱할 수 있습니다. `mido` 라이브러리를 사용하면 다음과 같이 MIDI 파일에서 노트 정보를 추출할 수 있습니다.



step3

화음 패턴 분석은 MIDI 파일에서 추출한 노트 정보를 기반으로 화음을 인식하고 분류하는 과정입니다. 이 과정에서 음악 이론 지식이 필요하며, 다양한 방법으로 구현할 수 있습니다.

예를 들어, 노트 시퀀스에서 일정한 간격으로 나타나는 노트 조합을 찾아 화음으로 정의할 수 있습니다. 또는, 노트 시퀀스에서 화음의 기능(function)을 파악하여 화음 진행(progression)을 분석할 수 있습니다.

화음 패턴 분석 방법은 음악 이론의 규칙과 패턴을 사용하여 구현할 수 있습니다. 예를 들어, 화음의 구성음(root, third, fifth 등)을 파악하여 화음의 종류(major, minor, diminished 등)를 인식하고 분류할 수 있습니다. 또는, 화음 진행의 패턴을 분석하여 음악의 키(key)와 코드 진행을 파악할 수 있습니다.

화음 패턴 분석 방법에 대한 자세한 설명과 예시는 다음 링크들을 참고하시기 바랍니다.

- [How to Analyze Chords](https://www.musictheory.net/lessons/40)

- [Chord Analysis: How to Analyze Chord Progressions](https://www.libertyparkmusic.com/chord-analysis-analyze-chord-progressions/)

- [Chord Recognition Algorithm](https://stackoverflow.com/questions/1161918/chord-recognition-algorithm)



step 4

import mido # MIDI 파일 열기 mid = mido.MidiFile('Minor Prog 2 (i-VII-I-V).mid') # 노트 정보 추출 notes = [] for track in mid.tracks: for msg in track: if msg.type == 'note_on': notes.append(msg.note) # 출력 print(notes) 이 파일을 활용해서 chord 라는 폴더 안에 12 - B Major - G# Minor 11 - Bb Major - G Minor 09 - Ab Major - F Minor 10 - A Major - F# Minor 07 - Gb Major - Eb Minor 08 - G Major - E Minor 06 - F Major - D Minor 04 - Eb Major - C Minor 05 - E Major - C# Minor 02 - Db Major - Bb Minor 03 - D Major - B Minor 01 - C Major - A Minor 라는 폴더가 있고 각 폴더 안에는 4 Progressions 2 Extended Chords 3 Borrowed & Modal Chords 1 Triads 라는 폴더가 있고 또 그 안에 예를 들어 1 Triads 안에는 02 - A Minor 01 - C Major 가 들어있고 그 안에는 VII - G;B.mid V - G Maj.mid vi - A min.mid iii - E min.mid IV - F Maj.mid I - C Maj.mid ii - D min.mid All Triads (C Major).mid 파일들이 들어있어 이런 식으로 계층이 이루어져 있는데 어떻게 각 미디 파일로 string이 접근할 수 있을까? 예를 들어 "love" 는 긍정적인 단어라서 major 안의 midi 파일로 연결되면 좋겠고 "hate"은 부정적인 단어라서 minor 안의 midi 파일로 연결되면 좋겠어


라는 스텝을 추가하였고



step5

자연어 처리(NLP) 기술을 사용하여 문자열의 감정을 분석하고, 이를 기반으로 major나 minor 폴더로 연결하는 방법을 파이썬 코드로 구현하려면, 다음과 같은 방법으로 할 수 있습니다.


import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer

# 문자열의 감정 분석
def analyze_sentiment(text):
    # VADER 감정 분석기 초기화
    nltk.download('vader_lexicon')
    sid = SentimentIntensityAnalyzer()

    # 감정 점수 계산
    sentiment_scores = sid.polarity_scores(text)
    return sentiment_scores['compound']

# 문자열의 감정 분석
text = "love"
sentiment_score = analyze_sentiment(text)

# major/minor 폴더 선택
folder = "major" if sentiment_score >= 0 else "minor"

# 출력
print(f"Sentiment score: {sentiment_score}")
print(f"Selected folder: {folder}") 

Detail

chord 라는 폴더 안에

12 - B Major - G# Minor

11 - Bb Major - G Minor

09 - Ab Major - F Minor

10 - A Major - F# Minor

07 - Gb Major - Eb Minor

08 - G Major - E Minor

06 - F Major - D Minor

04 - Eb Major - C Minor

05 - E Major - C# Minor

02 - Db Major - Bb Minor

03 - D Major - B Minor

01 - C Major - A Minor

라는 폴더가 있어

앞의 digit 두 개를 랜덤하게 골라서 그 경로로 들어가

여기까지를 1단계라고 할게.

예를 들어

01 - C Major - A Minor

안으로 들어갔다고 치면

각 폴더 안에는

4 Progressions

2 Extended Chords

3 Borrowed & Modal Chords

1 Triads

라는 폴더들이 공통적으로 있어

여기까지가 2단계야

그러면 여기서 또 맨 앞의 숫자를 랜덤하게 골라서 그 경로로 들어가

그러면 또 그 안에 폴더가 들어있는데

여기서

2 Extended Chords

3 Borrowed & Modal Chords

1 Triads

여기까지가 3단계야

이 세 폴더 안에는

02 - A Minor

01 - C Major 가 들어있어

만약 입력된 문자열이 긍정적이라면 Major라는 문자가 포함된 폴더 경로로 들어가고 반대라면 Minor라는 문자가 포함된 경로로 들어가야해.

4 Progressions 은 조금 다르게 생겼어

1 Diatonic Triads

2 AdvancedProgressions

이렇게 이루어져 있는데

1 Diatonic Triads

안에 Major Progressions

Minor Progressions 가 들어있어

2 AdvancedProgressions

안에도

Major Progressions

Minor Progressions

이렇게 이뤄져 있어

여기서 또 만약 입력된 문자열이 긍정적이라면 Major라는 문자가 포함된 폴더 경로로 들어가고 반대라면 Minor라는 문자가 포함된 경로로 들어가야해.

만약 "I love you." 라는 문장을 받으면

["I","love","you", "."] 로 1차적으로 나눌건데,

I는 대문자니까 2단계에서 무조건 4 Progressions 경로로 들어가야해. 즉, 어떤 단어가 대문자로 시작하면 2단계는 4 Progressions 로 들어가야만해.

"love"는 우선 긍정적인 언어이기 때문에 일단 Major가 포함된 경로로만 들어갈 수 있어. 그런 뒤 4개의 char로 이뤄져있기 때문에 chord로 나오지 않고 각 소문자 하나씩 다시 경로로 랜덤하게 들어가야해. 그래서 한 음 씩 재생이 될 수 있는 midi note 가 되어야해. 예를 들어 "도 미 솔"를 이루는 미디파일에 최종 도착했을 때, 도 note가 끝난다음 미 note가 나오고 그 다음 솔 이 나와야하는거지.

물론 이 때 들어가는 무작위 숫자는 겹쳐서는 안 되고 긍정적인 단어이기 때문에 Major가 포함된 곳으로만 가야해.

"you"는 긍정부정이 없기 때문에 감정은 따지지 않아도 되지만 앞선 단어의 마지막 문자인 "e"가 도착한 경로의 다른 midi 파일로 시작해야만해. 그래야 문장대로 음악이 어우러지니까.

"." 혹은 "," 또는 "?" 는 문장이 끝난 것을 의미하기 때문에 이 다음에 나오는 문자열은 다시 1단계부터 시작해야해. 예를 들어

"I love you, I hate you" 라는 문장을 입력 받으면 "," 을 기준으로 위 법칙들을 다시 반복하는데, 역시 I는 대문자니까 2단계에서 무조건 4 Progressions으로 들어가고 hate는 부정적인 단어니까 Minor가 포함된 경로로, 들어가.

만약에 문장 입력이 끝났는데 구두점을 포함한 특수기호가 없다면 박자를 반으로 늦춰줘.

전체적으로 문장의 단어 length가 4보다 길면 tripple 16분의 1 박자로 단어 길이가 1보다 길고 3보다 작으면 16분의 1로, 단어 길이가 1보다 작으면 8분의 1 박자로 노래를 만들어줘

최종 출력 형태는 midi 파일 한 개야.

→ 이 다음 과정 부터는 엄청난 노가다가 시작되었었는데요…

import os
import random
from mido import MidiFile, MidiTrack, Message
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer

# Set MIDI folder path
midi_folder = "chord"

def analyze_sentiment(text):
    """
    Analyze the sentiment of the given text.
    Returns the sentiment score.
    """
    # Initialize VADER sentiment analyzer
    nltk.download('vader_lexicon')
    sid = SentimentIntensityAnalyzer()

    # Calculate sentiment score
    sentiment_scores = sid.polarity_scores(text)
    return sentiment_scores['compound']

def select_folder(base_folder, sentiment):
    """
    Select major or minor folder based on the sentiment of the given text.
    Returns the path of the selected folder.
    """
    # Select major or minor folder
    folder = "Major" if sentiment >= 0 else "Minor"
    for subfolder in os.listdir(base_folder):
        if folder in subfolder:
            return os.path.join(base_folder, subfolder)

def select_chord_progression_folder(base_folder):
    """
    Select chord progression folder randomly.
    Returns the path of the selected chord progression folder.
    """
    # Select chord progression randomly
    folders = [f for f in os.listdir(base_folder) if os.path.isdir(os.path.join(base_folder, f))]
    selected_folder = random.choice(folders)
    return os.path.join(base_folder, selected_folder)

def process_text(text):
    # Create MIDI file
    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)

    # Split text into sentences
    sentences = text.split(".")

    # Process each sentence
    for sentence in sentences:
        # Split sentence into words
        words = sentence.split()

        # Process each word
        for word in words:
            # Check if word is capitalized
            if word.istitle():
                # Enter 4 Progressions folder
                base_folder = os.path.join(midi_folder, "4 Progressions")
            else:
                # Enter random key folder (1st step)
                key_folders = [f for f in os.listdir(midi_folder) if os.path.isdir(os.path.join(midi_folder, f))]
                key_folder = random.choice(key_folders)
                base_folder = os.path.join(midi_folder, key_folder)

                # Enter random chord progression folder (2nd step)
                base_folder = select_chord_progression_folder(base_folder)

                # Check sentiment of word
                sentiment_score = analyze_sentiment(word)

                # Enter major or minor folder (3rd step)
                base_folder = select_folder(base_folder, sentiment_score)

            print(f"Selected folder for '{word}': {base_folder}")

            # Check length of word
            if len(word) > 4:
                duration = 160  # triple 16th note
            elif len(word) > 1:
                duration = 240  # 16th note
            else:
                duration = 480  # 8th note

            # Play MIDI notes for each character in word
            for char in word:
                note = ord(char) % 12 + 60  # map character to MIDI note number
                track.append(Message('note_on', note=note, velocity=64, time=0))
                track.append(Message('note_off', note=note, velocity=64, time=duration))

        # Check if sentence ends with punctuation
        if not sentence.endswith((".", ",", "?")):
            # Add pause to MIDI track
            track.append(Message('note_off', note=0, velocity=0, time=960))  # half note pause

    # Save MIDI file
    mid.save('output.mid')

process_text("I love you. I hate you.")

여기까지 한 다음 과정을 바꾸기 시작했습니다.

import os
import random
from mido import MidiFile, MidiTrack, Message
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
import glob

# Set MIDI folder path
midi_folder = "/Users/mac/git/privateGPT/gpt2music/Unison MIDI Chord Pack"

def analyze_sentiment(text):
    """
    Analyze the sentiment of the given text.
    Returns the sentiment score.
    """
    # Initialize VADER sentiment analyzer
    nltk.download('vader_lexicon')
    sid = SentimentIntensityAnalyzer()

    # Calculate sentiment score
    sentiment_scores = sid.polarity_scores(text)
    return sentiment_scores['compound']

def select_folder(base_folder, sentiment):
    """
    Select major or minor folder based on the sentiment of the given text.
    Returns the path of the selected folder.
    """
    # Select major or minor folder
    folder = "Major" if sentiment >= 0 else "Minor"
    for subfolder in os.listdir(base_folder):
        if folder in subfolder:
            return os.path.join(base_folder, subfolder)

def select_chord_progression_folder(base_folder):
    """
    Select chord progression folder randomly.
    Returns the path of the selected chord progression folder.
    """
    try:
        # Select chord progression randomly
        folders = [f for f in os.listdir(base_folder) if os.path.isdir(os.path.join(base_folder, f))]
        selected_folder = random.choice(folders)
        return os.path.join(base_folder, selected_folder)
    except FileNotFoundError:
        print(f"Folder not found: {base_folder}")
        return None


def process_text(text):
    # Create MIDI file
    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)

    # Split text into sentences
    sentences = text.split(".")

    # Process each sentence
    for sentence in sentences:
        # Split sentence into words
        words = sentence.split()

        # Process each word
        for word in words:
            # Check if word is capitalized
            if word.istitle():
                # Enter 4 Progressions folder (1st step)
                base_folder = os.path.join(midi_folder, "4 Progressions")
                
                # Enter random chord progression folder (2nd step)
                base_folder = select_chord_progression_folder(base_folder)
                
                if base_folder is None:
                    print(f"No chord progression folder found for '{word}'")
                    continue
            else:
                # Enter random key folder (1st step)
                key_folders = [f for f in os.listdir(midi_folder) if os.path.isdir(os.path.join(midi_folder, f))]
                key_folder = random.choice(key_folders)
                base_folder = os.path.join(midi_folder, key_folder)

                # Enter random chord progression folder (2nd step)
                base_folder = select_chord_progression_folder(base_folder)

                # Check sentiment of word
                sentiment_score = analyze_sentiment(word)

                # Enter major or minor folder (3rd step)
                base_folder = select_folder(base_folder, sentiment_score)
                
                if base_folder is None:
                    print(f"No major/minor folder found for '{word}'")
                    continue

            print(f"Selected folder for '{word}': {base_folder}")

            # Search for MIDI files within selected folder and its subfolders
            midi_files = glob.glob(os.path.join(base_folder, "**/*.mid"), recursive=True)

            # Select random MIDI file from list of MIDI files
            selected_midi_path = random.choice(midi_files)

            print(f"Selected MIDI file: {selected_midi_path}")

            # Read and play selected MIDI file
            selected_mid = MidiFile(selected_midi_path)
            for msg in selected_mid.play():
                msg.time = round(msg.time)
                track.append(msg)

        break  # only process first sentence

    # Save MIDI file
    mid.save('output.mid')

process_text("You are a monster, why you always hit me? But still I love you so much")

그런 다음 현재 사용하고 있는 코드는

import os
import random
from mido import MidiFile, MidiTrack, Message
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
import glob

# Set MIDI folder path
midi_folder = "./Unison MIDI Chord Pack"

def analyze_sentiment(text):
    """
    Analyze the sentiment of the given text.
    Returns the sentiment score.
    """
    # Initialize VADER sentiment analyzer
    nltk.download('vader_lexicon')
    sid = SentimentIntensityAnalyzer()

    # Calculate sentiment score
    sentiment_scores = sid.polarity_scores(text)
    return sentiment_scores['compound']

def select_folder(base_folder, sentiment):
    """
    Select major or minor folder based on the sentiment of the given text.
    Returns the path of the selected folder.
    """
    # Select major or minor folder
    folder = "Major" if sentiment >= 0 else "Minor"
    for subfolder in os.listdir(base_folder):
        if folder in subfolder:
            return os.path.join(base_folder, subfolder)

def select_chord_progression_folder(base_folder):
    """
    Select chord progression folder randomly.
    Returns the path of the selected chord progression folder.
    """
    try:
        # Select chord progression randomly
        folders = [f for f in os.listdir(base_folder) if os.path.isdir(os.path.join(base_folder, f))]
        selected_folder = random.choice(folders)
        return os.path.join(base_folder, selected_folder)
    except FileNotFoundError:
        print(f"Folder not found: {base_folder}")
        return None

def process_text(text, beats_per_measure, note_values_mapping):
    # Create MIDI file
    mid = MidiFile()
    output_track = MidiTrack()
    mid.tracks.append(output_track)

    # Initialize total time variable
    total_time = 0

    # Split text into sentences
    sentences = text.split(".")

    # Process each sentence
    for sentence in sentences:
        # Split sentence into words
        words = sentence.split()

        # Process each word
        for word in words:
            # Filter out punctuation and unwanted characters
            word = ''.join(c for c in word if c.isalpha())

            # Check if the word is not empty after filtering and has at least one character
            if word and len(word) > 0:
                # Check if the word is capitalized (Verse)
                if word[0].isupper():
                    # Enter 4 Progressions folder (1st step)
                    base_folder = os.path.join(midi_folder, "4 Progressions")

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    if base_folder is None:
                        print(f"No chord progression folder found for '{word}'")
                        continue
                else:
                    # Enter random key folder (1st step)
                    key_folders = [f for f in os.listdir(midi_folder) if os.path.isdir(os.path.join(midi_folder, f))]
                    key_folder = random.choice(key_folders)
                    base_folder = os.path.join(midi_folder, key_folder)

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    # Check sentiment of word
                    sentiment_score = analyze_sentiment(word)

                    # Enter major or minor folder (3rd step)
                    base_folder = select_folder(base_folder, sentiment_score)

                    if base_folder is None:
                        print(f"No major/minor folder found for '{word}'")
                        continue

                print(f"Selected folder for '{word}': {base_folder}")

                # Search for MIDI files within selected folder and its subfolders
                midi_files = glob.glob(os.path.join(base_folder, "**/*.mid"), recursive=True)

                # Select random MIDI file from list of MIDI files
                selected_midi_path = random.choice(midi_files)

                print(f"Selected MIDI file: {selected_midi_path}")

                # Read selected MIDI file
                selected_mid = MidiFile(selected_midi_path)

                # Calculate the duration of each character in the word
                char_durations = []
                for char in word:
                    char_duration = note_values_mapping.get(char, 1)  # Each character represents a duration
                    char_durations.append(char_duration)

                # Calculate the total duration for the entire word
                word_duration = sum(char_durations)

                # Adjust the duration of the notes
                modified_messages = []
                current_time = total_time
                for char, char_duration in zip(word, char_durations):
                    for msg in selected_mid.play():
                        msg.time += current_time
                        msg.time = round(msg.time)
                        modified_messages.append(msg)

                    for i in range(len(modified_messages) - 1, -1, -1):
                        msg = modified_messages[i]
                        if msg.type == 'note_on':
                            note_off_msg = None
                            for j in range(i + 1, len(modified_messages)):
                                if modified_messages[j].type == 'note_off' and modified_messages[j].note == msg.note:
                                    note_off_msg = modified_messages[j]
                                    break
                            if note_off_msg:
                                adjusted_duration = round(char_duration * beats_per_measure)
                                if adjusted_duration <= 0:
                                    adjusted_duration = 1  # Set a minimum duration of 1 tick to avoid negative duration
                                note_off_msg.time = msg.time + adjusted_duration

                    current_time += word_duration

                # Append modified messages to the output track
                for msg in modified_messages:
                    output_track.append(msg)

                # Update total time variable
                total_time = current_time

    # Save MIDI file
    mid.save('output11.mid')

# Customization settings
lyrics = """
I'm super shy, super shy
But wait a minute while I make you mine, make you mine
"""

beats_per_measure = 32
note_values_mapping = {
    't': 0.6667,  # Tuplet (2/3 times the base duration)
    'e': 0.5,     # Eighth note (0.5 beat)
    's': 0.25,    # Sixteenth note (0.25 beat)
    'q': 1,       # Quarter note (1 beat)
    'dq': 1.5,    # Dotted quarter note (1.5 beats)
    'h': 2,       # Half note (2 beats)
    'dh': 3,      # Dotted half note (3 beats)
    'w': 4        # Whole note (4 beats)
}

# Test the function with the given example
process_text(lyrics, beats_per_measure, note_values_mapping)

이렇게 midi 를 만들었고

결과는 아래와 같았다

output11.mid


그런데 여기서 또 수정함

import os
import random
from mido import MidiFile, MidiTrack, Message
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
import glob

# Set MIDI folder path
midi_folder = "./Unison MIDI Chord Pack"

def analyze_sentiment(text):
    """
    Analyze the sentiment of the given text.
    Returns the sentiment score.
    """
    # Initialize VADER sentiment analyzer
    nltk.download('vader_lexicon')
    sid = SentimentIntensityAnalyzer()

    # Calculate sentiment score
    sentiment_scores = sid.polarity_scores(text)
    return sentiment_scores['compound']

def select_chord_progression_folder(base_folder):
    """
    Select chord progression folder randomly.
    Returns the path of the selected chord progression folder.
    """
    try:
        # Select chord progression randomly
        folders = [f for f in os.listdir(base_folder) if os.path.isdir(os.path.join(base_folder, f))]
        selected_folder = random.choice(folders)
        return os.path.join(base_folder, selected_folder)
    except FileNotFoundError:
        print(f"Folder not found: {base_folder}")
        return None

def process_text(text, beats_per_measure, note_values_mapping):
    # Create MIDI file
    mid = MidiFile()
    output_track = MidiTrack()
    mid.tracks.append(output_track)

    # Initialize total time variable
    total_time = 0

    # Split text into sentences
    sentences = text.split(".")

    # Process each sentence
    for sentence in sentences:
        # Split sentence into words
        words = sentence.split()

        # Process each word
        for word in words:
            # Filter out punctuation and unwanted characters
            word = ''.join(c for c in word if c.isalpha())

            # Check if the word is not empty after filtering and has at least one character
            if word and len(word) > 0:
                # Check if the word is capitalized (Verse)
                if word[0].isupper():
                    # Enter 4 Progressions folder (1st step)
                    base_folder = os.path.join(midi_folder, "4 Progressions")

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    if base_folder is None:
                        print(f"No chord progression folder found for '{word}'")
                        continue
                else:
                    # Enter random key folder (1st step)
                    key_folders = [f for f in os.listdir(midi_folder) if os.path.isdir(os.path.join(midi_folder, f))]
                    key_folder = random.choice(key_folders)
                    base_folder = os.path.join(midi_folder, key_folder)

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    if base_folder is None:
                        print(f"No chord progression folder found for '{word}'")
                        continue

                print(f"Selected folder for '{word}': {base_folder}")

                # Search for MIDI files within selected folder and its subfolders
                midi_files = glob.glob(os.path.join(base_folder, "**/*.mid"), recursive=True)

                # Select random MIDI file from list of MIDI files
                selected_midi_path = random.choice(midi_files)

                print(f"Selected MIDI file: {selected_midi_path}")

                # Read selected MIDI file
                selected_mid = MidiFile(selected_midi_path)

                # Calculate the duration of each character in the word
                char_durations = []
                for char in word:
                    char_duration = note_values_mapping.get(char, 1)  # Each character represents a duration
                    char_durations.append(char_duration)

                # Calculate the total duration for the entire word
                word_duration = sum(char_durations)

                # Adjust the duration of the notes
                modified_messages = []
                current_time = total_time
                for char, char_duration in zip(word, char_durations):
                    for msg in selected_mid.play():
                        msg.time += current_time
                        msg.time = round(msg.time)
                        modified_messages.append(msg)

                    for i in range(len(modified_messages) - 1, -1, -1):
                        msg = modified_messages[i]
                        if msg.type == 'note_on':
                            note_off_msg = None
                            for j in range(i + 1, len(modified_messages)):
                                if modified_messages[j].type == 'note_off' and modified_messages[j].note == msg.note:
                                    note_off_msg = modified_messages[j]
                                    break
                            if note_off_msg:
                                adjusted_duration = round(char_duration * beats_per_measure)
                                if adjusted_duration <= 0:
                                    adjusted_duration = 1  # Set a minimum duration of 1 tick to avoid negative duration
                                note_off_msg.time = msg.time + adjusted_duration

                    current_time += word_duration

                # Append modified messages to the output track
                for msg in modified_messages:
                    output_track.append(msg)

                # Update total time variable
                total_time = current_time

    # Save MIDI file
    mid.save('output11.mid')

# Customization settings
lyrics = """
I'm super shy, super shy
But wait a minute while I make you mine, make you mine
"""

beats_per_measure = 32
note_values_mapping = {
    't': 0.6667,  # Tuplet (2/3 times the base duration)
    'e': 0.5,     # Eighth note (0.5 beat)
    's': 0.25,    # Sixteenth note (0.25 beat)
    'q': 1,       # Quarter note (1 beat)
    'dq': 1.5,    # Dotted quarter note (1.5 beats)
    'h': 2,       # Half note (2 beats)
    'dh': 3,      # Dotted half note (3 beats)
    'w': 4        # Whole note (4 beats)
}

# Test the function with the given example
process_text(lyrics, beats_per_measure, note_values_mapping)

그랬더니

이렇게 아닌 경우에도 경로 접근이 가능해짐

문제는 mid 파일의 note pitch (column7)이 반복이 심했다. char 별로 다른 음이 나오게 하고 싶었다
The repetition in column 7, which represents the Note Pitch, occurs because the code currently reads and processes the same MIDI file for each character in a word. As a result, the MIDI messages for the same word will be repeated multiple times, leading to the observed duplication.

To address this issue and avoid repeating MIDI messages for the same word, we need to modify the code to read the MIDI file only once for each word and then adjust the timing of the notes accordingly for each character in the word.


import os
import random
from mido import MidiFile, MidiTrack

# ... (unchanged code) ...

def process_text(text, beats_per_measure, note_values_mapping):
    # Create MIDI file
    mid = MidiFile()
    output_track = MidiTrack()
    mid.tracks.append(output_track)

    # Initialize total time variable
    total_time = 0

    # Split text into sentences
    sentences = text.split(".")

    # Process each sentence
    for sentence in sentences:
        # Split sentence into words
        words = sentence.split()

        # Process each word
        for word in words:
            # Filter out punctuation and unwanted characters
            word = ''.join(c for c in word if c.isalpha())

            # Check if the word is not empty after filtering and has at least one character
            if word and len(word) > 0:
                # Check if the word is capitalized (Verse)
                if word[0].isupper():
                    # Enter 4 Progressions folder (1st step)
                    base_folder = os.path.join(midi_folder, "4 Progressions")

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    if base_folder is None:
                        print(f"No chord progression folder found for '{word}'")
                        continue
                else:
                    # Enter random key folder (1st step)
                    key_folders = [f for f in os.listdir(midi_folder) if os.path.isdir(os.path.join(midi_folder, f))]
                    key_folder = random.choice(key_folders)
                    base_folder = os.path.join(midi_folder, key_folder)

                    # Enter random chord progression folder (2nd step)
                    base_folder = select_chord_progression_folder(base_folder)

                    if base_folder is None:
                        print(f"No chord progression folder found for '{word}'")
                        continue

                print(f"Selected folder for '{word}': {base_folder}")

                # Search for MIDI files within selected folder and its subfolders
                midi_files = glob.glob(os.path.join(base_folder, "**/*.mid"), recursive=True)

                # Select random MIDI file from list of MIDI files
                selected_midi_path = random.choice(midi_files)

                print(f"Selected MIDI file: {selected_midi_path}")

                # Read selected MIDI file
                selected_mid = MidiFile(selected_midi_path)

                # Calculate the duration of each character in the word
                char_durations = []
                for char in word:
                    char_duration = note_values_mapping.get(char, 1)  # Each character represents a duration
                    char_durations.append(char_duration)

                # Calculate the total duration for the entire word
                word_duration = sum(char_durations)

                # Adjust the duration of the notes
                modified_messages = []
                current_time = total_time
                for msg in selected_mid.play():
                    for i, (char, char_duration) in enumerate(zip(word, char_durations)):
                        if msg.type == 'note_on':
                            note_off_msg = None
                            for j in range(i + 1, len(modified_messages)):
                                if modified_messages[j].type == 'note_off' and modified_messages[j].note == msg.note:
                                    note_off_msg = modified_messages[j]
                                    break
                            if note_off_msg:
                                adjusted_duration = round(char_duration * beats_per_measure)
                                if adjusted_duration <= 0:
                                    adjusted_duration = 1  # Set a minimum duration of 1 tick to avoid negative duration
                                note_off_msg.time = msg.time + adjusted_duration

                    msg.time += current_time
                    msg.time = round(msg.time)
                    modified_messages.append(msg)

                # Append modified messages to the output track
                for msg in modified_messages:
                    output_track.append(msg)

                # Update total time variable
                total_time += word_duration

    # Save MIDI file
    mid.save('output11_2_fixed.mid')

# ... (unchanged code) ...





supershy의 가사를 좀 변형을 부탁한 뒤 그걸 바탕으로 노래를 불렀다.

하는 과정에 대한 영상은

2
2개의 답글


👀 지피터스 AI스터디 13기 둘러보기

지피터스 채용

2천만 크리에이터의 원-소스 멀티-유즈 노하우를 함께 실행할 팀원을 찾고 있습니다!

수 백개의 AI 활용법을 발견하는

AI 스터디 13기 모집

⏰ ~10/31(목) 23:59까지

👉 이 게시글도 읽어보세요