CoolFace
Apppublic

FrankAst/Spice

sourceHugging Facemitupdated 4y agoView on Hugging Face
0likes
app_deploy.py507 linesDownload Raw Back to root
1# -*- coding: utf-8 -*-2"""tp3__1_-1.ipynb3 4Automatically generated by Colaboratory.5 6Original file is located at7    https://colab.research.google.com/drive/1_Sjx5G1BW689ggZJAJ4P7kCZndOobNCp8"""9 10# Install Gradio11#!pip install gradio -q12 13# Install timidy14#!sudo apt-get install -q -y timidity libsndfile115 16# All the imports to deal with sound data17#!pip install pydub numba==0.48 librosa music2118 19# Import Libraries20 21import gradio as gr22import time23 24import tensorflow as tf25import tensorflow_hub as hub26 27import numpy as np28import matplotlib.pyplot as plt29import librosa30from librosa import display as librosadisplay31 32import logging33import math34import statistics35import sys36 37from IPython.display import Audio, Javascript38from scipy.io import wavfile39 40from base64 import b64decode41 42import music2143from pydub import AudioSegment44 45logger = logging.getLogger()46logger.setLevel(logging.ERROR)47 48#print("tensorflow: %s" % tf.__version__)49#print("librosa: %s" % librosa.__version__)50 51# The audio input file52# Now the hardest part: Record your singing! :)53 54# We provide four methods to obtain an audio file:55 56# 1.   Record audio directly in Gradio57# 2.   Use a file saved on Google Drive58 59# Use a file saved on Google Drive60#INPUT_SOURCE = 'https://storage.googleapis.com/download.tensorflow.org/data/c-scale-metronome.wav'61 62#!wget --no-check-certificate 'https://storage.googleapis.com/download.tensorflow.org/data/c-scale-metronome.wav' -O c-scale.wav63 64#uploaded_file_name = 'c-scale.wav'65 66#uploaded_file_name67 68# Function that converts the user-created audio to the format that the model 69# expects: bitrate 16kHz and only one channel (mono).70 71EXPECTED_SAMPLE_RATE = 1600072 73# Funciones #74def convert_audio_for_model(user_file, output_file='converted_audio_file.wav'):75  audio = AudioSegment.from_file(user_file)76  audio = audio.set_frame_rate(EXPECTED_SAMPLE_RATE).set_channels(1)77  audio.export(output_file, format="wav")78  return output_file79 80MAX_ABS_INT16 = 32768.081 82def plot_stft(x, sample_rate, show_black_and_white=False):83  x_stft = np.abs(librosa.stft(x, n_fft=2048))84  fig, ax = plt.subplots()85  fig.set_size_inches(20, 10)86  x_stft_db = librosa.amplitude_to_db(x_stft, ref=np.max)87 88  if(show_black_and_white):89    librosadisplay.specshow(data=x_stft_db, 90                            y_axis='log', 91                            sr=sample_rate, 92                            cmap='gray_r')93  else:94    librosadisplay.specshow(data=x_stft_db, 95                            y_axis='log', 96                            sr=sample_rate)97 98  plt.colorbar(format='%+2.0f dB')99 100  return fig101 102# Loading audio samples from the wav file:103#sample_rate, audio_samples = wavfile.read(converted_audio_file, 'rb')104 105#fig = plot_stft(audio_samples / MAX_ABS_INT16 , sample_rate=EXPECTED_SAMPLE_RATE)106 107# Executing the Model108# Loading the SPICE model is easy:109model = hub.load("https://tfhub.dev/google/spice/2")110 111def plot_pitch_conf(pitch_outputs,confidence_outputs):112  fig, ax = plt.subplots()113  fig.set_size_inches(20, 10)114  plt.plot(pitch_outputs, label='pitch')115  plt.plot(confidence_outputs, label='confidence')116  plt.legend(loc="lower right")117  return fig118 119def plot_pitch_conf_notes(confident_pitch_outputs_x,confident_pitch_outputs_y):120  fig, ax = plt.subplots()121  fig.set_size_inches(20, 10)122  ax.set_ylim([0, 1])123  plt.scatter(confident_pitch_outputs_x, confident_pitch_outputs_y, )124  plt.scatter(confident_pitch_outputs_x, confident_pitch_outputs_y, c="r")125  return fig126 127def output2hz(pitch_output):128  # Constants taken from https://tfhub.dev/google/spice/2129  PT_OFFSET = 25.58130  PT_SLOPE = 63.07131  FMIN = 10.0;132  BINS_PER_OCTAVE = 12.0;133  cqt_bin = pitch_output * PT_SLOPE + PT_OFFSET;134  return FMIN * 2.0 ** (1.0 * cqt_bin / BINS_PER_OCTAVE)135 136def espectro_notas(audio_samples,EXPECTED_SAMPLE_RATE,confident_pitch_outputs_x,confident_pitch_values_hz):137  fig, ax = plt.subplots()138  plot_stft(audio_samples / MAX_ABS_INT16 , 139            sample_rate=EXPECTED_SAMPLE_RATE, show_black_and_white=True)140  # Note: conveniently, since the plot is in log scale, the pitch outputs 141  # also get converted to the log scale automatically by matplotlib.142  plt.scatter(confident_pitch_outputs_x, confident_pitch_values_hz, c="r")143  return fig144 145def hz2offset(freq):146    # This measures the quantization error for a single note.147    if freq == 0:  # Rests always have zero error.148      return None149    # Quantized note.150    h = round(12 * math.log2(freq / C0))151    return 12 * math.log2(freq / C0) - h152 153def quantize_predictions(group, ideal_offset):154  # Group values are either 0, or a pitch in Hz.155  non_zero_values = [v for v in group if v != 0]156  zero_values_count = len(group) - len(non_zero_values)157 158  # Create a rest if 80% is silent, otherwise create a note.159  if zero_values_count > 0.8 * len(group):160    # Interpret as a rest. Count each dropped note as an error, weighted a bit161    # worse than a badly sung note (which would 'cost' 0.5).162    return 0.51 * len(non_zero_values), "Rest"163  else:164    # Interpret as note, estimating as mean of non-rest predictions.165    h = round(166        statistics.mean([167            12 * math.log2(freq / C0) - ideal_offset for freq in non_zero_values168        ]))169    octave = h // 12170    n = h % 12171    note = note_names[n] + str(octave)172    # Quantization error is the total difference from the quantized note.173    error = sum([174        abs(12 * math.log2(freq / C0) - ideal_offset - h)175        for freq in non_zero_values176    ])177    return error, note178 179def get_quantization_and_error(pitch_outputs_and_rests, predictions_per_eighth,180                               prediction_start_offset, ideal_offset):181  # Apply the start offset - we can just add the offset as rests.182  pitch_outputs_and_rests = [0] * prediction_start_offset + \183                            pitch_outputs_and_rests184  # Collect the predictions for each note (or rest).185  groups = [186      pitch_outputs_and_rests[i:i + predictions_per_eighth]187      for i in range(0, len(pitch_outputs_and_rests), predictions_per_eighth)188  ]189 190  quantization_error = 0191 192  notes_and_rests = []193  for group in groups:194    error, note_or_rest = quantize_predictions(group, ideal_offset)195    quantization_error += error196    notes_and_rests.append(note_or_rest)197 198  return quantization_error, notes_and_rests199 200def main(audio):201 202  # Preparing the audio data203  # Now  we  have the  audio,  let's  convert it to the expected format and then 204  # listen to it!205  # The SPICE model needs as input an audio file at a sampling rate of 16kHz and206  # with only one channel (mono). 207  # To help you with this part, we created a function(`convert_audio_for_model`) 208  #to convert any wav file you have to the model's expected format:209 210 211  # Converting to the expected format for the model212  # in all the input 4 input method before, the uploaded file name is at213  # the variable uploaded_file_name214  converted_audio_file = convert_audio_for_model(audio)215 216  # Loading audio samples from the wav file:217  sample_rate, audio_samples = wavfile.read(converted_audio_file, 'rb')218 219  audio_samples = audio_samples / float(MAX_ABS_INT16)220 221 222  # We now feed the audio to the SPICE tf.hub model to obtain pitch and uncertainty outputs as tensors.223  model_output = model.signatures["serving_default"](tf.constant(audio_samples, tf.float32))224 225  pitch_outputs = model_output["pitch"]226  uncertainty_outputs = model_output["uncertainty"]227 228  # 'Uncertainty' basically means the inverse of confidence.229  confidence_outputs = 1.0 - uncertainty_outputs230 231 232  confidence_outputs = list(confidence_outputs)233  pitch_outputs = [ float(x) for x in pitch_outputs]234 235  indices = range(len (pitch_outputs))236  confident_pitch_outputs = [ (i,p)  237  for i, p, c in zip(indices, pitch_outputs, confidence_outputs) if  c >= 0.9  ]238  confident_pitch_outputs_x, confident_pitch_outputs_y = zip(*confident_pitch_outputs)239 240  confident_pitch_values_hz = [ output2hz(p) for p in confident_pitch_outputs_y ]241  242 243  #Plot waves244  fig1 = plt.figure()245  plt.plot(audio_samples)246 247  #Plot 248  fig2 = plot_stft(audio_samples / MAX_ABS_INT16 , sample_rate=EXPECTED_SAMPLE_RATE)249 250  #Plot Pitch & Confidence251  fig3 = plot_pitch_conf(pitch_outputs,confidence_outputs)252 253  254  #Plot Pitch & Confidence Notes255  fig4 = plot_pitch_conf_notes(confident_pitch_outputs_x,confident_pitch_outputs_y)256 257  #Plot Espectro + Notes258  fig5 = espectro_notas(audio_samples,EXPECTED_SAMPLE_RATE,confident_pitch_outputs_x,confident_pitch_values_hz)259 260 261  # ############################################################################262  # Converting to musical notes ################################################263 264  # Now that we have the pitch values, let's convert them to notes!265  # This  is  part  is  challenging  by itself. We have to take into account two 266  # things:267  #   1. the rests (when there's no singing) 268  #   2. the size of each note (offsets) 269 270  # ----------------------------------------------------------------------------271  ### 1: Adding zeros to the output to indicate when there's no singing272 273  pitch_outputs_and_rests = [274    output2hz(p) if c >= 0.9 else 0275    for i, p, c in zip(indices, pitch_outputs, confidence_outputs)276  ]277 278  # ----------------------------------------------------------------------------279  ### 2: Adding note offsets280  # When  a person  sings freely,  the melody may have an offset to the absolute 281  # pitch values that notes can represent.282  # Hence, to  convert  predictions  to  notes,  one  needs  to correct for this 283  # possible offset.284  # This is what the following code computes.285 286  A4 = 440287  C0 = A4 * pow(2, -4.75)288  note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]289 290  def hz2offset(freq):291    # This measures the quantization error for a single note.292    if freq == 0:  # Rests always have zero error.293      return None294    # Quantized note.295    h = round(12 * math.log2(freq / C0))296    return 12 * math.log2(freq / C0) - h297 298 299  # The ideal offset is the mean quantization error for all the notes300  # (excluding rests):301  offsets = [hz2offset(p) for p in pitch_outputs_and_rests if p != 0]302  #print("offsets: ", offsets)303  off = offsets304 305  ideal_offset = statistics.mean(offsets)306  #print("ideal offset: ", ideal_offset)307  ideal_off  = ideal_offset308 309  # We can now use some heuristics to try and estimate the most likely  sequence 310  # of notes that were sung.311  # The ideal offset computed above is one ingredient - but we also need to know 312  # the speed (how many predictions make, say, an eighth?), and the time  offset 313  # to start quantizing.  To keep it simple, we'll just try different speeds and 314  # time offsets and measure the quantization error, using in the end the values 315  # that minimize this error.316 317  def quantize_predictions(group, ideal_offset):318  # Group values are either 0, or a pitch in Hz.319    non_zero_values = [v for v in group if v != 0]320    zero_values_count = len(group) - len(non_zero_values)321 322    # Create a rest if 80% is silent, otherwise create a note.323    if zero_values_count > 0.8 * len(group):324      # Interpret as a rest. Count each dropped note as an error, weighted a bit325      # worse than a badly sung note (which would 'cost' 0.5).326      return 0.51 * len(non_zero_values), "Rest"327    else:328      # Interpret as note, estimating as mean of non-rest predictions.329      h = round(330          statistics.mean([331            12 * math.log2(freq / C0) - ideal_offset for freq in non_zero_values332          ]))333      octave = h // 12334      n = h % 12335      note = note_names[n] + str(octave)336      # Quantization error is the total difference from the quantized note.337      error = sum([338          abs(12 * math.log2(freq / C0) - ideal_offset - h)339          for freq in non_zero_values340      ])341    return error, note342 343 344  def get_quantization_and_error(pitch_outputs_and_rests, predictions_per_eighth,345                                 prediction_start_offset, ideal_offset):346    # Apply the start offset - we can just add the offset as rests.347    pitch_outputs_and_rests = [0] * prediction_start_offset + \348                              pitch_outputs_and_rests349    # Collect the predictions for each note (or rest).350    groups = [351        pitch_outputs_and_rests[i:i + predictions_per_eighth]352        for i in range(0, len(pitch_outputs_and_rests), predictions_per_eighth)353    ]354 355    quantization_error = 0356 357    notes_and_rests = []358    for group in groups:359      error, note_or_rest = quantize_predictions(group, ideal_offset)360      quantization_error += error361      notes_and_rests.append(note_or_rest)362 363    return quantization_error, notes_and_rests364 365 366  best_error = float("inf")367  best_notes_and_rests = None368  best_predictions_per_note = None369 370  for predictions_per_note in range(20, 65, 1):371    for prediction_start_offset in range(predictions_per_note):372 373      error, notes_and_rests = get_quantization_and_error(374          pitch_outputs_and_rests, predictions_per_note,375          prediction_start_offset, ideal_offset)376 377      if error < best_error:      378        best_error = error379        best_notes_and_rests = notes_and_rests380        best_predictions_per_note = predictions_per_note381 382  # At this point, best_notes_and_rests contains the best quantization.383  # Since we don't need to have rests at the beginning, let's remove these:384  #while best_notes_and_rests[0] == 'Rest':385  #  best_notes_and_rests = best_notes_and_rests[1:]386  # Also remove silence at the end.387  #while best_notes_and_rests[-1] == 'Rest':388  #  best_notes_and_rests = best_notes_and_rests[:-1]389  390  # ____________________________________________________________________________391  # Now let's write the quantized notes as sheet music score!392  # To do it we will use two libraries: [music21](http://web.mit.edu/music21/) and 393  # [Open Sheet Music Display](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay)394  # **Note:** for simplicity, we assume here that all notes have the same duration 395  # (a half note).396 397  # Creating the sheet music score.398  sc = music21.stream.Score()399  # Adjust the speed to match the actual singing.400  bpm = 60 * 60 / best_predictions_per_note401  #print ('bpm: ', bpm)402  a = music21.tempo.MetronomeMark(number=bpm)403  sc.insert(0,a)404 405  for snote in best_notes_and_rests:   406      d = 'half'407      if snote == 'Rest':      408        sc.append(music21.note.Rest(type=d))409      else:410        sc.append(music21.note.Note(snote, type=d))411 412 413  # @title [Run this] Helper  function to use Open Sheet Music Display (JS code) 414  # to show a music score415  from IPython.core.display import  HTML, Javascript416  from IPython import display417  import json, random418 419  def showScore(score):420      xml = open(score.write('musicxml')).read()421      showMusicXML(xml)422    423  def showMusicXML(xml):424      DIV_ID = "OSMD_div"425      a = display(HTML('<div id="'+DIV_ID+'">loading OpenSheetMusicDisplay</div>'))426      script = """427      var div_id = {{DIV_ID}};428      function loadOSMD() { 429          return new Promise(function(resolve, reject){430              if (window.opensheetmusicdisplay) {431                  return resolve(window.opensheetmusicdisplay)432              }433              // OSMD script has a 'define' call which conflicts with requirejs434              var _define = window.define // save the define object 435              window.define = undefined // now the loaded script will ignore requirejs436              var s = document.createElement( 'script' );437              s.setAttribute( 'src', "https://cdn.jsdelivr.net/npm/opensheetmusicdisplay@0.7.6/build/opensheetmusicdisplay.min.js" );438              //s.setAttribute( 'src', "/custom/opensheetmusicdisplay.js" );439              s.onload=function(){440                  window.define = _define441                  resolve(opensheetmusicdisplay);442              };443              document.body.appendChild( s ); // browser will try to load the new script tag444          }) 445      }446      loadOSMD().then((OSMD)=>{447          window.openSheetMusicDisplay = new OSMD.OpenSheetMusicDisplay(div_id, {448            drawingParameters: "compacttight"449          });450          openSheetMusicDisplay451              .load({{data}})452              .then(453                function() {454                  openSheetMusicDisplay.render();455                }456              );457      })458      """.replace('{{DIV_ID}}',DIV_ID).replace('{{data}}',json.dumps(xml))459      display(Javascript(script))460      return a461 462  # rendering the music score463  ###partitura = showScore(sc)464  #print(best_notes_and_rests)465 466 467 468  # ____________________________________________________________________________469  # Let's convert the music notes to a MIDI file and listen to it.470  # To create this file, we can use the stream we created before.471 472  # Saving the recognized musical notes as a MIDI file473  ##converted_audio_file_as_midi = converted_audio_file[:-4] + '.mid'474  ##fp = sc.write('midi', fp=converted_audio_file_as_midi)475 476  ##wav_from_created_midi = converted_audio_file_as_midi.replace(' ', '_') + "_midioutput.wav"477  #print(wav_from_created_midi)478 479  # To listen to it on colab, we need to convert it back to wav. An easy way  of 480  # doing that is using Timidity.481 482  #!timidity $converted_audio_file_as_midi -Ow -o $wav_from_created_midi483  return converted_audio_file, fig1, fig2, fig3, fig4,fig5, bpm, best_notes_and_rests#, wav_from_created_midi484  #return converted_audio_file, fig1, fig2, fig3, fig4,fig5, bpm, best_notes_and_rests, partitura, wav_from_created_midi485 486link = "https://www.tensorflow.org/hub/tutorials/spice?hl=es-419&authuser=2"487 488iface = gr.Interface(489    fn=main, 490    title= "Trabajo Práctico N°3 - Detección de tono con SPICE",491    description="Implementación de Modelo con GitHub + Hugging Face🤗-- 🔊✅ " + "Basado en: " + link, 492    inputs = [gr.inputs.Audio(source= "microphone" , type="filepath",label="Ingrese Audio")],493    outputs= [gr.outputs.Audio(label="Audio Original"), 494              gr.outputs.Plot(type="auto",label="Gráfico de Frecuencias"),495              gr.outputs.Plot(type="auto",label="Especto"),496              gr.outputs.Plot(type="auto",label="Pitch Confidence"),497              gr.outputs.Plot(type="auto",label="Notas"),498              gr.outputs.Plot(type="auto",label="Espectro+Notas"),499              gr.outputs.Textbox(label="bpm"),500              gr.outputs.Textbox(label="partitura")],#,501              #gr.outputs.Textbox(type="html",label="partitura1"),502              #gr.outputs.Audio(label="midi")],503    interpretation = "default",504)505 506iface.launch(debug=True)507