#!/usr/bin/python -u
# coding: utf-8

#--------------------------------------------------------------------
# Fichero:  ca0007.py
# Objetivo: Composición algorítmica 0007
# Autor:    Pedro Reina
# Fecha:    D.17.7.2016
# Licencia: CC0 1.0 Universal
#           https://creativecommons.org/publicdomain/zero/1.0/
# Historia:
#   S.13.6.2015: versión inicial
#   D.17.7.2016: pythonización
#--------------------------------------------------------------------

#------------------------------------------------
# Constantes de la composición
#------------------------------------------------

# Cuántos compases tendrá la composición
Compases = 80

# Cuántos pulsos tendrá cada compás
Pulsos = 8

# El tiempo de espera entre cada pulso
Espera = 0.1

# Si se desea ver mensajes de depuracion
Depuracion = 0

#------------------------------------------------
# La función que realiza la composición
#------------------------------------------------

def Composicion():

    # Calculamos el total de pulsos que vamos a generar
    Duracion = Compases * Pulsos + 1

    Piel1 = MIDI_InstrumentoPiel()
    Piel2 = MIDI_InstrumentoPiel()
    Piel3 = MIDI_InstrumentoPiel()
    Piel4 = MIDI_InstrumentoPiel()
    Metal1 = MIDI_InstrumentoMetal()
    Metal2 = MIDI_InstrumentoMetal()

    # Generamos los pulsos
    for i in range(1, Duracion):

        # Mostramos el contador de pulsos
        CA_Texto('('+str(i)+')')

        Modulo = i%8

        if i%16 == 1:
            Piel1 = Piel2
            Piel2 = Piel3
            Piel3 = Piel4
            Piel4 = MIDI_InstrumentoPiel()
            Metal1 = Metal2
            Metal2 = MIDI_InstrumentoMetal()

        if Modulo in (1,3,5,7):
            MIDI_Nota(Piel1, MIDI_VOLUMEN/2)

        if Modulo in (2,4,6,0):
            MIDI_Nota(Piel2, MIDI_VOLUMEN/2)

        if i>Duracion/8 and Modulo in (1,3):
            MIDI_Nota(Piel3, MIDI_VOLUMEN)

        if i>Duracion/4 and Modulo in (5,7):
            MIDI_Nota(Piel4, MIDI_VOLUMEN)

        if i>Duracion/2 and Modulo in (2,4):
            MIDI_Nota(Metal1, MIDI_VOLUMEN)

        if i>3*Duracion/4 and Modulo in (6,0):
            MIDI_Nota(Metal2, MIDI_VOLUMEN)

        # Esperamos un momento
        time.sleep (Espera)

#------------------------------------------------
# Módulos necesarios
#------------------------------------------------

# Para gestionar el entorno MIDI
import pypm

# Para manejar el reloj
import time

# Para generar números aleatorios
import random

#------------------------------------------------
# Constantes del sistema
#------------------------------------------------

# El nombre de la unidad MIDI out
MIDI_SALIDA = 'USB Midi MIDI 1'

# El código MIDI para lanzar un evento Note On en el canal 10
MIDI_NOTAON = 0x99

# El código MIDI para lanzar un evento Note Off en el canal 10
MIDI_NOTAOFF = 0x89

# El valor máximo del volumen
MIDI_VOLUMEN = 0x7F

# El valor mínimo del volumen
MIDI_SILENCIO = 0x00

# Las listas de instrumentos válidos
MIDI_ListaPiel = (35, 36, 37, 38, 39, 40, 41, 43, 45, 47, 48, 50, 58, 62, 63,
                  64, 75, 82, 84, 85, 86, 87, 88, 89, 91, 93)
MIDI_ListaMetal = (42, 44, 46, 49, 51, 53, 54, 55, 56, 57, 67, 68, 69, 90)

#------------------------------------------------
# Variables globales del sistema
#------------------------------------------------

# El manejador para enviar eventos MIDI
MIDI_Out = 0x00

# Los totales de instrumentos disponibles
MIDI_TotalPiel = 0
MIDI_TotalMetal = 0

#------------------------------------------------
# Funciones
#------------------------------------------------

#------------------------------------------------
# Escribe un texto
def CA_Texto (Mensaje):
    global Depuracion

    # Si nos lo piden, imprimimos el texto
    if Depuracion:
        print Mensaje,

#------------------------------------------------
# Inicia todo el sistema MIDI
def MIDI_Inicio():
    global MIDI_TotalPiel, MIDI_TotalMetal, MIDI_Out

    # Iniciamos el sistema Python-MIDI
    pypm.Initialize()

    # Obtenemos la unidad que nos interesa
    Unidad = MIDI_BuscaSalida()

    # La imprimimos
    CA_Texto(MIDI_SALIDA + ' => ' + str(Unidad) + '\n')

    # Abrimos el puerto MIDI
    Latencia = 0
    MIDI_Out = pypm.Output(Unidad, Latencia)

    # Informamos de cuántos instrumentos tenemos
    CA_Texto('Instrumentos de piel => ' + str(len(MIDI_ListaPiel)) + '\n')
    CA_Texto('Instrumentos de metal => ' + str(len(MIDI_ListaMetal)) + '\n')

#------------------------------------------------
# Termina todo el sistema MIDI
def MIDI_Final():
    global MIDI_Out

    # Eliminamos la salida MIDI
    del MIDI_Out

    # Terminamos el sistema Python-MIDI
    pypm.Terminate()

#------------------------------------------------
# Envía una nota a la unidad MIDI
def MIDI_Nota (Instrumento, Volumen):
    # Si nos lo piden, imprimimos el instrumento
    if Depuracion:
        print Instrumento,

    # Nota on
    MIDI_Out.WriteShort(MIDI_NOTAON, Instrumento, Volumen)

    # Nota off
    MIDI_Out.WriteShort(MIDI_NOTAOFF, Instrumento, MIDI_SILENCIO)

#------------------------------------------------
# Elige una piel al azar
def MIDI_InstrumentoPiel():
    return random.choice(MIDI_ListaPiel)

#------------------------------------------------
# Elige un metal al azar
def MIDI_InstrumentoMetal():
    return random.choice(MIDI_ListaMetal)

#------------------------------------------------
# Busca el número de la salida MIDI
def MIDI_BuscaSalida():

    # Vemos cuántas unidades MIDI hay disponibles
    TotalUnidades = pypm.CountDevices()

    # Las examinamos todas
    for Numero in range(TotalUnidades):

        # Obtenemos los datos de la unidad
        (Interfaz, Nombre, Entrada, Salida, Estado) = pypm.GetDeviceInfo(Numero)

        # Si es una unidad de salida
        if Salida == 1:

            # Vemos si es la que queremos
            if Nombre == MIDI_SALIDA:
                Respuesta = Numero

    # Devolvemos el resultado
    return Respuesta

#------------------------------------------------
# El programa
#------------------------------------------------

# Iniciamos el sistema
MIDI_Inicio()

# Hacemos la composición
Composicion()

# Terminamos el sistema
MIDI_Final()
