Volumen
Problema a resolver
Los archivos WAV son un formato de archivo común para representar audio. Los archivos WAV almacenan audio como una secuencia de "muestras": números que representan el valor de alguna señal de audio en un momento determinado. Los archivos WAV comienzan con un "encabezado" de 44 bytes que contiene información sobre el archivo en sí, incluyendo el tamaño del archivo, el número de muestras por segundo y el tamaño de cada muestra. Después del encabezado, el archivo WAV contiene una secuencia de muestras, cada una de las cuales es un entero de 2 bytes (16 bits) que representa la señal de audio en un momento determinado.
Escalar cada valor de muestra por un factor determinado tiene el efecto de cambiar el volumen del audio. Multiplicar cada valor de muestra por 2.0, por ejemplo, tendrá el efecto de duplicar el volumen del audio original. Multiplicar cada muestra por 0.5, por otro lado, tendrá el efecto de reducir el volumen a la mitad.
En un archivo llamado volume.c
en una carpeta llamada volume
, escribe un programa para modificar el volumen de un archivo de audio.
Demostración
Código de Distribución
Para este problema, ampliarás la funcionalidad del código que te proporciona el personal de CS50.
Inicia sesión en cs50.dev, haz clic en la ventana de tu terminal y ejecuta cd
por sí mismo. Deberías encontrar que la solicitud de la ventana de tu terminal se asemeja a la siguiente:
$
A continuación, ejecuta
wget https://cdn.cs50.net/2023/fall/psets/4/volume.zip
para descargar un ZIP llamado volume.zip
en tu espacio de códigos.
Luego ejecuta
unzip volume.zip
para crear una carpeta llamada volume
. Ya no necesitas el archivo ZIP, así que puedes ejecutar
rm volume.zip
y responder con "y" seguido de Enter en el mensaje para eliminar el archivo ZIP que descargaste.
Ahora escribe
cd volume
seguido de Enter para moverte (es decir, abrir) a ese directorio. Tu solicitud ahora debe parecerse a la siguiente.
volume/ $
Si todo tuvo éxito, debes ejecutar
ls
y ver un archivo llamado volume.c
. La ejecución de code volume.c
debe abrir el archivo donde escribirás tu código para este conjunto de problemas. Si no es así, ¡revisa tus pasos y mira si puedes determinar dónde te equivocaste!
Detalles de la implementación
Completa la implementación de volume.c
, de tal manera que cambie el volumen de un archivo de sonido por un factor determinado.
- El programa debe aceptar tres argumentos de línea de comandos. El primero es
input
, que representa el nombre del archivo de audio original. El segundo esoutput
, que representa el nombre del nuevo archivo de audio que se debe generar. El tercero esfactor
, que es la cantidad por la cual se debe escalar el volumen del archivo de audio original.- Por ejemplo, si
factor
es2.0
, entonces tu programa debe duplicar el volumen del archivo de audio eninput
y guardar el archivo de audio recién generado enoutput
.
- Por ejemplo, si
- Tu programa primero debe leer el encabezado del archivo de entrada y escribir el encabezado en el archivo de salida.
- Tu programa debe leer el resto de los datos del archivo WAV, una muestra de 16 bits (2 bytes) a la vez. Tu programa debe multiplicar cada muestra por el
factor
y escribir la nueva muestra en el archivo de salida.- Puedes asumir que el archivo WAV usará valores de 16 bits con signo como muestras. En la práctica, los archivos WAV pueden tener un número variable de bits por muestra, pero asumiremos muestras de 16 bits para este problema.
- Tu programa, si utiliza
malloc
, no debe perder ninguna memoria.
Consejos
Entender el código en volume.c
En primer lugar, observa que volume.c
ya está configurado para tomar tres argumentos de línea de comandos, input
, output
y factor
.
main
toma unint
,argc
y una matriz dechar *
s (¡cadenas!),argv
.- Si
argc
, el número de argumentos en la línea de comandos, incluido el programa en sí, no es igual a 4, el programa imprimirá su uso correcto y saldrá con el código de estado 1.int main(int argc, char \*argv[]) { // Verificar argumentos de línea de comandos if (argc != 4) { printf("Uso: ./volume input.wav output.wav factor\n"); return 1; } // ... }
A continuación, volume.c
utiliza fopen
para abrir los dos archivos proporcionados como argumentos de línea de comandos.
- Es una buena práctica verificar si el resultado de llamar a
fopen
esNULL
. Si lo es, el archivo no se encontró o no se pudo abrir.// Abrir archivos y determinar factor de escalado FILE *input = fopen(argv[1], "r"); if (input == NULL) { printf("No se pudo abrir el archivo.\n"); return 1; } FILE *output = fopen(argv[2], "w"); if (output == NULL) { printf("No se pudo abrir el archivo.\n"); return 1; }
Más tarde, estos archivos se cierran con fclose
. ¡Siempre que llames a fopen
, debes llamar posteriormente a fclose
!
// Cerrar archivos
fclose(input);
fclose(output);
Sin embargo, antes de cerrar los archivos, observa que tenemos algunas tareas pendientes.
// TODO: Copiar encabezado del archivo de entrada al archivo de salida
// TODO: Leer muestras del archivo de entrada y escribir datos actualizados en el archivo de salida
Es probable que necesites saber el factor por el cual escalar el volumen, por lo que volume.c
ya convierte el tercer argumento de línea de comandos en un float
para ti.
float factor = atof(argv[3]);
Copiar encabezado WAV desde el archivo de entrada al archivo de salida
Tu primera tarea es copiar el encabezado del archivo WAV desde entrada
y escribirlo en salida
. Sin embargo, primero deberás aprender sobre algunos tipos de datos especiales.
Hasta ahora, hemos visto diferentes tipos en C, incluyendo int
, bool
, char
, double
, float
y long
. Sin embargo, dentro de un archivo de encabezado llamado stdint.h
están las declaraciones de otros tipos que nos permiten definir con mucha precisión el tamaño (en bits) y el signo (con signo o sin signo) de un entero. Dos tipos en particular serán útiles cuando trabajemos con archivos WAV:
uint8_t
es un tipo que almacena un entero sin signo (es decir, no negativo) de 8 bits (¡por eso 8!) (¡por esouint
!). Podemos tratar cada byte del encabezado de un archivo WAV como un valoruint8_t
.int16_t
es un tipo que almacena un entero con signo (es decir, positivo o negativo) de 16 bits. Podemos tratar cada muestra de audio en un archivo WAV como un valorint16_t
.
Probablemente desearás crear un arreglo de bytes para almacenar los datos del encabezado del archivo WAV que leerás desde el archivo de entrada. Usando el tipo uint8_t
para representar un byte, puedes crear un arreglo de n
bytes para tu encabezado con la sintaxis como
uint8_t header[n];
reemplazando n
con el número de bytes. Luego puedes usar header
como argumento para fread
o fwrite
para leer o escribir desde el encabezado.
Recuerda que el encabezado de un archivo WAV siempre tiene exactamente 44 bytes de longitud. Ten en cuenta que volume.c
ya define una variable para ti llamada HEADER_SIZE
, igual al número de bytes en el encabezado.
La siguiente es una pista bastante grande, ¡pero así es como podrías lograr esta tarea!
// Copiar encabezado desde el archivo de entrada al archivo de salida
uint8_t header[HEADER_SIZE];
fread(header, HEADER_SIZE, 1, input);
fwrite(header, HEADER_SIZE, 1, output);
Escribir datos actualizados en el archivo de salida
Tu siguiente tarea es leer muestras desde entrada
, actualizar esas muestras y escribir las muestras actualizadas en salida
. Al leer archivos, es común crear un "buffer" en el cual almacenar datos temporalmente. Ahí puedes modificar los datos y, una vez que estén listos, escribir los datos del buffer en un nuevo archivo.
Recuerda que podemos usar el tipo int16_t
para representar una muestra de un archivo WAV. Para almacenar una muestra de audio, entonces, puedes crear una variable de buffer con una sintaxis como:
// Crear un buffer para una sola muestra
int16_t buffer;
Con un buffer para muestras en su lugar, ahora puedes leer datos en él, una muestra a la vez. ¡Intenta usar fread
para esta tarea! Puedes usar &buffer
, la dirección de buffer
, como argumento para fread
o fwrite
para leer o escribir desde el buffer. (Recuerda que el operador &
se usa para obtener la dirección de la variable).
// Crear un buffer para una sola muestra
int16_t buffer;
// Leer muestra individual en el buffer
fread(&buffer, sizeof(int16_t), 1, input)
Ahora, para aumentar (o disminuir) el volumen de una muestra, solo necesitas multiplicarla por algún factor.
// Crear un buffer para una sola muestra
int16_t buffer;
// Leer muestra individual en el buffer
fread(&buffer, sizeof(int16_t), 1, input)
// Actualizar volumen de la muestra
buffer *= factor;
Y finalmente, puedes escribir esa muestra actualizada en salida
:
// Crear un buffer para una sola muestra
int16_t buffer;
// Leer muestra individual desde entrada en el buffer
fread(&buffer, sizeof(int16_t), 1, input)
// Actualizar volumen de la muestra
buffer *= factor;
// Escribir muestra actualizada en un nuevo archivo
fwrite(&buffer, sizeof(int16_t), 1, output);
Solo hay un problema: necesitarás seguir leyendo una muestra en tu buffer, actualizando su volumen y escribiendo la muestra actualizada en el archivo de salida mientras queden muestras para leer.
- Afortunadamente, por su documentación,
fread
devolverá el número de elementos de datos leídos exitosamente. ¡Puedes encontrar esto útil para verificar cuándo has llegado al final del archivo! - Ten en cuenta que no hay ninguna razón por la que no puedas llamar a
fread
dentro de un condicional de un lazowhile
. Podrías, por ejemplo, hacer un llamado afread
como el siguiente:while (fread(...)) { }
Es una gran pista, pero mira a continuación una forma eficiente de resolver este problema:
// Crear un buffer para una sola muestra
int16_t buffer;
// Leer muestra individual desde entrada en el buffer mientras queden muestras para leer
while (fread(&buffer, sizeof(int16_t), 1, input) != 0)
{
// Actualizar volumen de la muestra
buffer *= factor;
// Escribir muestra actualizada en un nuevo archivo
fwrite(&buffer, sizeof(int16_t), 1, output);
}
Como la versión de C que estás usando trata a los valores distintos de cero como true
y a los valores cero como false
, podrías simplificar la sintaxis anterior a la siguiente:
// Crear un buffer para una sola muestra
int16_t buffer;
// Leer muestra individual desde entrada en el buffer mientras queden muestras para leer
while (fread(&buffer, sizeof(int16_t), 1, input))
{
// Actualizar volumen de la muestra
buffer *= factor;
// Escribir muestra actualizada en un nuevo archivo
fwrite(&buffer, sizeof(int16_t), 1, output);
}
Tutorial
¿No estás seguro de cómo resolverlo?
Cómo probar
Tu programa debe comportarse según los ejemplos siguientes.
$ ./volumen entrada.wav salida.wav 2.0
Cuando escuches salida.wav
(por ejemplo, haciendo clic con control en salida.wav
en el explorador de archivos, seleccionando Descargar y luego abriendo el archivo en un reproductor de audio en tu computadora), ¡debe ser el doble de fuerte que entrada.wav
!
$ ./volumen entrada.wav salida.wav 0.5
Cuando escuches salida.wav
, ¡debe ser la mitad de fuerte que entrada.wav
!
Precisión
check50 cs50/problems/2024/x/volumen
Estilo
style50 volumen.c
Cómo enviar
submit50 cs50/problems/2024/x/volumen