Páginas

17 de dezembro de 2012

Criando um metrônomo em Java com a IDE NetBeans

Esse é um tutorial cujo objetivo é criar um simulador de metrônomo em Java e analisar seu código fonte. Estarei utilizando como base o software criado por mim chamado JMetrônomo, lançado em 23/07/2012.

Usaremos a versão 7 do Java Development Kit e também o NetBeans. Este último não é essencial, mas ajuda na criação de formulários e na compilação.Você pode baixar o código fonte do JMetrônomo nesse link.

É importante saber um pouco de Java e Orientação a Objetos para não ficar totalmente perdido no código, porém, creio ter deixado comentários e análises suficientes para a compreensão. Não deixe ler o tutorial se ainda não dominar o assunto, pois assim já estará estudando esse modelo de programação de maneira prática.

Para começar, vamos primeiro entender o funcionamento de um metrônomo, utilizando um texto da Wikipédia e um vídeo no Youtube.

 "O metrônomo é um relógio que mede o tempo (andamento) musical. Produzindo pulsos de duração regular, ele pode ser utilizado para fins de estudo ou interpretação musical."



Fica definido que a função do metrônomo é medir o tempo musical e que existem dois tipos de metrônomo: o mecânico e o eletrônico. Seus pulsos são regulares, isto é, têm uma duração e intervalo constante. Essa informação será fundamental para a realização do nosso software, afinal, também é a base do funcionamento do metrônomo.

Um ponto importante presente no vídeo é que há batidas fortes e fracas, que dependem do tipo de compasso da música. Apesar de serem sons diferentes, eles não deixam de formar pulsos regulares.

O metrônomo que vamos criar terá duas classes básicas:
  1. a classe frmMetronomo do formulário, onde o usuário insere a velocidade das batidas em BPM(batidas por minuto) e a quantidade de tempos em um compasso(o primeiro tempo do compasso é o tempo forte e os outros serão tempos fracos);
  2. a classe metronomo, onde o computador irá simular um metrônomo de acordo com as informações fornecidas.

Formulário do JMetrônomo, criado com auxílio da ferramenta NetBeans:

Tela principal do JMetrônomo

Nesta janela, o usuário pode tanto selecionar a velocidade pelo slidebar como especificar na caixa de texto. Depois, ele seleciona o compasso X por X, sendo que o segundo número não altera o funcionamento do compasso, servindo apenas de referência. O usuário pode iniciar ou parar a execução do metrônomo. Há ainda botões que enviam para a minha página na internet, exibem informações sobre o software e saem do mesmo.

Ainda não vamos programar esta janela. Julgo ser melhor trabalhar primeiro a classe metronomo.

Classe metronomo:

package jmetronomo;

import java.util.Timer;
import java.util.TimerTask;

import java.io.InputStream;
import java.io.FileInputStream;
import java.io.File;

import sun.audio.*;  


//classe metrônomo
public class metronomo{
    //atributos que serão enviados pelo formulário ou por outro lugar
    private int BPM;
    private int Tempos;
    private int Nota;
  
    //atributos que serão usados apenas nessa classe
    private double sleep_timer;
    private int tempoatual;
  
    //cria um novo objeto Timer da classe que foi importada importado
    Timer timer = new Timer();
  
    //Construtor que 'pede' parâmetros
    public metronomo(int BPM, int Tempos, int Nota) {
    setBPM(BPM);
    setTempos(Tempos);
    setNota(Nota);
    }
  
  
    //Métodos GET e SET
    public int getBPM() {
        return BPM;
    }
  
    public void setBPM(int BPM) {
        this.BPM = BPM;
    }

    public int getTempos() {
        return Tempos;
    }

    public void setTempos(int Tempos) {
        this.Tempos = Tempos;
    }

    public int getNota() {
        return Nota;
    }

    public void setNota(int Nota) {
        this.Nota = Nota;
    }
 
    public double getSleep_timer() {
        return sleep_timer;
    }

    public void setSleep_timer(double sleep_timer) {
        this.sleep_timer = sleep_timer;
    }

    public int getTempoatual() {
        return tempoatual;
    }

    public void setTempoatual(int tempoatual) {
        this.tempoatual = tempoatual;
    }
  
    //fim dos métodos GET e SET
  
    //método principal que será usado para iniciar o metrônomo
    public void run() {
       
        //fórmula usada para transformar as Batidas por Minuto em um número compatível com o timer
        setSleep_timer( 1.0 / getBPM() * 60000.0);
      
        //iniciando o timer pela classe BEEP, que executará o som a cada intervalo de tempo definido no comando acima
        timer.schedule(new beep(), 0, (long) getSleep_timer());
    }
  
    //classe do beep
    public class beep extends TimerTask {
        public void run() {
              
                //se a variável tempoatual for 0, some um tempo (começa no 1º) e termine o método
                if (getTempoatual() == 0) {
                    setTempoatual(getTempoatual() + 1);
                    return;
                }
              
                // se for o primeiro tempo, o beep será forte, caso o contrário, fraco
                if (getTempoatual() == 1) {
                    //chama o método do beep forte(ver abaixo)
                    BeepForte();
                }
                else {
                    //chama o método do beep fraco(ver abaixo)
                    BeepFraco();
                }
              
                //se o tempo atual atingir a quantidade de tempos de compasso definida pelo usuário, é zerado o compasso
                if (tempoatual == getTempos()) {
                    // zerando o tempo
                    setTempoatual(0);
                }
              
                // avancar um tempo
        setTempoatual(getTempoatual() + 1);

        }
    }

    //método para terminar a execução do programa, simplismente cancela o timer declarado no início e ligado no método run()
    public void encerrar() {
        timer.cancel();
    }

  
    //método do beep forte
    public void BeepForte() {
      
        try{
            //comandos necessários para tocar o som presente no arquivo 'beepforte.wav'
            //obs: acho que este arquivo fica fora do pacote .jar, o que está dentro não consegui executar ainda.
            File diretorio = new File("beepforte.wav");
            InputStream in = new FileInputStream(diretorio);
            AudioStream as = new AudioStream(in);
            AudioPlayer.player.start(as);    
       }
          
       catch(java.io.IOException e) { System.out.println(e); }  
    }
  
    public void BeepFraco() {
      
        try{
            //comandos necessários para tocar o som presente no arquivo 'beepfraco.wav'
            //obs: acho que este arquivo fica fora do pacote .jar, o que está dentro não consegui executar ainda.
            File diretorio = new File("beepfraco.wav");
          
            InputStream in = new FileInputStream(diretorio);
            AudioStream as = new AudioStream(in);   
            AudioPlayer.player.start(as);    
        }
          
       catch(java.io.IOException e) { System.out.println(e); }  
    }
  
}
Fazendo uma rápida leitura, 'metronomo' é uma classe que têm como atributos:
  • BPM: Velocidade em Batidas por Minuto
  • Tempos: Quantidade de tempos no compasso.
  • Nota: Nota equivalente ao tempo. Não tem utilidade nessa classe, ficando aberto à possíveis implementações.
  • Sleep_timer: Valor que indica o intervalo de tempo entre os beeps. É gerado pela conversão do BPM.
  • tempoatual: Indica em qual tempo está o compasso durante a execução.
O método construtor pede BPM, Tempos e Nota. Isso significa que você só poderá criar um objeto da classe 'metronomo' se passar esses atributos. Faz sentido, pois para iniciar o metrônomo precisaremos deles.

Após o construtor, estão os métodos GET/SET. É comum na programação orientada a objetos 'encapsular' os atributos da classe dentro desses métodos. Nesse exemplo esses métodos são praticamente 'inúteis', porém, poderia usá-los para limitar a velocidade BPM e os  Tempos no compasso.

Após os métodos GET/SET, temos o método run(), que começa a executar o metrônomo. Ele possui dois comandos: o primeiro, que converte BPM para o intervalo de tempo que o timer irá usar, e o segundo, que configura o timer para executar a cada intervalo de tempo o beep(presente na classe 'beep').

Sobre o timer: é um recurso presente na maioria das linguagens de programação (talvez todas, não tenho ciência) que funciona executando uma determinada tarefa a cada período de tempo. Por isso, se enquadra perfeitamente em nosso projeto. O timer nesse caso para com o método encerrar()

A classe 'beep' está contida dentro da classe 'metronomo'. Ela extende da classe TimerTask (algo como tarefa do timer), isso quer dizer que ela é um TimerTask com algo a mais: o nosso código. Há uma sobreescrita* do método run().

 *Sobreescrita é uma substituição do método original por um novo. Nesse caso, o método run() que provavelmente não faria nada, agora irá gerar nossos beeps.

Dentro do método run() temos vários comandos: 
  • O primeiro é uma condição que se o tempo atual for 0, ele não executa mais nada e soma um tempo. Isso acontecerá apenas na primeira passagem do método beep.
  • O segundo é outra condição que se o tempo atual for 1, ele exeuta o beep forte, senão, o fraco.
  • O terceiro é outra condição que se o tempo for igual à quantidade informada pelo usuário, é zerado a quantidade atual de tempos.
  • O quarto comando adiciona um tempo.
Vale lembrar que o timer criado funciona como um loop: é executado várias vezes e só para quando alcançar uma determinada condição. Nesse caso, só irá parar quando for executado o método encerrar().

Por fim, os métodos BeepForte() e BeepFraco() executam o arquivo de som correspondente. Infelizmente não consegui rodar o arquivo dentro do pacote .jar, quando for executar é necessário que os dois arquivos de som estejem fora do arquivo .jar. Caso você saiba chamar um arquivo dentro do pacote .jar por favor comente.

Voltando para o nosso formulário, vamos programar o botão Iniciar. No NetBeans basta clicar duas vezes no botão e ele prepará automaticamente o método.

Início da classe frmMetrônomo:
public class frmMetronomo extends javax.swing.JFrame {

    metronomo metro;
    int rodando = 0;


// ........
Botão Iniciar:
private void btnIniciarActionPerformed(java.awt.event.ActionEvent evt) {                                          
       
        //'declarando' variáveis e lendo-as
        int bpm;
        int nota;
        int tempos;
       
        //se o radio button estiver selecionado para selecionar
        if (rdbSelecionar.isSelected()) {
            bpm = slideVelo.getValue();
        }
        //se estiver selecionado para especificar
        else {
            bpm = Integer.parseInt(txtVelocidade.getText());
        }
       
        //lendo os tempos e a nota
        tempos = Integer.parseInt( (String) cboTempos.getSelectedItem());
        nota = Integer.parseInt( (String) cboNota.getSelectedItem());
       
        if (rodando == 1) {
            metro.encerrar();
        }
               
        metro = new metronomo(bpm, tempos,nota);
        metro.run();
        rodando = 1;
    } 
A classe do formulário possui muito código gerado automaticamente pelo NetBeans. Logo após a declaração da classe, criei o objeto metro da classe metronomo (se fosse criado no método do botão iniciar, poderiam rodar vários metronomos ao mesmo tempo e também seria impossível pará-los, o que não é desejado).

No método do botão iniciar declaramos as variáveis que iremos enviar para a nossa classe 'metronomo'. A condição que vem logo após verifica qual modo de inserção do BPM está selecionado: se é pelo slidebar ou pela caixa de texto. Após isso ele lê, convertendo se necessário. Os tempos e a nota serão retirados de um combobox, por isso o comando está grande: nele, primero o objeto é convertido para string e depois para inteiro.

Antes de executar o metrônomo, ele verica se o mesmo já está sendo executado e, se sim,  para-o. Após isso ele instancia o metrônomo passando os valores recebidos no formulário. Executamos assim o método run() e a variável rodando recebe 1.

Há ainda alguns outros métodos da classe frmMetronomo:

Parar:
private void btnPararActionPerformed(java.awt.event.ActionEvent evt) {                                        
         if (rodando == 1) {
            metro.encerrar();
            rodando = 0;
         }
    }
Simplismente: se estiver rodando, chame o método encerrar do objeto metro.

slideVeloStateChanged:
private void slideVeloStateChanged(javax.swing.event.ChangeEvent evt) {                                      
        // atualizando lblvelocidade
        lblVelocidade.setText(Integer.toString(slideVelo.getValue()));
    }
Esse é um evento que diz: quando o valor do slideVelo mudar, ou seja, quando o usuário arrastar o slidebar, o sistema irá atualizar o texto que está logo abaixo indicano a velocidade.

Botão Página na Web:
private void btnWebActionPerformed(java.awt.event.ActionEvent evt) {                                      
        // TODO add your handling code here:
        Desktop d = Desktop.getDesktop(); 
        try { 
           d.browse( new URI( "http://www.jonasweb.orgfree.com/softwares" ) ); 
        } 
        catch ( IOException e ) { 
            System.out.println(e); 
         } 
         catch ( URISyntaxException e ) { 
            System.out.println(e); 
         } 
    }                                     
Este código abre a página indicada no navegador padrão.  

Botão Sair:
 private void btnSairActionPerformed(java.awt.event.ActionEvent evt) {                                       
        System.exit(0);
    }
System.exit(0) fecha todo o software.

Botão Sobre:
private void btnSobreActionPerformed(java.awt.event.ActionEvent evt) {                                        
        JOptionPane.showMessageDialog(rootPane, "JMetrônomo versão 0.9 BETA. \n"
                + "Desenvolvido por Jonas Oliveira Francisco em 23/07/2012. \n"
                + "Visite a página na web para mais informações");
 }   
Exibe uma JOptionPane com meu texto.

Bem, acredito que isto seja o suficiente para o nosso projeto. Após compilar e empacotar o programa, o resultado será esse:

Download

Também fiz uma versão para web, apenas criando um formulário parecido só que para web. No NetBeans basta procurar por "Form Applet" para o software rodar no navegador como se fosse um programa em flash. Neste caso, o resultado será este.

Assim termina o nosso software de metrônomo e espero que tenham não só aprendido a programar um metrônomo, mas também adquirido mais experiência com programação em java e programação orientada a objetos. Ainda sou iniciante em java e não só aceito, como peço sugestões para melhorar o software.

Detalhes finais:
  • A ideia de criar o metrônomo surgiu nas aulas de VB.NET do curso de Informática. Estava testando a funcionalidade Timer e coloquei para executar um beep(). Percebi a semelhança com o metrônomo e decidi programar um em Java, pois queria conhecer mais essa linguagem de programação.
  • Se fisesse em VB.NET seria muito mais fácil, mas em Java aprendi muito sobre orientação a objetos, como por exemplo na hora de criar o timer: precisei criar uma classe que extende da TimerTask e sobreescreve o método run() da mesma. Considerando também que toda a programação em Java ocorre nesse modelo de programação, foi muito útil ter criado o software nessa linguagem. É mais difícil porém aprende-se mais.
  • Se alguém poder me enviar sons melhores eu aceito. Eu queria executar um som de percursão MIDI, acho que ficaria melhor nesse programa.
  • Eu criei outros softwares e também alguns jogos. Basta acessar meu site.