03 – Javascript – Web Audio API – Convertendo Notas Midi em Frequência
Transformando Notas Midi em Frequências
Definindo Notas com Cálculos Matemáticos
Vamos usar a matemática para definir as notas cromaticamente em 4 faixas horizontais no browser.
Para entender melhor o que é Musical Instrument Digital Interface (MIDI), acesse:
http://newt.phys.unsw.edu.au/jw/notes.html
Podemos dizer que MIDI é um padrão usado para fazer com que o som gerado por diferentes sintetizadores corresponda exatamente às mesmas notas dos instrumentos, ou seja, as frequências das notas.
Contruindo nosso segundo sintetizador web
São três arquivos para esse experimento, mas, o projeto compartilha as pastas css e dependencies, como no exemplo do Theremin.
Obs. As pastas css e dependencies em comum, estão no mesmo nível da pasta de cada projeto.
Dentro da pasta do projeto que eu chamei de Convertendo-Notas-Midi-em-Frequencia temos:
- Uma pasta css com o color.css que é um css específico para esse experimento;
- O index_math_notes.html;
- E o script math_notes.js.
O conteúdo do Web_Audio_API/Convertendo-Notas-Midi-em-Frequencia/css/color.css é:
* {
box-sizing: border-box;
}
body,
html {
height: 100%;
padding: 0;
margin: 0;
font-family: 'Andale Mono', monospace;
font-size: 18px;
text-align: center;
cursor: default;
}
h3 {
display: inline-block;
margin: 0;
padding: 1em;
background-color: white;
-webkit-user-select: none;
}
p {
max-width: 600px;
margin: 0 auto;
margin-bottom: 2em;
line-height: 1.5em;
text-align: left;
}
a {
padding: 0.1em 0.25em;
background-color: #FF4D51;
text-decoration: none;
color: white;
}
a:hover {
background-color: #ff1a1f;
}
.intro {
max-width: 600px;
margin: 0 auto;
}
ul {
list-style-type: none;
text-align: left;
}
ul li {
line-height: 1.5em;
}
.boxes {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
cursor: default;
}
.box {
width: 7.6923076923076925%;
height: 100%;
float: left;
background-color: #d0d0d0;
}
.box:nth-child(2n) {
background-color: #f4f4f4;
}
.horizontal_band {
position: absolute;
left: 0;
right: 0;
}
.horizontal_band--1 {
top: 25%;
bottom: 50%;
background-color: rgba(255, 0, 0, 0.2);
}
.horizontal_band--2 {
top: 50%;
bottom: 25%;
background-color: rgba(255, 0, 0, 0.4);
}
.horizontal_band--3 {
top: 75%;
bottom: 0;
background-color: rgba(255, 0, 0, 0.6);
}
* {
box-sizing: border-box;
}
body,
html {
height: 100%;
padding: 0;
margin: 0;
font-family: 'Andale Mono', monospace;
font-size: 18px;
text-align: center;
cursor: default;
}
h3 {
display: inline-block;
margin: 0;
padding: 1em;
background-color: white;
-webkit-user-select: none;
}
p {
max-width: 600px;
margin: 0 auto;
margin-bottom: 2em;
line-height: 1.5em;
text-align: left;
}
a {
padding: 0.1em 0.25em;
background-color: #FF4D51;
text-decoration: none;
color: white;
}
a:hover {
background-color: #ff1a1f;
}
.intro {
max-width: 600px;
margin: 0 auto;
}
ul {
list-style-type: none;
text-align: left;
}
ul li {
line-height: 1.5em;
}
.boxes {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
cursor: default;
}
.box {
width: 7.6923076923076925%;
height: 100%;
float: left;
background-color: #d0d0d0;
}
.box:nth-child(2n) {
background-color: #f4f4f4;
}
.horizontal_band {
position: absolute;
left: 0;
right: 0;
}
.horizontal_band--1 {
top: 25%;
bottom: 50%;
background-color: rgba(0, 153, 255, 0.2);
}
.horizontal_band--2 {
top: 50%;
bottom: 25%;
background-color: rgba(0, 153, 255, 0.4);
}
.horizontal_band--3 {
top: 75%;
bottom: 0;
background-color: rgba(0, 92, 153, 0.6);
}
O css anterior é específico para o index_math_notes.html, ele define cores horizontais e muda a cor do fundo de dois em dois para dá o efeito de quadriculado na tela do browser.
O Web_Audio_API/Convertendo-Notas-Midi-em-Frequencia/index_math_notes.html é:
<html>
<head>
<title>Web Audio API - Usando a matemática para definir notas</title>
<script src="../dependencies/jquery.min.js"></script>
<script src="math_notes.js"></script>
<link rel="stylesheet/less" href="../css/site.less">
<link rel="stylesheet" type="text/css" href="./css/color.css">
<script src="../dependencies/less.min.js"></script>
</head>
<body>
<div class="boxes">
<!-- Faixas verticais -->
<div class="box"></div><div class="box"></div>
<div class="box"></div><div class="box"></div>
<div class="box"></div><div class="box"></div>
<div class="box"></div><div class="box"></div>
<div class="box"></div><div class="box"></div>
<div class="box"></div><div class="box"></div>
<div class="box"></div>
<!-- Faixas horizontais -->
<div class="horizontal_band horizontal_band--1"></div><!--Segunda faixa horizontal-->
<div class="horizontal_band horizontal_band--2"></div><!--Terceira faixa horizontal-->
<div class="horizontal_band horizontal_band--3"></div><!--Quarta faixa horizontal-->
</div>
<h3>Usando a matemática para definir notas</h3>
<h2><!-- Mostra as coordenadas -->
<span id="x">x = 0</span>
<span id="y">y = 0</span>
</h2>
</body>
</html>
O index_math_notes.html declara as dependências de scripts e css no head do html, inclusive o nosso: Convertendo-Notas-Midi-em-Frequencia/css/color.css específico para esse experimento.
No body ele tem uma class chamada boxes, onde serão colocados os boxes individuais, representando as linhas verticais.
E as classe horizontal_band fazendo as linhas horizontais.
Abaixo do H3 tem um H2 com um span dentro, só para mostrar as coordenadas dos eixos X e Y do movimento do mouse.
O Web_Audio_API/Convertendo-Notas-Midi-em-Frequencia/math_notes.js é:
$( function() {
// pega um contexto de áudio
var ctx = new AudioContext();
// variável que vai representar o oscilador
var osc;
// obtém os elementos span x e y para que possamos exibir valores deles
var horizontal = $('#x');
var vertical = $('#y');
// pega a largura e a altura
var width = $(window).width();
var height = $(window).height();
var x = 0;
var y = 0;
// recalcula a largura e a altura se o tamanho da janela mudar
$(window).resize( function() {
width = $(this).width();
height = $(this).height();
});
$('body').on('mousedown', function(e) {
if (osc) {
osc.stop(0);
}
// cria o oscilador
osc = ctx.createOscillator();
// define o tipo do oscilador
osc.type = 'triangle'; // sine, triangle, sawtooth
// a função mtof recebe uma nota midi como argumento e retorna a freqüência correspondente em Hertz.
// a função mtof define a frequência com base nos valores x e y
osc.frequency.value = mtof(x + y);
// conecta-o à saída, isto é, o destino
osc.connect(ctx.destination);
// inicia a nota
osc.start(0);
});
$('body').on('mouseup', function(e) {
// para a nota quando desclica o botão do mouse (mouseup)
osc.stop(0);
});
$('body').on('mousemove', function(e) {
var x = e.clientX;
var y = e.clientY;
//Mostra as coordenadas na tela
horizontal.text("X = " + x);
vertical.text("Y = " + y);
// faz algumas contas para colocar os valores em intervalos de números:
// converte (0 <-> largura da janela) para (0 <-> 13) e arredonda a parte decimal pare baixo
x = 45 + Math.floor( e.clientX / width * 13 );
// converte (0 <-> altura da janela) para (0 <-> 4) e arredonda a parte decimal pare baixo
y = Math.floor( e.clientY / height * 4 ) * 12;
if (!osc) {
return;
}
// versão menos precisa
//osc.frequency.value = mtof(x + y);
// versão usando setValueAtTime que é mais preciso em relação ao tempo
osc.frequency.setValueAtTime( mtof(x + y), ctx.currentTime );
});
});
// mtof = notas midi para Frequencias
// input: 0 - 127 (embora você pudesse subir mais se quisesse)
// output: frequencia em Hz, de ~8Hz até ~12543Hz
function mtof(note) {
return ( Math.pow(2, ( note-69 ) / 12) ) * 440.0;
}
O código acima inicia o contexto de audio, declara a variável osc que vai representar o oscillator.
Depois pega a largura e a altura da janela e define as coordenadas x e y como zero (0).
Em seguida recalcula a largura e a altura se o tamanho da janela mudar.
Logo depois ele pega o click do mouse no corpo da página, com o evento mousedown, aí, se já existe um oscillador funcionando ele para esse oscilador.
Agora é criado um oscilador e definido o tipo de onda que o ele vai usar.
Após isso, é usada a função mtof que converte midi em frequência para gerar as frequências baseadas nos eixos x e y e conecta o oscilador a à saída, isto é, ao destino.
Nesse ponto, o oscillator é iniciado de fato.
Depois, vem a funçao que para a nota quando o botão do mouse é liberado do click, ou seja, desclicado. É o evento mouseup.
Agora a função que pega o movimento do mouse e faz as contas matemáticas para definir as alturas das notas em cada pondo dos quadriculados formados pelas linha horizintais e verticais.
Logo abaixo duas linhas:
horizontal.text(“X = ” + x);
vertical.text(“Y = ” + y);
Só pra mostrar as coordenadas na tela do browser.
Foi usada a função Math.floor para efetuar o cálculo para a definição da altura das notas.
A Math.floor funciona da seguinte forma, ela arredonda o número para baixo e dispensa a casa decimal.
Math.floor( 45.95); // 45
Math.floor(-45.95); // -46
Através do evento do mouse e.clientX pega a posição do eixo x na tela e e.clientY a posição do y, com esses números é feita a conta.
Converte (0 <-> largura da janela) para (0 <-> 13) e arredonda a parte decimal pare baixo com o Math.floor, com:
x = 45 + Math.floor( e.clientX / width * 13 );
E converte (0 <-> altura da janela) para (0 <-> 4) e arredonda a parte decimal pare baixo com:
y = Math.floor( e.clientY / height * 4 ) * 12;
Se não tiver nenhum oscilador em execução ele simplesmente retorna.
Em osc.frequency.setValueAtTime( mtof(x + y), ctx.currentTime );
Os valores de x e y são passados para o mtof que usa o currentTime do contexto, para ter uma maior precisão ao invés de usar só o osc.frequency.value = mtof(x + y);
que não oferece uma precisão tão boa.
E por último a função mtof é definida.
Ela recebe a nota midi baseada na posição x e y do mouse quando clicado e arrastado pelo corpo da página, faz as contas baseado nas especificações midi e retorna a frequência relativa a cada nota na escala cromática.