function [tablacue, err] = cueread(fid, flac_exe_dir)
   // Recupera cues y texto asociado de archivos wav y flac
   //
   // Uso: 
   //       [tablacue, err] = cueread(fid)
   //       [tablacue, err] = cueread(fid, flac_exe_path)
   // donde:
   //       fid:          Identificador de archivo obtenido  
   //                     previamente mediante mopen o ruta
   //                     completa de un archivo wav o flac
   //       flac_exe_dir: Directorio del codec flac.exe
   //                     En el caso de archivos wav este
   //                     argumento no se utiliza
   //       tablacue:     Tabla en formato de cell-array 
   //                     cuyas filas contienen información
   //                     de los cues en orden cronológico
   //                     con el siguiente contenido: 
   //                     Nº_de_evento, Muestra_inicial,
   //                     Longitud, Etiqueta, Descripción
   //       err:          Mensaje de error
   //
   // NOTA: 1) En el caso de archivos flac la función extrae
   //          los cues originalmente incluidos en el archivo
   //          wav convertido a flac mediante la opción "export
   //          foreign metadata", no las cue sheets del formato
   //          flac
   //       2) Si en el caso de un archivo flac no se incluye
   //          la ruta del codec flac.exe, la función intenta
   //          buscar el codec en las siguiente ubicaciones: 
   //          SCI + "tools\flac\flac.exe"
   //          pwd() + "\flac.exe"
   //          pwd() + "\flac\flac.exe" 
   //          Si no se lo encuentra, se pedirá explícitamente
   //          ingresar la ruta. El archivo flac.exe se puede
   //          descargar de
   //          https://ftp.osuosl.org/pub/xiph/releases/flac/
   //          como archivo zip. Una vez descomprimido contiene
   //          dos archivos: flac.exe and metaflac.exe. Ubicar
   //          el primereo en un directorio adecado
   //
   // Ejemplo:
   // 
   //        fid = mopen("onda.wav")
   //        [tablacue, siz, err] = cueread(fid)
   //
   // -----------------------------------
   // Autor: Federico Miyara, Pablo Miechi 
   // 2007-09-25
   // 2007-10-24
   // 2012-10-01
   // 2013-05-13
   // 2020-02-23
   // 2021-07-24 
   
   // DATOS PARA PRUEBA - ELIMINAR
   if 1==2
      archivo = "c:\testcue.wav";
      archivo = "H:\Grupo ruido\Aural\RECORDING210-211.flac";
      archivo = "H:\Grupo ruido\Aural\RECORDING210-211a.flac";
      archivo = "H:\Grupo ruido\Aural\RECORDING210-211.wav";
      fid = archivo;
      fid = mopen(fid, "rb")
      //mclose(fid)
   end

   // Si se piden las tres variables de salida, se
   // preasigna un valor a la variable err indicadora
   // de error para evitar que si no hay error quede
   // sin asignar
   err = 0;

   // Retrieve extension
   if type(fid)==1
      // fid is the id of open file
      [a, b, name] = file(fid);
      ext = fileext(name);
   elseif type(fid)==10
      // fid is the file full path
      ext = fileext(fid);
   end
   
   if convstr(ext)==".flac"
      // Convert flac to a short wav file keeping cues
      // The file name, fil, is random and at the end 
      // will be deleted
      file_path = fid;  
      fil = flac2shortwav(file_path, flac_exe_dir);
      fid = fil;
   elseif ext==".wav"
      // Do nothing, the file is already a wav file
   else
      // The file is not a wav file
      msg = "The file extension should be .wav or .flac\n";
      mprintf(msg)
      err = "The file is not .wav nor .flac"
      // Exit the function
      return
   end

   // Lectura de los primeros 50 bytes (donde está el
   // encabezado del archivo) y almacenamiento en el 
   // vector head
   if type(fid)==10
      fid = mopen(fid, "rb");
   end
   mseek(0, fid);
   head = mget(50, "uc", fid); 

   // Verificación de que los primeros 4 bytes sean 
   // la estructura RIFF
   if ascii(head(1:4))~="RIFF"
      mprintf("No se encontró la estructura RIFF\n");  
      tablacue = {};
      siz = 0;
      err = "No es un archivo RIFF";
      // Sale de la función
      return
   end

   // Verificación de que el archivo tiene formato WAV
   if ascii(head(9:12))~="WAVE"       
      mprintf("No es un archivo con formato WAV\n");  
      if argn(1)==3
         tablacue = {};
         siz = 0;
         err = 'No es un archivo WAV';
      end
      // Sale de la función
      return
   end

   // Determinación del tamaño del archivo
   tama = le2dec(head(5:8)) + 8;


   // ##############################################  
   // Bucle de búsqueda del encabezado 'data' para 
   // encontrar el tamaño de los datos de audio.
   // ##############################################

   n = 1;
   i = 1;
   while i==1,     
      if ascii(head(n:n+3))=="data"
         // Se encontró el encabezado "data"
         i = 0;
         // Los 4 bytes siguientes al encabezado  
         // "data" contienen el tamaño del audio
         // en formato little-endian. Hay que
         // convertirlo para tener el valor real
         // en bytes.
         siz = le2dec(head(n+4:n+7));
         // Se guarda la posición del byte inicial
         // del encabezado "data".
         pos = n;                
         //mprintf("El tamaño de los datos de audio es %d bytes\n", siz);
      else
         n = n + 1;
      end
   end


   // ##############################################
   // Lectura de los últimos bytes del archivo donde
   // se encuentra la información de CUE.
   // ##############################################

   // Si no hay más datos después del audio, terminar
   // la función
   if siz+n+7 >= tama
      tablacue = {};
      mprintf("El archivo no contiene cues\n");
      err = "El archivo no contiene cues";
      return
   end

   // Se posiciona el puntero en el byte siguiente a aquél donde 
   // terminan los datos de audio.
   mseek(siz + n + 7, fid);

   // Se lee desde la posición del puntero hasta el final
   // del archivo y se almacena en tail
   tail = mget(tama - (siz+n+7), "uc", fid);

   // Cerrar archivo
   mclose(fid);
   
   // Borrar el archivo si se partió de un archivo
   // flac y se obtuvo un wav asociado
   if isdef("fil")
      deletefile(fil)
   end
   
   // Longitud de los metadatos  
   long = length(tail);


   // ##############################################
   // Bucle de búsqueda del encabezado 'cue' para
   // obtener la lista de cues.
   // ##############################################

   n = 1;
   i = 1;
   while i==1     
      // Se recorre la cola del archivo hasta 
      // encontrar el encabezado 'cue'
      if ascii(tail(n:n+3))=="cue "
         // Se encontró el encabezado cue
         i = 0;
         // Se desplaza 8 posiciones para encontrar
         // la cant de cues, que está expresada con
         // cuatro bytes en formato little-endian
         // NOTA: Los 4 pimeros bytes después de
         // "cue " representan la longitud del chunk
         // de cues.
         pos = n + 8;
         cant = le2dec(tail(pos:pos+3));
         if cant==1
            //mprintf("El archivo tiene %d cue\n", cant);
         else
            //mprintf("El archivo tiene %d cues\n", cant);
         end
      else
         n = n + 1;
      end
   end

   // Se posiciona el puntero en el inicio del primer
   // cue, lo cual sucede inmediatamente después de 
   // los 4 bytes que indican la cantidad de cues
   pos = pos + 4; 

   // Se crea un cell-array para guardar datos de cue.
   tabla = cell(cant,5);


   // ################################################
   // Bucle de asignación de las dos primeras columnas
   // del cell-array.
   // ################################################

   for k=1:cant
      // Se guarda en la primera columna de la tabla
      // el ID del cue, que es un número de cuatro
      // bytes en formato little-endian (típicamente
      // es un número relativamente pequeño)
      id = le2dec(tail(pos:pos+3));
      tabla{k,1} = id;

      // Se genera un vector idd cuyo su componente
      // de índice igual a id contiene el número de
      // la fila de la tabla correspondiente a id.
      // Esto es para evitar que la tabla tenga más
      // filas que cues, lo que podría ocurrir cuando
      // se borra desde el editor de audio algún cue. 
      // Por ejemplo, si originalmente hay tres cues y
      // se borra el primero, quedan los identificados
      // con id = 2 e id = 3. Entonces la fila 1 de la
      // tabla contiene el cue 2 y la fila 2, el cue 3. 
      idd(id) = k;

      // Se guarda en la segunda columna de la tabla
      // el número de muestra de incio del cue, que es
      // un número de 4 bytes en formato little-endian 
      // ubicado 20 bytes después del inicio del cue
      tabla{k, 2} = le2dec(tail(pos+20:pos+23));

      // Se actualiza el puntero ya que entre dos cues 
      // sucesivos hay 24 bytes, distribuidos así: 
      // a) ID: 4 bytes
      // b) Número de muestra a la que corresponde el
      //    cue: 4 bytes
      // c) Palabra clave 'data': 4 bytes
      // d) Comienzo del chunk: 4 bytes (son 0 para
      //    PCM)
      // e) Comienzo del block: 4 bytes (son 0 para 
      //    PCM)
      // f) Offset (coincide con el número de muestra:
      // 4 bytes
      pos = pos + 24;
   end

   // Se guarda el puntero actual para continuar con
   // la búsqueda de encabezados.
   n = pos; 


   // ###############################################   
   // Bucle de busqueda del encabezado 'ltxt' para
   // obtener la duracion en muestras de cada cue,
   // correspondiente a la tercera columna
   // ###############################################

   for k=1:cant   
      // Bandera indicadora de que no se encontró
      // el encabezado "ltxt"
      i = 1;
      while i==1,
         if ascii(tail(n:n+3))=="ltxt"
            // Se encontró el encabezado "ltxt"
            i = 0;

            // Longitud de texto + 4 bytes que
            // contienen ID
            slt = le2dec(tail(n+4:n+7));  

            // Lectura de ID
            id = le2dec(tail(n+8:n+11));   

            // Se guarda la duración del cue en
            // columna 3 de la tabla
            tabla{idd(id),3} = le2dec(tail(n+12:n+15));        

            // Se ubica el puntero al final del
            // chunk para iniciar la búsqueda del 
            // próximo ltxt
            n = n + 12 + slt - 4;  
         else
            n = n + 1;
         end
      end
   end


   // #################################################   
   // Bucle de busqueda del encabezado 'labl' para 
   // obtener los nombres de los cues, correspondientes
   // a la cuarta columna.
   // #################################################

   for k=1:cant
      i = 1;
      while i==1,     
         if ascii(tail(n:n+3))=="labl"
            // Se encontró el encabezado 'labl'
            i = 0;

            // Los siguientes 4 bytes contienen la
            // longitud del texto del label + los 
            // 4 bytes que contienen la ID (en 
            // formato little-endian)
            sl = le2dec(tail(n+4:n+7));

            // Lectura de ID
            id = le2dec(tail(n+8:n+11)); 

            // Lectura texto contenido en labl y
            // guardado en columna 4
            tabla{idd(id),4} = ascii(tail(n+12:n+12+sl-4-1));        

            // Se ubica el puntero al final del
            // chunk para que encuentre el próximo
            // labl
            n = n + 12 + sl - 4;
         else
            n = n + 1;
         end
      end
   end


   // ###############################################   
   // Bucle de búsqueda del encabezado 'note' para 
   // obtener las anotaciones de los cues. 
   // ###############################################

   // Se inicializa el puntero al comienzo de la cola.
   // Sería más eficiente colocarlo al final del 
   // anterior, pero se corre el riesgo de que el 
   // orden de los chunks de metadatos esté alterado 
   // y entonces se produzca un error
   n = 1;
   for k=1:cant   
      // Bandera indicadora de que no se encontró el
      // encabezado 'note'
      i = 1;
      while i==1,     
         if ascii(tail(n:n+3))=="note"
            // Se encontró el encabezado 'note'
            i = 0;

            // Longitud de texto + 4 bytes 
            // que contienen ID
            sn = le2dec(tail(n+4:n+7));  

            // Lectura de ID
            id = le2dec(tail(n+8:n+11));         

            // Lectura texto contenido en note y
            // guardado en columna 5
            tabla{idd(id),5} = ascii(tail(n+12:n+12+sn-4-1));

            // Se ubica el puntero al final del
            // chunk para que encuentre el próximo
            // note. El último (-4) se debe a que 
            // se llega al final del archivo y no
            // hay mas n's para comparar con 'note'.
            n = n + 12 + sn - 4 - 4;
         elseif n<long-3
            n = n + 1;
         else
            // si se llegó al final de la cola no
            // se sigue buscando
            break
         end
      end
   end


   // ###############################################
   // Se ordena la tabla de menor a mayor según 
   // muestra inicial
   // ###############################################

   // Se convierte la segunda columna del cell-array,
   // que contiene los números de muestra iniciales
   // a vector para poder ordenarla.
   for i=1:cant
      col(i) = tabla{i,2};
   end

   // Se reordena el vector col, conservando en I un
   // registro de las permutaciones de índices
   [Y, I] = gsort(col, "r", "i");

   // Se crea nuevo cell-array para guardar datos
   // ordenados.
   tablacue = cell(cant,5);  

   // Se reescribe el cell array ordenado
   for i=1:cant
      // Se reordenan las columnas 2 a 5. La primera
      // columna no se reordena, de modo que su
      // contenido indica el número de fila en la 
      // tabla reordenada y no el identificador
      // original del cue.
      tablacue{i,1} = i;
      for j=2:5
         tablacue{i,j} = tabla{I(i),j};
      end
   end

endfunction


// #################################################
// Funciones locales
// #################################################

function y = le2dec(x)
   // Converts "little-endian" bytes to decimal 
   // Usage:
   //        y = le2dec(x)
   // where
   //        x: string of little-endian ordered bytes
   //           (the first byte is the least significant
   //        y: the corresponding decimal number
   //
   // It is applied to binary files containing bytes
   // representing numbers
   // Example: 
   // 
   //        x = [14, 8]
   // 
   // corresponds to 
   // 
   //        y = 256*8 + 14
   //
   // ----------------------------------------
      
   // Length of x
   s = length(x);

   // Decimal value corresponding to a number in base
   // 256 ordered from least to most significant
   y = sum(256.^[0:s-1].*x);
   
endfunction

function fil = flac2shortwav(file_path, flac_exe_dir)
   // Converts flac file to short wav containing cues
   // Usage:
   //       fil = flac2shortwav(file_path)
   // where
   //       file_path:    full path of file to convert
   //       flac_exe_dir: directory of the codec flac.exe
   //       fil:          full path of the converted
   //                     file, chosen randomly    
   
   rhs1 = argn(2);
   
   if rhs1<2
      // Retrieve or ask for flac.exe full path   
      if isfile(SCI + "tools\flac\flac.exe")
         flac_exe_dir = SCI + "tools\flac\";
      elseif isfile(pwd() + "\flac.exe")
         flac_exe_dir = pwd() + "\";
      elseif isfile(pwd() + "\flac\flac.exe")
         flac_exe_dir = pwd() + "\flac\";
      else
         // Ask for the codec directory
         msg = "Input directory to flac.exe: ";
         flac_exe_dir = input(msg, "s");
         // If the directory does not end with "\",
         // append it 
         if ascii(ascii(flac_exe_dir)($))~="\"
            flac_exe_dir = flac_exe_dir + "\";
         end
      end
   end

   // Temporary file selected at random
   folder = fileparts(file_path)
   already = 0;
   while already==0; 
      fil = folder + msprintf("%.0f", floor(rand()*1e15)) + ".wav";
      // Checks the name is not already in use
      if ~isfile(fil)
         already = 1;
      end
   end
      
   // Position of the colon symbol within the codec path
   k = strindex(flac_exe_dir, ":");

   // DOS command to choose drive
   com1 = part(flac_exe_dir,k-1:k);

   // DOS command to change to the flac codec directory
   com2 = "cd " + flac_exe_dir;

   // Prepare a command line to run flac.exe with 
   // several options
   // Decode
   com3 = "flac -d ";
   // Read only 2 samples 
   com3 = com3 + "--skip=" + string(0) + " --until=" + string(1);
   // Keep foreign metadata (cues, etc.)
   com3 = com3 + " --keep-foreign-metadata";
   // Assign temporary file name to output
   com3 = com3 + " -o " + """" + fil + """"; 
   // Apply the options to the input flac file
   com3 = com3 + " """ + file_path + """";

   // Generate command string with the && separator
   commands = com1 + " && " + com2 + " && " + com3; 

   // Run commands in a DOS window 
   [output, bOK, exitcode] = dos(commands);
   
endfunction

