Spaces:
Runtime error
Runtime error
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)
|