Richard Zhu commited on
Commit
d4afb7f
Β·
1 Parent(s): bdd05b2

initial commit

Browse files
chord_harp/.DS_Store ADDED
Binary file (8.2 kB). View file
 
chord_harp/ChordEstimation ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit bdd05b2236130f01ca2875a30297f3f368fbf470
chord_harp/ChordRecognitionMIDITrainedExtractor ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 19b81f694dae7782111b1be0e39e66f42fa1615f
chord_harp/Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.8-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ COPY packages.txt /app/packages.txt
7
+ RUN apt-get update && \
8
+ xargs apt-get install -y --no-install-recommends < /app/packages.txt && \
9
+ rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install Python dependencies
12
+ COPY requirements.txt /app/requirements.txt
13
+ ENV PIP_NO_BUILD_ISOLATION=1
14
+ RUN pip install --no-cache-dir -U pip wheel Cython
15
+ RUN pip install --no-cache-dir setuptools==59.8.0
16
+ RUN pip install --no-cache-dir numpy==1.23.5
17
+ RUN pip install --no-cache-dir -r /app/requirements.txt
18
+ RUN pip install --no-cache-dir --no-build-isolation madmom
19
+
20
+ # Apply madmom patch
21
+ COPY patch_madmom.py /app/patch_madmom.py
22
+ RUN python /app/patch_madmom.py
23
+ RUN python -c "import madmom; print('madmom import OK')"
24
+
25
+ # Copy the rest of the app
26
+ COPY . /app
27
+
28
+ # Set working directory to chord recognition repo so model files are found
29
+ WORKDIR /app/ChordRecognitionMIDITrainedExtractor
30
+
31
+ ENV PORT=7860
32
+ EXPOSE 7860
33
+
34
+ CMD ["python", "/app/app.py"]
chord_harp/README.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Automatic Chord Recognition
3
+ emoji: 🎡
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Automatic Chord Recognition (PyHARP)
11
+
12
+ This is a [PyHARP](https://github.com/TEAMuP-dev/pyharp) app that estimates chord progressions from audio using the model proposed in:
13
+
14
+ > Wu, Y., & Li, W. (2019). Automatic Audio Chord Recognition With MIDI-Trained Deep Feature and BLSTM-CRF Sequence Decoding Model. *IEEE/ACM Transactions on Audio, Speech, and Language Processing*, 27(2), 355–366.
15
+
16
+ ## How it works
17
+ 1. Upload an audio file in HARP
18
+ 2. The model extracts a Harmonic CQT spectrogram
19
+ 3. A CNN extracts pitch class features (trained on MIDI data)
20
+ 4. A BLSTM-CRF decodes the chord label sequence
21
+ 5. Time-stamped chord labels are returned and displayed in HARP
chord_harp/app.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import sys
5
+ import os
6
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "ChordRecognitionMIDITrainedExtractor"))
7
+
8
+ import numpy as np
9
+ import networks as N
10
+ from librosa.core import cqt, load, note_to_hz
11
+ import const as C
12
+ import utils as U
13
+
14
+ from pyharp import ModelCard, build_endpoint, LabelList, AudioLabel, OutputLabel
15
+ import gradio as gr
16
+
17
+ # ── Load models once at startup ──────────────────────────────────────────────
18
+ cnn_feat_extractor = N.FullCNNFeatExtractor()
19
+ cnn_feat_extractor.load(C.DEFAULT_CONVNETFILE)
20
+
21
+ decoder = N.NBLSTMCRF()
22
+ decoder.load("nblstm_crf.model")
23
+
24
+ # ── Model card ───────────────────────────────────────────────────────────────
25
+ model_card = ModelCard(
26
+ name="Automatic Chord Recognition",
27
+ description="Estimates chord progressions from audio using a CNN feature extractor trained on MIDI data and a BLSTM-CRF sequence decoder. Outputs time-stamped chord labels.",
28
+ author="Wu & Li (2019), wrapped by PyHARP",
29
+ tags=["chord recognition", "harmony", "MIR"],
30
+ )
31
+
32
+ # ── Processing function ───────────────────────────────────────────────────────
33
+ def process_fn(input_audio_path: str) -> LabelList:
34
+ # Load audio
35
+ y, sr = load(input_audio_path, sr=C.SR)
36
+
37
+ # Extract Harmonic-CQT
38
+ fmin = note_to_hz("C1")
39
+ hcqt = np.stack([
40
+ np.abs(cqt(
41
+ y, sr=C.SR, hop_length=C.H, n_bins=C.BIN_CNT,
42
+ bins_per_octave=C.OCT_BIN, fmin=fmin * (h + 1),
43
+ filter_scale=2, tuning=None
44
+ )).T.astype(np.float32)
45
+ for h in range(C.CQT_H)
46
+ ])
47
+
48
+ # Extract deep feature
49
+ feat = cnn_feat_extractor.GetFeature(U.PreprocessSpec(hcqt)).data
50
+
51
+ # Decode chord label sequence
52
+ labels = decoder.argmax(feat)
53
+
54
+ # Build LabelList for HARP
55
+ output_labels = LabelList()
56
+ cur_label = labels[0]
57
+ st = 0
58
+
59
+ for i in range(labels.size):
60
+ if labels[i] != cur_label:
61
+ ed = i
62
+ feat_seg = feat[st:ed, :]
63
+ chord_sign = U.voc.ChordSignature7thbass(cur_label, feat_seg, sevenths=True, inv=True)
64
+ start_sec = float(st * C.H) / C.SR
65
+ end_sec = float(ed * C.H) / C.SR
66
+
67
+ if chord_sign != "N":
68
+ output_labels.labels.append(
69
+ AudioLabel(
70
+ t=start_sec,
71
+ label=chord_sign,
72
+ duration=end_sec - start_sec,
73
+ description=f"Chord: {chord_sign} ({start_sec:.2f}s - {end_sec:.2f}s)",
74
+ )
75
+ )
76
+ cur_label = labels[i]
77
+ st = i
78
+
79
+ # Handle last segment
80
+ feat_seg = feat[st:labels.size, :]
81
+ chord_sign = U.voc.ChordSignature7thbass(cur_label, feat_seg)
82
+ start_sec = float(st * C.H) / C.SR
83
+ end_sec = float(labels.size * C.H) / C.SR
84
+ if chord_sign != "N":
85
+ output_labels.labels.append(
86
+ AudioLabel(
87
+ t=start_sec,
88
+ label=chord_sign,
89
+ duration=end_sec - start_sec,
90
+ description=f"Chord: {chord_sign} ({start_sec:.2f}s - {end_sec:.2f}s)",
91
+ )
92
+ )
93
+
94
+ return output_labels
95
+
96
+
97
+ # ── Gradio endpoint ───────────────────────────────────────────────────────────
98
+ with gr.Blocks() as demo:
99
+ input_components = [
100
+ gr.Audio(type="filepath", label="Input Audio").harp_required(True),
101
+ ]
102
+
103
+ output_components = [
104
+ gr.JSON(label="Output Labels"),
105
+ ]
106
+
107
+ app = build_endpoint(
108
+ model_card=model_card,
109
+ input_components=input_components,
110
+ output_components=output_components,
111
+ process_fn=process_fn,
112
+ )
113
+
114
+ demo.queue().launch(share=True, show_error=False, pwa=True)
chord_harp/packages.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gcc
2
+ g++
3
+ libsndfile1
4
+ ffmpeg
5
+ git
chord_harp/patch_madmom.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Patch madmom to fix deprecated sklearn and other compatibility issues.
4
+ """
5
+ import os
6
+ import site
7
+
8
+ def patch_file(filepath, old, new):
9
+ if not os.path.exists(filepath):
10
+ print(f"Skipping (not found): {filepath}")
11
+ return
12
+ with open(filepath, "r") as f:
13
+ content = f.read()
14
+ if old in content:
15
+ content = content.replace(old, new)
16
+ with open(filepath, "w") as f:
17
+ f.write(content)
18
+ print(f"Patched: {filepath}")
19
+ else:
20
+ print(f"No patch needed: {filepath}")
21
+
22
+ # Find madmom install location
23
+ site_packages = site.getsitepackages()[0]
24
+ madmom_base = os.path.join(site_packages, "madmom")
25
+
26
+ # Fix sklearn.externals.joblib import
27
+ patch_file(
28
+ os.path.join(madmom_base, "ml", "hmm.py"),
29
+ "from sklearn.externals import joblib",
30
+ "import joblib"
31
+ )
32
+
33
+ print("Madmom patching complete.")
chord_harp/requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==5.28.0
2
+ git+https://github.com/TEAMuP-dev/pyharp.git@v0.3.0
3
+ chainer==7.8.1
4
+ librosa
5
+ scipy
6
+ mir_eval
7
+ joblib