terça-feira, 13 de abril de 2010

Teclado musical para PC speaker

Até o início dos anos 90, os micro-computadores compatíveis com o IBM PC utilizavam o PC speaker para reproduzir músicas polifônicas ou efeitos sonoros. Com a criação das placas de som, capazes de reproduzirem sons complexos independentemente da CPU, o PC speaker ficou com seu uso basicamente no processo de inicialização da máquina ou em terminais em modo texto, para comunicação de códigos de erro, avisos etc.

O PC speaker cria ondas sonoras usando o Intel 8253/8254, um chip Temporizador de Intervalo Programável (Programmable Interval Timer, PIT). O PIT é o mais antigo temporizador usado nos IBM PC compatíveis. É usado para execução de funções de contagem e cronometragem através de um oscilador de cristal de 1,193182 MHz e contém três contadores: o contador 0 é usado pelo sistema operacional para o tempo do sistema; o contador 1 foi usado historicamente para a RAM; e o contador 2 é usado pelo PC speaker. Nos PCs x86 modernos o PIT não está mais incluído em um chip separado, sua funcionalidade está incluída no chipset "southbridge" da placa mãe.

O programa abaixo explora um recurso disponível nos terminais que é a reprodução de "beep" pelo PC speaker. A função ioctl(), da linguagem C, fornece uma interface para a manipulação do terminal e o argumento "KIOCSOUND" desta função gera um som no PC speaker.

O código fonte apresentado a seguir é uma versão bem simplificada e foi baseado no programa Beep (http://www.johnath.com/beep/). Contém apenas o necessário para a reprodução dos sons e com uma interface básica para o usuário. Está adequado para ser compilado no ambiente Linux. Por razões de segurança do sistema, provavelmente somente o root tem permissão de acesso ao dispositivo "/dev/sonsole", sendo assim, um usuário comum não conseguirá reproduzir som, a não ser que faça algumas alterações no ambiente. Segue o código:

/*
  Teclado musical para o speaker interno, em ambiente Linux.
  Compilar com o camando: gcc teclado-musical.c -o teclado-musical
*/

#include <fcntl.h>       /* O_WRONLY */
#include <stdio.h>       /* fprintf, stderr, printf */
#include <stdlib.h>      /* exit */
#include <unistd.h>      /* STDIN_FILENO */
#include <linux/kd.h>    /* KIOCSOUND */
#include <termios.h>     /* ICANON, ECHO, TCSANOW */

#define CLOCK_TICK_RATE 1193182  /* Frequência do oscilador do chip i8254
da placa mãe, em Hz */
#define DURACAO_PADRAO  200  /* Em milisegundos */

#define DO  262  /* Frequências das notas */
#define RE  294
#define MI  330
#define FA  349
#define SOL 392
#define LA  440
#define SI  494

#define ESC 27  /* Valor decimal ASCII da tecla de saída do programa */

char meugetch() {  /* Função que substitui o getchar() para não emitir echo
na tela e ser necessário o <enter> */
  
  struct termios tant, tnovo; /* Cria as variáveis com a estrutura termios */
  
  char ch;
  
  tcgetattr( STDIN_FILENO, &tant );  /* Backup dos atributos do terminal
  atual */
  tnovo = tant;  /* Cópia dos atributos atuais para um novo terminal */
  tnovo.c_lflag &= ~( ICANON | ECHO );  /* Desativa caracteres especiais
  e o echo no modo local */
  tcsetattr( STDIN_FILENO, TCSANOW, &tnovo );  /* Usa imediatamente os
  novos atributos */
  ch = getchar();  /* Lê um caractere do teclado */
  tcsetattr( STDIN_FILENO, TCSANOW, &tant );  /* Usa de volta os
  atributos anteriores */
  
  return ch;
}

void toca_nota(int freq, int duracao) {  /* Reproduz o som no console */

  int console_fd;  /* Se a variável receber -1 significa que o arquivo não
  pode ser aberto */

  if((console_fd = open("/dev/console", O_WRONLY)) == -1) {  /* Abre o
    console para somente escrita e checa se não houve erro */
    fprintf(stderr, "Impossivel abrir /dev/console para escrita.\n");
    /* Imprime na saída de erro */
    perror("open");  /* Imprime a mensagem de erro na saída de erro
    padrão */
    exit(1);  /* Saída com erro */
  }

  if(ioctl(console_fd, KIOCSOUND, (int)(CLOCK_TICK_RATE/freq)) < 0) {
    /* Gera o som no console */
    perror("ioctl");  /* Imprime a mensagem de erro na saída de erro
    padrão */
  }
  
  usleep(1000*duracao);  /* Tempo de espera para a duração do som */
  ioctl(console_fd, KIOCSOUND, 0);  /* Encerra o som */
  close(console_fd);  /* Fecha o uso da escrita no console */

}

int main() {
  
  char tecla;

  printf("Digite teclas zxcvbnm para notas\nou ESC para sair\n");
  do{
    tecla = meugetch();
    switch(tecla){
      case 'z':
         toca_nota(DO,DURACAO_PADRAO);
         break;
      case 'x':
         toca_nota(RE,DURACAO_PADRAO);
         break;
      case 'c':
         toca_nota(MI,DURACAO_PADRAO);
         break;
      case 'v':
         toca_nota(FA,DURACAO_PADRAO);
         break;
      case 'b':
         toca_nota(SOL,DURACAO_PADRAO);
         break;
      case 'n':
         toca_nota(LA,DURACAO_PADRAO);
         break;
      case 'm':
         toca_nota(SI,DURACAO_PADRAO);
         break;
    }
  }while(tecla != ESC);
  
  return 0;
}

A função meugetch() não é necessária para a reprodução de som pelo PC speaker, o que ela faz é tratar a entrada pelo teclado para que não seja necessário pressionar a tecla Enter em cada nota. Caso prefira, com a biblioteca ncurses também é possível aprimorar esta entrada pelo teclado.

Nenhum comentário:

Postar um comentário