Spaces:
Sleeping
Sleeping
xylo player
Browse files- app.py +91 -0
- requirements.txt +2 -0
- synthetizer.py +80 -0
app.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from interactive_pipe import interactive_pipeline, interactive, Control, Image
|
| 2 |
+
from synthetizer import NOTE_FREQUENCIES, get_note
|
| 3 |
+
from interactive_pipe.data_objects.audio import Audio
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from time import sleep
|
| 6 |
+
import argparse
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def select_note(note="C4", context=None):
|
| 12 |
+
context["note"] = note
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def create_note(context={}):
|
| 16 |
+
note = context.get("note", "C4")
|
| 17 |
+
audio_signal = get_note(note)
|
| 18 |
+
return audio_signal
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def play_note(audio_signal: np.ndarray, context={}):
|
| 22 |
+
note = context.get("note", "C4")
|
| 23 |
+
file_name = Path(f"__{note}.wav")
|
| 24 |
+
if not file_name.exists():
|
| 25 |
+
Audio.save_audio(audio_signal, str(file_name), 44100)
|
| 26 |
+
while not file_name.exists():
|
| 27 |
+
sleep(0.01)
|
| 28 |
+
print("waiting for file")
|
| 29 |
+
assert file_name.exists()
|
| 30 |
+
context["__set_audio"](file_name)
|
| 31 |
+
context["__play"]()
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def display_color(context={}):
|
| 35 |
+
note = context.get("note", "C4")
|
| 36 |
+
return get_color(note, size=(256, 256))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_color(note, size=(256, 256)):
|
| 40 |
+
colors = {
|
| 41 |
+
"red": (1.0, 0.0, 0.0),
|
| 42 |
+
"orange": (1.0, 0.65, 0.0),
|
| 43 |
+
"yellow": (1.0, 1.0, 0.0),
|
| 44 |
+
"green": (0.0, 0.5, 0.0),
|
| 45 |
+
"blue": (0.0, 0.0, 1.0),
|
| 46 |
+
"dark blue": (0.0, 0.0, 0.55),
|
| 47 |
+
"purple": (0.5, 0.0, 0.5),
|
| 48 |
+
"pink": (1.0, 0.75, 0.8)
|
| 49 |
+
}
|
| 50 |
+
notes_translation = ["DO", "RE", "MI", "FA", "SOL", "LA", "SI", "DO"]
|
| 51 |
+
index = list(NOTE_FREQUENCIES.keys()).index(note)
|
| 52 |
+
color = colors.get(list(colors.keys())[index], [0., 0., 0.])
|
| 53 |
+
img = np.ones((size[1], size[0], 3)) * np.array(color)[None, None, :]
|
| 54 |
+
text = notes_translation[index]
|
| 55 |
+
font_scale = 4
|
| 56 |
+
thickness = 2
|
| 57 |
+
text_size = cv2.getTextSize(
|
| 58 |
+
text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness)[0]
|
| 59 |
+
text_x = (size[0] - text_size[0]) // 2
|
| 60 |
+
text_y = (size[1] + text_size[1]) // 2
|
| 61 |
+
cv2.putText(
|
| 62 |
+
img,
|
| 63 |
+
text,
|
| 64 |
+
(text_x, text_y),
|
| 65 |
+
cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 0), thickness
|
| 66 |
+
)
|
| 67 |
+
return img
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def xylo_player():
|
| 71 |
+
select_note()
|
| 72 |
+
audio = create_note()
|
| 73 |
+
play_note(audio)
|
| 74 |
+
out_image = display_color()
|
| 75 |
+
return out_image
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
if __name__ == '__main__':
|
| 79 |
+
parser = argparse.ArgumentParser(description='Xylophone synthesizer')
|
| 80 |
+
parser.add_argument('-b', '--backend', type=str,
|
| 81 |
+
default='gradio', choices=['gradio', 'qt'])
|
| 82 |
+
args = parser.parse_args()
|
| 83 |
+
all_notes = list(NOTE_FREQUENCIES.keys())
|
| 84 |
+
icon_list = [Path(f"__{note}.jpg") for note in all_notes]
|
| 85 |
+
for note, icon in zip(all_notes, icon_list):
|
| 86 |
+
img = get_color(note, size=(512, 512))
|
| 87 |
+
Image.save_image(img, icon)
|
| 88 |
+
interactive(note=Control("C4", all_notes, icons=icon_list))(select_note)
|
| 89 |
+
|
| 90 |
+
interactive_pipeline(gui=args.backend, cache=False,
|
| 91 |
+
audio=True)(xylo_player)()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactive-pipe
|
| 2 |
+
wavio
|
synthetizer.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
NOTE_FREQUENCIES = {
|
| 3 |
+
'C4': 261.63, # Middle C
|
| 4 |
+
'D4': 293.66,
|
| 5 |
+
'E4': 329.63,
|
| 6 |
+
'F4': 349.23,
|
| 7 |
+
'G4': 392.00,
|
| 8 |
+
'A4': 440.00,
|
| 9 |
+
'B4': 493.88,
|
| 10 |
+
'C5': 523.25
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
# from pydub import AudioSegment
|
| 14 |
+
# import simpleaudio as sa
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def generate_sine_wave(frequency, duration, sample_rate=44100, amplitude=0.5):
|
| 18 |
+
"""
|
| 19 |
+
Generate a sine wave for a given frequency, duration, and sample rate.
|
| 20 |
+
"""
|
| 21 |
+
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
|
| 22 |
+
wave = amplitude * np.sin(2 * np.pi * frequency * t)
|
| 23 |
+
return wave
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def apply_decay(wave, decay_factor=0.0001):
|
| 27 |
+
"""
|
| 28 |
+
Apply an exponential decay to the waveform to simulate the damping effect.
|
| 29 |
+
"""
|
| 30 |
+
decay = np.exp(-decay_factor * np.arange(len(wave)))
|
| 31 |
+
return wave * decay
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def create_xylophone_note(frequency, duration=0.5, sample_rate=44100):
|
| 35 |
+
"""
|
| 36 |
+
Create a synthesized xylophone note using sine waves and damping.
|
| 37 |
+
"""
|
| 38 |
+
# Generate the fundamental frequency
|
| 39 |
+
wave = generate_sine_wave(frequency, duration, sample_rate)
|
| 40 |
+
|
| 41 |
+
# Add overtones (harmonics) to mimic the xylophone's metallic timbre
|
| 42 |
+
overtone1 = generate_sine_wave(
|
| 43 |
+
frequency * 2.5, duration, sample_rate, amplitude=0.3)
|
| 44 |
+
overtone2 = generate_sine_wave(
|
| 45 |
+
frequency * 4.0, duration, sample_rate, amplitude=0.2)
|
| 46 |
+
|
| 47 |
+
# Combine the fundamental frequency and overtones
|
| 48 |
+
combined_wave = wave + overtone1 + overtone2
|
| 49 |
+
|
| 50 |
+
# Apply decay to simulate the note fading out
|
| 51 |
+
combined_wave = apply_decay(combined_wave)
|
| 52 |
+
|
| 53 |
+
# Normalize to 16-bit range and convert to audio segment
|
| 54 |
+
# audio_data = (combined_wave * 32767).astype(np.int16)
|
| 55 |
+
audio_data = combined_wave
|
| 56 |
+
return audio_data
|
| 57 |
+
# audio_segment = AudioSegment(audio_data.tobytes(
|
| 58 |
+
# ), frame_rate=sample_rate, sample_width=2, channels=1)
|
| 59 |
+
|
| 60 |
+
# return audio_segment
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# def play_note(note):
|
| 64 |
+
# """
|
| 65 |
+
# Play a given AudioSegment note using simpleaudio.
|
| 66 |
+
# """
|
| 67 |
+
# play_obj = sa.play_buffer(
|
| 68 |
+
# note.raw_data, num_channels=1, bytes_per_sample=2, sample_rate=note.frame_rate)
|
| 69 |
+
# play_obj.wait_done()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def get_note(note: str) -> np.ndarray:
|
| 73 |
+
# Example usage to create and play a sequence of xylophone notes
|
| 74 |
+
|
| 75 |
+
return create_xylophone_note(NOTE_FREQUENCIES.get(note, 'C4'))
|
| 76 |
+
# Generate and play each note
|
| 77 |
+
# for note_name, frequency in note_frequencies.items():
|
| 78 |
+
# print(f"Playing {note_name}")
|
| 79 |
+
# note = create_xylophone_note(frequency)
|
| 80 |
+
# play_note(note)
|