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

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

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

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

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

# El tiempo de espera entre cada pulso
Espera = 0.08

# Si se desea ver mensajes de depuracion
Depuracion = 0

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

def Composicion():

    # Generamos los compases
    for i in range(1, Compases+1):

        ListaBombo = CA_Lista(Pulsos, 24)
        ListaTom = CA_Lista(Pulsos, 16)
        if i>20 and i<25: Toques = 12
        else:             Toques = 8
        ListaCaja = CA_Lista(Pulsos, Toques)
        ListaPlato = CA_Lista(Pulsos, Toques)

        # Generamos los pulsos
        for j in range(1, Pulsos+1):

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

            if j in ListaBombo:
                Bombo = MIDI_Bombo()
                if CA_Aleatorio() < 80: Volumen = MIDI_Volumen1
                else:                   Volumen = MIDI_Volumen2
                MIDI_Nota(Bombo, Volumen)

            if (i>5 and i<31) and (j in ListaTom):
                Tom = MIDI_Tom()
                if CA_Aleatorio() < 70: Volumen = MIDI_Volumen2
                else:                   Volumen = MIDI_Volumen3
                MIDI_Nota(Tom, Volumen)

            if (i>10 and i<29) and (j in ListaCaja):
              Caja = MIDI_Caja()
              if CA_Aleatorio() < 60: Volumen = MIDI_Volumen3
              else:                   Volumen = MIDI_Volumen4
              MIDI_Nota(Caja, Volumen)

            if (i>15 and i<27) and (j in ListaPlato):
              Plato = MIDI_Plato()
              if CA_Aleatorio() < 40: Volumen = MIDI_Volumen1
              else:                   Volumen = MIDI_Volumen4
              MIDI_Nota(Plato, 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

# Usaremos solo estos cuatro volúmenes
MIDI_Volumen1 = 8
MIDI_Volumen2 = 32
MIDI_Volumen3 = 64
MIDI_Volumen4 = 127

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

# Las listas de instrumentos válidos
MIDI_ListaBombo = (35, 36, 41, 43, 45, 84, 85, 89)
MIDI_ListaTom = (40, 47, 48, 91, 93)
MIDI_ListaCaja = (37, 38, 40, 58, 86, 87, 88)
MIDI_ListaPlato = (42, 44, 46, 49, 51, 53, 55, 57, 90)

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

# El manejador para enviar eventos MIDI
MIDI_Out = 0x00

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

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

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

#------------------------------------------------
# Obtiene un número aleatorio entre 1 y 100
def CA_Aleatorio():
    Respuesta = random.randrange(1,100)
    CA_Texto('{'+str(Respuesta)+'}')
    return Respuesta

#------------------------------------------------
# Obtiene una lista de números aleatorios
def CA_Lista (Maximo, Total):
    Respuesta = range(1,Maximo+1)
    random.shuffle (Respuesta)
    Respuesta = Respuesta[0:Total]
    return Respuesta

#------------------------------------------------
# Inicia todo el sistema MIDI
def MIDI_Inicio():
    global 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 bombo => ' + str(len(MIDI_ListaBombo)) + '\n')
    CA_Texto ('Instrumentos de tom => ' + str(len(MIDI_ListaTom)) + '\n')
    CA_Texto ('Instrumentos de caja => ' + str(len(MIDI_ListaCaja)) + '\n')
    CA_Texto ('Instrumentos de plato => ' + str(len(MIDI_ListaPlato)) + '\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 un bombo al azar
def MIDI_Bombo():
    return random.choice(MIDI_ListaBombo)

#------------------------------------------------
# Elige un tom al azar
def MIDI_Tom():
    return random.choice(MIDI_ListaTom)

#------------------------------------------------
# Elige una caja al azar
def MIDI_Caja():
    return random.choice(MIDI_ListaCaja)

#------------------------------------------------
# Elige un plato al azar
def MIDI_Plato():
    return random.choice(MIDI_ListaPlato)

#------------------------------------------------
# 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()
