Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, random, re
|
2 |
+
from fractions import Fraction
|
3 |
+
from midiutil.MidiFile import MIDIFile
|
4 |
+
import streamlit as st
|
5 |
+
import mido, openai
|
6 |
+
|
7 |
+
if 'path' not in st.session_state:
|
8 |
+
st.session_state['path'] = os.path.realpath(os.path.dirname(__file__))
|
9 |
+
if 'sessionID' not in st.session_state:
|
10 |
+
st.session_state['sessionID'] = random.randint(0,99999999)
|
11 |
+
if 'history' not in st.session_state:
|
12 |
+
st.session_state['history'] = []
|
13 |
+
if 'downloadable' not in st.session_state:
|
14 |
+
st.session_state['downloadable'] = False
|
15 |
+
notes = [['C'], ['Db', 'C#'], ['D'], ['Eb', 'D#'], ['E'], ['F'], ['Gb', 'F#'], ['G'], ['Ab', 'G#'], ['A'], ['Bb', 'A#'], ['B']]
|
16 |
+
monster = r'(?<![A-Za-z\d])([A-G](?:#|b)?\d(?:-\d+(?:\/\d+)?(?:-\d+(?:\.\d+)?)?)+)(?![A-Za-z\d])'
|
17 |
+
|
18 |
+
def noteToInt(n):
|
19 |
+
oct = int(n[-1])
|
20 |
+
letter = n[:-1]
|
21 |
+
id = 0
|
22 |
+
for ix, x in enumerate(notes):
|
23 |
+
for y in x:
|
24 |
+
if letter == y:
|
25 |
+
id = ix
|
26 |
+
return id+oct*12+12
|
27 |
+
|
28 |
+
def midiToStr(mPath):
|
29 |
+
midIn = mido.MidiFile(os.path.expanduser(mPath))
|
30 |
+
ticks = midIn.ticks_per_beat
|
31 |
+
midOut = []
|
32 |
+
globalT = 0
|
33 |
+
opens = {}
|
34 |
+
for track in midIn.tracks:
|
35 |
+
for msg in track:
|
36 |
+
if msg.type == 'note_on' or msg.type == 'note_off':
|
37 |
+
globalT += msg.time/ticks
|
38 |
+
if msg.note in opens:
|
39 |
+
noteTime = opens[msg.note]
|
40 |
+
noteTime = int(noteTime) if noteTime.is_integer() else noteTime
|
41 |
+
noteDur = str(Fraction((globalT-noteTime)/4))
|
42 |
+
noteDur = str(round((globalT-noteTime),3)) if len(noteDur)>=6 else noteDur
|
43 |
+
midOut.append('-'.join([notes[msg.note%12][0]+str(msg.note//12-1), noteDur, str(round(noteTime,3))]))
|
44 |
+
del opens[msg.note]
|
45 |
+
if msg.type == 'note_on':
|
46 |
+
opens[msg.note] = globalT
|
47 |
+
return ', '.join(midOut)
|
48 |
+
|
49 |
+
st.markdown('# GPT-4 2 Midi\n##### plus conversion tools for use with Chat-GPT\napp by [d3nt](https://github.com/d3n7/)')
|
50 |
+
main, m2t, t2m = st.tabs(['GPT4-To-Midi', 'Midi-2-Text', 'Text-2-Midi'])
|
51 |
+
|
52 |
+
with main:
|
53 |
+
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.')
|
54 |
+
with st.expander('System Prompt'):
|
55 |
+
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.\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.')
|
56 |
+
openaikey = st.text_input('OpenAI API Key', type='password')
|
57 |
+
modelV = st.selectbox('Model', ('GPT-4', 'GPT-3.5-Turbo'))
|
58 |
+
col1, col2 = st.columns(2)
|
59 |
+
with col1:
|
60 |
+
newSession = st.checkbox('New Session', True)
|
61 |
+
with col2:
|
62 |
+
showOutput = st.checkbox('Show Output', True)
|
63 |
+
uploadMidi = st.file_uploader('Upload a midi file (OPTIONAL)')
|
64 |
+
col3, col4 = st.columns(2)
|
65 |
+
with col3:
|
66 |
+
if st.button('Ask GPT'):
|
67 |
+
if userPrompt != '' and sysPrompt != '' and openaikey != '':
|
68 |
+
if newSession:
|
69 |
+
st.session_state['history'] = [{'role': 'system', 'content': sysPrompt}]
|
70 |
+
|
71 |
+
prompt = userPrompt
|
72 |
+
if uploadMidi:
|
73 |
+
filename = ''.join(uploadMidi.name.split('.')[:-1])+str(st.session_state['sessionID'])+'.'+''.join(uploadMidi.name.split('.')[-1])
|
74 |
+
midiPath = os.path.join(st.session_state['path'], filename)
|
75 |
+
with open(midiPath, 'wb') as f:
|
76 |
+
f.write(uploadMidi.getbuffer())
|
77 |
+
prompt += '\n'+midiToStr(midiPath)
|
78 |
+
os.remove(midiPath)
|
79 |
+
st.session_state['history'].append({'role': 'user', 'content': prompt})
|
80 |
+
|
81 |
+
openai.api_key = openaikey
|
82 |
+
with st.spinner('Talking to OpenAI...'):
|
83 |
+
r = openai.ChatCompletion.create(
|
84 |
+
model=modelV.lower(),
|
85 |
+
messages=st.session_state['history']
|
86 |
+
)
|
87 |
+
response = r['choices'][0]['message']['content']
|
88 |
+
st.session_state['history'].append({'role': 'assistant', 'content': response})
|
89 |
+
|
90 |
+
noteInfo = []
|
91 |
+
for i in re.findall(monster, response):
|
92 |
+
n = i.split('-')
|
93 |
+
noteInfo.append([noteToInt(n[0]), float(Fraction(n[1])) * 4, float(n[2])]) #note, duration, time
|
94 |
+
|
95 |
+
song = MIDIFile(1, deinterleave=False)
|
96 |
+
for i in noteInfo:
|
97 |
+
pitch, dur, time = i
|
98 |
+
song.addNote(0, 0, pitch, time, dur, 100)
|
99 |
+
with open(os.path.join(st.session_state['path'], 'output.mid'), 'wb') as f:
|
100 |
+
song.writeFile(f)
|
101 |
+
if not st.session_state['downloadable']:
|
102 |
+
st.session_state['downloadable'] = True
|
103 |
+
else:
|
104 |
+
st.warning('Make sure OpenAI key, prompt, and system prompt are entered', icon='⚠️')
|
105 |
+
with col4:
|
106 |
+
if st.session_state['downloadable']:
|
107 |
+
with open(os.path.join(st.session_state['path'], 'output.mid'), 'rb') as f:
|
108 |
+
st.download_button('Download Midi', f, file_name='output.mid')
|
109 |
+
if showOutput:
|
110 |
+
with st.container():
|
111 |
+
for i in st.session_state['history']:
|
112 |
+
st.text(i['role']+': '+i['content']+'\n')
|
113 |
+
|
114 |
+
with m2t:
|
115 |
+
inMidi = st.file_uploader('Input')
|
116 |
+
if st.button('Convert', key='1'):
|
117 |
+
if inMidi:
|
118 |
+
filename = ''.join(inMidi.name.split('.')[:-1]) + str(st.session_state['sessionID']) + '.' + ''.join(inMidi.name.split('.')[-1])
|
119 |
+
midiPath = os.path.join(st.session_state['path'], filename)
|
120 |
+
with open(midiPath, 'wb') as f:
|
121 |
+
f.write(inMidi.getbuffer())
|
122 |
+
st.text_area('Output', midiToStr(midiPath))
|
123 |
+
os.remove(midiPath)
|
124 |
+
|
125 |
+
with t2m:
|
126 |
+
inText = st.text_input('Input')
|
127 |
+
if st.button('Convert', key='2'):
|
128 |
+
noteInfo = []
|
129 |
+
for i in re.findall(monster, inText):
|
130 |
+
n = i.split('-')
|
131 |
+
noteInfo.append([noteToInt(n[0]), float(Fraction(n[1])) * 4, float(n[2])])
|
132 |
+
song = MIDIFile(1, deinterleave=False)
|
133 |
+
for i in noteInfo:
|
134 |
+
pitch, dur, time = i
|
135 |
+
song.addNote(0, 0, pitch, time, dur, 100)
|
136 |
+
with open(os.path.join(st.session_state['path'], 't2m.mid'), 'wb') as f:
|
137 |
+
song.writeFile(f)
|
138 |
+
with open(os.path.join(st.session_state['path'], 't2m.mid'), 'rb') as f:
|
139 |
+
st.download_button('Download Midi', f, file_name='output.mid')
|