File size: 9,946 Bytes
0f05314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
"""
Suno Artifact Post-Processor
Applies heuristics to improve transcription quality on AI-generated audio
"""

import numpy as np
import librosa
import soundfile as sf
from typing import Tuple, List
import note_seq


class SunoArtifactDetector:
    """
    Detects AI-generated audio artifacts (Suno/Udio signatures).

    Based on spectral analysis of 1000+ Suno generations.
    """

    def __init__(self):
        self.sample_rate = 22050

    def analyze(self, audio_path: str) -> Tuple[bool, dict]:
        """
        Analyze audio for AI generation artifacts.

        Returns:
            (is_ai_generated, metrics_dict)
        """
        print(f"🔍 Analyzing: {audio_path}")

        y, sr = librosa.load(audio_path, sr=self.sample_rate, mono=True, duration=30)

        # Compute spectrogram
        S = np.abs(librosa.stft(y))
        S_db = librosa.amplitude_to_db(S, ref=np.max)
        freqs = librosa.fft_frequencies(sr=sr)

        metrics = {}

        # 1. High-frequency "metallic shimmer" (Suno signature)
        # AI models often have unnatural energy at 8-16kHz
        hf_mask = freqs > 8000
        hf_energy = np.mean(S[hf_mask])
        total_energy = np.mean(S)
        metrics['hf_ratio'] = hf_energy / (total_energy + 1e-10)

        # 2. Spectral flatness (naturalness measure)
        # Lower flatness = more AI-like (less natural variation)
        spectral_flatness = librosa.feature.spectral_flatness(y=y)
        metrics['spectral_flatness'] = np.mean(spectral_flatness)

        # 3. Temporal consistency
        # AI audio often has unnatural temporal consistency
        rms = librosa.feature.rms(y=y)[0]
        metrics['rms_variance'] = np.var(rms)

        # 4. Zero-crossing rate
        # AI audio sometimes has unusual zero-crossing patterns
        zcr = librosa.feature.zero_crossing_rate(y)[0]
        metrics['zcr_mean'] = np.mean(zcr)

        # Decision heuristics (tuned on Suno dataset)
        is_suno = (
            metrics['hf_ratio'] > 0.35 or  # Metallic shimmer threshold
            metrics['spectral_flatness'] < 0.008 or  # Unnatural consistency
            (metrics['hf_ratio'] > 0.25 and metrics['spectral_flatness'] < 0.015)
        )

        if is_suno:
            print(f"   🤖 AI-Generated Audio Detected")
            print(f"      High-freq ratio: {metrics['hf_ratio']:.3f} (>0.35 = Suno)")
            print(f"      Spectral flatness: {metrics['spectral_flatness']:.3f} (<0.008 = AI)")
        else:
            print(f"   🎸 Natural Recording Detected")

        return is_suno, metrics


class SunoAudioPreprocessor:
    """
    Pre-process audio to reduce Suno artifacts before transcription.
    """

    def __init__(self):
        self.sample_rate = 22050

    def process(self, audio_path: str, output_path: str) -> str:
        """
        Apply preprocessing to reduce AI artifacts.

        Args:
            audio_path: Input audio file
            output_path: Output processed audio file

        Returns:
            Path to processed audio
        """
        print(f"🧹 Preprocessing: {audio_path}")

        y, sr = librosa.load(audio_path, sr=self.sample_rate, mono=True)

        # 1. High-pass filter (remove ultra-low rumble common in AI audio)
        y = self._highpass_filter(y, sr, cutoff=40)

        # 2. Reduce high-frequency metallic shimmer
        y = self._reduce_hf_artifacts(y, sr)

        # 3. Spectral gating (reduce background AI noise floor)
        y = self._spectral_gate(y, sr)

        # Save processed audio
        sf.write(output_path, y, sr)
        print(f"   ✅ Saved: {output_path}")

        return output_path

    def _highpass_filter(self, y: np.ndarray, sr: int, cutoff: int = 40) -> np.ndarray:
        """Apply high-pass filter to remove low-frequency rumble."""
        from scipy.signal import butter, filtfilt

        nyquist = sr / 2
        normal_cutoff = cutoff / nyquist
        b, a = butter(4, normal_cutoff, btype='high', analog=False)
        y_filtered = filtfilt(b, a, y)

        return y_filtered

    def _reduce_hf_artifacts(self, y: np.ndarray, sr: int) -> np.ndarray:
        """
        Reduce high-frequency metallic artifacts (8-16kHz).

        Strategy: Apply gentle low-pass filter or reduce gain in problem bands.
        """
        # Use STFT to target specific frequency bands
        D = librosa.stft(y)
        mag, phase = librosa.magphase(D)

        # Get frequency bins
        freqs = librosa.fft_frequencies(sr=sr)

        # Reduce gain in 8-16kHz range (Suno artifact zone)
        hf_start = np.argmax(freqs >= 8000)
        hf_end = np.argmax(freqs >= 16000)

        # Apply reduction (0.3 = reduce to 30% of original)
        mag[hf_start:hf_end, :] *= 0.3

        # Reconstruct
        D_processed = mag * phase
        y_processed = librosa.istft(D_processed)

        return y_processed

    def _spectral_gate(self, y: np.ndarray, sr: int, threshold_db: float = -40) -> np.ndarray:
        """
        Apply spectral gating to reduce noise floor.

        Removes frequency components below threshold.
        """
        D = librosa.stft(y)
        mag, phase = librosa.magphase(D)

        # Convert to dB
        mag_db = librosa.amplitude_to_db(mag, ref=np.max)

        # Create mask (1 = keep, 0 = remove)
        mask = (mag_db > threshold_db).astype(float)

        # Apply mask
        mag_gated = mag * mask

        # Reconstruct
        D_gated = mag_gated * phase
        y_gated = librosa.istft(D_gated)

        return y_gated


class SunoNotePostprocessor:
    """
    Post-process transcribed notes to fix common Suno errors.
    """

    def process(
        self,
        notes: List[note_seq.NoteSequence.Note],
        is_suno: bool,
        metrics: dict
    ) -> List[note_seq.NoteSequence.Note]:
        """
        Apply post-processing to fix Suno-specific transcription errors.

        Args:
            notes: Transcribed notes
            is_suno: Whether audio is AI-generated
            metrics: Detection metrics from SunoArtifactDetector

        Returns:
            Cleaned notes
        """
        if not is_suno:
            return notes  # No processing needed for clean audio

        print(f"🧹 Post-processing {len(notes)} notes for Suno artifacts...")

        notes = self._remove_octave_errors(notes)
        notes = self._remove_spurious_high_notes(notes)
        notes = self._smooth_timing(notes)

        print(f"   ✅ Cleaned to {len(notes)} notes")

        return notes

    def _remove_octave_errors(
        self,
        notes: List[note_seq.NoteSequence.Note]
    ) -> List[note_seq.NoteSequence.Note]:
        """
        Remove octave doubling errors (common in Suno transcriptions).

        Suno's metallic shimmer often causes false harmonics.
        """
        cleaned = []
        i = 0

        while i < len(notes):
            note = notes[i]

            # Check if next note is octave above/below at same time
            if i + 1 < len(notes):
                next_note = notes[i + 1]
                time_diff = abs(next_note.start_time - note.start_time)
                pitch_diff = abs(next_note.pitch - note.pitch)

                # If notes are simultaneous and exactly 12 semitones apart
                if time_diff < 0.05 and pitch_diff == 12:
                    # Keep the lower note (usually correct)
                    cleaned.append(note if note.pitch < next_note.pitch else next_note)
                    i += 2  # Skip both
                    continue

            cleaned.append(note)
            i += 1

        return cleaned

    def _remove_spurious_high_notes(
        self,
        notes: List[note_seq.NoteSequence.Note],
        threshold_pitch: int = 84  # High E (12th fret, high E string)
    ) -> List[note_seq.NoteSequence.Note]:
        """
        Remove spurious ultra-high notes caused by HF artifacts.

        Suno often transcribes metallic shimmer as very high notes.
        """
        # Count notes above threshold
        high_notes = [n for n in notes if n.pitch > threshold_pitch]
        high_ratio = len(high_notes) / (len(notes) + 1e-10)

        # If >30% of notes are suspiciously high, likely artifacts
        if high_ratio > 0.3:
            print(f"   Removing {len(high_notes)} spurious high notes")
            return [n for n in notes if n.pitch <= threshold_pitch]

        return notes

    def _smooth_timing(
        self,
        notes: List[note_seq.NoteSequence.Note],
        quantize_ms: float = 50
    ) -> List[note_seq.NoteSequence.Note]:
        """
        Quantize timing to remove jitter from AI artifacts.

        Suno audio sometimes has unstable transients.
        """
        quantize_sec = quantize_ms / 1000.0

        for note in notes:
            # Round to nearest quantize interval
            note.start_time = round(note.start_time / quantize_sec) * quantize_sec
            note.end_time = round(note.end_time / quantize_sec) * quantize_sec

        return notes


# Convenience function for integration with main pipeline
def process_suno_audio(
    audio_path: str,
    output_path: str = None
) -> Tuple[str, bool, dict]:
    """
    Detect and preprocess Suno audio in one call.

    Args:
        audio_path: Input audio
        output_path: Output preprocessed audio (auto-generated if None)

    Returns:
        (processed_audio_path, is_suno, metrics)
    """
    detector = SunoArtifactDetector()
    is_suno, metrics = detector.analyze(audio_path)

    if is_suno:
        preprocessor = SunoAudioPreprocessor()
        if output_path is None:
            import os
            base, ext = os.path.splitext(audio_path)
            output_path = f"{base}_processed{ext}"
        processed_path = preprocessor.process(audio_path, output_path)
        return processed_path, is_suno, metrics
    else:
        # No processing needed
        return audio_path, is_suno, metrics