This is a freshened up version of #1723.
Quoting the original PR:
> Impleme…ntation of Flash's SampleDataEvent using WebAudio on HTML5 and OpenAL on Windows/Neko.
> Though untested it should work on other targets using on OpenAL.
In the time since the original PR was proposed, Chrome has [banned autoplaying audio](https://developer.chrome.com/blog/autoplay/#webaudio), so here's a modified version of that test program supporting modern browser & desktop via neko/hl targets:
```haxe
package;
import openfl.display.BitmapData;
import openfl.events.MouseEvent;
import openfl.events.Event;
import openfl.display.InteractiveObject;
import openfl.display.SimpleButton;
import openfl.display.Bitmap;
import openfl.Assets;
import openfl.display.Sprite;
import openfl.media.Sound;
import openfl.events.SampleDataEvent;
import openfl.utils.ByteArray;
class Main extends Sprite {
private var sampleRate:Int;
private var bufferSize:Int = 8192;
private var bpm:Int = 90;
private var numberOfRows:Int = 48; // 48=3/4 time | 64=4/4 time
private var currentRow:Int = 0;
private var quarterNoteLength:Float;
private var sixteenthNoteLength:Float;
private var numOctaves:Int = 8;
private var patterns:Array<Dynamic> = new Array();
private var currentPattern:Int = 0;
private var songOrder:Array<Int> = [0, 1];
private var notes:Array<String> = ["c-", "c#", "d-", "d#", "e-", "f-", "f#", "g-", "g#", "a-", "a#", "b-"];
private var frequencies:Array<Dynamic> = new Array();
private var samplePosition:Int = 0;
private var position:Int = 0;
private var channel1:Dynamic = {
volume: .05,
waveForm: "",
frequency: [],
noteTriggered: false,
envelopePos: 0
};
private var envelope:Array<Float> = new Array();
private var echoBytes:ByteArray = new ByteArray();
private var maxEchoBytes:UInt = 8192 * 256;
private var echoing:Bool = true;
private var echoDelay:Float;
private var echoPosition:UInt = 0;
public function new() {
super();
var bitmapData = new BitmapData(120, 80, false, 0x010203FF);
var pressButton = new BitmapData(120, 80, false, 0x008800FF);
var bitmap = new Bitmap(bitmapData);
var pressbitmap = new Bitmap(pressButton);
var button = new SimpleButton(bitmap, bitmap, pressbitmap, bitmap);
button.x = (stage.stageWidth - bitmap.width) / 2;
button.y = (stage.stageHeight - bitmap.height) / 2;
addChild(button);
button.addEventListener(MouseEvent.CLICK, startSound);
}
function startSound(e:MouseEvent) {
var sound:Sound = new Sound();
sampleRate = sound.sampleRate;
quarterNoteLength = sampleRate * 60 / bpm;
sixteenthNoteLength = quarterNoteLength / 2 / 2;
echoDelay = sixteenthNoteLength * 4;
for (a in 0...numOctaves) {
for (b in 0...notes.length) {
frequencies.push([notes[b % notes.length] + a, 16.35 * Math.pow(2, frequencies.length / 12)]);
}
}
patterns.push([
"g-2|g-4", "", "", "a-4", "a#4", "", "d-2|a-4", "", "g-4", "", "f-4", "", "g-2|g-4", "", "d-4", "", "", "", "", "", "", "", "d-4", "",
"g-2|g-4", "", "", "a-4", "a#4", "", "c-2|c-5", "", "a#4", "", "c-5", "", "d-2|d-5", "", "", "", "", "", "", "", "", "", "d-5", ""
]);
patterns.push([
"g-2|d-5", "", "", "d#5", "d-5", "", "f-2|c-5", "", "a#4", "", "a-4", "", "d#2|a#4", "", "c-5", "", "a#4", "", "d-2|a-4", "", "", "", "d-4", "",
"g-2|g-4", "", "", "a-4", "g-4", "", "d-2|f-4", "", "d-4", "", "f-4", "", "g-2|g-4", "", "", "", "", "", "", "", "", "", "", ""
]);
Reflect.setProperty(channel1, "waveForm", "sawtooth");
for (c in 0...Std.int(quarterNoteLength)) {
if (c < sixteenthNoteLength * 1) {
envelope.push(1.0);
} else {
if (c < sixteenthNoteLength * 3) {
envelope.push(.4);
} else {
envelope.push(0.0);
}
}
}
updateRow();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();
}
private function updateRow():Void {
var tempNote:String = patterns[songOrder[currentPattern]][currentRow];
if (tempNote != "") {
var tempArray:Array<Float> = new Array();
Reflect.setProperty(channel1, "frequency", []);
if (tempNote.indexOf("|") == -1) {
// single note
tempArray.push(findFrequency(tempNote));
} else {
// chord
var tempNotes:Array<String> = tempNote.split("|");
for (a in 0...tempNotes.length) {
tempArray.push(findFrequency(tempNotes[a]));
}
}
Reflect.setProperty(channel1, "noteTriggered", true);
Reflect.setProperty(channel1, "frequency", tempArray);
}
}
private function onSampleData(event:SampleDataEvent):Void {
var tempArray:Array<Float>;
var addDataL:Float = 0.0;
var addDataR:Float = 0.0;
var volumeModifier:Float = 0.0;
trace("Here?");
var sampleData:Float = 0.0;
for (i in 0...bufferSize) {
if (++samplePosition == sixteenthNoteLength) {
if (++currentRow == numberOfRows) {
currentRow = 0;
if (++currentPattern == songOrder.length) {
currentPattern = 0;
}
}
updateRow();
samplePosition = 0;
}
if (Reflect.field(channel1, "noteTriggered")) {
Reflect.setProperty(channel1, "envelopePos", 0);
Reflect.setProperty(channel1, "noteTriggered", false);
}
if (Reflect.field(channel1, "envelopePos") + 1 < envelope.length) {
var tempInt:Int = Reflect.field(channel1, "envelopePos") + 1;
Reflect.setProperty(channel1, "envelopePos", tempInt);
}
volumeModifier = envelope[Reflect.field(channel1, "envelopePos")];
tempArray = Reflect.field(channel1, "frequency");
if (tempArray.length == 1) {
sampleData = generate(Reflect.field(channel1, "waveForm"), position, tempArray[0], Reflect.field(channel1, "volume") * volumeModifier);
} else {
sampleData = generate(Reflect.field(channel1, "waveForm"), position, tempArray[0], Reflect.field(channel1, "volume") * volumeModifier);
sampleData += generate(Reflect.field(channel1, "waveForm"), position, tempArray[1], Reflect.field(channel1, "volume") * volumeModifier);
}
if (echoing) {
if (position > echoDelay) {
var oldPos:UInt = echoBytes.position;
echoBytes.position = echoPosition;
addDataL = echoBytes.readFloat() / 4;
addDataR = echoBytes.readFloat() / 4;
if (echoPosition + 8 < maxEchoBytes) {
echoPosition += 8;
} else {
echoPosition = 0;
}
echoBytes.position = oldPos;
}
}
event.data.writeFloat(sampleData + addDataL);
event.data.writeFloat(sampleData + addDataR);
echoBytes.writeFloat(sampleData);
echoBytes.writeFloat(sampleData);
if (echoBytes.position == maxEchoBytes) {
echoBytes.position = 0;
}
position++;
}
}
private function findFrequency(inpNote:String):Float {
var retVal:Float = 0.0;
for (a in 0...frequencies.length) {
if (frequencies[a][0] == inpNote) {
retVal = frequencies[a][1];
break;
}
}
return retVal;
}
private function generate(waveForm:String, pos:Int, frequency:Float, volume:Float):Float {
var retVal:Float = 0.0;
switch (waveForm) {
case "square":
retVal = Math.sin((pos) * Math.PI * 2 / sampleRate * frequency) > 0 ? volume : -volume;
case "sine":
retVal = Math.sin((pos) * Math.PI * 2 / sampleRate * frequency * 2) * volume;
case "sawtooth":
retVal = (2 * (pos % (sampleRate / frequency)) / (sampleRate / frequency) - 1) * volume;
}
return retVal;
}
}
```