Yongkang ZOU commited on
Commit
77ae978
·
1 Parent(s): f388e55

update musicxml viz

Browse files
Files changed (1) hide show
  1. app.py +38 -32
app.py CHANGED
@@ -7,7 +7,7 @@ from pathlib import Path
7
  from basic_pitch.inference import predict_and_save
8
  from basic_pitch import ICASSP_2022_MODEL_PATH
9
  from music21 import converter
10
- import shutil
11
 
12
  # 加载环境变量
13
  load_dotenv()
@@ -15,19 +15,15 @@ MUSICGEN_API_URL = os.getenv("MUSICGEN_API_URL")
15
 
16
  # 确保输出目录存在
17
  Path("output").mkdir(exist_ok=True)
18
- Path("static").mkdir(exist_ok=True)
19
 
20
- def wav_to_midi_and_musicxml(wav_path: str, timestamp: str) -> Path:
21
- """
22
- 将 wav 文件转换为 MIDI 和 MusicXML
23
- """
24
  output_dir = Path("output")
25
 
26
  # 清理旧 MIDI 文件
27
  for f in output_dir.glob("*_basic_pitch.mid"):
28
  f.unlink()
29
 
30
- # 生成 MIDI
31
  predict_and_save(
32
  audio_path_list=[wav_path],
33
  output_directory=str(output_dir),
@@ -48,32 +44,43 @@ def wav_to_midi_and_musicxml(wav_path: str, timestamp: str) -> Path:
48
  musicxml_path = output_dir / f"generated_{timestamp}.musicxml"
49
  score.write("musicxml", fp=musicxml_path)
50
 
51
- return musicxml_path
52
-
53
- def build_verovio_iframe(musicxml_path: Path) -> str:
54
- static_dir = Path("static")
55
- static_dir.mkdir(exist_ok=True)
56
- target = static_dir / musicxml_path.name
57
- shutil.copy(musicxml_path, target)
58
-
59
- return f"""
60
- <iframe
61
- src="/verovio.html?load=static/{target.name}"
62
- width="100%"
63
- height="500px"
64
- style="border: none;"
65
- ></iframe>
 
 
 
 
 
 
 
 
 
 
66
  """
 
67
 
68
 
69
  def generate_music_and_score(melody_file, prompt):
70
  if not MUSICGEN_API_URL:
71
- return None, "❌ 未配置 API URL,请检查 .env 文件"
72
 
73
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
74
  wav_out_path = f"output/generated_{timestamp}.wav"
75
 
76
- # 1. 调用 MusicGen 接口
77
  try:
78
  with open(melody_file, "rb") as f:
79
  files = {"melody": ("hum.wav", f, "audio/wav")}
@@ -85,21 +92,20 @@ def generate_music_and_score(melody_file, prompt):
85
  if response.status_code != 200:
86
  return None, f"❌ API 错误 {response.status_code}: {response.text}"
87
 
88
- # 保存生成的 wav
89
  with open(wav_out_path, "wb") as out:
90
  out.write(response.content)
91
 
92
- # 2. wav → midi → musicxml
93
  try:
94
- musicxml_path = wav_to_midi_and_musicxml(wav_out_path, timestamp)
95
  except Exception as e:
96
  return wav_out_path, f"⚠️ 音乐生成成功,但乐谱转换失败:{e}"
97
 
98
- # 3. 构造 iframe
99
- iframe_html = build_verovio_iframe(musicxml_path)
100
- return wav_out_path, iframe_html
 
101
 
102
- # Gradio 接口
103
  demo = gr.Interface(
104
  fn=generate_music_and_score,
105
  inputs=[
@@ -111,7 +117,7 @@ demo = gr.Interface(
111
  gr.HTML(label="🎼 乐谱预览 (Verovio)")
112
  ],
113
  title="🎵 MusicGen + 乐谱生成器",
114
- description="上传一段哼唱 (.wav) 和文字描述,自动生成音乐,并以 Verovio 可视化乐谱。"
115
  )
116
 
117
  if __name__ == "__main__":
 
7
  from basic_pitch.inference import predict_and_save
8
  from basic_pitch import ICASSP_2022_MODEL_PATH
9
  from music21 import converter
10
+ import base64
11
 
12
  # 加载环境变量
13
  load_dotenv()
 
15
 
16
  # 确保输出目录存在
17
  Path("output").mkdir(exist_ok=True)
 
18
 
19
+ def wav_to_musicxml(wav_path: str, timestamp: str) -> str:
 
 
 
20
  output_dir = Path("output")
21
 
22
  # 清理旧 MIDI 文件
23
  for f in output_dir.glob("*_basic_pitch.mid"):
24
  f.unlink()
25
 
26
+ # 使用 basic-pitch 生成 MIDI
27
  predict_and_save(
28
  audio_path_list=[wav_path],
29
  output_directory=str(output_dir),
 
44
  musicxml_path = output_dir / f"generated_{timestamp}.musicxml"
45
  score.write("musicxml", fp=musicxml_path)
46
 
47
+ with open(musicxml_path, "r", encoding="utf-8") as f:
48
+ musicxml_content = f.read()
49
+
50
+ return musicxml_content
51
+
52
+
53
+ def render_musicxml_html(musicxml_content: str) -> str:
54
+ musicxml_b64 = base64.b64encode(musicxml_content.encode("utf-8")).decode("utf-8")
55
+
56
+ html = f"""
57
+ <div id="verovio-container"></div>
58
+ <script type="module">
59
+ import 'https://www.verovio.org/javascript/app/verovio-app.js';
60
+
61
+ const container = document.getElementById('verovio-container');
62
+ const app = new Verovio.App(container, {{
63
+ defaultView: 'responsive',
64
+ defaultZoom: 3,
65
+ enableResponsive: true,
66
+ enableDocument: false
67
+ }});
68
+
69
+ const musicxml = atob("{musicxml_b64}");
70
+ app.loadData(musicxml);
71
+ </script>
72
  """
73
+ return html
74
 
75
 
76
  def generate_music_and_score(melody_file, prompt):
77
  if not MUSICGEN_API_URL:
78
+ return None, "❌ 未配置 MUSICGEN_API_URL,请检查 .env 文件"
79
 
80
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
81
  wav_out_path = f"output/generated_{timestamp}.wav"
82
 
83
+ # Step 1: 调用 MusicGen API
84
  try:
85
  with open(melody_file, "rb") as f:
86
  files = {"melody": ("hum.wav", f, "audio/wav")}
 
92
  if response.status_code != 200:
93
  return None, f"❌ API 错误 {response.status_code}: {response.text}"
94
 
 
95
  with open(wav_out_path, "wb") as out:
96
  out.write(response.content)
97
 
98
+ # Step 2: MusicXML
99
  try:
100
+ musicxml_content = wav_to_musicxml(wav_out_path, timestamp)
101
  except Exception as e:
102
  return wav_out_path, f"⚠️ 音乐生成成功,但乐谱转换失败:{e}"
103
 
104
+ # Step 3: 生成 HTML
105
+ verovio_html = render_musicxml_html(musicxml_content)
106
+ return wav_out_path, verovio_html
107
+
108
 
 
109
  demo = gr.Interface(
110
  fn=generate_music_and_score,
111
  inputs=[
 
117
  gr.HTML(label="🎼 乐谱预览 (Verovio)")
118
  ],
119
  title="🎵 MusicGen + 乐谱生成器",
120
+ description="上传哼唱音频和描述,生成音乐并以 Verovio 可视化乐谱(纯 JS)"
121
  )
122
 
123
  if __name__ == "__main__":