| import numpy as np |
| import librosa |
| import soundfile as sf |
| from music21 import chord, note, stream, tempo, instrument |
| import random |
| import os |
|
|
| def generate_accompaniment(emotion, sentiment_score, tempo=100, output_path="accompaniment.wav"): |
| """ |
| Generate musical accompaniment based on emotion and sentiment |
| |
| Args: |
| emotion (str): Dominant emotion (Happy, Sad, Angry, Fear, Surprise) |
| sentiment_score (float): Sentiment score from -1 (negative) to 1 (positive) |
| tempo (int): Tempo in BPM |
| output_path (str): Path to save the audio file |
| |
| Returns: |
| str: Path to the generated audio file |
| """ |
| |
| if emotion == "Happy" or sentiment_score > 0.3: |
| |
| scales = [ |
| ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'], |
| ['G3', 'A3', 'B3', 'C4', 'D4', 'E4', 'F#4', 'G4'] |
| ] |
| chord_progressions = [ |
| [['C4', 'E4', 'G4'], ['G3', 'B3', 'D4'], ['A3', 'C4', 'E4'], ['F3', 'A3', 'C4']], |
| [['C4', 'E4', 'G4'], ['F3', 'A3', 'C4'], ['G3', 'B3', 'D4'], ['C4', 'E4', 'G4']] |
| ] |
| |
| elif emotion == "Sad" or sentiment_score < -0.3: |
| |
| scales = [ |
| ['A3', 'B3', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4'], |
| ['D3', 'E3', 'F3', 'G3', 'A3', 'Bb3', 'C4', 'D4'] |
| ] |
| chord_progressions = [ |
| [['A3', 'C4', 'E4'], ['F3', 'A3', 'C4'], ['G3', 'B3', 'D4'], ['E3', 'G3', 'B3']], |
| [['A3', 'C4', 'E4'], ['D3', 'F3', 'A3'], ['F3', 'A3', 'C4'], ['E3', 'G3', 'B3']] |
| ] |
| |
| elif emotion == "Angry": |
| |
| scales = [ |
| ['E3', 'F3', 'G#3', 'A3', 'B3', 'C4', 'D#4', 'E4'], |
| ['B3', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4'] |
| ] |
| chord_progressions = [ |
| [['E3', 'G#3', 'B3'], ['A3', 'C4', 'E4'], ['F3', 'A3', 'C4'], ['B3', 'D4', 'F4']], |
| [['E3', 'G#3', 'B3'], ['D3', 'F3', 'A3'], ['C3', 'E3', 'G3'], ['B2', 'D3', 'F3']] |
| ] |
| |
| else: |
| |
| scales = [ |
| ['D3', 'E3', 'F3', 'G3', 'A3', 'B3', 'C4', 'D4'], |
| ['E3', 'F#3', 'G3', 'A3', 'B3', 'C#4', 'D4', 'E4'] |
| ] |
| chord_progressions = [ |
| [['D3', 'F3', 'A3'], ['C3', 'E3', 'G3'], ['Bb2', 'D3', 'F3'], ['A2', 'C3', 'E3']], |
| [['E3', 'G3', 'B3'], ['A3', 'C4', 'E4'], ['D3', 'F#3', 'A3'], ['G3', 'B3', 'D4']] |
| ] |
| |
| |
| scale = random.choice(scales) |
| progression = random.choice(chord_progressions) |
| |
| |
| s = stream.Stream() |
| |
| |
| t = tempo.MetronomeMark(number=tempo) |
| s.append(t) |
| |
| |
| if emotion == "Happy": |
| inst = instrument.Piano() |
| elif emotion == "Sad": |
| inst = instrument.StringInstrument() |
| elif emotion == "Angry": |
| inst = instrument.ElectricGuitar() |
| else: |
| inst = instrument.Harp() |
| |
| s.append(inst) |
| |
| |
| for i in range(4): |
| for chord_notes in progression: |
| |
| c = chord.Chord(chord_notes) |
| c.quarterLength = 1.0 |
| s.append(c) |
| |
| |
| melody_part = stream.Part() |
| melody_part.append(instrument.Flute()) |
| |
| |
| for i in range(16): |
| if random.random() < 0.2: |
| n = note.Rest() |
| else: |
| |
| if emotion == "Happy": |
| |
| pitch = scale[random.randint(0, len(scale)-1)] |
| elif emotion == "Sad": |
| |
| idx = min(max(0, int(np.random.normal(3, 1))), len(scale)-1) |
| pitch = scale[idx] |
| else: |
| pitch = random.choice(scale) |
| |
| n = note.Note(pitch) |
| |
| |
| if emotion == "Angry": |
| n.volume.velocity = 100 |
| elif emotion == "Sad": |
| n.volume.velocity = 60 |
| |
| |
| if random.random() < 0.3: |
| n.quarterLength = 2.0 |
| else: |
| n.quarterLength = 1.0 |
| |
| melody_part.append(n) |
| |
| s.append(melody_part) |
| |
| |
| midi_path = "temp_midi.mid" |
| s.write('midi', fp=midi_path) |
| |
| |
| try: |
| from midi2audio import FluidSynth |
| fs = FluidSynth() |
| fs.midi_to_audio(midi_path, output_path) |
| |
| print(f"Musical accompaniment saved to {output_path}") |
| |
| |
| if os.path.exists(midi_path): |
| os.remove(midi_path) |
| |
| return output_path |
| |
| except ImportError: |
| print("FluidSynth not available. Creating synthetic audio instead.") |
| |
| return _generate_synthetic_audio(emotion, sentiment_score, tempo, output_path) |
|
|
| def _generate_synthetic_audio(emotion, sentiment_score, tempo, output_path): |
| """Generate synthetic audio using numpy when FluidSynth is not available""" |
| |
| spb = 60.0 / tempo |
| |
| |
| sr = 22050 |
| |
| |
| duration = spb * 16 |
| |
| |
| total_samples = int(sr * duration) |
| |
| |
| if emotion == "Happy" or sentiment_score > 0.3: |
| |
| freqs = [261.63, 329.63, 392.00] |
| elif emotion == "Sad" or sentiment_score < -0.3: |
| |
| freqs = [220.00, 261.63, 329.63] |
| elif emotion == "Angry": |
| |
| freqs = [246.94, 293.66, 349.23] |
| else: |
| |
| freqs = [293.66, 392.00, 440.00] |
| |
| |
| audio = np.zeros(total_samples) |
| |
| |
| for i in range(4): |
| chord_start = int(i * 4 * spb * sr) |
| chord_end = int((i + 1) * 4 * spb * sr) |
| |
| |
| if i == 0: |
| freq_shift = 1.0 |
| elif i == 1: |
| freq_shift = 5.0/4.0 |
| elif i == 2: |
| freq_shift = 6.0/5.0 |
| else: |
| freq_shift = 4.0/3.0 |
| |
| |
| chord_audio = np.zeros(chord_end - chord_start) |
| |
| for freq in freqs: |
| |
| t = np.linspace(0, (chord_end - chord_start) / sr, chord_end - chord_start, False) |
| |
| |
| adjusted_freq = freq * freq_shift |
| |
| |
| note = 0.2 * np.sin(2 * np.pi * adjusted_freq * t) |
| |
| |
| envelope = np.ones_like(t) |
| attack = int(0.02 * len(t)) |
| decay = int(0.1 * len(t)) |
| release = int(0.2 * len(t)) |
| |
| envelope[:attack] = np.linspace(0, 1, attack) |
| envelope[-release:] = np.linspace(1, 0, release) |
| |
| |
| note = note * envelope |
| |
| |
| chord_audio += note |
| |
| |
| chord_audio = chord_audio / np.max(np.abs(chord_audio)) |
| |
| |
| audio[chord_start:chord_end] += chord_audio |
| |
| |
| melody_audio = np.zeros_like(audio) |
| |
| |
| note_duration = int(0.5 * spb * sr) |
| |
| if emotion == "Happy": |
| notes_per_measure = 4 |
| elif emotion == "Sad": |
| notes_per_measure = 2 |
| else: |
| notes_per_measure = 3 |
| |
| for measure in range(4): |
| for note_idx in range(notes_per_measure): |
| |
| start = measure * 4 * spb * sr + note_idx * (4 * spb * sr / notes_per_measure) |
| start = int(start) |
| |
| |
| end = start + int(0.9 * (4 * spb * sr / notes_per_measure)) |
| |
| if end > len(melody_audio): |
| end = len(melody_audio) |
| |
| |
| if emotion == "Happy": |
| freq = random.choice([392.00, 440.00, 493.88, 523.25]) |
| elif emotion == "Sad": |
| freq = random.choice([329.63, 349.23, 392.00, 440.00]) |
| else: |
| freq = random.choice([293.66, 329.63, 349.23, 392.00]) |
| |
| |
| t = np.linspace(0, (end - start) / sr, end - start, False) |
| |
| |
| note_audio = 0.3 * np.sin(2 * np.pi * freq * t) |
| note_audio += 0.15 * np.sin(2 * np.pi * freq * 2 * t) |
| note_audio += 0.05 * np.sin(2 * np.pi * freq * 3 * t) |
| |
| |
| envelope = np.ones_like(t) |
| attack = int(0.1 * len(t)) |
| release = int(0.3 * len(t)) |
| |
| envelope[:attack] = np.linspace(0, 1, attack) |
| envelope[-release:] = np.linspace(1, 0, release) |
| |
| note_audio = note_audio * envelope |
| |
| |
| melody_audio[start:end] += note_audio |
| |
| |
| melody_audio = 0.6 * melody_audio / np.max(np.abs(melody_audio)) |
| |
| |
| final_audio = audio + melody_audio |
| |
| |
| final_audio = 0.9 * final_audio / np.max(np.abs(final_audio)) |
| |
| |
| sf.write(output_path, final_audio, sr) |
| |
| print(f"Synthetic musical accompaniment saved to {output_path}") |
| |
| return output_path |