Lectura y escritura de música por frecuencia y tiempo




Presentación


Este proyecto nace a partir de la lectura del Articulo publicado por Kiko Correoso en la pagina Haciendo música con python que me dio la idea de diseñar un programa que permita a traves de la lectura de un archivo que contiene los valores de frecuencia, tiempo y escala repoducir música, como asi tambien escribir estos archivos.

El programa que aca pongo a consideración lee un archivo de texto donde se han escrito en cada linea los valores de la frecuencia, tiempo y escala separados por comas de las notas musicales que se van emitiendo secuencialmente.

El funcionamiento es bien intuitivo iniciado el programa aparece la siguiente pantalla:

En ella debemos elegir el boton "Seleccionar Tema" y se desplegara la siguiente ventana:

Que nos muestra todos los archivos txt que hay en el directorio donde esta el programa, alli seleccionaremos el archivo que deseamos que sea leido/reproducido y se desplegara la siguiente ventana:

Al hacer clic en "Ejecutar Tema" se iniciara la reproducción del tema mientras vemos moverse la imagen de una bailarina. al terminar el tema regresa a la pantala inicial.

Para escribir un nuevo tema, en la pantalla inicial elegimos el boton "Agregar Tema" con lo que se nos presentara la siguiente ventana:

En ella podemos ir ingresando una por una las notas del tema y al hacer clic en "Guardar" se iran escribiendo en un archivo llamado tema.txt. Debe tenerse en cuenta que la cuarta octava se corresponde con las notas de la clave de sol.

Para terminar solo se debe salir y volveremos a la pagina inicial.

En virtud que siempre las nuevas notas musicales se agregaran al archivo tema.txt resulta necesario cuando terminamos de escribir un tema musical renombrar el archivo.


Código


El código es el siguiente.



import tkinter as tk
from tkinter import filedialog
#from tkinter import *
from tkinter import ttk
import numpy as np
import sounddevice as sd
from PIL import Image, ImageTk
import sys


def botonera():

    for widget in ventana.winfo_children():
        widget.destroy()    


    boton_seleccionar = tk.Button(ventana, text="Seleccionar Tema", command=seleccionar_tema)
    boton_seleccionar.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    boton_seleccionar.place(x=150, y=100)

    boton_agregar = tk.Button(ventana, text="Agregar Tema", command=cargar_temas)
    boton_agregar.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    boton_agregar.place(x=150, y=200)

    boton_salir = tk.Button(ventana, text="Salir", command=salir)
    boton_salir.configure(width=30, height=1, bg="#76D7C4", font="Arial 14 bold italic", fg="#229954")
    boton_salir.place(x=150, y=300)


#Salida del programa
def salir():
    sys.exit()


#Definir nota
def frec(nota: int, octava: int) -> int:
    expo = octava * 12 + (nota - 58)
    return int(640 * ((2 ** (1 / 12)) ** expo))

#Ejecutar nota
def beep(nota: int, octava: int, duracion: int) -> None:
    cuadros = 44100
    t = np.linspace(0, duracion / 1000, int(cuadros * duracion / 1000))
    frecuencia = frec(nota, octava)
    data = np.sin(2 * np.pi * frecuencia * t)
    sd.play(data, cuadros)
    sd.wait()

#Seleccionar archivo a ejecutar
def seleccionar_tema():
    global temas
       
    temas = filedialog.askopenfilename(filetypes=[("Temas Musicales", "*.txt")])
    for widget in ventana.winfo_children():
        widget.destroy()
    eti_tema = tk.Label(ventana, text="Tema Musical")
    eti_tema.configure(width=15, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#EC7063")
    eti_tema.place(x=100, y=50)
    titulo = temas.split("/")
    x = len(titulo) - 1
    cont_titulo = tk.Label(ventana, text=titulo[x][:-4])
    cont_titulo.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    cont_titulo.place(x=250, y=50)
    boton_ejecutar = tk.Button(ventana, text="Ejecutar Tema", command=ejecutar_tema)
    boton_ejecutar.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    boton_ejecutar.place(x=150, y=200)
    boton_salir = tk.Button(ventana, text="Salir", command=salir)
    boton_salir.configure(width=30, height=1, bg="#76D7C4", font="Arial 14 bold italic", fg="#229954")
    boton_salir.place(x=150, y=300)


#Ejecutar sonidos del archivo
def ejecutar_tema():
    for widget in ventana.winfo_children():
        widget.destroy()
    eti_tema = tk.Label(ventana, text="Tema Musical")
    eti_tema.configure(width=15, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#EC7063")
    eti_tema.place(x=100, y=50)
    titulo = temas.split("/")
    x = len(titulo) - 1
    cont_titulo = tk.Label(ventana, text=titulo[x][:-4])
    cont_titulo.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    cont_titulo.place(x=250, y=50)
    
    # Cargar los cuadros del GIF de la imagen a mostrar
    gif = Image.open("musica.gif")
    cuadros = []
    try:
        while True:
            cuadros.append(ImageTk.PhotoImage(gif))
            gif.seek(len(cuadros))  
    except EOFError:
        pass
    
    # Mostrar la imagen en un canvas
    canvas = tk.Canvas(ventana, width=200, height=200)
    canvas.place(x=200, y=100)
    canvas.create_image(0, 0, image=cuadros[0], anchor=tk.NW)
    x = 0
    with open(temas, 'r') as musica:
        notas = musica.readlines()
    for nota in notas:
        x = x + 1
        valores = nota.split(",")
        beep(int(valores[0]), int(valores[1]), int(valores[2]))
        print(valores[0])
        print(valores[1])
        print(valores[2])
        # Actualizar el cuadro del GIF en el canvas
        canvas.itemconfig(1, image=cuadros[x])
        ventana.update()
        if x == 9:
            x = -1

    botonera()

def cargar_temas():
    global entrada_octava
    global lista_notas
    global lista_tipos 
    
    for widget in ventana.winfo_children():
        widget.destroy()
    # Etiqueta para la selección de la nota musical
    etiqueta_notas = tk.Label(ventana, text="Nota musical:")
    etiqueta_notas.configure(height=1, bg="#E67E22", font="Arial 14", fg="#7B241C")
    etiqueta_notas.place(x = 35, y = 50)

    # Lista de selección múltiple para la nota musical
    notas = ["Do", "Re", "Mi", "Fa", "Sol", "La", "Si"]
    lista_notas = ttk.Combobox(ventana)
    lista_notas['values'] = notas
    lista_notas.place(x = 150, y = 50, width=50, height=26)



    # Etiqueta para la selección del tipo de nota
    etiqueta_tipos = tk.Label(ventana, text="Tipo de nota:")
    etiqueta_tipos.configure(height=1, bg="#E67E22", font="Arial 14", fg="#7B241C")
    etiqueta_tipos.place(x = 210, y = 50)

    # Lista de selección múltiple para el tipo de nota
    tipos = ["Redonda", "Blanca", "Negra", "Corchea", "Semicorchea", "Fusa", "Semifusa"]
    lista_tipos = ttk.Combobox(ventana)
    lista_tipos['values'] = tipos
    lista_tipos.place(x = 320, y = 50, width=100, height=26)


    # Etiqueta y entrada para ingresar la octava
    etiqueta_octava = tk.Label(ventana, text="Octava:")
    etiqueta_octava.configure(height=1, bg="#E67E22", font="Arial 14", fg="#7B241C")
    etiqueta_octava.place(x = 430, y = 50)

    entrada_octava = tk.Entry(ventana)
    entrada_octava.insert(0, "4")
    entrada_octava.place(x = 490, y = 50, height=26, width=30)

    # Botón para guardar la información
    boton_guardar = tk.Button(ventana, text="Guardar", command=guardar_informacion)
    boton_guardar.configure(width=30, height=1, bg="#FDEDEC", font="Arial 14 bold italic", fg="#C0392B")
    boton_guardar.place(x = 150, y = 150)
    boton_salir = tk.Button(ventana, text="Salir", command=botonera)
    boton_salir.configure(width=30, height=1, bg="#76D7C4", font="Arial 14 bold italic", fg="#229954")
    boton_salir.place(x=150, y=300)    

def guardar_informacion():
    val_nota= ["1", "3", "5", "6", "8", "10", "12"]
    val_fig = ["4000", "2000", "1000", "500", "250", "125", "62"]
    octava = entrada_octava.get()
    sel_nota = lista_notas.get()
    ind_nota = lista_notas.current()
    sel_tipo = lista_tipos.get() 
    ind_tipo = lista_tipos.current()
    archivo = open("tema.txt", "a")
    archivo.write(f"{val_nota[ind_nota]}, {octava}, {val_fig[ind_tipo]}\n")
    archivo.close()
    print(sel_nota)
    print(val_nota[ind_nota])
    print(sel_tipo)
    print(val_fig[ind_tipo])
    print(octava)
    

    # Reiniciar los campos
    lista_notas.set('')
    lista_tipos.set('')
    entrada_octava.delete(0, tk.END)
    entrada_octava.insert(0, "4")



ventana = tk.Tk()
ventana.title("Lector de música")
ventana.geometry('600x400')
ventana.configure(bg='#A2D9CE')
botonera()
ventana.mainloop()
    

Un ejemplo de los archivos de texto que el programa lee, es el siguiente con la melodía del Feliz Cumpleaños.



        8,4,500
        8,4,500
        10,4,500
        8,4,500
        1,5,500
        12,4,1000
        8,4,500
        8,4,500
        10,4,500
        8,4,500
        3,5,500
        1,5,1000
        1,5,250
        5,5,250
        8,5,500
        5,5,500
        1,5,500
        12,4,500 
        10,4,500
        6,5,250
        6,5,250
        5,5,500
        1,5,500
        3,5,500
    

Instalacion del programa


Para instalar el programa se debe tener instalado Python 3, y en una carpeta se debe colocar un archivo con extensión "*.py" que contenga el código

En la misma capeta se debe descargar el archivo *.gif de la presentación del programa y renombrarlo como musica.gif

Finalmente en la misma carpeta se creara un archivo con el nombre "Feliz Cumpleaños.txt" en el que se copiara el ejemplo transcrito mas arriba

Como siempre espero que el proyecto sea de interes y quedo a disposición para comentarios y consultas en mi correo electronico carlosvaccaro1960@gmail.com


Cerrar