Transformar la cámara web en un controlador MIDI para Reaper (gratis)

Carlos E. Flores

Zombie
Miembro del Equipo
OVERLORD
Se incorporó
17 Marzo 2005
Mensajes
29.450
Este proyecto lo realicé con código creado a medida por Gemini.

Esto nace por una necesidad de controlar los cambios de volumen, especialmente para los sonidos de orquesta. Pero también sirve para controlar otros parámetros.
La idea es hacerlo como al dirigir una orquesta, indicando con la mano cuándo subir o bajar el volumen.

La aplicación es gratis y va sin ninguna garantía de ningún tipo.

## 1. Instalación Inicial (Solo una vez)

* Descarga e instala el cable virtual loopMIDI desde este enlace:


* Abre loopMIDI, haz clic en el botón "+" (abajo a la izquierda) para crear tu puerto virtual. No cierres la ventana, que quede detrás de los otros procesos.

1787584278345.png


* Guarda el archivo Cam & Hand.exe en una carpeta de fácil acceso, como tu Escritorio (descarga primero el .rar).

1787584502222.png


## 2. Orden de Encendido (Cada vez que trabajes)

* Abre loopMIDI para asegurar que el cable virtual esté activo.
* Abre Cam & Hand.exe con doble clic y verifica que el botón en la pantalla de video esté en "ON".
* Abre Reaper de forma normal.

## 3. Configuración en Reaper

* Ve al menú superior, haz clic en "Actions" y selecciona "Show action list...".
* En el buscador superior (Filter), escribe exactamente esto: Adjust last touched parameter
* Selecciona la acción de la lista llamada "FX: Adjust last touched parameter (MIDI CC/OSC only)".
* Haz clic en el botón "Add..." (abajo a la izquierda de la ventana).
* Mueve tu mano frente a la cámara para que el sistema capture el MIDI CC 119 y presiona "OK".
* Cierra la ventana de la lista de acciones.

1787584762313.png


## 4. Flujo de Trabajo Musical

* Abre el plugin o instrumento virtual en el que vayas a trabajar.
* Haz un clic con el ratón sobre cualquier perilla que desees mover (volumen, modulación, brillo, etc.).
* Mueve tu mano frente a la cámara; ese parámetro te obedecerá al instante.
* Si necesitas detener el control temporalmente, haz clic con el ratón sobre el botón verde "ON" en la ventana de tu cámara para pausarlo.
*Si requiere grabar el volumen, revisar que está todo bien configurado en las automatizaciones:

1787585154571.png




Python:
import cv2
import mediapipe as mp
import mido

def nada(x):
    pass

# Estado global del botón de encendido/apagado
activo = True

# Función para detectar los clics del mouse en la pantalla
def click_raton(evento, x, y, flags, param):
    global activo
    if evento == cv2.EVENT_LBUTTONDOWN:
        # Coordenadas donde estará dibujado nuestro botón
        if 10 <= x <= 150 and 40 <= y <= 80:
            activo = not activo

puertos = mido.get_output_names()
puerto_correcto = next((p for p in puertos if 'loopMIDI' in p), None)

if not puerto_correcto:
    print("Error: No se encontró loopMIDI. Ábrelo y crea el puerto.")
    exit()

salida_midi = mido.open_output(puerto_correcto)
print(f"Éxito. Conectado a: {puerto_correcto}")

mp_manos = mp.solutions.hands
mp_dibujo = mp.solutions.drawing_utils
manos = mp_manos.Hands(max_num_hands=1, min_detection_confidence=0.7, min_tracking_confidence=0.7)

cap = cv2.VideoCapture(0)
cv2.namedWindow('Controlador MIDI')
cv2.setWindowProperty('Controlador MIDI', cv2.WND_PROP_TOPMOST, 1)

# Asignar el detector de clics del mouse a nuestra ventana
cv2.setMouseCallback('Controlador MIDI', click_raton)

cv2.createTrackbar('CC Min (Fondo)', 'Controlador MIDI', 0, 127, nada)
cv2.createTrackbar('CC Max (Techo)', 'Controlador MIDI', 127, 127, nada)
cv2.createTrackbar('Tope Arriba', 'Controlador MIDI', 20, 100, nada)
cv2.createTrackbar('Tope Abajo', 'Controlador MIDI', 80, 100, nada)
cv2.createTrackbar('Suavizado', 'Controlador MIDI', 85, 99, nada)

valor_suavizado = 0.0
ultimo_valor_enviado = -1
mano_detectada_previamente = False

while cap.isOpened():
    exito, frame = cap.read()
    if not exito:
        break

    frame = cv2.flip(frame, 1)

    cc_min = cv2.getTrackbarPos('CC Min (Fondo)', 'Controlador MIDI')
    cc_max = cv2.getTrackbarPos('CC Max (Techo)', 'Controlador MIDI')
    limite_sup = cv2.getTrackbarPos('Tope Arriba', 'Controlador MIDI') / 100.0
    limite_inf = cv2.getTrackbarPos('Tope Abajo', 'Controlador MIDI') / 100.0
    suavizado = cv2.getTrackbarPos('Suavizado', 'Controlador MIDI') / 100.0

    if limite_sup >= limite_inf:
        limite_sup = limite_inf - 0.05

    alto, ancho, _ = frame.shape
    cv2.line(frame, (0, int(limite_sup * alto)), (ancho, int(limite_sup * alto)), (0, 255, 0), 2)
    cv2.line(frame, (0, int(limite_inf * alto)), (ancho, int(limite_inf * alto)), (0, 0, 255), 2)

    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    resultados = manos.process(frame_rgb)

    # ---------------------------------------------------------
    # DIBUJAR EL BOTÓN INTERACTIVO
    # ---------------------------------------------------------
    color_boton = (0, 200, 0) if activo else (0, 0, 200)
    texto_boton = "ON" if activo else "PAUSA"
    
    cv2.rectangle(frame, (10, 40), (150, 80), color_boton, -1)
    cv2.rectangle(frame, (10, 40), (150, 80), (255, 255, 255), 2)
    cv2.putText(frame, texto_boton, (45, 67), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv2.LINE_AA)
    # ---------------------------------------------------------

    if resultados.multi_hand_landmarks:
        mano = resultados.multi_hand_landmarks[0]
        mp_dibujo.draw_landmarks(frame, mano, mp_manos.HAND_CONNECTIONS)
        
        indice_y = mano.landmark[8].y
        rango_fisico = limite_inf - limite_sup
        
        posicion_bruta = (limite_inf - indice_y) / rango_fisico
        rango_val = cc_max - cc_min
        valor_objetivo = cc_min + (posicion_bruta * rango_val)
        
        if not mano_detectada_previamente:
            valor_suavizado = valor_objetivo
            mano_detectada_previamente = True
        else:
            valor_suavizado = (valor_suavizado * suavizado) + (valor_objetivo * (1.0 - suavizado))
        
        v_min_real = min(cc_min, cc_max)
        v_max_real = max(cc_min, cc_max)
        valor_final = int(max(v_min_real, min(v_max_real, valor_suavizado)))
        
        if activo:
            if valor_final != ultimo_valor_enviado:
                mensaje = mido.Message('control_change', channel=0, control=119, value=valor_final)
                salida_midi.send(mensaje)
                ultimo_valor_enviado = valor_final
            cv2.putText(frame, f"MIDI ENVIADO: {valor_final}", (170, 67), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
        else:
            ultimo_valor_enviado = -1
    else:
        mano_detectada_previamente = False

    cv2.imshow('Controlador MIDI', frame)
    
    # También puedes presionar la barra espaciadora en tu teclado para pausar
    tecla = cv2.waitKey(1) & 0xFF
    if tecla == ord('q'):
        break
    elif tecla == ord(' '):
        activo = not activo

salida_midi.close()
cap.release()
cv2.destroyAllWindows()
 
Última modificación:

Carlos E. Flores

Zombie
Miembro del Equipo
OVERLORD
Se incorporó
17 Marzo 2005
Mensajes
29.450
Super interesante, no me imagine que se podía usar la cámara para algo como esto.

Hace mucho que lo tenía como idea. Con controladores nunca me acomodaba bien automatizar el volumen. Y probé con faders, faders digitales y potenciómetros.

No es quizás una solución definitiva, ya que lleva varios pasos, pero de que funciona, funciona.
 

browsons

Capo
Se incorporó
29 Noviembre 2005
Mensajes
348
Hace mucho que lo tenía como idea. Con controladores nunca me acomodaba bien automatizar el volumen. Y probé con faders, faders digitales y potenciómetros.

No es quizás una solución definitiva, ya que lleva varios pasos, pero de que funciona, funciona.
Todo gran invento parte de una necesidad , lo importante es resolverla.
 
Última modificación:
Subir