Página Inicial > Actionscript, Audio, Comunidade, Flash Platform, Flash Player > Astro – FileReference + Sound API

Astro – FileReference + Sound API

Sound API

Mais um post sobre as novas funcionalidades do Flash Player 10.

Depois de ter visto uma entrada no Coisas Interactivas sobre uma aplicação de scratching numa Nintendo DS, lembrei-me de procurar o que já havia sido feito pela comunidade com as potencialidades da nova Sound API que permite um acesso de baixo nível às amostras de áudio. Ainda não existe nada de tão elaborado, mas é expectável que assim que saia a release final do plugin aparecam frameworks à semelhança com o que aconteceu com o 3D.

As novidades são o evento flash.events.SamplesCallbackEvent que é disparado quando as amostras no buffer estão a acabar para que se possa injectar novas amostras. É por exemplo o ideal para ter um loop limpo, i.e., contínuo e sem atraso.
Associado a este evento existe uma propriedade da classe Sound que permite injectar amostras directamente no buffer, samplesCallbackData do tipo ByteArray.

Isto pode ser visto em acção:

A novidade mais interessante é quando conjugamos a funcionalidade anterior com um novo método da classe Sound que permite extrair informação do áudio, por exemplo um ficheiro carregado do servidor ou até mesmo um ficheiro carregado localmente (mais adiante). Como é fácil de imaginar as potencialidades são enormes: equalização, efeitos, edição, etc.

(...).extract(target:ByteArray, length:Number, startPosition:Number =-1):Number

O protótipo do método é intuitivo mas o que faz é copiar n amostras (length) para um instância ByteArray e como parâmetro adicional é possível definir a posição da primeira amostra (startPosition), por defeito o ponteiro não é alterado entre leituras. Ou seja; se lermos 1024Bytes numa primeira leitura, na segunda leitura o ponteiro vai apontar para o 1025º Byte. Por fim o método devolve o número de Bytes lidos.
Nota: O número de amostras tem de estar compreendido entre 512 e 8192.

Antes de injectar as amostras, estas podem ser manipulas. No exemplo de equalização, separar as diversas bandas com um banco de análise -> Amplificar/Atenuar cada banda -> obter as amostras alteradas com um banco de síntese.

Alguns exemplos em acção:

FileReference

Esta API já existia em versões anteriores do Flash Player e é utilizada para controlar a transferência de um ficheiro entre cliente e servidor. Até agora para manipular um ficheiro presente no cliente era necessário fazer o upload para o servidor e carregar de volta para o Flash Player presente no cliente. Como é fácil de ver se a operação implicar manipular e devolver um ficheiro do cliente o processo pode ser moroso.

No Astro foram introduzidos dois métodos para ler/salvar um ficheiro localmente mas necessitam de interacção com o utilizador prévia, i.e., não é possível antes de o utilizador indicar qual o ficheiro. Mais detalhes sobre estes novos métodos podem consultados na documentação oficial, indicada pelo Lee Brimelow (Platform Evangelist da Adobe) .

Exemplo

Para finalizar fiz um exemplo básico utilizando ambas API's.
Ler um ficheiro local e mudar a sua velocidade de reprodução apenas à custa de descartar amostras em intervalos regulares.( método pouco robusto)

Existe um problema que é o seguinte: as amostras a ser injectadas no buffer têm de ser em formato PCM mas os dados que temos acesso ao ler o ficheiro correspondem ao ficheiro mp3 que está codificado.
Por isso é necessário usar um workaround que se sustenta na capacidade do Flash Player criar um SWF em bytecode na memoria. O método não paece muito simples (1,2), mas felizmente hoje o (?)Christopher Martin-Sperry (FlexibleFactory) disponibilizou um biblioteca que faz o serviço por nós com a nuance de perder a informação relativa à ID3 tag no processo. No final obtem-se um objecto Sound com o qual já podemos utilizar o método extract() para ter acesso a amostra codificadas em PCM.

Actionscript:
package{

import flash.net.*;
import flash.utils.ByteArray;
import flash.display.*;
import flash.events.*;
import flash.media.*;

import org.audiofx.mp3.MP3FileReferenceLoader;
import org.audiofx.mp3.MP3SoundEvent;
[SWF(width="400", height="400", backgroundColor="#FFFFFF", framerate="30")]
public class Main extends Sprite{

private const BUFFER_LENGHT:Number = 8192;

private var fileRef:FileReference;
private var loader:MP3FileReferenceLoader;
private var loadedSound:Sound;
private var internalSound:Sound;
private var soundData:ByteArray;

private var btn2:Speeder;

public function Main():void
{
/** UI - Estas classes estão num ficheiro SWC*/
var btn:Button = new Button();
btn2 = new Speeder();
btn2.x = 200;
btn2.y = 200;
btn.x = btn2.x;
btn.y = btn2.y - btn2.height;
addChild(btn2);
addChild(btn);
btn.buttonMode = true;
btn.addEventListener(MouseEvent.CLICK, clickHandler);
/** End UI */

loader=new MP3FileReferenceLoader();
loader.addEventListener(MP3SoundEvent.COMPLETE,mp3LoaderCompleteHandler);

fileRef = new FileReference();
fileRef.addEventListener(Event.SELECT, selectHandler);
}

private function clickHandler(evt:MouseEvent):void
{
fileRef.browse([ new FileFilter("Ficheiros Áudio", "*.mp3")]);
}

private function selectHandler(evt:Event):void
{
loader.getSound(fileRef);
btn2.file(evt.target.name);
}

private function mp3LoaderCompleteHandler(evt:MP3SoundEvent):void
{
loadedSound = evt.sound as Sound;
internalSound = new Sound();
internalSound.addEventListener(Event.SAMPLES_CALLBACK, sampleCallback);
soundData = new ByteArray();
internalSound.play();
}
private function sampleCallback(evt:SamplesCallbackEvent):void
{
soundData.position = 0;
var len:Number = loadedSound.extract(soundData,BUFFER_LENGHT);
if ( len <BUFFER_LENGHT ) {
len += loadedSound.extract(soundData,BUFFER_LENGHT-len,0);
}

soundData.position = 0;
for (var i:uint = 0; i <BUFFER_LENGHT; i++)
{
soundData.position += btn2.speed;
var left:Number = soundData.readFloat();
var right:Number = soundData.readFloat();
internalSound.samplesCallbackData.writeFloat(left);
internalSound.samplesCallbackData.writeFloat(right);
}

}

}
}

UPDATE: Este post foi publicado quando o flash player 10 ainda estava em fase beta, algumas parte da API mudaram para a versão release o que faz com que o código deste exemplo não funcione.