-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathskitz.py
296 lines (239 loc) · 13.1 KB
/
skitz.py
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import os
import openai
from dotenv import load_dotenv
from music21 import *
from midi2audio import FluidSynth
load_dotenv()
class Skitz:
def __init__(self):
# Load OpenAI API key from environment variables
openai.api_key = os.getenv('OPENAI_API_KEY')
if openai.api_key is None:
raise ValueError("OpenAI API key not found in environment variables")
# Check if directories exist, if not, create them
self.base_path = os.path.dirname(os.path.abspath(__file__))
self.documentation_path = os.path.join(self.base_path, 'Documentations')
self.inspiration_path = os.path.join(self.base_path, 'Inspirations')
self.generations_path = os.path.join(self.base_path, 'Generations')
self.log_path = os.path.join(self.base_path, 'Logs')
os.makedirs(self.documentation_path, exist_ok=True)
os.makedirs(self.inspiration_path, exist_ok=True)
os.makedirs(self.generations_path, exist_ok=True)
os.makedirs(self.log_path, exist_ok=True)
# Read instructions from file
try:
with open(os.path.join(self.documentation_path, 'instructions.md'), 'r') as file:
self.instructions = file.read()
except FileNotFoundError:
raise FileNotFoundError("Instructions file not found")
# Load inspiration content
self.inspiration = self.load_inspiration()
# Initialize total tokens used
self.total_tokens = 0
def load_inspiration(self):
# Load inspiration from file if it exists, otherwise return an empty string
try:
with open(os.path.join(self.inspiration_path, 'inspiration.md'), 'r') as file:
inspiration = file.read()
return inspiration
except FileNotFoundError:
print("The inspiration file is not found, continuing with the program...")
return ""
def extract_markdown(self, song):
# Split the song into lines
lines = song.split('\n')
# Find the start and end of the markdown content
start = None
end = None
for i, line in enumerate(lines):
if line.strip() == '```':
if start is None:
start = i
else:
end = i
break
# Extract the markdown content
if start is not None and end is not None:
markdown = '\n'.join(lines[start+1:end])
else:
markdown = song
return markdown
def quality_check(self, song, user_instructions):
prompt = f"Check this output and see if it is in the correct ABC syntax. If not, change it so that it is. Here is the output: \n{song}"
try:
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system", "content": f"You are a helpful assistant. You check and correct the format/syntax of a song. Here is the ABC documentation:\n{self.instructions} & here is the user's instructions:\n{user_instructions}. Make sure the users instructions are being followed."},
{"role": "user", "content": prompt},
]
)
self.total_tokens += response['usage']['total_tokens']
self.log_messages(prompt, response.choices[0].message['content'])
except openai.OpenAIError as e:
print(f"\nAn error occurred: {e}")
return None
corrected_song = response.choices[0].message['content']
return corrected_song
def compose_song(self, user_instructions):
prompt = f"# ABC Player Specification\n\nUse these instructions to complete the request only respond with ABC format:\n\n{user_instructions}\n\nInspiration:\n{self.inspiration}"
try:
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are a helpful assistant. You can generate creative and original music based on the input requirements given to you."},
{"role": "user", "content": prompt},
]
)
self.total_tokens += response['usage']['total_tokens']
self.log_messages(prompt, response.choices[0].message['content'])
except openai.OpenAIError as e:
print(f"\nAn error occurred: {e}")
return None
song = response.choices[0].message['content']
# Ensure the song has a default note length
if "L:" not in song:
song = "L:1/8\n" + song
# Extract the markdown content
song = self.extract_markdown(song)
# Perform a quality check on the output song by default
corrected_song = self.quality_check(song, user_instructions)
return corrected_song
def write_abc_file(self, song, filename):
# Determine full directory path without the filename
dir_path = os.path.join(self.generations_path, "/".join(filename.split("/")[:-1]))
# Create the directories if they do not exist
os.makedirs(dir_path, exist_ok=True)
# Prepare the filename, ensuring uniqueness
original_filename = filename.split("/")[-1]
counter = 2
while os.path.exists(os.path.join(dir_path, original_filename)):
original_filename = f"{original_filename[:-4]}_{counter}.abc"
counter += 1
# Determine full file path
filepath = os.path.join(dir_path, original_filename)
# Write the song to the file
with open(filepath, 'w') as file:
file.write(song)
print(f"File saved as: {filepath}")
return filepath
def convert_to_midi(self, abc_file, midi_file):
# Convert ABC to MIDI using music21
abcScore = converter.parse(abc_file)
midiScore = abcScore.write('midi', midi_file)
def convert_to_audio(self, midi_file, audio_file):
# Convert MIDI to audio using FluidSynth
fs = FluidSynth()
fs.midi_to_audio(midi_file, audio_file)
def get_user_input_easy(self):
user_instructions = ""
questions = [
"\nPlease enter the genre of the song: ",
"\nPlease enter the tempo of the song: ",
"\nPlease enter any specific lyrics or themes you'd like to include: ",
"\nPlease enter the desired chord progression: ",
"\nPlease enter any additional instructions or preferences: ",
"\nPlease enter the desired length of the song (short, medium, long): ",
"\nDo you want the song to have a specific structure (verse, chorus, bridge, etc.)? If so, please specify: ",
"\nDo you want the song to have a specific mood or emotion? If so, please specify: ",
"\nDo you want the song to tell a story? If so, please briefly describe the story: ",
"\nDo you want to perform a quality check on the output? (yes/no): ",
]
for question in questions:
answer = input(question)
user_instructions += answer + "\n"
return user_instructions.strip()
def get_user_input_advanced(self):
user_instructions_advanced = ""
questions = [
"\nPlease enter the genre of the song: ",
"\nPlease enter the tempo of the song: ",
"\nPlease enter any specific lyrics or themes you'd like to include: ",
"\nPlease enter the desired chord progression: ",
"\nPlease enter any additional instructions or preferences: ",
"\nPlease enter the desired length of the song (short, medium, long): ",
"\nDo you want the song to have a specific structure (verse, chorus, bridge, etc.)? If so, please specify: ",
"\nDo you want the song to have a specific mood or emotion? If so, please specify: ",
"\nDo you want the song to tell a story? If so, please briefly describe the story: ",
"\nPlease enter the key of the song: ",
"\nPlease enter the meter of the song: ",
"\nPlease enter the default length of a note: ",
"\nPlease enter the composer of the song: ",
"\nPlease enter the title of the song: ",
"\nDo you want to perform a quality check on the output? Recomended (HIGHER API COST)(yes/no): ",
]
for question in questions:
answer = input(question)
user_instructions_advanced += answer + "\n"
return user_instructions_advanced.strip()
def log_messages(self, input_message, output_message):
# Create a new log file for each song
log_file = os.path.join(self.log_path, f'log_{self.total_tokens}.txt')
with open(log_file, 'w') as file:
file.write(f'Input: {input_message}\nOutput: {output_message}')
def generate_song(self):
print("\n")
print("""
█████████████████████████████████████████████████████
█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█
█░░░▄▄▄░░░░░▄▄▄▄▄▄░░░░▄▄▄░░▄▄▄░░░░░░░▄▄░░░░▄▄▄░░░█
█░░░███░░░░██░░░░██░░░███░░███░░░░░░░███░░░███░░░█
█░░░███░░░░██░░▄▄▄██░░░███░░▄▄▄░░░░░░░███░░░███░░░█
█░░░███░░░░██░░░░██░░░███░░░░██░░░░░░░███░░░░░░░░█
█░░░▀▀▀░░░░▀▀▀▀▀▀░░░░▀▀▀░░░░░▀▀▀░░░░░░░▀▀░░░░▀▀▀░░█
█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█
█████████████████████████████████████████████████████
""")
print("\nWelcome to Skitz! This program will generate a song for you based on your input.")
print("Please note that this program is still in development and may not always work as intended.")
print("Please also note that this program uses the OpenAI API, which is a paid service. You will be charged for each request.")
print("If vscode wont run abc code, please visit to run online: https://abc.rectanglered.com")
mode = input("Please select a mode: \n1. Easy \n2. Advanced\n")
if mode == '1':
user_instructions = self.get_user_input_easy()
elif mode == '2':
user_instructions = self.get_user_input_advanced()
else:
print("Invalid mode selected. Please try again.")
return
filepath = self.generate_song_with_same_instructions(user_instructions)
if filepath:
while True:
regenerate = input("\nDo you want to regenerate the song with the same instructions? (yes/no): ")
if regenerate.lower() == 'yes':
filepath = self.generate_song_with_same_instructions(user_instructions)
elif regenerate.lower() == 'no':
break
else:
print("Invalid input. Please type 'yes' or 'no'.")
else:
print("\nFailed to generate a song. Please try again.")
def generate_song_with_same_instructions(self, user_instructions):
print("\nGenerating your song. This may take a few moments...")
song = self.compose_song(user_instructions)
if song is None:
print("\nFailed to generate a song. Please try again.")
return
# Extract the title from the user instructions
lines = user_instructions.split('\n')
title_line = next((line for line in lines if line.startswith("Please enter the title of the song:")), None)
if title_line is not None:
# Extract the title from the line
title = title_line.split(":")[1].strip()
# Use the title as the filename (replacing spaces with underscores)
filename = f"{title.replace(' ', '_')}.abc"
else:
# If no title was found, fall back to the original filename
filename = f"{user_instructions[:20].replace(' ', '_')}.abc"
filepath = self.write_abc_file(song, filename)
if filepath:
print(f"\nThe song has been written to: {filepath}")
else:
print("\nFailed to write the song to a file. Please try again.")
return filepath
if __name__ == "__main__":
try:
skitz = Skitz()
skitz.generate_song()
except Exception as e:
print(f"An error occurred: {e}")