File size: 8,101 Bytes
3614695
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ac09a98
 
3614695
 
 
 
 
 
 
 
 
 
 
ac09a98
3614695
 
 
 
 
 
 
 
 
 
 
 
 
 
ac09a98
 
 
 
3614695
 
 
 
 
88bea0d
ac09a98
3614695
 
 
 
 
ac09a98
3614695
 
 
 
 
 
 
 
 
 
 
 
ac09a98
 
3614695
ac09a98
3614695
 
 
 
 
 
 
ac09a98
3614695
 
 
 
 
 
 
 
 
 
 
 
 
ac09a98
3614695
ac09a98
 
 
 
3614695
 
ac09a98
3614695
ac09a98
 
 
 
3614695
ac09a98
 
062929a
3614695
 
 
 
 
 
 
b30d841
168ab78
3614695
 
 
 
 
 
 
 
 
 
 
 
 
ac09a98
3614695
 
 
 
 
ac09a98
3614695
ac09a98
3614695
ac09a98
 
 
 
3614695
ac09a98
3614695
ac09a98
 
 
 
3614695
ac09a98
 
3614695
 
 
168ab78
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os, random, re
from fractions import Fraction
from midiutil.MidiFile import MIDIFile
import streamlit as st
import mido, openai

if 'path' not in st.session_state:
    st.session_state['path'] = os.path.realpath(os.path.dirname(__file__))
if 'sessionID' not in st.session_state:
    st.session_state['sessionID'] = random.randint(0,99999999)
if 'history' not in st.session_state:
    st.session_state['history'] = []
if 'downloadable' not in st.session_state:
    st.session_state['downloadable'] = False
notes = [['C'], ['Db', 'C#'], ['D'], ['Eb', 'D#'], ['E'], ['F'], ['Gb', 'F#'], ['G'], ['Ab', 'G#'], ['A'], ['Bb', 'A#'], ['B']]
monsters = [r'(?<![A-Za-z\d])([A-G](?:#|b)?\d-(?:\d+\/\d+|\d+))(?![A-Za-z\d])', r'(?<![A-Za-z\d])([A-G](?:#|b)?\d(?:-\d+(?:\/\d+)?(?:-\d+(?:\.\d+)?)?)+)(?![A-Za-z\d])']
examples = ['\n\nNotation looks like this:\n(Note-duration)\nC4-1/4, Eb4-1/4, D4-1/8, Eb4-1/8, C4-1/4', '\n\nNotation looks like this:\n(Note-duration-time in beats)\nC4-1/4-0, Eb4-1/8-2.5, D4-1/4-3, F4-1/4-3 etc.']

def noteToInt(n):
    oct = int(n[-1])
    letter = n[:-1]
    id = 0
    for ix, x in enumerate(notes):
        for y in x:
            if letter == y:
                id = ix
    return id+oct*12+12

def midiToStr(mPath, nIndex):
    midIn = mido.MidiFile(os.path.expanduser(mPath))
    ticks = midIn.ticks_per_beat
    midOut = []
    globalT = 0
    opens = {}
    for track in midIn.tracks:
        for msg in track:
            if msg.type == 'note_on' or msg.type == 'note_off':
                globalT += msg.time/ticks
                if msg.note in opens:
                    noteTime = opens[msg.note]
                    noteTime = int(noteTime) if noteTime.is_integer() else noteTime
                    noteDur = str(Fraction((globalT-noteTime)/4))
                    noteDur = str(round((globalT-noteTime),3)) if len(noteDur)>=6 else noteDur
                    if nIndex:
                        midOut.append('-'.join([notes[msg.note%12][0]+str(msg.note//12-1), noteDur, str(round(noteTime,3))]))
                    else:
                        midOut.append('-'.join([notes[msg.note%12][0]+str(msg.note//12-1), noteDur]))
                    del opens[msg.note]
                if msg.type == 'note_on':
                    opens[msg.note] = globalT
    return ', '.join(midOut)

st.markdown('# GPT-4 2 Midi\n#### AI Generated Polyphonic Music\n##### plus conversion tools for use with Chat-GPT\napp by [d3nt](https://github.com/d3n7/)')
notation = st.selectbox('Notation', ('Polyphonic', 'Monophonic'))
main, m2t, t2m = st.tabs(['GPT4-To-Midi', 'Midi-2-Text', 'Text-2-Midi'])

with main:
    userPrompt = st.text_input('Prompt', 'Full piece of sad music with multiple parts. Plan out the structure beforehand, including chords, parts (soprano, alto, tenor, bass), meter, etc.')
    with st.expander('System Prompt'):
        sysPrompt = st.text_input('', 'You are MusicGPT, a music creation and completion chat bot that. When a user gives you a prompt, you return them a song showing the notes, durations, and times that they occur. Respond with just the music.')
    openaikey = st.text_input('OpenAI API Key', type='password')
    modelV = st.selectbox('Model', ('GPT-4', 'GPT-3.5-Turbo'))
    col1, col2 = st.columns(2)
    with col1:
        newSession = st.checkbox('New Session', True)
    with col2:
        showOutput = st.checkbox('Show Output', True)
    uploadMidi = st.file_uploader('Upload a midi file (OPTIONAL)')
    col3, col4 = st.columns(2)
    with col3:
        if st.button('Ask GPT'):
            if userPrompt != '' and sysPrompt != '' and  openaikey != '':
                notationIndex = int(notation=='Polyphonic')

                if newSession:
                    st.session_state['history'] = [{'role': 'system', 'content': sysPrompt+examples[notationIndex]}]

                prompt = userPrompt
                if uploadMidi:
                    filename = ''.join(uploadMidi.name.split('.')[:-1])+str(st.session_state['sessionID'])+'.'+''.join(uploadMidi.name.split('.')[-1])
                    midiPath = os.path.join(st.session_state['path'], filename)
                    with open(midiPath, 'wb') as f:
                        f.write(uploadMidi.getbuffer())
                    prompt += '\n'+midiToStr(midiPath, notationIndex)
                    os.remove(midiPath)
                st.session_state['history'].append({'role': 'user', 'content': prompt})

                openai.api_key = openaikey
                with st.spinner('Talking to OpenAI...'):
                    r = openai.ChatCompletion.create(
                        model=modelV.lower(),
                        messages=st.session_state['history']
                    )
                response = r['choices'][0]['message']['content']
                st.session_state['history'].append({'role': 'assistant', 'content': response})

                noteInfo = []
                for i in re.findall(monsters[notationIndex], response):
                    n = i.split('-')
                    if notationIndex:
                        noteInfo.append([noteToInt(n[0]), float(Fraction(n[1]))*4, float(n[2])])  #note, duration, time
                    else:
                        noteInfo.append([noteToInt(n[0]), float(Fraction(n[1]))*4])  # note, duration

                song = MIDIFile(1, deinterleave=False)
                time = 0
                for i in noteInfo:
                    if notationIndex:
                        pitch, dur, time = i
                    else:
                        pitch, dur = i
                    song.addNote(0, 0, pitch, time, dur, 100)
                    if not notationIndex:
                        time += dur
                with open(os.path.join(st.session_state['path'], 'out.mid'), 'wb') as f:
                    song.writeFile(f)
                if not st.session_state['downloadable']:
                    st.session_state['downloadable'] = True
            else:
                st.warning('Make sure OpenAI key, prompt, and system prompt are entered', icon='⚠️')
    with col4:
        if st.session_state['downloadable']:
            with open(os.path.join(st.session_state['path'], 'out.mid'), 'rb') as f:
                st.download_button('Download Midi', f, file_name='song.mid', key='main')
    if showOutput:
        with st.container():
            for i in st.session_state['history']:
                st.text(i['role']+': '+i['content']+'\n')

with m2t:
    inMidi = st.file_uploader('Input')
    if st.button('Convert', key='1'):
        if inMidi:
            filename = ''.join(inMidi.name.split('.')[:-1]) + str(st.session_state['sessionID']) + '.' + ''.join(inMidi.name.split('.')[-1])
            midiPath = os.path.join(st.session_state['path'], filename)
            with open(midiPath, 'wb') as f:
                f.write(inMidi.getbuffer())
            st.text_area('Output', midiToStr(midiPath, notation=='Polyphonic'))
            os.remove(midiPath)

with t2m:
    inText = st.text_input('Input')
    if st.button('Convert', key='2'):
        notationIndex = int(notation=='Polyphonic')
        noteInfo = []
        for i in re.findall(monsters[notationIndex], inText):
            n = i.split('-')
            if notationIndex:
                noteInfo.append([noteToInt(n[0]), float(Fraction(n[1])) * 4, float(n[2])])  # note, duration, time
            else:
                noteInfo.append([noteToInt(n[0]), float(Fraction(n[1])) * 4])  # note, duration
        song = MIDIFile(1, deinterleave=False)
        time = 0
        for i in noteInfo:
            if notationIndex:
                pitch, dur, time = i
            else:
                pitch, dur = i
            song.addNote(0, 0, pitch, time, dur, 100)
            if not notationIndex:
                time += dur
        with open(os.path.join(st.session_state['path'], 't2m.mid'), 'wb') as f:
            song.writeFile(f)
        with open(os.path.join(st.session_state['path'], 't2m.mid'), 'rb') as f:
            st.download_button('Download Midi', f, file_name='song.mid', key='t2m')