Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- app.py +98 -0
- chords.lab +4 -0
- moonarch.py +74 -0
- requirements.txt +5 -0
app.py
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import base64
|
3 |
+
import os
|
4 |
+
import time
|
5 |
+
from moonarch import MusicToChordsConverter
|
6 |
+
from mido import MidiFile, MidiTrack, Message
|
7 |
+
import pretty_midi
|
8 |
+
|
9 |
+
# Set the home directory path for moonarch
|
10 |
+
home_directory = os.getcwd()
|
11 |
+
|
12 |
+
# Your app content
|
13 |
+
st.title("Moonarch Chords Analyzer: Chords")
|
14 |
+
st.write("Extract chords from any given music.")
|
15 |
+
|
16 |
+
# Set the title of the app
|
17 |
+
st.title("Your Music, Perfected by AI")
|
18 |
+
|
19 |
+
# Display the header text
|
20 |
+
st.header("Experience the Future of Sound")
|
21 |
+
st.write("""
|
22 |
+
Everything you need to create and release your music, including samples, plugins, unlimited distribution, and the world's best AI mastering engine.
|
23 |
+
""")
|
24 |
+
|
25 |
+
# Display the Start button
|
26 |
+
if st.button("Start using Monaarch"):
|
27 |
+
st.write("Welcome to Monaarch! Let's start creating amazing music.")
|
28 |
+
|
29 |
+
# Add upload option
|
30 |
+
audio_file = st.file_uploader("Upload a song", type=["mp3", "wav"])
|
31 |
+
|
32 |
+
if audio_file is not None:
|
33 |
+
st.write("File uploaded successfully.")
|
34 |
+
|
35 |
+
# Placeholder for progress bar
|
36 |
+
progress_bar = st.progress(0)
|
37 |
+
|
38 |
+
# Simulate file processing
|
39 |
+
for percent_complete in range(100):
|
40 |
+
time.sleep(0.01)
|
41 |
+
progress_bar.progress(percent_complete + 1)
|
42 |
+
|
43 |
+
st.write("File processing complete.")
|
44 |
+
|
45 |
+
if st.button('Find Chords'):
|
46 |
+
with st.spinner('Extracting chords and generating MIDI...'):
|
47 |
+
# Convert the uploaded file to a file path
|
48 |
+
file_name_without_ext = os.path.splitext(audio_file.name)[0]
|
49 |
+
audio_file_path = os.path.join('/tmp', audio_file.name)
|
50 |
+
with open(audio_file_path, 'wb') as f:
|
51 |
+
f.write(audio_file.getbuffer())
|
52 |
+
|
53 |
+
# Convert music to chords and save as MIDI
|
54 |
+
output_midi_file = f'{file_name_without_ext}.mid'
|
55 |
+
converter = MusicToChordsConverter(audio_file_path)
|
56 |
+
converter.recognize_chords()
|
57 |
+
converter.generate_midi()
|
58 |
+
|
59 |
+
# Updated save_midi method to handle PrettyMIDI object and proper message formatting
|
60 |
+
def save_midi(self, output_file):
|
61 |
+
midi = MidiFile()
|
62 |
+
track = MidiTrack()
|
63 |
+
midi.tracks.append(track)
|
64 |
+
|
65 |
+
# Ensure self.midi_chords is a PrettyMIDI object
|
66 |
+
if not isinstance(self.midi_chords, pretty_midi.PrettyMIDI):
|
67 |
+
raise TypeError(f"self.midi_chords is not a PrettyMIDI object: {type(self.midi_chords)}")
|
68 |
+
|
69 |
+
# Iterate over instruments and notes in the PrettyMIDI object
|
70 |
+
for instrument in self.midi_chords.instruments:
|
71 |
+
for note in instrument.notes:
|
72 |
+
# Ensure note, velocity, and time are within valid MIDI data byte range
|
73 |
+
midi_note = min(max(note.pitch, 0), 127)
|
74 |
+
velocity = min(max(note.velocity, 0), 127)
|
75 |
+
start_time = max(int(note.start * 1000), 0) # Convert to milliseconds
|
76 |
+
end_time = max(int(note.end * 1000), 0) # Convert to milliseconds
|
77 |
+
duration = end_time - start_time # Calculate duration
|
78 |
+
|
79 |
+
# Add note_on and note_off messages with correct formatting
|
80 |
+
track.append(Message('note_on', note=midi_note, velocity=velocity, time=start_time))
|
81 |
+
track.append(Message('note_off', note=midi_note, velocity=0, time=duration))
|
82 |
+
|
83 |
+
midi.save(output_file)
|
84 |
+
|
85 |
+
# Assign the updated method to the converter instance
|
86 |
+
converter.save_midi = save_midi.__get__(converter, MusicToChordsConverter)
|
87 |
+
converter.save_midi(output_midi_file)
|
88 |
+
|
89 |
+
st.success('Chords extraction and MIDI generation complete!')
|
90 |
+
|
91 |
+
# Provide a button to download the MIDI file
|
92 |
+
with open(output_midi_file, 'rb') as f:
|
93 |
+
st.download_button(
|
94 |
+
label="Download MIDI file",
|
95 |
+
data=f,
|
96 |
+
file_name=output_midi_file,
|
97 |
+
mime='audio/midi'
|
98 |
+
)
|
chords.lab
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
0.0 1.6718367346938776 D:maj
|
2 |
+
1.6718367346938776 3.4829931972789114 Bb:maj
|
3 |
+
3.4829931972789114 5.340589569160998 C:maj
|
4 |
+
5.340589569160998 8.17342403628118 D:maj
|
moonarch.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import autochord
|
2 |
+
import pretty_midi
|
3 |
+
import librosa
|
4 |
+
class MusicToChordsConverter:
|
5 |
+
def __init__(self, audio_file):
|
6 |
+
self.audio_file = audio_file
|
7 |
+
self.chords = None
|
8 |
+
self.midi_chords = pretty_midi.PrettyMIDI()
|
9 |
+
self.instrument_chords = pretty_midi.Instrument(program=0) # Acoustic Grand Piano
|
10 |
+
|
11 |
+
def recognize_chords(self):
|
12 |
+
"""
|
13 |
+
Perform chord recognition on the audio file.
|
14 |
+
"""
|
15 |
+
self.chords = autochord.recognize(self.audio_file, lab_fn='chords.lab')
|
16 |
+
|
17 |
+
def chord_to_midi_notes(self, chord_name):
|
18 |
+
"""
|
19 |
+
Map chord names to MIDI notes.
|
20 |
+
|
21 |
+
Args:
|
22 |
+
chord_name (str): The chord name to be mapped.
|
23 |
+
|
24 |
+
Returns:
|
25 |
+
list: A list of MIDI notes corresponding to the chord.
|
26 |
+
"""
|
27 |
+
note_mapping = {
|
28 |
+
'C:maj': ['C4', 'E4', 'G4'],
|
29 |
+
'C:min': ['C4', 'E-4', 'G4'],
|
30 |
+
'D:maj': ['D4', 'F#4', 'A4'],
|
31 |
+
'D:min': ['D4', 'F4', 'A4'],
|
32 |
+
'E:maj': ['E4', 'G#4', 'B4'],
|
33 |
+
'E:min': ['E4', 'G4', 'B4'],
|
34 |
+
'F:maj': ['F4', 'A4', 'C5'],
|
35 |
+
'F:min': ['F4', 'A-4', 'C5'],
|
36 |
+
'G:maj': ['G4', 'B4', 'D5'],
|
37 |
+
'G:min': ['G4', 'B-4', 'D5'],
|
38 |
+
'A:maj': ['A4', 'C#5', 'E5'],
|
39 |
+
'A:min': ['A4', 'C5', 'E5'],
|
40 |
+
'B:maj': ['B4', 'D#5', 'F#5'],
|
41 |
+
'B:min': ['B4', 'D5', 'F#5']
|
42 |
+
}
|
43 |
+
return note_mapping.get(chord_name, [])
|
44 |
+
|
45 |
+
def generate_midi(self):
|
46 |
+
"""
|
47 |
+
Generate a MIDI file from the recognized chords.
|
48 |
+
"""
|
49 |
+
for chord in self.chords:
|
50 |
+
start_time = chord[0]
|
51 |
+
end_time = chord[1]
|
52 |
+
chord_name = chord[2]
|
53 |
+
if chord_name != 'N': # Ignore no-chord
|
54 |
+
chord_notes = self.chord_to_midi_notes(chord_name)
|
55 |
+
for note_name in chord_notes:
|
56 |
+
midi_note = pretty_midi.Note(
|
57 |
+
velocity=100,
|
58 |
+
pitch=librosa.note_to_midi(note_name),
|
59 |
+
start=start_time,
|
60 |
+
end=end_time
|
61 |
+
)
|
62 |
+
self.instrument_chords.notes.append(midi_note)
|
63 |
+
|
64 |
+
self.midi_chords.instruments.append(self.instrument_chords)
|
65 |
+
|
66 |
+
def save_midi(self, output_file):
|
67 |
+
"""
|
68 |
+
Save the generated MIDI file to disk.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
output_file (str): The path where the MIDI file should be saved.
|
72 |
+
"""
|
73 |
+
self.midi_chords.write(output_file)
|
74 |
+
print(f"Saved chords to {output_file}")
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
autochord
|
3 |
+
tf_keras
|
4 |
+
keras==3
|
5 |
+
pretty_midi
|