Theremin Meteo · Integración de API externa

Repositorio de GitHub: https://github.com/dimecris/theremin-API

Emulación realizada en Android Studio con  las siguientes versiones:
Android Studio: Android Studio Otter 3 Feature Drop (2025.2.3)
SDK utilizado para compilación: API Level 36
Dispositivo de pruebas (emulador): Android 16.0 “Baklava” — API Level 36.1

Clima, movimiento y sonido como sistema expresivo

Esta segunda entrega del proyecto Theremin Meteo parte de la base desarrollada en la PR1 y amplía la aplicación con una nueva capa conceptual y técnica: la integración de una API externa que transforma el clima real en sonido y visualización generativa.

El objetivo de esta fase no ha sido añadir funcionalidades accesorias, sino profundizar en la idea central del proyecto, convirtiendo el theremin en una experiencia audiovisual sensible al contexto ambiental y al propio cuerpo del usuario.

1. Problemática abordada

Una de las problemáticas detectadas en la exploración inicial del proyecto es que muchas aplicaciones musicales digitales reproducen esquemas tradicionales de interacción (botones, teclas, sliders), lo que limita la expresividad corporal y la experimentación sonora, especialmente en contextos no profesionales.

Además, el uso de datos externos —como el clima— suele reducirse a un papel informativo o decorativo, sin una integración real en la lógica creativa de la aplicación.

Theremin Meteo aborda estas cuestiones proponiendo un sistema donde:

  • La interacción se basa en el gesto corporal y no en controles convencionales.
  • El entorno (clima real) se integra como un parámetro creativo activo, no como información secundaria.
  • El usuario puede generar sonido y visuales sin conocimientos musicales previos.

La aplicación no busca precisión técnica ni comportamiento “de instrumento clásico”, sino facilitar una experiencia expresiva, lúdica y exploratoria, válida tanto para “pasar un buen rato” como para experimentar con ideas musicales o performativas.

2. Ideación inicial de la nueva funcionalidad

En la PR1, la aplicación se centraba en la relación directa entre gesto y sonido mediante la inclinación del dispositivo. Para la PR2, la idea fue extender esa relación incorporando un tercer elemento: el entorno.

La pregunta que guió esta fase fue:

¿Qué pasaría si el instrumento no solo respondiera al movimiento del usuario, sino también al clima del lugar donde se encuentra?

A partir de esta idea, el theremin deja de ser únicamente un instrumento gestual y se convierte en un sistema reactivo donde:

  • El movimiento controla el tono y la intensidad.
  • El clima define la escala musical, el timbre y la estética visual.
  • Cada ciudad genera una experiencia sonora distinta.

3. Evolución respecto a la PR1

Mientras que en la PR1 el foco estaba en la interacción gesto–sonido, en esta segunda fase se introduce una capa de interpretación de datos externos en tiempo real. Esta evolución se concreta en:

  • Integración de una API meteorológica pública (Open-Meteo).
  • Uso del clima como estado global que afecta a sonido y visuales.
  • Incorporación de nuevas interacciones nativas: shake para cambiar ciudad y haptics como refuerzo táctil.
  • Refinamiento de la arquitectura modular para absorber mayor complejidad (environment + estado normalizado).
  • Diseño de una pantalla de bienvenida (intro) que explica cómo usar la app.
  • Ocultación de controles avanzados para mantener una interfaz limpia (settings menos intrusivos).
  • Simplificación y minimalización de la UI sin perder funcionalidad.
  • Bloqueo de pantalla del dispositivo en horizontal para una mejor experiencia de visualización de ondas.

Además, aunque el enunciado no exigía desarrollar para iOS, decidí implementarlo también para probar el comportamiento real en un dispositivo físico (permisos, audio y renderizado), y no depender únicamente del simulador.

4. Elección de la API externa

4.1. Proceso creativo de selección

Durante la fase de ideación se valoraron varias posibilidades de APIs externas: datos culturales (eventos por ciudad), datos de sonido (catálogos), APIs de imagen/arte generativo, e incluso datos “biométricos” simulados. Finalmente opté por una API meteorológica por tres motivos:

  • Es una variable universal y dinámica (cambia por ciudad y por momento).
  • Permite asociaciones sensoriales bastante intuitivas (frío/calor, calma/tormenta, claridad/niebla).
  • Datos externos que modifican la experiencia y no solo se muestran.

4.2. API elegida: Open-Meteo

La API elegida para esta fase es Open-Meteo, ya que cumple los requisitos del enunciado:

  • Es gratuita.
  • No requiere autenticación ni API Key.
  • Permite peticiones desde navegador (CORS).
  • Tiene documentación clara y endpoints estables.

Se utiliza tanto el geocoding (ciudad → coordenadas) como el endpoint meteorológico (coordenadas → clima actual).

5. Integración técnica de la API

La integración se encapsuló en un módulo específico (environment.js) para mantener la lógica desacoplada del resto de módulos (audio, sensores, visualización y storage). El flujo es:

  1. El usuario escribe una ciudad o agita el móvil y obtiene una ciudad aleatoria de un array.
  2. Se resuelve por geocoding (lat/lon/timezone).
  3. Se consulta el clima actual.
  4. Se construye un “estilo meteorológico” normalizado (0..1) y se decide la escala/timbre.
  5. Se aplica al audio y al sistema visual.

5.1. Geocoding: ciudad → coordenadas

async geocodeCity(city) {
  const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city.trim())}&count=1&language=es&format=json`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Geocoding error: ${res.status}`);

  const data = await res.json();
  if (!data.results?.length) throw new Error('Ciudad no encontrada');

  const r = data.results[0];
  return {
    name: `${r.name}${r.admin1 ? ', ' + r.admin1 : ''}${r.country ? ', ' + r.country : ''}`,
    lat: r.latitude,
    lon: r.longitude,
    timezone: r.timezone || 'auto'
  };
}

5.2. Weather: coordenadas → clima actual

async fetchMeteo(lat, lon, timezone = 'auto') {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,relative_humidity_2m,cloud_cover,wind_speed_10m,visibility,precipitation,weather_code,is_day&timezone=${encodeURIComponent(timezone)}`;

  const res = await fetch(url);
  if (!res.ok) throw new Error(`Meteo error: ${res.status}`);

  const { current: c } = await res.json();
  return {
    temperature: c.temperature_2m,
    humidity: c.relative_humidity_2m,
    cloudCover: c.cloud_cover,
    windSpeed: c.wind_speed_10m,
    visibility: c.visibility,
    precipitation: c.precipitation,
    weatherCode: c.weather_code,
    isDay: c.is_day === 1
  };
}

6. Diagrama de flujo (visión global)

Para clarificar el funcionamiento general de la aplicación, incluyo un diagrama de flujo que resume el ciclo completo de interacción: desde la pantalla de bienvenida y permisos, hasta la consulta dinámica del clima, el mapeo audiovisual y el loop interactivo en tiempo real.

Diagrama de flujo de Theremin Glitch

7. Mapeo clima → sonido → visual

Para que el clima afecte a la app de forma reutilizable (y no como reglas aisladas), una decisión clave fue normalizar variables meteorológicas a rangos comunes 0..1. Esto permite aplicar el mismo “estado climático” al motor visual (p5.js) y al motor sonoro (Web Audio API) con transiciones suaves.

7.1. Normalización (0..1) y estado global

// Normalizar: convierte un valor real a 0..1 y lo limita
norm(value, min, max) {
  return Math.max(0, Math.min(1, (value - min) / (max - min)));
}

// Ejemplo de construcción de estilo climático 
buildWeatherStyle(meteo) {
  return {
    t01: this.norm(meteo.temperature, -20, 40),
    h01: this.norm(meteo.humidity, 0, 100),
    c01: this.norm(meteo.cloudCover, 0, 100),
    w01: this.norm(meteo.windSpeed, 0, 30),
    vis01: this.norm(meteo.visibility, 0, 10000),
    p01: this.norm(meteo.precipitation, 0, 10),
    // + paleta, flags y rawData...
  };
}

7.2. Qué afecta realmente al sonido

En esta versión, el clima afecta al sonido a través de decisiones musicales (para mantener musicalidad) y mediante una capa de modulación ambiental basada en Web Audio API:

  • Escala musical: Se usa para cuantizar la frecuencia y mantener coherencia melódica.
  • Tipo de onda: Cambia el timbre base del oscilador (sine/square/sawtooth/triangle).
  • Humedad: Se aplica como un filtro paso bajo (low-pass), haciendo el sonido más “apagado” cuando aumenta la humedad.
  • Viento: Se aplica como vibrato (LFO), incrementando la oscilación de la frecuencia a medida que aumenta la velocidad del viento.

Nota: No se han implementado reverb o distorsión (queda como mejora futura), pero el sistema ya introduce variaciones audibles claras mediante filtro y vibrato, además de escala y timbre.

7.3. Decisión musical según clima (WMO + temperatura)

decideScale(meteo) {
  const { temperature: t, weatherCode: w } = meteo;

  if (this.isStormCode(w)) return { scaleName: 'blues', mood: 'Tormentoso', waveType: 'square' };
  if (this.isSnowCode(w))  return { scaleName: 'lydian', mood: 'Nevado', waveType: 'sine' };
  if (this.isRainCode(w))  return { scaleName: 'dorian', mood: 'Lluvioso', waveType: 'triangle' };
  if (this.isFogCode(w))   return { scaleName: 'pentatonic_minor', mood: 'Neblinoso', waveType: 'sine' };

  if (t < 0)   return { scaleName: 'pentatonic_minor', mood: 'Gélido', waveType: 'sine' };
  if (t < 10)  return { scaleName: 'dorian', mood: 'Frío', waveType: 'triangle' };
  if (t < 20)  return { scaleName: 'pentatonic_major', mood: 'Templado', waveType: 'sine' };
  if (t < 30)  return { scaleName: 'mixolydian', mood: 'Cálido', waveType: 'sawtooth' };

  return { scaleName: 'major', mood: 'Caluroso', waveType: 'sine' };
}

7.4. Aplicación del resultado al audio

// main.js (simplificado)
const meteo = await env.fetchMeteo(loc.lat, loc.lon, loc.timezone);
const decision = env.decideScale(meteo);

thereminAudio.setScale(decision.scaleName);
thereminAudio.setWaveType(decision.waveType);

const style = env.buildWeatherStyle(meteo);
thereminAudio.setEnvironment(style);

8. Funcionalidades nativas e interacción

La app hace uso de funcionalidades del dispositivo integradas con Capacitor para potenciar la interacción:

  • Sensores de orientación: Control continuo de frecuencia y volumen (theremin gestual).
  • Shake: Cambio aleatorio de ciudad (interacción rápida y “lúdica”).
  • Feedback háptico (Haptics): Confirma acciones sin recargar la interfaz.
  • Permisos: Especialmente relevantes en iOS para sensores.

8.1. Haptics: refuerzo táctil

Se incorporaron vibraciones sutiles para reforzar acciones clave, aportando confirmación física sin añadir ruido visual:

// Al iniciar audio
Haptics.impact({ style: ImpactStyle.Light });

// Al detectar shake (cambio de ciudad)
Haptics.impact({ style: ImpactStyle.Medium });

// Al detener audio
Haptics.impact({ style: ImpactStyle.Light });

// Al cambiar tipo de onda
Haptics.impact({ style: ImpactStyle.Light });

8.2. Bloqueo de orientación horizontal

Para mantener una experiencia consistente (composición visual y lectura del canvas), se bloqueó la orientación en modo landscape dentro de la configuración nativa de Android y de iOS:

// Android – AndroidManifest.xml
<activity
  android:name=".MainActivity"
  android:screenOrientation="landscape"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
  android:label="@string/title_activity_main"
  android:theme="@style/AppTheme.NoActionBarLaunch"
  android:launchMode="singleTask"
  android:exported="true">

// iOS – info.plist
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeRight</string>

9. Justificación del cambio: de p5.sound a Web Audio API

Durante el desarrollo surgió la duda de si gestionar el audio con p5.sound (siguiendo el enfoque del tutorial e incluyendo la librería en public/) o pasar a un enfoque más directo con Web Audio API. En la fase de pruebas reales en dispositivo se detectaron comportamientos inconsistentes y errores, y diferencias entre plataformas.

Por este motivo, el audio se implementó finalmente con Web Audio API, creando explícitamente un AudioContext y una cadena de nodos que permite síntesis y modulación ambiental: OscillatorNode (oscilador principal), BiquadFilterNode (filtro low-pass) y GainNode (control de volumen), además de un LFO para vibrato.

Ejemplo técnico (Web Audio API)

// Cadena de audio (simplificada)
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

this.osc = this.audioContext.createOscillator();
this.filter = this.audioContext.createBiquadFilter();  // low-pass (humedad)
this.gain = this.audioContext.createGain();

this.osc.connect(this.filter);
this.filter.connect(this.gain);
this.gain.connect(this.audioContext.destination);

// LFO (viento → vibrato)
this.lfo = this.audioContext.createOscillator();
this.lfoGain = this.audioContext.createGain();
this.lfo.connect(this.lfoGain);
this.lfoGain.connect(this.osc.frequency);

Actualización por frame (gesto → frecuencia / volumen)

// update(tiltX, tiltY) (simplificado)
const now = this.audioContext.currentTime;

// Frecuencia cuantizada + glide
const normX = (tiltX + 1) / 2;
const rawFreq = this.minFreq + normX * (this.maxFreq - this.minFreq);
const quantizedFreq = this.quantizeToScale(rawFreq);

this.osc.frequency.cancelScheduledValues(now);
this.osc.frequency.setValueAtTime(this.osc.frequency.value, now);
this.osc.frequency.exponentialRampToValueAtTime(quantizedFreq, now + this.glideTime);

// Volumen suavizado
const normY = (tiltY + 1) / 2;
const volume = Math.max(0, Math.min(1, normY));

this.gain.gain.cancelScheduledValues(now);
this.gain.gain.setValueAtTime(this.gain.gain.value, now);
this.gain.gain.linearRampToValueAtTime(volume * 0.3, now + 0.03);

10. Integración del feedback docente

A partir del feedback recibido tras la PR1, en esta segunda fase se aplicaron mejoras concretas:

  • Se incorporó una pantalla de bienvenida con explicación de gestos.
  • Se reorganizó la interfaz para mostrar/ocultar ajustes y mantener el diseño limpio.
  • Se priorizó una única funcionalidad principal bien resuelta: crear una experiencia musical/visual sensible al gesto y al clima.
  • Se orientó el proyecto hacia un uso exploratorio y performativo (crear textura sonora, probar ciudades, improvisar).

11. Bocetos, capturas e iteraciones

A lo largo del proceso se desarrollaron bocetos y prototipos que ayudaron a iterar desde una interfaz más “técnica” a una versión final más minimalista y utilizable.

Wireframes 


Prototipos

12. Problemas encontrados y soluciones

  • Gestión de permisos de sensores en iOS: se resolvió solicitando permisos explícitos antes de activar listeners cuando fue necesario en pruebas.
  • Valores no finitos en sensores / audio: se reforzó la validación y normalización para evitar NaN/Infinity antes de mapear a sonido.
  • Audio bloqueado en móvil: se estructuró la UX para iniciar el audio tras interacción del usuario (botón de inicio), siguiendo las políticas del navegador.
  • Diferencias entre plataformas: se priorizó el testing en dispositivo real (iOS) y virtual (Android) y se ajustaron decisiones técnicas para robustez.
  • Consistencia visual: bloqueo de orientación en Android e iOS para mantener composición y experiencia de uso.
  • En iOS hay una propiedad que es shake undoneText y la tuve que desabilitar en el manifiensto y en .list

13. Conclusiones

En esta PR2, Theremin Meteo se consolida como un MVP funcional (producto mínimo viable) donde gesto, entorno y sonido forman un único sistema expresivo. La API meteorológica no actúa como un añadido superficial, sino como un elemento estructural que transforma la experiencia: cada ciudad y cada clima producen una identidad sonora y visual distinta.

El proyecto demuestra cómo los datos externos pueden integrarse de forma creativa y significativa en una aplicación interactiva, manteniendo una interfaz clara, una arquitectura modular y un foco bien definido.

El desarrollo de este proyecto me ha fascinado por la sinergia entre lo humano y lo externo. Pone el foco en la expresión artística del usuario, pero introduce el contexto ambiental como parte activa del proceso creativo. En este sentido, el instrumento no suena igual cada día, porque el entorno tampoco es el mismo. La variabilidad climática introduce matices que afectan tanto a la textura sonora como a la atmósfera visual, haciendo que cada sesión sea ligeramente distinta.

14. Uso de herramientas de IA

Durante el desarrollo se han utilizado herramientas de inteligencia artificial como apoyo (no como sustitución) para resolver dudas técnicas, revisar código y estructurar la documentación:

  • GitHub Copilot: sugerencias de implementación y correcciones puntuales.
  • ChatGPT: apoyo para resolver dudas, contrastar decisiones y estructurar la memoria del proyecto.

Las decisiones finales de código, diseño, implementación y redacción las he tomado personalmente durante todo el proceso.

15. Bibliografía (formato Harvard, en HTML)

Anexo

1.-Opciones de publicación de la aplicación

Para la distribución de la aplicación desarrollada se han investigado dos vías principales: la publicación en una tienda oficial (Google Play Store) y la publicación directa en la web. A continuación se describen brevemente ambos procesos y se justifica la opción elegida.


Publicación en tienda oficial: Google Play Store

Publicar una aplicación en Google Play implica un proceso formal que garantiza ciertos estándares de calidad, seguridad y cumplimiento legal.

  • Registro como desarrolladora: Es necesario crear una cuenta en Google Play Console, con un pago único de 25 USD.
  • Preparación técnica: La aplicación debe compilarse en formato Android App Bundle (AAB) y firmarse digitalmente.
  • Ficha de la aplicación: Se deben añadir descripciones, imágenes, iconos, categoría y datos de contacto.
  • Aspectos legales: Es obligatorio declarar permisos, uso de datos y enlazar una política de privacidad.
  • Revisión: Google revisa la aplicación antes de su publicación para comprobar que cumple sus políticas.

Este método permite una distribución oficial y gran visibilidad, pero requiere más tiempo, trámites legales y adaptación a normas de la plataforma.

Publicación de una app Android mediante descarga directa (APK)

Una aplicación Android puede distribuirse fuera de las tiendas oficiales ofreciendo un archivo instalable en formato APK directamente desde una página web. Este sistema permite que cualquier persona con un dispositivo Android descargue e instale la aplicación sin necesidad de usar Google Play.

1. Generación del archivo APK

El proceso comienza compilando la aplicación en modo release desde Android Studio. Si la app está desarrollada con Capacitor, primero se debe generar el build web y sincronizar el proyecto nativo:

npm run build
npx cap sync android
npx cap open android

Desde Android Studio se genera un APK firmado, ya que Android no permite instalar aplicaciones de distribución si no están firmadas digitalmente. Esta firma se realiza mediante un keystore propio, que también permitirá publicar futuras actualizaciones de la app.

2. Verificación en un dispositivo real

Antes de publicarlo, es recomendable instalar el APK manualmente en un móvil Android para comprobar que la aplicación se abre correctamente, que los sensores funcionan y que no existen errores críticos en la versión de producción.

3. Publicación en la web

Una vez validado, el archivo .apk se sube al servidor y se crea una página de descarga que incluya:

  • Nombre y versión de la aplicación
  • Tamaño del archivo
  • Botón de descarga directa
  • Instrucciones de instalación
  • Aviso de seguridad

4. Instrucciones para la persona usuaria

Como la aplicación no proviene de Google Play, Android solicitará un permiso especial para instalar aplicaciones externas. Es importante explicar el proceso de forma clara:

  1. Descargar el archivo APK
  2. Abrirlo desde el dispositivo
  3. Permitir la instalación desde el navegador o gestor de archivos
  4. Completar la instalación

Este aviso es normal en cualquier instalación externa a la tienda oficial. Forma parte de las medidas de seguridad del sistema Android.

5. Ventajas y limitaciones de este método

La distribución mediante APK desde una web permite:

  • Publicar actualizaciones de forma inmediata
  • Evitar procesos de revisión de tiendas oficiales
  • Tener control total sobre la distribución
  • Facilitar el acceso directo desde la página del proyecto

Como contrapartida, el proceso de instalación requiere un paso adicional por parte de la persona usuaria (permitir instalaciones externas), lo que puede generar cierta fricción o desconfianza si no se explica adecuadamente.

2.- Posibles mejoras y líneas de evolución futura

Aunque Theremin Meteo se presenta como un MVP plenamente funcional  el proyecto deja abiertas diversas líneas de mejora y expansión que permitirían profundizar aún más en su potencial expresivo, interactivo y artístico.

Mejoras en el sistema visual

En el ámbito visual, una posible evolución del proyecto sería el refinamiento del sistema de partículas y de las capas atmosféricas.

  • Un sistema de nubes más orgánicas, con mayor variación de escala, velocidad y profundidad.
  • Mejoras en el movimiento de las partículas, incorporando fuerzas dependientes del viento o del gesto del usuario.
  • Capas visuales adicionales que reaccionen de forma más diferenciada a cada variable meteorológica.

Estas mejoras permitirían reforzar la sensación de inmersión y hacer que el entorno visual responda de forma aún más rica y matizada al clima real.

Mejoras en el sistema sonoro

Como evolución futura, se podrían incorporar:

  • Nuevas capas sonoras (texturas o sonidos ambientales) activadas por parámetros específicos de la API.
  • Una mayor variedad de timbres y síntesis en función de combinaciones climáticas (por ejemplo, viento + lluvia).
  • Procesamiento dinámico adicional (reverberación, filtros o modulación) controlado de forma más granular por variables como humedad, visibilidad o precipitación.

Estas ampliaciones permitirían que el instrumento no solo reaccione al clima, sino que construya paisajes sonoros más complejos y narrativos.

Publicaciones Similares

Deja una respuesta