O Pacote Java MIDI Roteiro Principais Classes e Interfaces do Pacote MIDI Acessando Recursos MIDI Carregando Seqüências MIDI Transmitindo e Recebendo Mensagens MIDI Gravando e Editando Seqüências MIDI Recursos Avançados de Sequencer Sintetizando Som Principais Classes e Interfaces do Pacote MIDI Classe MidiMessage MidiMessage é uma classe abstrata que representa uma mensagem MIDI pura; Possui três subclasses: ShortMessages : tipo mais comum, possui um byte de status e no máximo dois de dados (Ex.: Note On); SysexMessages: podem possuir muito bytes e contém instruções especificas do fabricante; MetaMessages: ocorre apenas em arquivos MIDI, contem, por exemplo, configurações de tempo; Classe MidiEvent MidiEvent é uma classe que engloba mensagens MIDI puras junto com informação de tempo; A classe MidiEvent possui métodos para especificar e também obter informação de timestamp: long getTick () void setTick (long tick) Classe Sequence Sequence representa uma composição musical que pode ser lida de um arquivo ou criada em tempo real; É composto por uma coleção de Tracks onde estão armazenados os MidiEvents; Sequence Tracks MidiEvents Interface MidiDevice Objetos que implementem a interface MidiDevice são capazes de enviar e receber mensagens MIDI; Possui métodos para abrir e fechar um dispositivo; Inclui uma classe interna, MidiDevice.Info, que fornece uma descrição textual do dispositivo; Interfaces Transmitter e Receiver Dispositivos MIDI possuem objetos transmissores e objetos receptores; Objetos transmissores implementam a interface Transmitter e objetos receptores implementam a interface Receiver; Interface Sequencer Um sequenciador é um dispositivo para captura e execução de seqüências de eventos MIDI; Normalmente, possui tanto transmissores quanto receptores; Sequenciadores implementam a interface Sequencer que é uma subinterface de MidiDevice; Interface Synthesizer Sintetizadores implementam a interface Synthesizer (subinterface de MidiDevice); Um sintetizador possui canais, representados por objetos que implementem a interface MidiChannel; Normalmente um sintetizador gera sons em resposta a mensagens enviadas aos seus receptores; Acessando Recursos MIDI Classe MidiSystem Assim como a classe AudioSystem, permite descobrir e acessar os dispositivos instalados no sistema; Permite obter os seguintes recursos: Sequenciadores; Sintetizadores; Transmissores; Receptores; Dados a partir de arquivos MIDI; Dados a partir de arquivos de soundbank; Obtendo Dispositivos Padrões Uma aplicação típica inicia obtendo os dispositivos necessários (sequenciadores, sintetizadores, etc.); A classe MidiSystem inclui métodos para obter dispositivos padrão: static static static static Sequencer getSequencer (); Synthesizer getSynthesizer (); Receiver getReceiver (); Transmitter getTransmitter (); Obtendo Dispositivos Instalados A classe MidiSystem possui um método para obter informações sobre os dispositivos instalados: static MidiDevice.Info[] getMidiDeviceInfo () Para obter um dispositivo deve-se utilizar o método: static MidiDevice getMidiDevice (MidiDevice.Info info) Exemplo 1: Obtendo os sintetizadores instalados Vector synthInfo; MidiDevice device; MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo (); for (int i = 0; i < infos.length; i++) { try { device = MidiSystem.getMidiDevice (infos[i]); } catch (MidiUnavailableException e) { // tratar ou throw a exceção } if (device instanceof Synthesizer) { synthInfos.add (infos[i]); } } Abrindo Dispositivos Para reservar um dispositivo : método open() da interface MidiDevice: if (!device.isOpen ()) { try { device.open (); } catch (MidiUnavailableException e) { // tratar ou throw a exceção } } Carregando Seqüências MIDI Carregando um Arquivo MIDI Duas maneiras: Carregar um arquivo MIDI em um InputStream e depois usar: Sequencer.setSequence (InputStream stream) Criar, explicitamente, um objeto Sequence e depois carregá-lo em um Sequencer (Necessário quando se deseja editar uma seqüência MIDI) Exemplo 2: Criando e Carregando um Objeto Sequence try { File midiFile = new File (“seq1.mid”); Sequence seq = MidiSystem.getSequence (midiFile); sequencer.setSequence (seq); } catch (Exception e) { // tratar ou throw a exceção } Transmitindo e Recebendo Mensagens MIDI Dispositivos, Receptores e Transmissores Diferentes MidiDevice podem ser interconectados possibilitando que dados fluam entre eles; MidiDevice possui um ou mais objetos auxiliares que implementam as interfaces Transmitter ou Receiver; Cada transmissor só pode ser conectado a um receptor e vice-versa; Conectando-se a um Dispositivo A interface Transmitter possui um método que permite estabelecer a conexão com o Receiver para o qual serão enviadas as mensagens: void setReceiver (Receiver receiver) Após terminada a conexão deve-se invocar o método close() de Transmitter e Receiver para que os mesmos sejam liberados; Exemplo 2: Conectando um Sequenciador a um Sintetizador Sequencer seq; Transmitter seqTrans; Synthesizer synth; Receiver synthRcvr; try { seq = MidiSystem.getSequencer (); seqTrans = seq.getTransmitter (); synth = MidiSystem.getSynthesizer (); synthRcvr = synth.getReceiver (); seqTrans.setReceiver (synthRcvr); } catch (MidiUnavailableException e) { // tratar ou throw a exceção } Exercício 1 Abrir e tocar um arquivo MIDI; Como enviar a mesma mensagem para diferentes dispositivos ? Basta usar mais de um transmissor e mais de um receptor; MidiDevice possui métodos para descobrir quantos transmissores e receptores um dispositivo suporta: int getMaxTransmitters () int getMaxReceivers () Exemplo 3: Conectando um Porta de Entrada a um Sequenciador e a um Sintetizador Sequencer seq; Synthesizer synth; MidiDevice inputPort; // Obter e abrir os três dispositivos Transmitter inPortTrans1, inPortTrans2; Receiver synthRcvr, seqReceiver; try { inPortTrans = inputPort.getTransmitter (); synthRcvr = synth.getReceiver (); inPortTrans1.setReceiver (synthRcvr); inPortTrans2 = inputPort.getTransmitter (); seqRcvr = seq.getReceiver (); inPortTrans2.setReceiver (seqRcvr); } catch (MidiUnavailableException e) { // tratar ou throw a exceção } Quando Usar um Sequenciador É possível enviar mensagens MIDI diretamente para um dispositivo sem usar um sequenciador; Essa técnica pode ser utilizada quando o próprio programa cria as mensagens em tempo real; Ex.: Programa que simula um piano Sequenciadores são utilizados para lidar com dados lidos de um arquivo MIDI e também para gravar dados MIDI; Enviando Mensagem para um Receptor sem Usar um Transmissor A interface Receiver contém um método que envia mensagens para o receptor: void send (MidiMessage message, long timeStamp) Criar um objeto ShortMessage e usar o método: Void setMessage (int command, int channel, int data1, int data2) Exemplo 4: Enviando uma Mensagem sem Usar um Transmissor ShortMessage msg = new ShortMessage (); // Tocar a nota Middle C (60) com velocidade = 93 msg.setMessage (ShortMessage.NOTE_ON, 0, 60, 93); long timeStamp = -1; Receiver rcvr = MidiSystem.getReceiver (); Rcvr.send (msg, timeStamp); Exercício 2 Enviar mensagens MIDI diretamente para um sintetizador sem usar um Sequenciador Roteiro Principais Classes e Interfaces do Pacote MIDI Acessando Recursos MIDI Carregando Seqüências MIDI Transmitindo e Recebendo Mensagens MIDI Gravando e Editando Seqüências MIDI Recursos Avançados de Sequencer Sintetizando Som Gravando e Editando Seqüências MIDI Tracks Arquivos MIDI são organizados em tracks; Normalmente cada track contem as notas de um único instrumento (não é obrigado pelo padrão); Arquivos MIDI são organizados em uma hierarquia de três níveis: Sequence; Track; MidiEvents; Sequence Tracks MidiEvents MidiEvents e Ticks Em um MidiEvent o tempo é expresso tendo como base o conceito de tick; A duração de um tick varia de acordo com a sequência MIDI e seu valor é armazenado no cabeçalho de um arquivo MIDI; O tamanho de um tick pode ser dado em duas unidades: Pulsos por quarto de nota (PPQ) Ticks por frame (SMPTE) Na API Java os valores de tick medem tempo cumulativo; Gravando e Salvando Sequences (1/2) 1. 2. 3. Obtenha um Sequencer através de MidiSystem; Estabeleça a conexão entre o Sequencer e o objeto que transmitirá as mensagens MIDI; Crie um novo objeto Sequence: Sequence (float divisionType, int resolution ) Sequence (float divisionType, int resolution, int numTracks ) 4. Crie um objeto Track caso isso não seja feito no construtor: Sequence.createTrack(); Gravando e Salvando Sequences (2/2) 5. Relacione o objeto Sequence criado com o Sequencer usado: Sequencer.setSequence(Sequence sequence) 6. Chame o método Sequencer.recordEnable (); 7. Chame o método Sequence.startRecording (); 8. 9. Quando terminar, chame Sequencer.stop () ou Sequencer.stopRecording (); Salve o objeto Sequence gravado usando MididSystem.write(); Editando uma Seqüência Objetos do tipo Sequence permitem que sejam adicionados ou removidos Tracks: Track createTrack () Boolean deleteTrack (Track track) As Tracks são armazenadas em um objeto Sequence através de um Vector; Editando uma Seqüência Os MidiEvents contidos em uma Track também são armazenados em um Vector; Os métodos de Track são: boolean add (MidiEvent event) MidiEvent get (int index) boolean remove (MidiEvent event) int size () long ticks () Exercício 3 Criar alguns eventos MIDI, com mensagens Note On e Note Off, e gravá-los em um SMF (Usar PPQ com, por exemplo, resolução 12; Recursos Avançados de Sequencer Posição de uma Sequence Obtendo a posição corrente do Sequencer em um Sequence: Long getTickPosition () Long getMicrosecondPosition () Movendo para um ponto arbitrário em um objeto Sequence: void setTickPosition (long Tick) void setMicrosecondPosition (long microsecond) Mudando a Velocidade de Execução A velocidade de uma seqüência é indicada pelo seu tempo; Pode-se mudar o tempo de uma seqüência através de eventos MIDI ou através da chamada de métodos de Sequencer: void setTempoInBPM (float bpm) void setTempoInMPQ (float mpq) void setTempoFactor (float factor) Mute e Solo em Tracks Pode-se escolher que Tracks irão contribuir para o stream de mensagens MIDI gerados pelo Sequencer; void setTrackMute (int track, boolean mute) void setTrackSolo (int track, boolean solo) Para verificar o status de uma Track: boolean getTrackMute (int track) boolean getTrackSolo (int track) Sincronizando com Outros Dispositivos Sequencer possui uma classe interna Sequencer.SyncMode que representa os modos como um Sequencer pode ser sincronizado; Um Sequencer pode ser sincronizado como Master e/ou Slave: void setMasterSyncMode (Sequencer.SyncMode sync) void setSlaveSyncMode (Sequencer.SyncMode sync) Listeners par Eventos Especiais Especificando Listeners para eventos especiais: Int[] addControllerEventListener (ControllerEventListener listener, int[] controllers) Boolean addMetaEventListener (MetaEventListener listener) Exercício 4 Alterar o tocador MIDI de modo que seja possível executar Mute e Solo em Tracks especificas; Sintetizando Som A Síntese de Sons em Java A arquitetura para síntese de sons em Java é composta de três interfaces Synthesizer MidiChannel Soundbank E quatro classes: Instrument Patch SoundbankResource VoiceStatus Verificando Quais Instrumentos Estão Carregados Para carregar o Soundbank padrão deve-se usar o seguinte método de Synthesizer: Soundbank getDefaultSoundbank () Para descobrir quais instrumentos estão atualmente carregados deve-se usar o seguinte método de Synthesizer: Instrument[] getLoadedInstruments() Carregando Instrumentos Para descobrir quais instrumentos pertencem ao Sintetizador: Instrument[] getAvailableInstruments () Um instrumento pode ser carregado usando: boolean loadInstrument (Intrument instrument) O instrumento será carregado na posição especificada pelo seu objeto Patch; Carregando Instrumentos Cada objeto do tipo Instrument possui um objeto Patch que especifica onde o instrumento deverá ser carregado; Esse local é definido pelo número do banco e número do programa; É possível carregar o instrumento em um outro local através do seguinte método de Synthesizer: boolean remapIntrument (Intrument from, Instrument to) Descarregando Instrumentos Existem três métodos para descarregar instrumentos: void unloadAllInstruments (Soundbank soundbank) void unloadInstrument(Instrument instrument) void unloadInstruments(Soundbank soundbank, Patch[] patchList) Acessando Canais Existem 2 maneiras para controlar o sintetizador sem usar um sequenciador: Como já foi visto, pode-se enviar uma mensagem diretamente a um receptor do sintetizador através do método send (); Outra opção seria interagir diretamente com objetos MidiChannel; Exemplo 6: Exemplo 4 Usando Canais Synthesizer synth = MidiSystem.getSynthesizer (); // Obtem todos os canais do sintetizador MidiChannel chan[] = synth.getChannels (); // Checa se o canal existe if (chan[4] != null) { chan[4].noteOn (60, 93); } Mute e Solo em Canais A API Java Sound adiciona a noção de Solo e Mute por canais; Essa operação é similar as de Mute e Solo para Tracks; São fornecidos quatro métodos: boolean getMute () boolean getSolo () void setMute (boolean muteState) void setSolo (boolean soloState) Alterando o Instrumento de um Canal Para descobrir qual o instrumento atualmente alocado a um canal deve-se usar: int getProgram () Para modificar o instrumento associado: void programChange (int program) OBS.: Instrumento deve estar carregado no sintetizador ! Exercício 5 Enviar mensagens Note On e Note Off diretamente para o canal de um sintetizador (sem usar MidiMessages). Permitir também a mudança de Instrumentos;