CoolFace
Apppublic

becaliang/Music_Generation_Project

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
generator.py177 linesDownload Raw Back to root
1from __future__ import print_function2 3import sys4import numpy as np5from music21 import midi, note, stream, tempo, instrument6from fractions import Fraction7 8import lstm9from grammar import unparse_grammar10from preprocess import get_multi_track_musical_data, get_multi_track_corpus_data11from qa import clean_up_notes, prune_grammar, prune_notes12 13# ---------------- Utility Functions ---------------- #14 15def sample_from_distribution(prob_dist, temperature=1.0):16    prob_dist = np.asarray(prob_dist).astype("float64")17    prob_dist = np.log(prob_dist + 1e-9) / temperature18    prob_dist = np.exp(prob_dist) / np.sum(np.exp(prob_dist))19    return np.argmax(np.random.multinomial(1, prob_dist, 1))20 21def predict_token(model, sequence_input, idx_to_token, temperature):22    prediction = model.predict(sequence_input, verbose=0)[0]23    index = sample_from_distribution(prediction, temperature)24    return idx_to_token[index]25 26def generate_abstract_sequence(model, corpus, grammar_data, vocab, token_to_idx, idx_to_token,27                               seq_len, diversity, max_duration=32.0, max_tries=1000):28    if len(corpus) < seq_len:29        print("⚠️ Not enough tokens in corpus to generate a sequence.")30        return ""31 32    start = np.random.randint(0, len(corpus) - seq_len)33    sequence = corpus[start:start + seq_len]34    output = []35    total_duration = 0.036 37    while total_duration <= max_duration:38        x_input = np.zeros((1, seq_len, len(vocab)))39        for t, token in enumerate(sequence):40            if token in token_to_idx:41                x_input[0, t, token_to_idx[token]] = 1.042 43        next_token = predict_token(model, x_input, idx_to_token, diversity)44 45        # Filter out invalid tokens46        attempt = 047        while (next_token.startswith("R") or "," not in next_token) and attempt < max_tries:48            next_token = predict_token(model, x_input, idx_to_token, diversity)49            attempt += 150        if attempt >= max_tries:51            next_token = np.random.choice(grammar_data).split(" ")[0]52 53        try:54            duration_val = float(Fraction(next_token.split(",")[1]))55        except Exception as e:56            print(f"⚠️ Skipping token due to error: {e}")57            continue58 59        sequence = sequence[1:] + [next_token]60        output.append(next_token)61        total_duration += duration_val62 63    return " ".join(output)64 65# ---------------- Main Music Generator ---------------- #66 67def generate_music(data_path, output_path, num_epochs=128):68    SEQ_LEN = 2069    TEMP = 1.0  # Increased for more creative output70    BPM = 9071    MAX_DURATION = 32.0  # Increased length of generated music72 73    multi_chords, multi_grammar_sequences = get_multi_track_musical_data(data_path)74    multi_corpus, vocab, token_to_idx, idx_to_token = get_multi_track_corpus_data(multi_grammar_sequences)75 76    if len(vocab) == 0:77        raise ValueError("❌ No vocabulary tokens found. Check your input MIDI file.")78 79    print(f"🧠 Training model with vocab size: {len(vocab)} tokens")80    model = lstm.build_multi_instrument_model(list(multi_corpus.values()), token_to_idx, SEQ_LEN, num_epochs)81    print("✅ Model training completed.")82 83    generated_score = stream.Score()84    generated_score.insert(0.0, tempo.MetronomeMark(number=BPM))85 86    for track_name, chords in multi_chords.items():87        part = stream.Part()88        part.id = str(track_name)89        try:90            part.append(instrument.fromString(str(track_name)))91        except Exception:92            part.append(instrument.Piano())93 94        grammar_sequences = multi_grammar_sequences.get(track_name, [])95        corpus = multi_corpus.get(track_name, [])96        curr_offset = 0.097 98        if isinstance(chords, dict):99            items = sorted(chords.items())100        elif isinstance(chords, list):101            items = list(enumerate(chords))102        else:103            print(f"⚠️ Skipped track '{track_name}': chords format not supported.")104            continue105 106        for measure_idx, measure_chords in items:107            chord_voice = stream.Voice()108 109            if not isinstance(measure_chords, (list, tuple)):110                measure_chords = [measure_chords]111 112            for ch in measure_chords:113                chord_voice.insert(ch.offset % 4, ch)114 115            grammar = generate_abstract_sequence(116                model=model,117                corpus=corpus,118                grammar_data=grammar_sequences,119                vocab=vocab,120                token_to_idx=token_to_idx,121                idx_to_token=idx_to_token,122                seq_len=SEQ_LEN,123                diversity=TEMP,124                max_duration=MAX_DURATION125            )126 127            if not grammar:128                print(f"⚠️ Empty grammar for {track_name}, measure {measure_idx}.")129                continue130 131            grammar = prune_grammar(grammar.replace(" A", " C").replace(" X", " C"))132 133            try:134                notes = unparse_grammar(grammar, chord_voice)135                notes = prune_notes(notes)136                notes = clean_up_notes(notes)137 138                for n in notes:139                    part.insert(curr_offset + n.offset, n)140                for ch in chord_voice:141                    part.insert(curr_offset + ch.offset, ch)142            except Exception as e:143                print(f"⚠️ Skipped measure {measure_idx} due to grammar error: {e}")144 145            curr_offset += 4.0146 147        generated_score.append(part)148 149    mf = midi.translate.streamToMidiFile(generated_score)150 151    print(f"# Tracks in MIDI: {len(mf.tracks)}")152    if len(generated_score.parts) == 0 or all(len(p.flat.notes) == 0 for p in generated_score.parts):153        raise ValueError("❌ Generated score is empty. No notes were added.")154    155    mf.open(output_path, "wb")156    mf.write()157    mf.close()158    print(f"Generated MIDI saved to: {output_path}")159 160 161# ---------------- CLI Entrypoint ---------------- #162 163def main(argv):164    try:165        num_epochs = int(argv[1])166        midi_file = argv[2] if len(argv) > 2 else "midi/Demon_1.MID"167        output_file = argv[3] if len(argv) > 3 else f"midi/generated_{num_epochs}_epochs.mid"168    except Exception:169        print("⚠️ Usage: python generator.py <epochs> <input.mid> <output.mid>")170        num_epochs = 128171        midi_file = "midi/Demon_1.MID"172        output_file = f"midi/generated_default_{num_epochs}_epochs.mid"173 174    generate_music(midi_file, output_file, num_epochs)175 176if __name__ == "__main__":177    main(sys.argv)