Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

View File

@ -0,0 +1,6 @@
package kha;
typedef AssetError = {
url: String,
?error: Dynamic,
}

325
Kha/Sources/kha/Assets.hx Normal file
View File

@ -0,0 +1,325 @@
package kha;
import haxe.io.Bytes;
import haxe.Unserializer;
using StringTools;
private typedef AssetDataObject = {
/** File name, given by khamake, used as identifier in `Assets.someList.get()` function **/
var name: String;
/** List of file paths, unified by khamake to single file with `name`. **/
var files: Array<String>;
/** File sizes in bytes **/
var file_sizes: Array<Int>;
/** Can be `image`, `sound`, `blob`, `font` and `video` **/
var type: String;
/** Original file width (only for images) **/
var ?original_width: Int;
/** Original file height (only for images) **/
var ?original_height: Int;
}
@:forward(name, files, file_sizes, type, original_width, original_height)
private abstract AssetData(AssetDataObject) from AssetDataObject {
@:op(a.b) function _get(key: String): Dynamic {
return Reflect.getProperty(this, key);
}
}
@:build(kha.internal.AssetsBuilder.build("image"))
private class ImageList {
public function new() {}
public function get(name: String): Image {
return Reflect.field(this, name);
}
}
@:build(kha.internal.AssetsBuilder.build("sound"))
private class SoundList {
public function new() {}
public function get(name: String): Sound {
return Reflect.field(this, name);
}
}
@:build(kha.internal.AssetsBuilder.build("blob"))
private class BlobList {
public function new() {}
public function get(name: String): Blob {
return Reflect.field(this, name);
}
}
@:build(kha.internal.AssetsBuilder.build("font"))
private class FontList {
public function new() {}
public function get(name: String): Font {
return Reflect.field(this, name);
}
}
@:build(kha.internal.AssetsBuilder.build("video"))
private class VideoList {
public function new() {}
public function get(name: String): Video {
return Reflect.field(this, name);
}
}
class Assets {
public static var images: ImageList = new ImageList();
public static var sounds: SoundList = new SoundList();
public static var blobs: BlobList = new BlobList();
public static var fonts: FontList = new FontList();
public static var videos: VideoList = new VideoList();
/**
* Moves from 0 to 1. Use for loading screens.
*/
public static var progress: Float;
/**
Loads all assets which were detected by khamake. When running khamake (doing so is Kha's standard build behavior)
it creates a files.json in the build/{target}-resources directoy which contains information about all assets which were found.
The `callback` parameter is always called after loading, even when some or all assets had failures.
An optional callback parameter `failed` is called for each asset that failed to load.
The filter parameter can be used to load assets selectively. The Dynamic parameter describes the asset,
it contains the very same objects which are listed in files.json.
Additionally by default all sounds are decompressed. The uncompressSoundsFilter can be used to avoid that.
Uncompressed sounds can still be played using Audio.stream which is recommended for music.
*/
public static function loadEverything(callback: () -> Void, ?filter: (item: AssetData) -> Bool, ?uncompressSoundsFilter: (soundItem: AssetData) -> Bool,
?failed: (err: AssetError) -> Void): Void {
final lists: Array<Dynamic> = [ImageList, SoundList, BlobList, FontList, VideoList];
final listInstances: Array<Dynamic> = [images, sounds, blobs, fonts, videos];
var fileCount = 0;
var byteCount = 0;
for (i in 0...lists.length) {
final list = lists[i];
for (file in Type.getInstanceFields(list)) {
if (file.endsWith("Description")) {
fileCount++;
}
else if (file.endsWith("Size")) {
var size: Int = Reflect.field(listInstances[i], file);
byteCount += size;
}
}
}
if (fileCount == 0) {
callback();
return;
}
var filesLeft = fileCount;
var bytesLeft = byteCount;
function onLoaded(bytes: Int): Void {
filesLeft--;
bytesLeft -= bytes;
progress = 1 - (bytesLeft / byteCount);
if (filesLeft == 0)
callback();
}
function onError(err: AssetError, bytes: Int): Void {
reporter(failed)(err);
onLoaded(bytes);
}
function loadFunc(desc: Dynamic, done: (bytes: Int) -> Void, failure: (err: AssetError, bytes: Int) -> Void): Void {
final name = desc.name;
final size = desc.file_sizes[0];
switch (desc.type) {
case "image":
Assets.loadImage(name, function(image: Image) done(size), function(err: AssetError) {
onError(err, size);
});
case "sound":
Assets.loadSound(name, function(sound: Sound) {
if (uncompressSoundsFilter == null || uncompressSoundsFilter(desc)) {
sound.uncompress(function() {
done(size);
});
}
else {
done(size);
}
}, function(err: AssetError) {
onError(err, size);
});
case "blob":
Assets.loadBlob(name, function(blob: Blob) done(size), function(err: AssetError) {
onError(err, size);
});
case "font":
Assets.loadFont(name, function(font: Font) done(size), function(err: AssetError) {
onError(err, size);
});
case "video":
Assets.loadVideo(name, function(video: Video) done(size), function(err: AssetError) {
onError(err, size);
});
}
}
for (i in 0...lists.length) {
final list = lists[i];
final listInstance = listInstances[i];
for (field in Type.getInstanceFields(list)) {
if (!field.endsWith("Description"))
continue;
final desc = Reflect.field(listInstance, field);
if (filter == null || filter(desc)) {
loadFunc(desc, onLoaded, onError);
}
else {
onLoaded(desc.file_sizes[0]);
}
}
}
}
/**
* Loads an image by name which was preprocessed by khamake.
*
* @param name The name as defined by the khafile.
* @param done A callback.
*/
public static function loadImage(name: String, done: Image->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = Reflect.field(images, name + "Description");
if (description == null) {
reporter(failed, pos)({url: name, error: "Name not found"});
return;
}
LoaderImpl.loadImageFromDescription(description, function(image: Image) {
Reflect.setField(images, name, image);
done(image);
}, reporter(failed, pos));
}
/**
* Loads an image from a path. Most targets support PNG and JPEG formats.
*
* @param path The path to the image file.
* @param readable If true, a copy of the image will be kept in main memory for image read operations.
* @param done A callback.
*/
public static function loadImageFromPath(path: String, readable: Bool, done: Image->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = {files: [path], readable: readable};
LoaderImpl.loadImageFromDescription(description, done, reporter(failed, pos));
}
public static var imageFormats(get, null): Array<String>;
static function get_imageFormats(): Array<String> {
return LoaderImpl.getImageFormats();
}
public static function loadBlob(name: String, done: Blob->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = Reflect.field(blobs, name + "Description");
if (description == null) {
reporter(failed, pos)({url: name, error: "Name not found"});
return;
}
LoaderImpl.loadBlobFromDescription(description, function(blob: Blob) {
Reflect.setField(blobs, name, blob);
done(blob);
}, reporter(failed, pos));
}
public static function loadBlobFromPath(path: String, done: Blob->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = {files: [path]};
LoaderImpl.loadBlobFromDescription(description, done, reporter(failed, pos));
}
public static function loadSound(name: String, done: Sound->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = Reflect.field(sounds, name + "Description");
if (description == null) {
reporter(failed, pos)({url: name, error: "Name not found"});
return;
}
return LoaderImpl.loadSoundFromDescription(description, function(sound: Sound) {
Reflect.setField(sounds, name, sound);
done(sound);
}, reporter(failed, pos));
}
public static function loadSoundFromPath(path: String, done: Sound->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = {files: [path]};
return LoaderImpl.loadSoundFromDescription(description, done, reporter(failed, pos));
}
public static var soundFormats(get, null): Array<String>;
static function get_soundFormats(): Array<String> {
return LoaderImpl.getSoundFormats();
}
public static function loadFont(name: String, done: Font->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = Reflect.field(fonts, name + "Description");
if (description == null) {
reporter(failed, pos)({url: name, error: "Name not found"});
return;
}
return LoaderImpl.loadFontFromDescription(description, function(font: Font) {
Reflect.setField(fonts, name, font);
done(font);
}, reporter(failed, pos));
}
public static function loadFontFromPath(path: String, done: Font->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = {files: [path]};
return LoaderImpl.loadFontFromDescription(description, done, reporter(failed, pos));
}
public static var fontFormats(get, null): Array<String>;
static function get_fontFormats(): Array<String> {
return ["ttf"];
}
public static function loadVideo(name: String, done: Video->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = Reflect.field(videos, name + "Description");
if (description == null) {
reporter(failed, pos)({url: name, error: "Name not found"});
return;
}
return LoaderImpl.loadVideoFromDescription(description, function(video: Video) {
Reflect.setField(videos, name, video);
done(video);
}, reporter(failed, pos));
}
public static function loadVideoFromPath(path: String, done: Video->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void {
var description = {files: [path]};
return LoaderImpl.loadVideoFromDescription(description, done, reporter(failed, pos));
}
public static var videoFormats(get, null): Array<String>;
static function get_videoFormats(): Array<String> {
return LoaderImpl.getVideoFormats();
}
public static function reporter(custom: AssetError->Void, ?pos: haxe.PosInfos) {
return custom != null ? custom : haxe.Log.trace.bind(_, pos);
}
}

47
Kha/Sources/kha/Blob.hx Normal file
View File

@ -0,0 +1,47 @@
package kha;
extern class Blob implements Resource {
public static function fromBytes(bytes: Bytes): Blob;
public static function alloc(size: Int): Blob;
public function sub(start: Int, length: Int): Blob;
public var length(get, null): Int;
public function writeU8(position: Int, value: Int): Void;
public function readU8(position: Int): Int;
public function readS8(position: Int): Int;
public function readU16BE(position: Int): Int;
public function readU16LE(position: Int): Int;
public function readU32LE(position: Int): Int;
public function readU32BE(position: Int): Int;
public function readS16BE(position: Int): Int;
public function readS16LE(position: Int): Int;
public function readS32LE(position: Int): Int;
public function readS32BE(position: Int): Int;
public function readF32LE(position: Int): Float;
public function readF32BE(position: Int): Float;
static function readF32(i: Int): Float;
public function toString(): String;
public function readUtf8String(): String;
public function toBytes(): Bytes;
public function unload(): Void;
}

35
Kha/Sources/kha/Canvas.hx Normal file
View File

@ -0,0 +1,35 @@
package kha;
/**
* Interface for a generic Canvas with different APIs,<br>
* that can be used to draw graphics.
*/
interface Canvas {
/**
* The width of the canvas in pixels.
*/
var width(get, null): Int;
/**
* The height of the canvas in pixels.
*/
var height(get, null): Int;
/**
* The Graphics1 interface object.<br>
* Basic setPixel operation.
*/
var g1(get, null): kha.graphics1.Graphics;
/**
* The Graphics2 interface object.<br>
* Use this for 2D operations.
*/
var g2(get, null): kha.graphics2.Graphics;
/**
* The Graphics4 interface object.<br>
* Use this for 3D operations.
*/
var g4(get, null): kha.graphics4.Graphics;
}

View File

@ -0,0 +1,17 @@
package kha;
class CodeStyle { // upper camel case class names
public function new() { // egyptian style curly brackets
}
public function doIt(): Void { // lower camel case method and function names
var i = 0;
switch (i) {
case 1: // case in same column as switch
playSfx(2);
}
}
public function playSfx(soundId: Int) { // lower camel case for parameters and locals, camel case is used for akronyms, too
}
}

191
Kha/Sources/kha/Color.hx Normal file
View File

@ -0,0 +1,191 @@
package kha;
/**
* A 32 bit ARGB color value which is represented as an Integer.
*/
enum abstract Color(Int) from Int from UInt to Int to UInt {
var Black = 0xff000000;
var White = 0xffffffff;
var Red = 0xffff0000;
var Blue = 0xff0000ff;
var Green = 0xff00ff00;
var Magenta = 0xffff00ff;
var Yellow = 0xffffff00;
var Cyan = 0xff00ffff;
var Purple = 0xff800080;
var Pink = 0xffffc0cb;
var Orange = 0xffffa500;
var Transparent = 0x00000000;
static inline var invMaxChannelValue: FastFloat = 1 / 255;
/**
* Creates a new Color object from a packed 32 bit ARGB value.
*/
public static inline function fromValue(value: Int): Color {
return new Color(value);
}
/**
* Creates a new Color object from components in the range 0 - 255.
*/
public static function fromBytes(r: Int, g: Int, b: Int, a: Int = 255): Color {
return new Color((a << 24) | (r << 16) | (g << 8) | b);
}
/**
* Creates a new Color object from components in the range 0 - 1.
*/
public static function fromFloats(r: FastFloat, g: FastFloat, b: FastFloat, a: FastFloat = 1): Color {
return new Color((Std.int(a * 255) << 24) | (Std.int(r * 255) << 16) | (Std.int(g * 255) << 8) | Std.int(b * 255));
}
/**
* Creates a new Color object from an HTML style `#AARRGGBB` / `#RRGGBB` / `#RGB` strings.
*/
public static function fromString(value: String) {
if (!(value.length == 4 || value.length == 7 || value.length == 9) || StringTools.fastCodeAt(value, 0) != "#".code) {
throw 'Invalid Color string: $value';
}
if (value.length == 4) {
final r = value.charAt(1);
final g = value.charAt(2);
final b = value.charAt(3);
value = '#$r$r$g$g$b$b';
}
var colorValue = Std.parseInt("0x" + value.substr(1));
if (value.length == 7) {
colorValue += 0xFF000000;
}
return fromValue(colorValue | 0);
}
/**
* Contains a byte representing the red color component.
*/
public var Rb(get, set): Int;
/**
* Contains a byte representing the green color component.
*/
public var Gb(get, set): Int;
/**
* Contains a byte representing the blue color component.
*/
public var Bb(get, set): Int;
/**
* Contains a byte representing the alpha color component (more exactly the opacity component - a value of 0 is fully transparent).
*/
public var Ab(get, set): Int;
/**
* Contains a float representing the red color component.
*/
public var R(get, set): FastFloat;
/**
* Contains a float representing the green color component.
*/
public var G(get, set): FastFloat;
/**
* Contains a float representing the blue color component.
*/
public var B(get, set): FastFloat;
/**
* Contains a float representing the alpha color component (more exactly the opacity component - a value of 0 is fully transparent).
*/
public var A(get, set): FastFloat;
function new(value: Int) {
this = value;
}
/**
* Return this Color instance as Int.
*/
public var value(get, set): Int;
inline function get_value(): Int {
return this;
}
inline function set_value(value: Int): Int {
this = value;
return this;
}
inline function get_Rb(): Int {
return (this & 0x00ff0000) >>> 16;
}
inline function get_Gb(): Int {
return (this & 0x0000ff00) >>> 8;
}
inline function get_Bb(): Int {
return this & 0x000000ff;
}
inline function get_Ab(): Int {
return this >>> 24;
}
inline function set_Rb(i: Int): Int {
this = (Ab << 24) | (i << 16) | (Gb << 8) | Bb;
return i;
}
inline function set_Gb(i: Int): Int {
this = (Ab << 24) | (Rb << 16) | (i << 8) | Bb;
return i;
}
inline function set_Bb(i: Int): Int {
this = (Ab << 24) | (Rb << 16) | (Gb << 8) | i;
return i;
}
inline function set_Ab(i: Int): Int {
this = (i << 24) | (Rb << 16) | (Gb << 8) | Bb;
return i;
}
inline function get_R(): FastFloat {
return get_Rb() * invMaxChannelValue;
}
inline function get_G(): FastFloat {
return get_Gb() * invMaxChannelValue;
}
inline function get_B(): FastFloat {
return get_Bb() * invMaxChannelValue;
}
inline function get_A(): FastFloat {
return get_Ab() * invMaxChannelValue;
}
inline function set_R(f: FastFloat): FastFloat {
this = (Std.int(A * 255) << 24) | (Std.int(f * 255) << 16) | (Std.int(G * 255) << 8) | Std.int(B * 255);
return f;
}
inline function set_G(f: FastFloat): FastFloat {
this = (Std.int(A * 255) << 24) | (Std.int(R * 255) << 16) | (Std.int(f * 255) << 8) | Std.int(B * 255);
return f;
}
inline function set_B(f: FastFloat): FastFloat {
this = (Std.int(A * 255) << 24) | (Std.int(R * 255) << 16) | (Std.int(G * 255) << 8) | Std.int(f * 255);
return f;
}
inline function set_A(f: FastFloat): FastFloat {
this = (Std.int(f * 255) << 24) | (Std.int(R * 255) << 16) | (Std.int(G * 255) << 8) | Std.int(B * 255);
return f;
}
}

View File

@ -0,0 +1,15 @@
package kha;
extern class Display {
public static var primary(get, never): Display;
public static var all(get, never): Array<Display>;
public var available(get, never): Bool;
public var name(get, never): String;
public var x(get, never): Int;
public var y(get, never): Int;
public var width(get, never): Int;
public var height(get, never): Int;
public var frequency(get, never): Int;
public var pixelsPerInch(get, never): Int;
public var modes(get, never): Array<DisplayMode>;
}

View File

@ -0,0 +1,15 @@
package kha;
class DisplayMode {
public var width: Int;
public var height: Int;
public var frequency: Int;
public var bitsPerPixel: Int;
public function new(width: Int, height: Int, frequency: Int, bitsPerPixel: Int) {
this.width = width;
this.height = height;
this.frequency = frequency;
this.bitsPerPixel = bitsPerPixel;
}
}

View File

@ -0,0 +1,5 @@
package kha;
extern class EnvironmentVariables {
public static function get(name: String): String;
}

View File

@ -0,0 +1,20 @@
package kha;
/*
FastFloat uses 32 bit floats wherever that is possible.
But JavaScript in particular only supports 64 bit floats.
Therefore when using FastFloat you will have different
precision on different targets and therefore it is
strongly advised to only use it where that does not
matter (typically graphics code, avoid it in gameplay
code at all costs).
*/
#if cpp
typedef FastFloat = cpp.Float32;
#elseif hl
typedef FastFloat = hl.F32;
#elseif java
typedef FastFloat = Single;
#else
typedef FastFloat = Float;
#end

View File

@ -0,0 +1,71 @@
package kha;
import kha.arrays.Float32Array;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
class Float32ArrayExtensions {
public static inline function setVector2(array: Float32Array, index: Int, vector: FastVector2) {
array.set(index + 0, vector.x);
array.set(index + 1, vector.y);
}
public static inline function setVector3(array: Float32Array, index: Int, vector: FastVector3) {
array.set(index + 0, vector.x);
array.set(index + 1, vector.y);
array.set(index + 2, vector.z);
}
public static inline function setVector4(array: Float32Array, index: Int, vector: FastVector4) {
array.set(index + 0, vector.x);
array.set(index + 1, vector.y);
array.set(index + 2, vector.z);
array.set(index + 3, vector.w);
}
public static inline function setColor(array: Float32Array, index: Int, color: Color) {
array.set(index + 0, color.R);
array.set(index + 1, color.G);
array.set(index + 2, color.B);
array.set(index + 3, color.A);
}
public static inline function setMatrix3(array: Float32Array, index: Int, matrix: FastMatrix3) {
array.set(index + 0, matrix._00);
array.set(index + 1, matrix._01);
array.set(index + 2, matrix._02);
array.set(index + 3, matrix._10);
array.set(index + 4, matrix._11);
array.set(index + 5, matrix._12);
array.set(index + 6, matrix._20);
array.set(index + 7, matrix._21);
array.set(index + 8, matrix._22);
}
public static inline function setMatrix4(array: Float32Array, index: Int, matrix: FastMatrix4) {
array.set(index + 0, matrix._00);
array.set(index + 1, matrix._01);
array.set(index + 2, matrix._02);
array.set(index + 3, matrix._03);
array.set(index + 4, matrix._10);
array.set(index + 5, matrix._11);
array.set(index + 6, matrix._12);
array.set(index + 7, matrix._13);
array.set(index + 8, matrix._20);
array.set(index + 9, matrix._21);
array.set(index + 10, matrix._22);
array.set(index + 11, matrix._23);
array.set(index + 12, matrix._30);
array.set(index + 13, matrix._31);
array.set(index + 14, matrix._32);
array.set(index + 15, matrix._33);
}
}

18
Kha/Sources/kha/Font.hx Normal file
View File

@ -0,0 +1,18 @@
package kha;
import haxe.io.Bytes;
extern class Font implements Resource {
function height(fontSize: Int): Float;
function width(fontSize: Int, str: String): Float;
function widthOfCharacters(fontSize: Int, characters: Array<Int>, start: Int, length: Int): Float;
function baseline(fontSize: Int): Float;
function unload(): Void;
// Portability warning, this works only on some platforms but can usually read ttf
static function fromBytes(bytes: Bytes): Font;
}

View File

@ -0,0 +1,60 @@
package kha;
/**
* The font style (bold, italic, ect).
*/
class FontStyle {
/**
* The default style.
*/
public static var Default(default, never): FontStyle = new FontStyle(false, false, false);
/**
* If the font is bold.
*/
var bold: Bool;
/**
* If the font is italic.
*/
var italic: Bool;
/**
* If the font is underlined.
*/
var underlined: Bool;
/**
* Initialize a new font style.
*
* @param bold If the font is bold, default = false.
* @param italic If the font is italic, default = false.
* @param underlined If the font is underlined, default = false.
*/
public function new(bold: Bool, italic: Bool, underlined: Bool) {
this.bold = bold;
this.italic = italic;
this.underlined = underlined;
}
/**
* Returns true if the font is bold.
*/
public function getBold(): Bool {
return bold;
}
/**
* Returns true if the font is italic.
*/
public function getItalic(): Bool {
return italic;
}
/**
* Returns true if the font is underlined.
*/
public function getUnderlined(): Bool {
return underlined;
}
}

View File

@ -0,0 +1,87 @@
package kha;
/**
* A Framebuffer object represents the framebuffer of a kha.Window, which
* typically contains a color, depth and stencil buffer. It is used to
* query Graphics interfaces for rendering images which are directly visible.
*/
class Framebuffer implements Canvas {
var window: Int;
var graphics1: kha.graphics1.Graphics;
var graphics2: kha.graphics2.Graphics;
var graphics4: kha.graphics4.Graphics;
//**var graphics5: kha.graphics5.Graphics;
@:noCompletion
@:noDoc
public function new(window: Int, g1: kha.graphics1.Graphics, g2: kha.graphics2.Graphics, g4: kha.graphics4.Graphics /*, ?g5: kha.graphics5.Graphics*/) {
this.window = window;
this.graphics1 = g1;
this.graphics2 = g2;
this.graphics4 = g4;
// this.graphics5 = g5;
}
@:noCompletion
@:noDoc
public function init(g1: kha.graphics1.Graphics, g2: kha.graphics2.Graphics, g4: kha.graphics4.Graphics /*, ?g5: kha.graphics5.Graphics*/): Void {
this.graphics1 = g1;
this.graphics2 = g2;
this.graphics4 = g4;
// this.graphics5 = g5;
}
/**
* Returns a kha.graphics1.Graphics interface for the framebuffer.
*/
public var g1(get, never): kha.graphics1.Graphics;
function get_g1(): kha.graphics1.Graphics {
return graphics1;
}
/**
* Returns a kha.graphics2.Graphics interface for the framebuffer.
*/
public var g2(get, never): kha.graphics2.Graphics;
function get_g2(): kha.graphics2.Graphics {
return graphics2;
}
/**
* Returns a kha.graphics4.Graphics interface for the framebuffer.
*/
public var g4(get, never): kha.graphics4.Graphics;
function get_g4(): kha.graphics4.Graphics {
return graphics4;
}
/**
* Returns a kha.graphics5.Graphics interface for the framebuffer.
*/
/*public var g5(get, never): kha.graphics5.Graphics;
private function get_g5(): kha.graphics5.Graphics {
return graphics5;
}*/
/**
* Returns the width of the framebuffer in pixels.
*/
public var width(get, null): Int;
function get_width(): Int {
return System.windowWidth(window);
}
/**
* Returns the height of the framebuffer in pixels.
*/
public var height(get, null): Int;
function get_height(): Int {
return System.windowHeight(window);
}
}

View File

@ -0,0 +1,21 @@
package kha;
@:structInit
class FramebufferOptions {
@:optional public var frequency: Int = 60;
@:optional public var verticalSync: Bool = true;
@:optional public var colorBufferBits: Int = 32;
@:optional public var depthBufferBits: Int = 16;
@:optional public var stencilBufferBits: Int = 8;
@:optional public var samplesPerPixel: Int = 1;
public function new(?frequency: Int = 60, ?verticalSync: Bool = true, ?colorBufferBits: Int = 32, ?depthBufferBits: Int = 16, ?stencilBufferBits: Int = 8,
?samplesPerPixel: Int = 1) {
this.frequency = frequency;
this.verticalSync = verticalSync;
this.colorBufferBits = colorBufferBits;
this.depthBufferBits = depthBufferBits;
this.stencilBufferBits = stencilBufferBits;
this.samplesPerPixel = samplesPerPixel;
}
}

View File

@ -0,0 +1,70 @@
package kha;
import haxe.io.Path;
import sys.FileSystem;
class HaxelibRunner {
static function main() {
if (Sys.systemName() != "Windows") {
var tools = [
Path.join([Sys.getCwd(), "Tools", "nodejs", "node"]),
Path.join([Sys.getCwd(), "Tools", "kravur", "kravur"]),
Path.join([Sys.getCwd(), "Tools", "oggenc", "oggenc"]),
Path.join([Sys.getCwd(), "Kore", "Tools", "krafix", "krafix"]),
Path.join([Sys.getCwd(), "Kore", "Tools", "kraffiti", "kraffiti"])
];
for (tool in tools)
chmod(tool);
}
var io = Path.join([Sys.getCwd(), "Tools", "nodejs", "node" + sysExt()]);
var args = Sys.args();
args.unshift(Path.join([Sys.getCwd(), "Tools", "khamake", "khamake.js"]));
var project = Path.normalize(args.pop());
args.push("--from");
args.push(project);
args.push("--to");
args.push(Path.join([project, "build"]));
args.push("--haxe");
args.push(haxePath());
args.push("--kha");
args.push(Path.normalize(Sys.getCwd()));
if (Sys.systemName() == "Windows")
Sys.exit(Sys.command('"' + io + '"', args));
else
Sys.exit(Sys.command(io, args));
}
static function chmod(path: String): Void {
Sys.command("chmod", ["a+x", path + sysExt()]);
}
static function haxePath(): String {
var path = Sys.getEnv("HAXEPATH");
if (path == null) {
path = "/usr/local/lib/haxe";
if (!FileSystem.exists(path) || !FileSystem.isDirectory(path)) {
path = "/usr/lib/haxe";
}
}
return Path.normalize(path);
}
static function sysExt(): String {
switch (Sys.systemName()) {
case "Linux":
var process = new sys.io.Process("uname", ["-m"]);
var value = process.stdout.readAll().toString();
return "-linux" + (value.indexOf("64") != -1 ? "64" : "32");
case "Windows":
return ".exe";
case "Mac":
return "-osx";
default:
return "";
}
}
}

66
Kha/Sources/kha/Image.hx Normal file
View File

@ -0,0 +1,66 @@
package kha;
import haxe.io.Bytes;
import kha.graphics4.DepthStencilFormat;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
extern class Image implements Canvas implements Resource {
public static function create(width: Int, height: Int, format: TextureFormat = TextureFormat.RGBA32, usage: Usage = Usage.StaticUsage,
readable: Bool = false): Image;
public static function create3D(width: Int, height: Int, depth: Int, format: TextureFormat = TextureFormat.RGBA32, usage: Usage = Usage.StaticUsage,
readable: Bool = false): Image;
public static function fromBytes(bytes: Bytes, width: Int, height: Int, format: TextureFormat = TextureFormat.RGBA32, usage: Usage = Usage.StaticUsage,
readable: Bool = false): Image;
public static function fromBytes3D(bytes: Bytes, width: Int, height: Int, depth: Int, format: TextureFormat = TextureFormat.RGBA32,
usage: Usage = Usage.StaticUsage, readable: Bool = false): Image;
public static function fromEncodedBytes(bytes: Bytes, fileExtention: String, doneCallback: Image->Void, errorCallback: String->Void,
readable: Bool = false): Void;
public static function fromVideo(video: Video): Image;
public static function createRenderTarget(width: Int, height: Int, format: TextureFormat = TextureFormat.RGBA32,
depthStencil: DepthStencilFormat = NoDepthAndStencil, antiAliasingSamples: Int = 1): Image;
public static var maxSize(get, null): Int;
public static var nonPow2Supported(get, null): Bool;
public function isOpaque(x: Int, y: Int): Bool;
public function unload(): Void;
public function lock(level: Int = 0): Bytes;
public function unlock(): Void;
public function getPixels(): Bytes;
public function generateMipmaps(levels: Int): Void;
public function setMipmaps(mipmaps: Array<Image>): Void;
public function setDepthStencilFrom(image: Image): Void;
public function clear(x: Int, y: Int, z: Int, width: Int, height: Int, depth: Int, color: Color): Void;
public var width(get, null): Int;
public var height(get, null): Int;
public var depth(get, null): Int;
public var format(get, null): TextureFormat;
public var realWidth(get, null): Int;
public var realHeight(get, null): Int;
public var g2(get, null): kha.graphics2.Graphics;
public var g4(get, null): kha.graphics4.Graphics;
}

238
Kha/Sources/kha/Kravur.hx Normal file
View File

@ -0,0 +1,238 @@
package kha;
import haxe.ds.Vector;
import haxe.io.Bytes;
import kha.graphics2.truetype.StbTruetype;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
class AlignedQuad {
public function new() {}
// top-left
public var x0: Float;
public var y0: Float;
public var s0: Float;
public var t0: Float;
// bottom-right
public var x1: Float;
public var y1: Float;
public var s1: Float;
public var t1: Float;
public var xadvance: Float;
}
class KravurImage {
var mySize: Float;
var chars: Vector<Stbtt_bakedchar>;
var texture: Image;
public var width: Int;
public var height: Int;
var baseline: Float;
public static var charBlocks: Array<Int>;
public function new(size: Int, ascent: Int, descent: Int, lineGap: Int, width: Int, height: Int, chars: Vector<Stbtt_bakedchar>, pixels: Blob) {
mySize = size;
this.width = width;
this.height = height;
this.chars = chars;
baseline = ascent;
for (char in chars) {
char.yoff += baseline;
}
texture = Image.create(width, height, TextureFormat.L8);
var bytes = texture.lock();
var pos: Int = 0;
for (y in 0...height)
for (x in 0...width) {
bytes.set(pos, pixels.readU8(pos));
++pos;
}
texture.unlock();
}
public function getTexture(): Image {
return texture;
}
public function getBakedQuad(q: AlignedQuad, char_index: Int, xpos: Float, ypos: Float): AlignedQuad {
if (char_index >= chars.length)
return null;
var ipw: Float = 1.0 / width;
var iph: Float = 1.0 / height;
var b = chars[char_index];
if (b == null)
return null;
var round_x: Int = Math.round(xpos + b.xoff);
var round_y: Int = Math.round(ypos + b.yoff);
q.x0 = round_x;
q.y0 = round_y;
q.x1 = round_x + b.x1 - b.x0;
q.y1 = round_y + b.y1 - b.y0;
q.s0 = b.x0 * ipw;
q.t0 = b.y0 * iph;
q.s1 = b.x1 * ipw;
q.t1 = b.y1 * iph;
q.xadvance = b.xadvance;
return q;
}
function getCharWidth(charIndex: Int): Float {
if (chars.length == 0)
return 0;
var offset = charBlocks[0];
if (charIndex < offset)
return chars[0].xadvance;
for (i in 1...Std.int(charBlocks.length / 2)) {
var prevEnd = charBlocks[i * 2 - 1];
var start = charBlocks[i * 2];
if (charIndex > start - 1)
offset += start - 1 - prevEnd;
}
if (charIndex - offset >= chars.length)
return chars[0].xadvance;
return chars[charIndex - offset].xadvance;
}
public function getHeight(): Float {
return mySize;
}
public function stringWidth(str: String): Float {
var width: Float = 0;
for (c in 0...str.length) {
width += getCharWidth(str.charCodeAt(c));
}
return width;
}
public function charactersWidth(characters: Array<Int>, start: Int, length: Int): Float {
var width: Float = 0;
for (i in start...start + length) {
width += getCharWidth(characters[i]);
}
return width;
}
public function getBaselinePosition(): Float {
return baseline;
}
}
class Kravur implements Resource {
// Do not put static data in Kravur because it is overwritten
// when <canvas> is used - but it's still used by the overwriting class.
var oldGlyphs: Array<Int>;
var blob: Blob;
var images: Map<Int, KravurImage> = new Map();
var fontIndex: Int;
public function new(blob: Blob, fontIndex: Int = 0) {
this.blob = blob;
this.fontIndex = fontIndex;
}
public static function fromBytes(bytes: Bytes, fontIndex: Int = 0): Kravur {
return new Kravur(Blob.fromBytes(bytes), fontIndex);
}
public function _get(fontSize: Int): KravurImage {
var glyphs = kha.graphics2.Graphics.fontGlyphs;
if (glyphs != oldGlyphs) {
oldGlyphs = glyphs;
// save first/last chars of sequences
KravurImage.charBlocks = [glyphs[0]];
var nextChar = KravurImage.charBlocks[0] + 1;
for (i in 1...glyphs.length) {
if (glyphs[i] != nextChar) {
KravurImage.charBlocks.push(glyphs[i - 1]);
KravurImage.charBlocks.push(glyphs[i]);
nextChar = glyphs[i] + 1;
}
else
nextChar++;
}
KravurImage.charBlocks.push(glyphs[glyphs.length - 1]);
}
var imageIndex = fontIndex * 10000000 + fontSize * 10000 + glyphs.length;
if (!images.exists(imageIndex)) {
var width: Int = 64;
var height: Int = 32;
var baked = new Vector<Stbtt_bakedchar>(glyphs.length);
for (i in 0...baked.length) {
baked[i] = new Stbtt_bakedchar();
}
var pixels: Blob = null;
var offset = StbTruetype.stbtt_GetFontOffsetForIndex(blob, fontIndex);
if (offset == -1) {
offset = StbTruetype.stbtt_GetFontOffsetForIndex(blob, 0);
}
var status: Int = -1;
while (status <= 0) {
if (height < width)
height *= 2;
else
width *= 2;
pixels = Blob.alloc(width * height);
status = StbTruetype.stbtt_BakeFontBitmap(blob, offset, fontSize, pixels, width, height, glyphs, baked);
}
// TODO: Scale pixels down if they exceed the supported texture size
var info = new Stbtt_fontinfo();
StbTruetype.stbtt_InitFont(info, blob, offset);
var metrics = StbTruetype.stbtt_GetFontVMetrics(info);
var scale = StbTruetype.stbtt_ScaleForPixelHeight(info, fontSize);
var ascent = Math.round(metrics.ascent * scale); // equals baseline
var descent = Math.round(metrics.descent * scale);
var lineGap = Math.round(metrics.lineGap * scale);
var image = new KravurImage(Std.int(fontSize), ascent, descent, lineGap, width, height, baked, pixels);
images[imageIndex] = image;
return image;
}
return images[imageIndex];
}
public function height(fontSize: Int): Float {
return _get(fontSize).getHeight();
}
public function width(fontSize: Int, str: String): Float {
return _get(fontSize).stringWidth(str);
}
public function widthOfCharacters(fontSize: Int, characters: Array<Int>, start: Int, length: Int): Float {
return _get(fontSize).charactersWidth(characters, start, length);
}
public function baseline(fontSize: Int): Float {
return _get(fontSize).getBaselinePosition();
}
public function setFontIndex(fontIndex: Int): Void {
this.fontIndex = fontIndex;
}
public function unload(): Void {
blob = null;
images = null;
}
}

View File

@ -0,0 +1,12 @@
package kha;
/**
* Interface representing a generic application resource.
* It can go from images, to sound or music, videos or blobs.
*/
interface Resource {
/**
* Unload the resource from memory. Normally called by the Loader.
*/
function unload(): Void;
}

View File

@ -0,0 +1,13 @@
package kha;
import kha.math.Vector2;
class Rotation {
public var center: Vector2;
public var angle: Float;
public function new(center: Vector2, angle: Float) {
this.center = center;
this.angle = angle;
}
}

176
Kha/Sources/kha/Scaler.hx Normal file
View File

@ -0,0 +1,176 @@
package kha;
import kha.math.FastMatrix3;
class TargetRectangle {
public var x: Float;
public var y: Float;
public var width: Float;
public var height: Float;
public var scaleFactor: Float;
public var rotation: ScreenRotation;
public inline function new(x: Float, y: Float, w: Float, h: Float, s: Float, r: ScreenRotation) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.scaleFactor = s;
this.rotation = r;
}
}
class Scaler {
public static function targetRect(width: Int, height: Int, destinationWidth: Int, destinationHeight: Int, rotation: ScreenRotation): TargetRectangle {
var scalex: Float;
var scaley: Float;
var scalew: Float;
var scaleh: Float;
var scale: Float;
switch (rotation) {
case ScreenRotation.RotationNone:
if (width / height > destinationWidth / destinationHeight) {
scale = destinationWidth / width;
scalew = width * scale;
scaleh = height * scale;
scalex = 0;
scaley = (destinationHeight - scaleh) * 0.5;
}
else {
scale = destinationHeight / height;
scalew = width * scale;
scaleh = height * scale;
scalex = (destinationWidth - scalew) * 0.5;
scaley = 0;
}
case ScreenRotation.Rotation90:
if (width / height > destinationHeight / destinationWidth) {
scale = destinationHeight / width;
scalew = width * scale;
scaleh = height * scale;
scalex = (destinationWidth - scaleh) * 0.5 + scaleh;
scaley = 0;
}
else {
scale = destinationWidth / height;
scalew = width * scale;
scaleh = height * scale;
scalex = 0 + scaleh;
scaley = (destinationHeight - scalew) * 0.5;
}
case ScreenRotation.Rotation180:
if (width / height > destinationWidth / destinationHeight) {
scale = destinationWidth / width;
scalew = width * scale;
scaleh = height * scale;
scalex = 0 + scalew;
scaley = (destinationHeight - scaleh) * 0.5 + scaleh;
}
else {
scale = destinationHeight / height;
scalew = width * scale;
scaleh = height * scale;
scalex = (destinationWidth - scalew) * 0.5 + scalew;
scaley = 0 + scaleh;
}
case ScreenRotation.Rotation270:
if (width / height > destinationHeight / destinationWidth) {
scale = destinationHeight / width;
scalew = width * scale;
scaleh = height * scale;
scalex = (destinationWidth - scaleh) * 0.5;
scaley = 0 + scalew;
}
else {
scale = destinationWidth / height;
scalew = width * scale;
scaleh = height * scale;
scalex = 0;
scaley = (destinationHeight - scalew) * 0.5 + scalew;
}
}
return new TargetRectangle(scalex, scaley, scalew, scaleh, scale, rotation);
}
public static function transformXDirectly(x: Int, y: Int, sourceWidth: Int, sourceHeight: Int, destinationWidth: Int, destinationHeight: Int,
rotation: ScreenRotation): Int {
var targetRect = targetRect(sourceWidth, sourceHeight, destinationWidth, destinationHeight, rotation);
switch (targetRect.rotation) {
case ScreenRotation.RotationNone:
return Std.int((x - targetRect.x) / targetRect.scaleFactor);
case ScreenRotation.Rotation90:
return Std.int((y - targetRect.y) / targetRect.scaleFactor);
case ScreenRotation.Rotation180:
return Std.int((targetRect.x - x) / targetRect.scaleFactor);
case ScreenRotation.Rotation270:
return Std.int((targetRect.y - y) / targetRect.scaleFactor);
}
}
/**
* Transform the X value from the given position in the source to a position in the destination canvas.
*
* @param x The X position.
* @param y The Y position.
* @param source The source image.
* @param destination The destination canvas.
* @param rotation The screen rotation.
*/
public static function transformX(x: Int, y: Int, source: Image, destination: Canvas, rotation: ScreenRotation): Int {
return transformXDirectly(x, y, source.width, source.height, destination.width, destination.height, rotation);
}
public static function transformYDirectly(x: Int, y: Int, sourceWidth: Int, sourceHeight: Int, destinationWidth: Int, destinationHeight: Int,
rotation: ScreenRotation): Int {
var targetRect = targetRect(sourceWidth, sourceHeight, destinationWidth, destinationHeight, rotation);
switch (targetRect.rotation) {
case ScreenRotation.RotationNone:
return Std.int((y - targetRect.y) / targetRect.scaleFactor);
case ScreenRotation.Rotation90:
return Std.int((targetRect.x - x) / targetRect.scaleFactor);
case ScreenRotation.Rotation180:
return Std.int((targetRect.y - y) / targetRect.scaleFactor);
case ScreenRotation.Rotation270:
return Std.int((x - targetRect.x) / targetRect.scaleFactor);
}
}
/**
* Transform the Y value from the given position in the source to a position in the destination canvas.
*
* @param x The X position.
* @param y The Y position.
* @param source The source image.
* @param destination The destination canvas.
* @param rotation The screen rotation.
*/
public static function transformY(x: Int, y: Int, source: Image, destination: Canvas, rotation: ScreenRotation): Int {
return transformYDirectly(x, y, source.width, source.height, destination.width, destination.height, rotation);
}
public static function scale(source: Image, destination: Canvas, rotation: ScreenRotation): Void {
var g = destination.g2;
g.pushTransformation(getScaledTransformation(source.width, source.height, destination.width, destination.height, rotation));
g.color = Color.White;
g.opacity = 1;
g.drawImage(source, 0, 0);
g.popTransformation();
}
public static function getScaledTransformation(width: Int, height: Int, destinationWidth: Int, destinationHeight: Int,
rotation: ScreenRotation): FastMatrix3 {
var rect = targetRect(width, height, destinationWidth, destinationHeight, rotation);
var sf = rect.scaleFactor;
var transformation = new FastMatrix3(sf, 0, rect.x, 0, sf, rect.y, 0, 0, 1);
switch (rotation) {
case RotationNone:
case Rotation90:
transformation = transformation.multmat(FastMatrix3.rotation(Math.PI / 2));
case Rotation180:
transformation = transformation.multmat(FastMatrix3.rotation(Math.PI));
case Rotation270:
transformation = transformation.multmat(FastMatrix3.rotation(Math.PI * 3 / 2));
}
return transformation;
}
}

View File

@ -0,0 +1,554 @@
package kha;
class TimeTask {
public var task: Void->Bool;
public var start: Float;
public var period: Float;
public var duration: Float;
public var next: Float;
public var id: Int;
public var groupId: Int;
public var active: Bool;
public var paused: Bool;
public function new() {}
}
class FrameTask {
public var task: Void->Bool;
public var priority: Int;
public var id: Int;
public var active: Bool;
public var paused: Bool;
public function new(task: Void->Bool, priority: Int, id: Int) {
this.task = task;
this.priority = priority;
this.id = id;
active = true;
paused = false;
}
}
class Scheduler {
static var timeTasks: Array<TimeTask>;
static var pausedTimeTasks: Array<TimeTask>;
static var outdatedTimeTasks: Array<TimeTask>;
static var timeTasksScratchpad: Array<TimeTask>;
static inline var timeWarpSaveTime: Float = 10.0;
static var frameTasks: Array<FrameTask>;
static var toDeleteFrame: Array<FrameTask>;
static var current: Float;
static var lastTime: Float;
static var lastFrameEnd: Float;
static var frame_tasks_sorted: Bool;
static var stopped: Bool;
static var vsync: Bool;
// Html5 target can update display frequency after some delay
#if kha_html5
static var onedifhz(get, never): Float;
static inline function get_onedifhz(): Float {
return 1.0 / Display.primary.frequency;
}
#else
static var onedifhz: Float;
#end
static var currentFrameTaskId: Int;
static var currentTimeTaskId: Int;
static var currentGroupId: Int;
static var DIF_COUNT = 3;
static var maxframetime = 0.5;
static var deltas: Array<Float>;
static var startTime: Float = 0;
static var activeTimeTask: TimeTask = null;
public static function init(): Void {
deltas = new Array<Float>();
for (i in 0...DIF_COUNT)
deltas[i] = 0;
stopped = true;
frame_tasks_sorted = true;
current = lastTime = lastFrameEnd = realTime();
currentFrameTaskId = 0;
currentTimeTaskId = 0;
currentGroupId = 0;
timeTasks = [];
pausedTimeTasks = [];
outdatedTimeTasks = [];
timeTasksScratchpad = [];
frameTasks = [];
toDeleteFrame = [];
}
public static function start(restartTimers: Bool = false): Void {
vsync = Window.get(0).vSynced;
#if !kha_html5
var hz = Display.primary != null ? Display.primary.frequency : 60;
if (hz >= 57 && hz <= 63)
hz = 60;
onedifhz = 1.0 / hz;
#end
stopped = false;
resetTime();
lastTime = realTime() - startTime;
for (i in 0...DIF_COUNT)
deltas[i] = 0;
if (restartTimers) {
for (timeTask in timeTasks) {
timeTask.paused = false;
}
for (frameTask in frameTasks) {
frameTask.paused = false;
}
}
}
public static function stop(): Void {
stopped = true;
}
public static function isStopped(): Bool {
return stopped;
}
static function warpTimeTasksBack(time: Float, tasks: Array<TimeTask>): Void {
for (timeTask in tasks) {
if (timeTask.start >= time) {
timeTask.next = timeTask.start;
}
else if (timeTask.period > 0) {
var sinceStart = time - timeTask.start;
var times = Math.ceil(sinceStart / timeTask.period);
timeTask.next = timeTask.start + times * timeTask.period;
}
}
}
public static function warp(time: Float): Void {
if (time < lastTime) {
current = time;
lastTime = time;
lastFrameEnd = time;
warpTimeTasksBack(time, outdatedTimeTasks);
warpTimeTasksBack(time, timeTasks);
for (task in outdatedTimeTasks) {
if (task.next >= time) {
timeTasksScratchpad.push(task);
}
}
for (task in timeTasksScratchpad) {
outdatedTimeTasks.remove(task);
}
for (task in timeTasksScratchpad) {
insertSorted(timeTasks, task);
}
while (timeTasksScratchpad.length > 0) {
timeTasksScratchpad.remove(timeTasksScratchpad[0]);
}
}
else if (time > lastTime) {
// TODO: Changing startTime line prevents clients from falling into a
// warp-forward-then-wait-for-systemtime-to-catch-up-loop that causes
// choppy movement (e.g. every 3rd frame forward 3 times).
// But it causes backwards jumps in originally constant movements.
// And on HTML5 packets are received while no frames are executed,
// which causes the client to overtakes the server and then move
// farther away with each packet while being unable to synch back
// (backwards warping is not allowed to change startTime).
startTime -= (time - lastTime);
current = time;
lastTime = time;
lastFrameEnd = time;
executeTimeTasks(time);
}
}
public static function executeFrame(): Void {
var real = realTime();
var now: Float = real - startTime;
var delta = now - lastTime;
var frameEnd: Float = lastFrameEnd;
if (delta >= 0) {
if (kha.netsync.Session.the() == null) {
// tdif = 1.0 / 60.0; //force fixed frame rate
if (delta > maxframetime) {
startTime += delta - maxframetime;
now = real - startTime;
delta = maxframetime;
frameEnd += delta;
}
else {
if (vsync) {
// var measured = delta;
// this is optimized not to run at exact speed
// but to run as fluid as possible
var frames = Math.round(delta / onedifhz);
if (frames < 1) {
return;
}
var realdif = frames * onedifhz;
delta = realdif;
for (i in 0...DIF_COUNT - 2) {
delta += deltas[i];
deltas[i] = deltas[i + 1];
}
delta += deltas[DIF_COUNT - 2];
delta /= DIF_COUNT;
deltas[DIF_COUNT - 2] = realdif;
frameEnd += delta;
// trace("Measured: " + measured + " Frames: " + frames + " Delta: " + delta + " ");
}
else {
for (i in 0...DIF_COUNT - 1) {
deltas[i] = deltas[i + 1];
}
deltas[DIF_COUNT - 1] = delta;
var next: Float = 0;
for (i in 0...DIF_COUNT) {
next += deltas[i];
}
next /= DIF_COUNT;
// delta = interpolated_delta; // average the frame end estimation
// lastTime = now;
frameEnd += next;
}
}
}
else {
frameEnd += delta;
}
lastTime = now;
if (!stopped) { // Stop simulation time
lastFrameEnd = frameEnd;
}
// Extend endpoint by paused time (individually paused tasks)
for (pausedTask in pausedTimeTasks) {
pausedTask.next += delta;
}
if (stopped) {
// Extend endpoint by paused time (running tasks)
for (timeTask in timeTasks) {
timeTask.next += delta;
}
}
executeTimeTasks(frameEnd);
// Maintain outdated task list
for (task in outdatedTimeTasks) {
if (task.next < frameEnd - timeWarpSaveTime) {
timeTasksScratchpad.push(task);
}
}
for (task in timeTasksScratchpad) {
outdatedTimeTasks.remove(task);
}
while (timeTasksScratchpad.length > 0) {
timeTasksScratchpad.remove(timeTasksScratchpad[0]);
}
}
current = frameEnd;
sortFrameTasks();
for (frameTask in frameTasks) {
if (!stopped && !frameTask.paused && frameTask.active) {
if (!frameTask.task())
frameTask.active = false;
}
}
for (frameTask in frameTasks) {
if (!frameTask.active) {
toDeleteFrame.push(frameTask);
}
}
while (toDeleteFrame.length > 0) {
frameTasks.remove(toDeleteFrame.pop());
}
}
static function executeTimeTasks(until: Float) {
while (timeTasks.length > 0) {
activeTimeTask = timeTasks[0];
if (activeTimeTask.next <= until) {
current = activeTimeTask.next;
activeTimeTask.next += activeTimeTask.period;
timeTasks.remove(activeTimeTask);
if (activeTimeTask.active && activeTimeTask.task()) {
if (activeTimeTask.period > 0
&& (activeTimeTask.duration == 0 || activeTimeTask.duration >= activeTimeTask.start + activeTimeTask.next)) {
insertSorted(timeTasks, activeTimeTask);
}
else {
archiveTimeTask(activeTimeTask, until);
}
}
else {
activeTimeTask.active = false;
archiveTimeTask(activeTimeTask, until);
}
}
else {
break;
}
}
activeTimeTask = null;
}
static function archiveTimeTask(timeTask: TimeTask, frameEnd: Float) {
#if sys_server
if (timeTask.next > frameEnd - timeWarpSaveTime) {
outdatedTimeTasks.push(timeTask);
}
#end
}
/**
* An approximation of the amount of time (in fractional seconds) that elapsed while the game was active.
* This value is optimized for achieving smooth framerates.
*/
public static function time(): Float {
return current;
}
/**
* The amount of time (in fractional seconds) that elapsed since the game started.
*/
public static function realTime(): Float {
return System.time;
}
public static function resetTime(): Void {
var now = System.time;
var dif = now - startTime;
startTime = now;
for (timeTask in timeTasks) {
timeTask.start -= dif;
timeTask.next -= dif;
}
for (i in 0...DIF_COUNT)
deltas[i] = 0;
current = 0;
lastTime = 0;
lastFrameEnd = 0;
}
public static function addBreakableFrameTask(task: Void->Bool, priority: Int): Int {
frameTasks.push(new FrameTask(task, priority, ++currentFrameTaskId));
frame_tasks_sorted = false;
return currentFrameTaskId;
}
public static function addFrameTask(task: Void->Void, priority: Int): Int {
return addBreakableFrameTask(function() {
task();
return true;
}, priority);
}
public static function pauseFrameTask(id: Int, paused: Bool): Void {
for (frameTask in frameTasks) {
if (frameTask.id == id) {
frameTask.paused = paused;
break;
}
}
}
public static function removeFrameTask(id: Int): Void {
for (frameTask in frameTasks) {
if (frameTask.id == id) {
frameTask.active = false;
break;
}
}
}
public static function generateGroupId(): Int {
return ++currentGroupId;
}
public static function addBreakableTimeTaskToGroup(groupId: Int, task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int {
var t = new TimeTask();
t.active = true;
t.task = task;
t.id = ++currentTimeTaskId;
t.groupId = groupId;
t.start = current + start;
t.period = 0;
if (period != 0)
t.period = period;
t.duration = 0; // infinite
if (duration != 0)
t.duration = t.start + duration;
t.next = t.start;
insertSorted(timeTasks, t);
return t.id;
}
public static function addTimeTaskToGroup(groupId: Int, task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int {
return addBreakableTimeTaskToGroup(groupId, function() {
task();
return true;
}, start, period, duration);
}
public static function addBreakableTimeTask(task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int {
return addBreakableTimeTaskToGroup(0, task, start, period, duration);
}
public static function addTimeTask(task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int {
return addTimeTaskToGroup(0, task, start, period, duration);
}
static function getTimeTask(id: Int): TimeTask {
if (activeTimeTask != null && activeTimeTask.id == id)
return activeTimeTask;
for (timeTask in timeTasks) {
if (timeTask.id == id) {
return timeTask;
}
}
for (timeTask in pausedTimeTasks) {
if (timeTask.id == id) {
return timeTask;
}
}
return null;
}
public static function pauseTimeTask(id: Int, paused: Bool): Void {
var timeTask = getTimeTask(id);
if (timeTask != null) {
pauseRunningTimeTask(timeTask, paused);
}
if (activeTimeTask != null && activeTimeTask.id == id) {
activeTimeTask.paused = paused;
}
}
static function pauseRunningTimeTask(timeTask: TimeTask, paused: Bool): Void {
timeTask.paused = paused;
if (paused) {
timeTasks.remove(timeTask);
pausedTimeTasks.push(timeTask);
}
else {
insertSorted(timeTasks, timeTask);
pausedTimeTasks.remove(timeTask);
}
}
public static function pauseTimeTasks(groupId: Int, paused: Bool): Void {
if (paused) {
for (timeTask in timeTasks) {
if (timeTask.groupId == groupId) {
pauseRunningTimeTask(timeTask, paused);
}
}
}
else {
for (timeTask in pausedTimeTasks) {
if (timeTask.groupId == groupId) {
pauseRunningTimeTask(timeTask, paused);
}
}
}
if (activeTimeTask != null && activeTimeTask.groupId == groupId) {
activeTimeTask.paused = paused;
}
}
public static function removeTimeTask(id: Int): Void {
var timeTask = getTimeTask(id);
if (timeTask != null) {
timeTask.active = false;
timeTasks.remove(timeTask);
}
}
public static function removeTimeTasks(groupId: Int): Void {
for (timeTask in timeTasks) {
if (timeTask.groupId == groupId) {
timeTask.active = false;
timeTasksScratchpad.push(timeTask);
}
}
for (timeTask in timeTasksScratchpad) {
timeTasks.remove(timeTask);
}
while (timeTasksScratchpad.length > 0) {
timeTasksScratchpad.remove(timeTasksScratchpad[0]);
}
if (activeTimeTask != null && activeTimeTask.groupId == groupId) {
activeTimeTask.active = false;
}
}
public static function numTasksInSchedule(): Int {
return timeTasks.length + frameTasks.length;
}
static function insertSorted(list: Array<TimeTask>, task: TimeTask) {
for (i in 0...list.length) {
if (list[i].next > task.next) {
list.insert(i, task);
return;
}
}
list.push(task);
}
static function sortFrameTasks(): Void {
if (frame_tasks_sorted)
return;
frameTasks.sort(function(a: FrameTask, b: FrameTask): Int {
return a.priority > b.priority ? 1 : ((a.priority < b.priority) ? -1 : 0);
});
frame_tasks_sorted = true;
}
}

View File

@ -0,0 +1,45 @@
package kha;
class ScreenCanvas implements Canvas {
static var instance: ScreenCanvas = null;
function new() {}
public static var the(get, null): ScreenCanvas;
static function get_the(): ScreenCanvas {
if (instance == null)
instance = new ScreenCanvas();
return instance;
}
public var width(get, null): Int;
function get_width(): Int {
return System.windowWidth();
}
public var height(get, null): Int;
function get_height(): Int {
return System.windowHeight();
}
public var g1(get, null): kha.graphics1.Graphics;
function get_g1(): kha.graphics1.Graphics {
return null;
}
public var g2(get, null): kha.graphics2.Graphics;
function get_g2(): kha.graphics2.Graphics {
return null;
}
public var g4(get, null): kha.graphics4.Graphics;
function get_g4(): kha.graphics4.Graphics {
return null;
}
}

View File

@ -0,0 +1,11 @@
package kha;
/**
* Enum with all the possible rotations.
*/
enum abstract ScreenRotation(Int) {
var RotationNone = 0;
var Rotation90 = 90;
var Rotation180 = 180;
var Rotation270 = 270;
}

View File

@ -0,0 +1,5 @@
package kha;
@:keep
@:build(kha.internal.ShadersBuilder.build())
class Shaders {}

97
Kha/Sources/kha/Sound.hx Normal file
View File

@ -0,0 +1,97 @@
package kha;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import kha.audio2.ogg.vorbis.Reader;
/**
* Contains compressed or uncompressed audio data.
*/
@:cppFileCode("\n#define STB_VORBIS_HEADER_ONLY\n#include <kinc/libs/stb_vorbis.c>")
class Sound implements Resource {
public var compressedData: Bytes;
public var uncompressedData: kha.arrays.Float32Array;
public var length: Float = 0; // in seconds
public var channels: Int = 0;
public var sampleRate: Int = 0;
public function new() {}
#if kha_kore
public function uncompress(done: Void->Void): Void {
if (uncompressedData != null) {
done();
return;
}
var samples: Int = 0;
var channels: Int = 0;
var samplesPerSecond: Int = 0;
untyped __cpp__("int16_t *data = nullptr");
untyped __cpp__("samples = stb_vorbis_decode_memory((uint8_t*)compressedData->b->GetBase(), compressedData->length, &channels, &samplesPerSecond, &data)");
if (channels == 1) {
length = samples / samplesPerSecond;
uncompressedData = new kha.arrays.Float32Array(samples * 2);
for (i in 0...samples) {
untyped __cpp__("*((float*)&this->uncompressedData->self.data[({0} * 2) * 4]) = data[{0}] / 32767.0f", i);
untyped __cpp__("*((float*)&this->uncompressedData->self.data[({0} * 2 + 1) * 4]) = data[{0}] / 32767.0f", i);
}
}
else {
length = samples / samplesPerSecond;
samples *= channels;
uncompressedData = new kha.arrays.Float32Array(samples);
for (i in 0...samples) {
untyped __cpp__("*((float*)&this->uncompressedData->self.data[{0} * 4]) = data[{0}] / 32767.0f", i);
}
}
this.channels = channels;
this.sampleRate = samplesPerSecond;
untyped __cpp__("delete[] data");
compressedData = null;
done();
}
#else
public function uncompress(done: Void->Void): Void {
#if (!kha_no_ogg)
if (uncompressedData != null) {
done();
return;
}
var output = new BytesOutput();
var header = Reader.readAll(compressedData, output, true);
var soundBytes = output.getBytes();
var count = Std.int(soundBytes.length / 4);
if (header.channel == 1) {
length = count / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
uncompressedData = new kha.arrays.Float32Array(count * 2);
for (i in 0...count) {
uncompressedData[i * 2 + 0] = soundBytes.getFloat(i * 4);
uncompressedData[i * 2 + 1] = soundBytes.getFloat(i * 4);
}
}
else {
length = count / 2 / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
uncompressedData = new kha.arrays.Float32Array(count);
for (i in 0...count) {
uncompressedData[i] = soundBytes.getFloat(i * 4);
}
}
channels = header.channel;
sampleRate = header.sampleRate;
compressedData = null;
done();
#end
}
#end
public function unload() {
compressedData = null;
uncompressedData = null;
}
}

View File

@ -0,0 +1,59 @@
package kha;
import haxe.io.Bytes;
import haxe.Serializer;
import haxe.Unserializer;
// A file in the storage system.
// Be aware that on some platforms files may be easily lost, such us Flash or HTML5.
class StorageFile {
public function read(): Blob {
return null;
}
public function write(data: Blob): Void {}
public function append(data: Blob): Void {}
public function canAppend(): Bool {
return false;
}
public function maxSize(): Int {
return -1;
}
public function writeString(data: String): Void {
var bytes = Bytes.ofString(data);
write(Blob.fromBytes(bytes));
}
public function appendString(data: String): Void {
var bytes = Bytes.ofString(data);
append(Blob.fromBytes(bytes));
}
public function readString(): String {
var blob = read();
if (blob == null)
return null;
else
return blob.toString();
}
public function writeObject(object: Dynamic): Void {
writeString(Serializer.run(object));
}
public function readObject(): Dynamic {
var s = readString();
if (s == null)
return null;
try {
return Unserializer.run(s);
}
catch (e:Dynamic) {
return null;
}
}
}

View File

@ -0,0 +1,11 @@
package kha;
class StringExtensions {
public static function toCharArray(s: String): Array<Int> {
var results = new Array<Int>();
for (i in 0...s.length) {
results.push(s.charCodeAt(i));
}
return results;
}
}

376
Kha/Sources/kha/System.hx Normal file
View File

@ -0,0 +1,376 @@
package kha;
import kha.WindowOptions;
@:structInit
class SystemOptions {
@:optional public var title: String = "Kha";
@:optional public var width: Int = -1;
@:optional public var height: Int = -1;
@:optional public var window: WindowOptions = null;
@:optional public var framebuffer: FramebufferOptions = null;
/**
* Used to provide parameters for System.start
* @param title The application title is the default window title (unless the window parameter provides a title of its own)
* and is used for various other purposes - for example for save data locations
* @param width Just a shortcut which overwrites window.width if set
* @param height Just a shortcut which overwrites window.height if set
* @param window Optionally provide window options
* @param framebuffer Optionally provide framebuffer options
*/
public function new(title: String = "Kha", ?width: Int = -1, ?height: Int = -1, window: WindowOptions = null, framebuffer: FramebufferOptions = null) {
this.title = title;
this.window = window == null ? {} : window;
if (width > 0) {
this.window.width = width;
this.width = width;
}
else {
this.width = this.window.width;
}
if (height > 0) {
this.window.height = height;
this.height = height;
}
else {
this.height = this.window.height;
}
if (this.window.title == null) {
this.window.title = title;
}
this.framebuffer = framebuffer == null ? {} : framebuffer;
}
}
typedef OldSystemOptions = {
?title: String,
?width: Int,
?height: Int,
?samplesPerPixel: Int,
?vSync: Bool,
?windowMode: WindowMode,
?resizable: Bool,
?maximizable: Bool,
?minimizable: Bool
}
@:allow(kha.SystemImpl)
class System {
static var renderListeners: Array<Array<Framebuffer>->Void> = [];
static var foregroundListeners: Array<Void->Void> = [];
static var resumeListeners: Array<Void->Void> = [];
static var pauseListeners: Array<Void->Void> = [];
static var backgroundListeners: Array<Void->Void> = [];
static var shutdownListeners: Array<Void->Void> = [];
static var dropFilesListeners: Array<String->Void> = [];
static var cutListener: Void->String = null;
static var copyListener: Void->String = null;
static var pasteListener: String->Void = null;
static var loginListener: Void->Void = null;
static var logoutListener: Void->Void = null;
static var theTitle: String;
@:deprecated("Use System.start instead")
public static function init(options: OldSystemOptions, callback: Void->Void): Void {
var features: kha.WindowFeatures = None;
if (options.resizable)
features |= WindowFeatures.FeatureResizable;
if (options.maximizable)
features |= WindowFeatures.FeatureMaximizable;
if (options.minimizable)
features |= WindowFeatures.FeatureMinimizable;
var newOptions: SystemOptions = {
title: options.title,
width: options.width,
height: options.height,
window: {
mode: options.windowMode,
windowFeatures: features
},
framebuffer: {
samplesPerPixel: options.samplesPerPixel,
verticalSync: options.vSync
}
};
start(newOptions, function(_) {
callback();
});
}
public static function start(options: SystemOptions, callback: Window->Void): Void {
theTitle = options.title;
SystemImpl.init(options, callback);
}
public static var title(get, never): String;
static function get_title(): String {
return theTitle;
}
@:deprecated("Use System.notifyOnFrames instead")
public static function notifyOnRender(listener: Framebuffer->Void, id: Int = 0): Void {
renderListeners.push(function(framebuffers: Array<Framebuffer>) {
if (id < framebuffers.length) {
listener(framebuffers[id]);
}
});
}
/**
* The provided listener is called when new framebuffers are ready for rendering into.
* Each framebuffer corresponds to the kha.Window of the same index, single-window
* applications always receive an array of only one framebuffer.
* @param listener
* The callback to add
*/
public static function notifyOnFrames(listener: Array<Framebuffer>->Void): Void {
renderListeners.push(listener);
}
/**
* Removes a previously set frames listener.
* @param listener
* The callback to remove
*/
public static function removeFramesListener(listener: Array<Framebuffer>->Void): Void {
renderListeners.remove(listener);
}
public static function notifyOnApplicationState(foregroundListener: Void->Void, resumeListener: Void->Void, pauseListener: Void->Void,
backgroundListener: Void->Void, shutdownListener: Void->Void): Void {
if (foregroundListener != null)
foregroundListeners.push(foregroundListener);
if (resumeListener != null)
resumeListeners.push(resumeListener);
if (pauseListener != null)
pauseListeners.push(pauseListener);
if (backgroundListener != null)
backgroundListeners.push(backgroundListener);
if (shutdownListener != null)
shutdownListeners.push(shutdownListener);
}
public static function removeApplicationStateListeners(foregroundListener: Void->Void, resumeListener: Void->Void, pauseListener: Void->Void,
backgroundListener: Void->Void, shutdownListener: Void->Void): Void {
if (foregroundListener != null)
foregroundListeners.remove(foregroundListener);
if (resumeListener != null)
resumeListeners.remove(resumeListener);
if (pauseListener != null)
pauseListeners.remove(pauseListener);
if (backgroundListener != null)
backgroundListeners.remove(backgroundListener);
if (shutdownListener != null)
shutdownListeners.remove(shutdownListener);
}
public static function notifyOnDropFiles(dropFilesListener: String->Void): Void {
dropFilesListeners.push(dropFilesListener);
}
public static function removeDropListener(listener: String->Void): Void {
dropFilesListeners.remove(listener);
}
public static function notifyOnCutCopyPaste(cutListener: Void->String, copyListener: Void->String, pasteListener: String->Void): Void {
System.cutListener = cutListener;
System.copyListener = copyListener;
System.pasteListener = pasteListener;
}
/*public static function copyToClipboard(text: String) {
SystemImpl.copyToClipboard(text);
}*/
public static function notifyOnLoginLogout(loginListener: Void->Void, logoutListener: Void->Void) {
System.loginListener = loginListener;
System.logoutListener = logoutListener;
}
public static function login(): Void {
SystemImpl.login();
}
public static function waitingForLogin(): Bool {
return SystemImpl.waitingForLogin();
}
public static function allowUserChange(): Void {
SystemImpl.allowUserChange();
}
public static function disallowUserChange(): Void {
SystemImpl.disallowUserChange();
}
static function render(framebuffers: Array<Framebuffer>): Void {
for (listener in renderListeners) {
listener(framebuffers);
}
}
static function foreground(): Void {
for (listener in foregroundListeners) {
listener();
}
}
static function resume(): Void {
for (listener in resumeListeners) {
listener();
}
}
static function pause(): Void {
for (listener in pauseListeners) {
listener();
}
}
static function background(): Void {
for (listener in backgroundListeners) {
listener();
}
}
static function shutdown(): Void {
for (listener in shutdownListeners) {
listener();
}
}
static function dropFiles(filePath: String): Void {
for (listener in dropFilesListeners) {
listener(filePath);
}
}
public static var time(get, null): Float;
static function get_time(): Float {
return SystemImpl.getTime();
}
public static function windowWidth(window: Int = 0): Int {
return Window.get(window).width;
}
public static function windowHeight(window: Int = 0): Int {
return Window.get(window).height;
}
public static var screenRotation(get, null): ScreenRotation;
static function get_screenRotation(): ScreenRotation {
return RotationNone;
}
public static var systemId(get, null): String;
static function get_systemId(): String {
return SystemImpl.getSystemId();
}
/**
* Pulses the vibration hardware on the device for time in milliseconds, if such hardware exists.
*/
public static function vibrate(ms: Int): Void {
return SystemImpl.vibrate(ms);
}
/**
* The IS0 639 system current language identifier.
*/
public static var language(get, never): String;
static function get_language(): String {
return SystemImpl.getLanguage();
}
/**
* Schedules the application to stop as soon as possible. This is not possible on all targets.
* @return Returns true if the application can be stopped
*/
public static function stop(): Bool {
return SystemImpl.requestShutdown();
}
public static function loadUrl(url: String): Void {
SystemImpl.loadUrl(url);
}
@:deprecated("This only returns a default value")
public static function canSwitchFullscreen(): Bool {
return true;
}
@:deprecated("Use the kha.Window API instead")
public static function isFullscreen(): Bool {
return Window.get(0).mode == WindowMode.Fullscreen || Window.get(0).mode == WindowMode.ExclusiveFullscreen;
}
@:deprecated("Use the kha.Window API instead")
public static function requestFullscreen(): Void {
Window.get(0).mode = WindowMode.Fullscreen;
}
@:deprecated("Use the kha.Window API instead")
public static function exitFullscreen(): Void {
Window.get(0).mode = WindowMode.Windowed;
}
@:deprecated("This does nothing")
public static function notifyOnFullscreenChange(func: Void->Void, error: Void->Void): Void {}
@:deprecated("This does nothing")
public static function removeFullscreenListener(func: Void->Void, error: Void->Void): Void {}
@:deprecated("This does nothing. On Windows you can use Window.resize instead after setting the mode to ExclusiveFullscreen")
public static function changeResolution(width: Int, height: Int): Void {}
@:deprecated("Use System.stop instead")
public static function requestShutdown(): Void {
stop();
}
@:deprecated("Use the kha.Window API instead")
public static var vsync(get, null): Bool;
static function get_vsync(): Bool {
return Window.get(0).vSynced;
}
@:deprecated("Use the kha.Display API instead")
public static var refreshRate(get, null): Int;
static function get_refreshRate(): Int {
return Display.primary.frequency;
}
@:deprecated("Use the kha.Display API instead")
public static function screenDpi(): Int {
return Display.primary.pixelsPerInch;
}
public static function safeZone(): Float {
return SystemImpl.safeZone();
}
public static function automaticSafeZone(): Bool {
return SystemImpl.automaticSafeZone();
}
public static function setSafeZone(value: Float): Void {
SystemImpl.setSafeZone(value);
}
public static function unlockAchievement(id: Int): Void {
SystemImpl.unlockAchievement(id);
}
}

100
Kha/Sources/kha/Video.hx Normal file
View File

@ -0,0 +1,100 @@
package kha;
/**
* This represents a Video file.
*/
class Video implements Resource {
/**
* The width of the video file in pixels.
*/
public function width(): Int {
return 100;
}
/**
* The height of the video file in pixels.
*/
public function height(): Int {
return 100;
}
/**
* Create a new media object instance.
*/
public function new(): Void {}
/**
* Play / resume the media element.
*
* @param loop If playing it looped, default = false.
*/
public function play(loop: Bool = false): Void {}
/**
* Call this every frame to update the video.
* This is not required on all targets but where it's not required the function just does nothing - so please call it.
*/
public function update(dt: Float): Void {}
/**
* Pause the media element.
*/
public function pause(): Void {}
/**
* Pause the stop element.
*/
public function stop(): Void {}
/**
* Return the media length, in milliseconds.
*/
public function getLength(): Int { // Milliseconds
return 0;
}
/**
* Return the media position, in milliseconds.
* Deprecated.
*/
public function getCurrentPos(): Int { // Milliseconds
return 0;
}
public var position(get, set): Int;
function get_position(): Int {
return 0;
}
function set_position(value: Int): Int {
return 0;
}
/**
* Return the media volume, between 0 and 1.
*/
public function getVolume(): Float { // [0, 1]
return 1;
}
/**
* Set the media volume, between 0 and 1.
*
* @param volume The new volume, between 0 and 1.
*/
public function setVolume(volume: Float): Void { // [0, 1]
}
/**
* If the media has finished or not.
*/
public function isFinished(): Bool {
return getCurrentPos() >= getLength();
}
/**
* Unload the resource from memory.
*/
public function unload(): Void {}
}

22
Kha/Sources/kha/Window.hx Normal file
View File

@ -0,0 +1,22 @@
package kha;
extern class Window {
public static function create(win: WindowOptions = null, frame: FramebufferOptions = null): Window;
public static function destroy(window: Window): Void;
public static function get(index: Int): Window;
public static var all(get, never): Array<Window>;
public function resize(width: Int, height: Int): Void;
public function move(x: Int, y: Int): Void;
public function changeWindowFeatures(features: WindowOptions.WindowFeatures): Void;
public function changeFramebuffer(frame: FramebufferOptions): Void;
public var x(get, set): Int;
public var y(get, set): Int;
public var width(get, set): Int;
public var height(get, set): Int;
public var mode(get, set): WindowMode;
public var visible(get, set): Bool;
public var title(get, set): String;
public function notifyOnResize(callback: Int->Int->Void): Void;
public function notifyOnPpiChange(callback: Int->Void): Void;
public var vSynced(get, never): Bool;
}

View File

@ -0,0 +1,7 @@
package kha;
enum abstract WindowMode(Int) {
var Windowed = 0; // Use an ordinary window
var Fullscreen = 1; // Regular fullscreen mode
var ExclusiveFullscreen = 2; // Exclusive fullscreen mode (switches monitor resolution, Windows only)
}

View File

@ -0,0 +1,42 @@
package kha;
enum abstract WindowFeatures(Int) to Int {
var None = 0;
var FeatureResizable = 1;
var FeatureMinimizable = 2;
var FeatureMaximizable = 4;
var FeatureBorderless = 8;
var FeatureOnTop = 16;
function new(value: Int) {
this = value;
}
@:op(A | B) static function or(a: WindowFeatures, b: WindowFeatures): WindowFeatures;
}
@:structInit
class WindowOptions {
@:optional public var title: String = null;
@:optional public var x: Int = -1;
@:optional public var y: Int = -1;
@:optional public var width: Int = 800;
@:optional public var height: Int = 600;
@:optional public var display: Int = -1;
@:optional public var visible: Bool = true;
@:optional public var windowFeatures: WindowFeatures = FeatureResizable | FeatureMaximizable | FeatureMinimizable;
@:optional public var mode: WindowMode = Windowed;
public function new(title: String = null, ?x: Int = -1, ?y: Int = -1, ?width: Int = 800, ?height: Int = 600, ?display: Int = -1, ?visible: Bool = true,
?windowFeatures: WindowFeatures, ?mode: WindowMode = WindowMode.Windowed) {
this.title = title;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.display = display;
this.visible = visible;
this.windowFeatures = (windowFeatures == null) ? WindowFeatures.FeatureResizable | WindowFeatures.FeatureMaximizable | WindowFeatures.FeatureMinimizable : windowFeatures;
this.mode = mode;
}
}

145
Kha/Sources/kha/Worker.hx Normal file
View File

@ -0,0 +1,145 @@
package kha;
import haxe.macro.Context;
import haxe.macro.Expr;
#if macro
import kha.internal.AssetsBuilder;
import sys.io.File;
#end
using haxe.macro.ExprTools;
#if (kha_html5 || kha_krom)
class Worker {
#if kha_in_worker
public static function notifyWorker(func: Dynamic->Void): Void {
#if !macro
js.Syntax.code("self").addEventListener("message", function(e) {
func(e.data);
});
#end
}
public static function postFromWorker(message: Dynamic): Void {
#if !macro
js.Syntax.code("self").postMessage(message);
#end
}
#else
#if macro
static var threads = new Array<String>();
#else
var worker: js.html.Worker;
#end
function new(file: String) {
#if !macro
worker = new js.html.Worker(file);
#end
}
@:noCompletion
public static function _create(file: String): Worker {
return new Worker(file);
}
public function notify(func: Dynamic->Void): Void {
#if !macro
worker.addEventListener("message", function(e) {
func(e.data);
});
#end
}
public function post(message: Dynamic): Void {
#if !macro
worker.postMessage(message);
#end
}
public static macro function create(expr: Expr) {
var name: String = expr.toString();
if (threads.indexOf(name) < 0) {
threads.push(name);
}
var threadstring = "";
for (thread in threads) {
threadstring += thread + "\n";
}
File.saveContent(AssetsBuilder.findResources() + "workers.txt", threadstring);
return Context.parse("kha.Worker._create(\"" + name + ".js\")", Context.currentPos());
}
#end
}
#end
#if kha_kore
import sys.thread.Thread;
import sys.thread.Tls;
import kha.Scheduler;
private class Message {
public final threadId: Int;
public final message: Dynamic;
public function new(message: Dynamic) {
this.threadId = @:privateAccess Worker.threadId.value;
this.message = message;
}
}
class Worker {
public static var _mainThread: Thread;
static var notifyFuncs: Array<Dynamic->Void> = [];
static var taskId: Int = -1;
static var nextThreadId: Int = 0;
static var threadId = new Tls<Int>();
var thread: Thread;
var id: Int;
function new(thread: Thread, id: Int) {
this.thread = thread;
this.id = id;
}
public static function create(clazz: Class<Dynamic>): Worker {
var id = nextThreadId++;
return new Worker(Thread.create(function() {
threadId.value = id;
Reflect.field(clazz, "main")();
}), id);
}
public function notify(func: Dynamic->Void): Void {
notifyFuncs[id] = func;
if (taskId != -1)
return;
taskId = Scheduler.addFrameTask(function() {
var message: Message = Thread.readMessage(false);
if (message != null) {
var func = notifyFuncs[message.threadId];
func(message.message);
}
}, 0);
}
public function post(message: Dynamic): Void {
thread.sendMessage(message);
}
public static function notifyWorker(func: Dynamic->Void): Void {
while (true) {
var message = Thread.readMessage(true);
if (message != null) {
func(message);
}
}
}
public static function postFromWorker(message: Dynamic): Void {
_mainThread.sendMessage(new Message(message));
}
}
#end

View File

@ -0,0 +1,82 @@
package kha.arrays;
/**
* Maps a byte array over a byte buffer, allowing for mixed-type access of its contents.
* This type unifies with all typed array classes, and vice-versa.
*/
extern class ByteArray {
/**
* Underlying byte buffer.
*/
var buffer(get, null): ByteBuffer;
/**
* Length in bytes of the byte array.
*/
var byteLength(get, null): Int;
/**
* Byte offset into the underlying byte buffer.
*/
var byteOffset(get, null): Int;
/**
* Creates a new array over a byte buffer.
* @param buffer underlying byte buffer
* @param byteOffset offset of the first byte of the array into the byte buffer, defaults to 0
* @param byteLength amount of bytes to map, defaults to entire buffer
*/
function new(buffer: ByteBuffer, ?byteOffset: Int, ?byteLength: Int): Void;
/**
* Creates a new array from scratch.
* @param byteLength number of bytes to create
* @return ByteArray
*/
static function make(byteLength: Int): ByteArray;
function getInt8(byteOffset: Int): Int;
function getUint8(byteOffset: Int): Int;
function getInt16(byteOffset: Int): Int;
function getUint16(byteOffset: Int): Int;
function getInt32(byteOffset: Int): Int;
function getUint32(byteOffset: Int): Int;
function getFloat32(byteOffset: Int): FastFloat;
function getFloat64(byteOffset: Int): Float;
function setInt8(byteOffset: Int, value: Int): Void;
function setUint8(byteOffset: Int, value: Int): Void;
function setInt16(byteOffset: Int, value: Int): Void;
function setUint16(byteOffset: Int, value: Int): Void;
function setInt32(byteOffset: Int, value: Int): Void;
function setUint32(byteOffset: Int, value: Int): Void;
function setFloat32(byteOffset: Int, value: FastFloat): Void;
function setFloat64(byteOffset: Int, value: Float): Void;
function getInt16LE(byteOffset: Int): Int;
function getUint16LE(byteOffset: Int): Int;
function getInt32LE(byteOffset: Int): Int;
function getUint32LE(byteOffset: Int): Int;
function getFloat32LE(byteOffset: Int): FastFloat;
function getFloat64LE(byteOffset: Int): Float;
function setInt16LE(byteOffset: Int, value: Int): Void;
function setUint16LE(byteOffset: Int, value: Int): Void;
function setInt32LE(byteOffset: Int, value: Int): Void;
function setUint32LE(byteOffset: Int, value: Int): Void;
function setFloat32LE(byteOffset: Int, value: FastFloat): Void;
function setFloat64LE(byteOffset: Int, value: Float): Void;
function getInt16BE(byteOffset: Int): Int;
function getUint16BE(byteOffset: Int): Int;
function getInt32BE(byteOffset: Int): Int;
function getUint32BE(byteOffset: Int): Int;
function getFloat32BE(byteOffset: Int): FastFloat;
function getFloat64BE(byteOffset: Int): Float;
function setInt16BE(byteOffset: Int, value: Int): Void;
function setUint16BE(byteOffset: Int, value: Int): Void;
function setInt32BE(byteOffset: Int, value: Int): Void;
function setUint32BE(byteOffset: Int, value: Int): Void;
function setFloat32BE(byteOffset: Int, value: FastFloat): Void;
function setFloat64BE(byteOffset: Int, value: Float): Void;
function subarray(start: Int, ?end: Int): ByteArray;
}

View File

@ -0,0 +1,18 @@
package kha.arrays;
/**
* Holds unformatted binary data into a byte buffer. Use ByteArray for access.
*/
extern class ByteBuffer {
public static function create(length: Int): ByteBuffer;
public var byteLength(get, null): Int;
/**
* Returns a shallow copy of a range of bytes from this buffer.
* @param begin start of the range, inclusive
* @param end end of the range, exclusive
* @return ByteBuffer
*/
public function slice(begin: Int, end: Int): ByteBuffer;
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Float32Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 2;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 4);
}
@:arrayAccess
public inline function get(k: Int): FastFloat {
return this.getFloat32(k * 4);
}
@:arrayAccess
public inline function set(k: Int, v: FastFloat): FastFloat {
this.setFloat32(k * 4, v);
return v;
}
public inline function subarray(start: Int, ?end: Int): Float32Array {
return this.subarray(start * 4, end != null ? end * 4 : end);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Float64Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 3;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 8);
}
@:arrayAccess
public inline function get(k: Int): Float {
return this.getFloat64(k * 8);
}
@:arrayAccess
public inline function set(k: Int, v: Float): Float {
this.setFloat64(k * 8, v);
return v;
}
public inline function subarray(start: Int, ?end: Int): Float64Array {
return this.subarray(start * 8, end != null ? end * 8 : end);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Int16Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 1;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 2);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getInt16(k * 2);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setInt16(k * 2, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Int16Array {
return this.subarray(start * 2, end != null ? end * 2 : null);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Int32Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 2;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 4);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getInt32(k * 4);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setInt32(k * 4, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Int32Array {
return this.subarray(start * 4, end != null ? end * 4 : null);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Int8Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength;
}
public function new(elements: Int) {
this = ByteArray.make(elements);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getInt8(k);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setInt8(k, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Int8Array {
return this.subarray(start, end);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Uint16Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 1;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 2);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getUint16(k * 2);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setUint16(k * 2, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Uint16Array {
return this.subarray(start * 2, end != null ? end * 2 : null);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Uint32Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength >> 2;
}
public function new(elements: Int) {
this = ByteArray.make(elements * 4);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getUint32(k * 4);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setUint32(k * 4, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Uint32Array {
return this.subarray(start * 4, end != null ? end * 4 : null);
}
}

View File

@ -0,0 +1,29 @@
package kha.arrays;
@:forward
abstract Uint8Array(ByteArray) from ByteArray to ByteArray {
public var length(get, never): Int;
inline function get_length(): Int {
return this.byteLength;
}
public function new(elements: Int) {
this = ByteArray.make(elements);
}
@:arrayAccess
public inline function get(k: Int): Int {
return this.getUint8(k);
}
@:arrayAccess
public inline function set(k: Int, v: Int): Int {
this.setUint8(k, v);
return get(k);
}
public inline function subarray(start: Int, ?end: Int): Uint8Array {
return this.subarray(start, end);
}
}

View File

@ -0,0 +1,17 @@
package kha.audio1;
import kha.Sound;
extern class Audio {
/**
* Plays a sound immediately.
* @param sound
* The sound to play
* @param loop
* Whether or not to automatically loop the sound
* @return A channel object that can be used to control the playing sound. Please be a ware that Null is returned when the maximum number of simultaneously played channels was reached.
*/
public static function play(sound: Sound, loop: Bool = false): AudioChannel;
public static function stream(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel;
}

View File

@ -0,0 +1,17 @@
package kha.audio1;
interface AudioChannel {
function play(): Void;
function pause(): Void;
function stop(): Void;
var length(get, null): Float; // Seconds
private function get_length(): Float;
var position(get, set): Float; // Seconds
private function get_position(): Float;
private function set_position(value: Float): Float;
var volume(get, set): Float;
private function get_volume(): Float;
private function set_volume(value: Float): Float;
var finished(get, null): Bool;
private function get_finished(): Bool;
}

View File

@ -0,0 +1,30 @@
package kha.audio2;
extern class Audio {
/**
* The samples per second natively used by the target system.
*/
public static var samplesPerSecond: Int;
/**
* Requests additional audio data.
* Beware: This is called from a separate audio thread on some targets.
* See kha.audio2.Audio1 for sample code.
*/
public static var audioCallback: Int->Buffer->Void;
/**
* Similar to kha.audio1.Audio.stream, but only for hardware accelerated audio playback.
* Expect this to return null and provide a pure software alternative.
* @param music The music we want to play.
* @param loop If we want the music to loop, default = false.
* @return On success returns a valid AudioChannel object. Otherwise returns null.
*/
public static function stream(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel;
/**
* Used in Kinc based backends to untangle the audio thread from the garbage collector.
* Be very careful please.
*/
public static var disableGcInteractions: Bool;
}

View File

@ -0,0 +1,197 @@
package kha.audio2;
#if cpp
import sys.thread.Mutex;
#end
import haxe.ds.Vector;
class Audio1 {
static inline var channelCount: Int = 32;
static var soundChannels: Vector<AudioChannel>;
static var streamChannels: Vector<StreamChannel>;
static var internalSoundChannels: Vector<AudioChannel>;
static var internalStreamChannels: Vector<StreamChannel>;
static var sampleCache1: kha.arrays.Float32Array;
static var sampleCache2: kha.arrays.Float32Array;
static var lastAllocationCount: Int = 0;
#if cpp
static var mutex: Mutex;
#end
@:noCompletion
public static function _init(): Void {
#if cpp
mutex = new Mutex();
#end
soundChannels = new Vector<AudioChannel>(channelCount);
streamChannels = new Vector<StreamChannel>(channelCount);
internalSoundChannels = new Vector<AudioChannel>(channelCount);
internalStreamChannels = new Vector<StreamChannel>(channelCount);
sampleCache1 = new kha.arrays.Float32Array(512);
sampleCache2 = new kha.arrays.Float32Array(512);
lastAllocationCount = 0;
Audio.audioCallback = mix;
}
static inline function max(a: Float, b: Float): Float {
return a > b ? a : b;
}
static inline function min(a: Float, b: Float): Float {
return a < b ? a : b;
}
public static function mix(samplesBox: kha.internal.IntBox, buffer: Buffer): Void {
var samples = samplesBox.value;
if (sampleCache1.length < samples) {
if (Audio.disableGcInteractions) {
trace("Unexpected allocation request in audio thread.");
for (i in 0...samples) {
buffer.data.set(buffer.writeLocation, 0);
buffer.writeLocation += 1;
if (buffer.writeLocation >= buffer.size) {
buffer.writeLocation = 0;
}
}
lastAllocationCount = 0;
Audio.disableGcInteractions = false;
return;
}
sampleCache1 = new kha.arrays.Float32Array(samples * 2);
sampleCache2 = new kha.arrays.Float32Array(samples * 2);
lastAllocationCount = 0;
}
else {
if (lastAllocationCount > 100) {
Audio.disableGcInteractions = true;
}
else {
lastAllocationCount += 1;
}
}
for (i in 0...samples) {
sampleCache2[i] = 0;
}
#if cpp
mutex.acquire();
#end
for (i in 0...channelCount) {
internalSoundChannels[i] = soundChannels[i];
}
for (i in 0...channelCount) {
internalStreamChannels[i] = streamChannels[i];
}
#if cpp
mutex.release();
#end
for (channel in internalSoundChannels) {
if (channel == null || channel.finished)
continue;
channel.nextSamples(sampleCache1, samples, buffer.samplesPerSecond);
for (i in 0...samples) {
sampleCache2[i] += sampleCache1[i] * channel.volume;
}
}
for (channel in internalStreamChannels) {
if (channel == null || channel.finished)
continue;
channel.nextSamples(sampleCache1, samples, buffer.samplesPerSecond);
for (i in 0...samples) {
sampleCache2[i] += sampleCache1[i] * channel.volume;
}
}
for (i in 0...samples) {
buffer.data.set(buffer.writeLocation, max(min(sampleCache2[i], 1.0), -1.0));
buffer.writeLocation += 1;
if (buffer.writeLocation >= buffer.size) {
buffer.writeLocation = 0;
}
}
}
public static function play(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel {
var channel: kha.audio2.AudioChannel = null;
if (Audio.samplesPerSecond != sound.sampleRate) {
channel = new ResamplingAudioChannel(loop, sound.sampleRate);
}
else {
#if sys_ios
channel = new ResamplingAudioChannel(loop, sound.sampleRate);
#else
channel = new AudioChannel(loop);
#end
}
channel.data = sound.uncompressedData;
var foundChannel = false;
#if cpp
mutex.acquire();
#end
for (i in 0...channelCount) {
if (soundChannels[i] == null || soundChannels[i].finished) {
soundChannels[i] = channel;
foundChannel = true;
break;
}
}
#if cpp
mutex.release();
#end
return foundChannel ? channel : null;
}
public static function _playAgain(channel: kha.audio2.AudioChannel): Void {
#if cpp
mutex.acquire();
#end
for (i in 0...channelCount) {
if (soundChannels[i] == channel) {
soundChannels[i] = null;
}
}
for (i in 0...channelCount) {
if (soundChannels[i] == null || soundChannels[i].finished || soundChannels[i] == channel) {
soundChannels[i] = channel;
break;
}
}
#if cpp
mutex.release();
#end
}
public static function stream(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel {
{
// try to use hardware accelerated audio decoding
var hardwareChannel = Audio.stream(sound, loop);
if (hardwareChannel != null)
return hardwareChannel;
}
var channel: StreamChannel = new StreamChannel(sound.compressedData, loop);
var foundChannel = false;
#if cpp
mutex.acquire();
#end
for (i in 0...channelCount) {
if (streamChannels[i] == null || streamChannels[i].finished) {
streamChannels[i] = channel;
foundChannel = true;
break;
}
}
#if cpp
mutex.release();
#end
return foundChannel ? channel : null;
}
}

View File

@ -0,0 +1,165 @@
package kha.audio2;
import kha.arrays.Float32Array;
@:headerCode("#include <kinc/threads/atomic.h>")
@:headerClassCode("volatile float kinc_volume; volatile int kinc_position; volatile int kinc_paused; volatile int kinc_stopped; volatile int kinc_looping;")
class AudioChannel implements kha.audio1.AudioChannel {
public var data: Float32Array = null;
#if cpp
var myVolume(get, set): Float;
inline function get_myVolume(): Float {
return untyped __cpp__("kinc_volume");
}
inline function set_myVolume(value: Float): Float {
untyped __cpp__("KINC_ATOMIC_EXCHANGE_FLOAT(&kinc_volume, (float){0})", value);
return value;
}
var myPosition(get, set): Int;
inline function get_myPosition(): Int {
return untyped __cpp__("kinc_position");
}
inline function set_myPosition(value: Int): Int {
untyped __cpp__("KINC_ATOMIC_EXCHANGE_32(&kinc_position, {0})", value);
return value;
}
var paused(get, set): Bool;
inline function get_paused(): Bool {
return untyped __cpp__("kinc_paused != 0");
}
inline function set_paused(value: Bool): Bool {
untyped __cpp__("KINC_ATOMIC_EXCHANGE_32(&kinc_paused, {0} ? 1 : 0)", value);
return value;
}
var stopped(get, set): Bool;
inline function get_stopped(): Bool {
return untyped __cpp__("kinc_stopped != 0");
}
inline function set_stopped(value: Bool): Bool {
untyped __cpp__("KINC_ATOMIC_EXCHANGE_32(&kinc_stopped, {0} ? 1 : 0)", value);
return value;
}
var looping(get, set): Bool;
inline function get_looping(): Bool {
return untyped __cpp__("kinc_looping != 0");
}
inline function set_looping(value: Bool): Bool {
untyped __cpp__("KINC_ATOMIC_EXCHANGE_32(&kinc_looping, {0} ? 1 : 0)", value);
return value;
}
#else
var myVolume: Float;
var myPosition: Int;
var paused: Bool;
var stopped: Bool;
var looping: Bool;
#end
public function new(looping: Bool) {
this.looping = looping;
stopped = false;
paused = false;
myPosition = 0;
myVolume = 1;
}
public function nextSamples(requestedSamples: Float32Array, requestedLength: Int, sampleRate: Int): Void {
if (paused || stopped) {
for (i in 0...requestedLength) {
requestedSamples[i] = 0;
}
return;
}
var requestedSamplesIndex = 0;
while (requestedSamplesIndex < requestedLength) {
for (i in 0...min(data.length - myPosition, requestedLength - requestedSamplesIndex)) {
requestedSamples[requestedSamplesIndex++] = data[myPosition++];
}
if (myPosition >= data.length) {
myPosition = 0;
if (!looping) {
stopped = true;
break;
}
}
}
while (requestedSamplesIndex < requestedLength) {
requestedSamples[requestedSamplesIndex++] = 0;
}
}
public function play(): Void {
paused = false;
stopped = false;
kha.audio1.Audio._playAgain(this);
}
public function pause(): Void {
paused = true;
}
public function stop(): Void {
myPosition = 0;
stopped = true;
}
public var length(get, null): Float; // Seconds
function get_length(): Float {
return data.length / kha.audio2.Audio.samplesPerSecond / 2; // 44.1 khz in stereo
}
public var position(get, set): Float; // Seconds
function get_position(): Float {
return myPosition / kha.audio2.Audio.samplesPerSecond / 2;
}
function set_position(value: Float): Float {
myPosition = Math.round(value * kha.audio2.Audio.samplesPerSecond * 2);
myPosition = max(min(myPosition, data.length), 0);
return value;
}
public var volume(get, set): Float;
function get_volume(): Float {
return myVolume;
}
function set_volume(value: Float): Float {
return myVolume = value;
}
public var finished(get, null): Bool;
function get_finished(): Bool {
return stopped;
}
static inline function max(a: Int, b: Int) {
return a > b ? a : b;
}
static inline function min(a: Int, b: Int) {
return a < b ? a : b;
}
}

View File

@ -0,0 +1,20 @@
package kha.audio2;
class Buffer {
public var channels: Int;
public var samplesPerSecond: Int;
public var data: kha.arrays.Float32Array;
public var size: Int;
public var readLocation: Int;
public var writeLocation: Int;
public function new(size: Int, channels: Int, samplesPerSecond: Int) {
this.size = size;
this.data = new kha.arrays.Float32Array(size);
this.channels = channels;
this.samplesPerSecond = samplesPerSecond;
readLocation = 0;
writeLocation = 0;
}
}

View File

@ -0,0 +1,137 @@
package kha.audio2;
import kha.arrays.Float32Array;
class ResamplingAudioChannel extends AudioChannel {
public var sampleRate: Int;
public function new(looping: Bool, sampleRate: Int) {
super(looping);
this.sampleRate = sampleRate;
}
public override function nextSamples(requestedSamples: Float32Array, requestedLength: Int, sampleRate: Int): Void {
if (paused || stopped) {
for (i in 0...requestedLength) {
requestedSamples[i] = 0;
}
return;
}
var requestedSamplesIndex = 0;
while (requestedSamplesIndex < requestedLength) {
for (i in 0...min(sampleLength(sampleRate) - myPosition, requestedLength - requestedSamplesIndex)) {
requestedSamples[requestedSamplesIndex++] = sample(myPosition++, sampleRate);
}
if (myPosition >= sampleLength(sampleRate)) {
myPosition = 0;
if (!looping) {
stopped = true;
break;
}
}
}
while (requestedSamplesIndex < requestedLength) {
requestedSamples[requestedSamplesIndex++] = 0;
}
}
inline function sample(position: Int, sampleRate: Int): Float {
var even = position % 2 == 0;
var factor = this.sampleRate / sampleRate;
if (even) {
position = Std.int(position / 2);
var pos = factor * position;
var pos1 = Math.floor(pos);
var pos2 = Math.floor(pos + 1);
pos1 *= 2;
pos2 *= 2;
var minimum = 0;
var maximum = data.length - 1;
maximum = maximum % 2 == 0 ? maximum : maximum - 1;
var a = (pos1 < minimum || pos1 > maximum) ? 0 : data[pos1];
var b = (pos2 < minimum || pos2 > maximum) ? 0 : data[pos2];
return lerp(a, b, pos - Math.floor(pos));
}
else {
position = Std.int(position / 2);
var pos = factor * position;
var pos1 = Math.floor(pos);
var pos2 = Math.floor(pos + 1);
pos1 = pos1 * 2 + 1;
pos2 = pos2 * 2 + 1;
var minimum = 1;
var maximum = data.length - 1;
maximum = maximum % 2 != 0 ? maximum : maximum - 1;
var a = (pos1 < minimum || pos1 > maximum) ? 0 : data[pos1];
var b = (pos2 < minimum || pos2 > maximum) ? 0 : data[pos2];
return lerp(a, b, pos - Math.floor(pos));
}
}
inline function lerp(v0: Float, v1: Float, t: Float) {
return (1 - t) * v0 + t * v1;
}
inline function sampleLength(sampleRate: Int): Int {
var value = Math.ceil(data.length * (sampleRate / this.sampleRate));
return value % 2 == 0 ? value : value + 1;
}
public override function play(): Void {
paused = false;
stopped = false;
kha.audio1.Audio._playAgain(this);
}
public override function pause(): Void {
paused = true;
}
public override function stop(): Void {
myPosition = 0;
stopped = true;
}
override function get_length(): Float {
return data.length / this.sampleRate / 2; // 44.1 khz in stereo
}
override function get_position(): Float {
return myPosition / kha.audio2.Audio.samplesPerSecond / 2;
}
override function set_position(value: Float): Float {
var pos = Math.round(value * kha.audio2.Audio.samplesPerSecond * 2.0);
pos = pos % 2 == 0 ? pos : pos + 1;
myPosition = max(min(pos, sampleLength(kha.audio2.Audio.samplesPerSecond)), 0);
return value;
}
override function get_volume(): Float {
return myVolume;
}
override function set_volume(value: Float): Float {
return myVolume = value;
}
override function get_finished(): Bool {
return stopped;
}
static inline function max(a: Int, b: Int) {
return a > b ? a : b;
}
static inline function min(a: Int, b: Int) {
return a < b ? a : b;
}
}

View File

@ -0,0 +1,101 @@
package kha.audio2;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import kha.audio2.ogg.vorbis.Reader;
#if (!cpp && !hl)
class StreamChannel implements kha.audio1.AudioChannel {
#if (!kha_no_ogg)
var reader: Reader;
#end
var atend: Bool = false;
var loop: Bool;
var myVolume: Float;
var paused: Bool = false;
public function new(data: Bytes, loop: Bool) {
myVolume = 1;
this.loop = loop;
#if (!kha_no_ogg)
reader = Reader.openFromBytes(data);
#end
}
public function nextSamples(samples: kha.arrays.Float32Array, length: Int, sampleRate: Int): Void {
if (paused) {
for (i in 0...length) {
samples[i] = 0;
}
return;
}
#if (!kha_no_ogg)
var count = reader.read(samples, Std.int(length / 2), 2, sampleRate, true) * 2;
if (count < length) {
if (loop) {
reader.currentMillisecond = 0;
}
else {
atend = true;
}
for (i in count...length) {
samples[i] = 0;
}
}
#end
}
public function play(): Void {
paused = false;
}
public function pause(): Void {
paused = true;
}
public function stop(): Void {
atend = true;
}
public var length(get, null): Float; // Seconds
function get_length(): Float {
#if (kha_no_ogg)
return 0.0;
#else
return reader.totalMillisecond / 1000.0;
#end
}
public var position(get, set): Float; // Seconds
function get_position(): Float {
#if (kha_no_ogg)
return 0.0;
#else
return reader.currentMillisecond / 1000.0;
#end
}
function set_position(value: Float): Float {
return value;
}
public var volume(get, set): Float;
function get_volume(): Float {
return myVolume;
}
function set_volume(value: Float): Float {
return myVolume = value;
}
public var finished(get, null): Bool;
function get_finished(): Bool {
return atend;
}
}
#end

View File

@ -0,0 +1,32 @@
package kha.audio2.ogg.tools;
import haxe.ds.Vector;
/**
* ...
* @author shohei909
*/
class Crc32
{
static inline var POLY:UInt = 0x04c11db7;
static var table:Vector<UInt>;
public static function init() {
if (table != null) {
return;
}
table = new Vector(256);
for (i in 0...256) {
var s:UInt = ((i:UInt) << (24:UInt));
for (j in 0...8) {
s = (s << 1) ^ (s >= ((1:UInt) << 31) ? POLY : 0);
}
table[i] = s;
}
}
public static inline function update(crc:UInt, byte:UInt):UInt
{
return (crc << 8) ^ table[byte ^ (crc >>> 24)];
}
}

View File

@ -0,0 +1,36 @@
package kha.audio2.ogg.tools;
/**
* ...
* @author shohei909
*/
class MathTools
{
public static inline function ilog(n:Int)
{
var log2_4 = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
// 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29)
return if (n < (1 << 14)) {
if (n < (1 << 4)) {
0 + log2_4[n];
} else if (n < (1 << 9)) {
5 + log2_4[n >> 5];
} else {
10 + log2_4[n >> 10];
}
} else if (n < (1 << 24)) {
if (n < (1 << 19)) {
15 + log2_4[n >> 15];
} else {
20 + log2_4[n >> 20];
}
} else if (n < (1 << 29)) {
25 + log2_4[n >> 25];
} else if (n < (1 << 31)) {
30 + log2_4[n >> 30];
} else {
0; // signed n returns 0
}
}
}

View File

@ -0,0 +1,545 @@
package kha.audio2.ogg.tools;
import haxe.ds.Vector;
/**
* modified discrete cosine transform
* @author shohei909
*/
class Mdct {
static public inline function inverseTransform(buffer:Vector<Float>, n:Int, a:Vector<Float>, b:Vector<Float>, c:Vector<Float>, bitReverse:Vector<Int>)
{
var n2 = n >> 1;
var n4 = n >> 2;
var n8 = n >> 3;
// @OPTIMIZE: reduce register pressure by using fewer variables?
//int save_point = temp_alloc_save(f);
var buf2 = new Vector(n2);
// twiddle factors
// IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio"
// See notes about bugs in that paper in less-optimal implementation 'inverseMdct_old' after this function.
// kernel from paper
// merged:
// copy and reflect spectral data
// step 0
// note that it turns out that the items added together during
// this step are, in fact, being added to themselves (as reflected
// by step 0). inexplicable inefficiency! this became obvious
// once I combined the passes.
// so there's a missing 'times 2' here (for adding X to itself).
// this propogates through linearly to the end, where the numbers
// are 1/2 too small, and need to be compensated for.
{
var dOffset = n2 - 2;
var aaOffset = 0;
var eOffset = 0;
var eStopOffset = n2;
while (eOffset != eStopOffset) {
buf2[dOffset + 1] = (buffer[eOffset + 0] * a[aaOffset + 0] - buffer[eOffset + 2] * a[aaOffset + 1]);
buf2[dOffset + 0] = (buffer[eOffset + 0] * a[aaOffset + 1] + buffer[eOffset + 2] * a[aaOffset + 0]);
dOffset -= 2;
aaOffset += 2;
eOffset += 4;
}
eOffset = n2 - 3;
while (dOffset >= 0) {
buf2[dOffset + 1] = (-buffer[eOffset + 2] * a[aaOffset + 0] - -buffer[eOffset + 0]*a[aaOffset + 1]);
buf2[dOffset + 0] = (-buffer[eOffset + 2] * a[aaOffset + 1] + -buffer[eOffset + 0]*a[aaOffset + 0]);
dOffset -= 2;
aaOffset += 2;
eOffset -= 4;
}
}
// now we use symbolic names for these, so that we can
// possibly swap their meaning as we change which operations
// are in place
var u = buffer;
var v = buf2;
// step 2 (paper output is w, now u)
// this could be in place, but the data ends up in the wrong
// place... _somebody_'s got to swap it, so this is nominated
{
var aaOffset = n2 - 8;
var eOffset0 = n4;
var eOffset1 = 0;
var dOffset0 = n4;
var dOffset1 = 0;
while (aaOffset >= 0) {
var v41_21:Float = v[eOffset0 + 1] - v[eOffset1 + 1];
var v40_20:Float = v[eOffset0 + 0] - v[eOffset1 + 0];
u[dOffset0 + 1] = v[eOffset0 + 1] + v[eOffset1 + 1];
u[dOffset0 + 0] = v[eOffset0 + 0] + v[eOffset1 + 0];
u[dOffset1 + 1] = v41_21*a[aaOffset + 4] - v40_20*a[aaOffset + 5];
u[dOffset1 + 0] = v40_20*a[aaOffset + 4] + v41_21*a[aaOffset + 5];
v41_21 = v[eOffset0 + 3] - v[eOffset1 + 3];
v40_20 = v[eOffset0 + 2] - v[eOffset1 + 2];
u[dOffset0 + 3] = v[eOffset0 + 3] + v[eOffset1 + 3];
u[dOffset0 + 2] = v[eOffset0 + 2] + v[eOffset1 + 2];
u[dOffset1 + 3] = v41_21*a[aaOffset + 0] - v40_20*a[aaOffset + 1];
u[dOffset1 + 2] = v40_20*a[aaOffset + 0] + v41_21*a[aaOffset + 1];
aaOffset -= 8;
dOffset0 += 4;
dOffset1 += 4;
eOffset0 += 4;
eOffset1 += 4;
}
}
// step 3
var ld = MathTools.ilog(n) - 1; // ilog is off-by-one from normal definitions
// optimized step 3:
// the original step3 loop can be nested r inside s or s inside r;
// it's written originally as s inside r, but this is dumb when r
// iterates many times, and s few. So I have two copies of it and
// switch between them halfway.
// this is iteration 0 of step 3
step3Iter0Loop(n >> 4, u, n2-1-n4*0, -(n >> 3), a);
step3Iter0Loop(n >> 4, u, n2-1-n4*1, -(n >> 3), a);
// this is iteration 1 of step 3
step3InnerRLoop(n >> 5, u, n2-1 - n8*0, -(n >> 4), a, 16);
step3InnerRLoop(n >> 5, u, n2-1 - n8*1, -(n >> 4), a, 16);
step3InnerRLoop(n >> 5, u, n2-1 - n8*2, -(n >> 4), a, 16);
step3InnerRLoop(n >> 5, u, n2-1 - n8*3, -(n >> 4), a, 16);
for (l in 2...((ld - 3) >> 1)) {
var k0 = n >> (l + 2);
var k0_2 = k0 >> 1;
var lim = 1 << (l+1);
for (i in 0...lim) {
step3InnerRLoop(n >> (l + 4), u, n2 - 1 - k0 * i, -k0_2, a, 1 << (l + 3));
}
}
for (l in ((ld - 3) >> 1)...(ld-6)) {
var k0 = n >> (l + 2);
var k1 = 1 << (l + 3);
var k0_2 = k0 >> 1;
var rlim = n >> (l+6);
var lim = 1 << (l+1);
var aOffset = 0;
var i_off = n2 - 1;
var r = rlim + 1;
while (--r > 0) {
step3InnerSLoop(lim, u, i_off, -k0_2, a, aOffset, k1, k0);
aOffset += k1 * 4;
i_off -= 8;
}
}
// iterations with count:
// ld-6,-5,-4 all interleaved together
// the big win comes from getting rid of needless flops
// due to the constants on pass 5 & 4 being all 1 and 0;
// combining them to be simultaneous to improve cache made little difference
step3InnerSLoopLd654(n >> 5, u, n2-1, a, n);
// output is u
// step 4, 5, and 6
// cannot be in-place because of step 5
{
// weirdly, I'd have thought reading sequentially and writing
// erratically would have been better than vice-versa, but in
// fact that's not what my testing showed. (That is, with
// j = bitreverse(i), do you read i and write j, or read j and write i.)
var brOffset = 0;
var dOffset0 = n4-4; // v
var dOffset1 = n2-4; // v
while (dOffset0 >= 0) {
var k4 = bitReverse[brOffset + 0];
v[dOffset1 +3] = u[k4+0];
v[dOffset1 +2] = u[k4+1];
v[dOffset0 +3] = u[k4+2];
v[dOffset0 +2] = u[k4+3];
k4 = bitReverse[brOffset + 1];
v[dOffset1 +1] = u[k4+0];
v[dOffset1 +0] = u[k4+1];
v[dOffset0 +1] = u[k4+2];
v[dOffset0 +0] = u[k4+3];
dOffset0 -= 4;
dOffset1 -= 4;
brOffset += 2;
}
}
// (paper output is u, now v)
// data must be in buf2
//assert(v == buf2);
// step 7 (paper output is v, now v)
// this is now in place
{
var cOffset = 0;
var dOffset = 0; // v
var eOffset = n2 - 4; // v
while (dOffset < eOffset) {
var a02 = v[dOffset + 0] - v[eOffset + 2];
var a11 = v[dOffset + 1] + v[eOffset + 3];
var b0 = c[cOffset + 1]*a02 + c[cOffset + 0]*a11;
var b1 = c[cOffset + 1]*a11 - c[cOffset + 0]*a02;
var b2 = v[dOffset + 0] + v[eOffset + 2];
var b3 = v[dOffset + 1] - v[eOffset + 3];
v[dOffset + 0] = b2 + b0;
v[dOffset + 1] = b3 + b1;
v[eOffset + 2] = b2 - b0;
v[eOffset + 3] = b1 - b3;
a02 = v[dOffset + 2] - v[eOffset + 0];
a11 = v[dOffset + 3] + v[eOffset + 1];
b0 = c[cOffset + 3]*a02 + c[cOffset + 2]*a11;
b1 = c[cOffset + 3]*a11 - c[cOffset + 2]*a02;
b2 = v[dOffset + 2] + v[eOffset + 0];
b3 = v[dOffset + 3] - v[eOffset + 1];
v[dOffset + 2] = b2 + b0;
v[dOffset + 3] = b3 + b1;
v[eOffset + 0] = b2 - b0;
v[eOffset + 1] = b1 - b3;
cOffset += 4;
dOffset += 4;
eOffset -= 4;
}
}
// data must be in buf2
// step 8+decode (paper output is X, now buffer)
// this generates pairs of data a la 8 and pushes them directly through
// the decode kernel (pushing rather than pulling) to avoid having
// to make another pass later
// this cannot POSSIBLY be in place, so we refer to the buffers directly
{
var bOffset = n2 - 8; //b
var eOffset = n2 - 8; //buf2
var dOffset0 = 0; //buffer
var dOffset1 = n2-4; //buffer
var dOffset2 = n2; //buffer
var dOffset3 = n - 4; //buffer
while (eOffset >= 0) {
var p3 = buf2[eOffset + 6]*b[bOffset + 7] - buf2[eOffset + 7]*b[bOffset + 6];
var p2 = -buf2[eOffset + 6]*b[bOffset + 6] - buf2[eOffset + 7]*b[bOffset + 7];
buffer[dOffset0 + 0] = p3;
buffer[dOffset1 + 3] = - p3;
buffer[dOffset2 + 0] = p2;
buffer[dOffset3 + 3] = p2;
var p1 = buf2[eOffset + 4]*b[bOffset + 5] - buf2[eOffset + 5]*b[bOffset + 4];
var p0 = -buf2[eOffset + 4]*b[bOffset + 4] - buf2[eOffset + 5]*b[bOffset + 5];
buffer[dOffset0 + 1] = p1;
buffer[dOffset1 + 2] = - p1;
buffer[dOffset2 + 1] = p0;
buffer[dOffset3 + 2] = p0;
p3 = buf2[eOffset + 2]*b[bOffset + 3] - buf2[eOffset + 3]*b[bOffset + 2];
p2 = -buf2[eOffset + 2]*b[bOffset + 2] - buf2[eOffset + 3]*b[bOffset + 3];
buffer[dOffset0 + 2] = p3;
buffer[dOffset1 + 1] = - p3;
buffer[dOffset2 + 2] = p2;
buffer[dOffset3 + 1] = p2;
p1 = buf2[eOffset + 0]*b[bOffset + 1] - buf2[eOffset + 1]*b[bOffset + 0];
p0 = -buf2[eOffset + 0]*b[bOffset + 0] - buf2[eOffset + 1]*b[bOffset + 1];
buffer[dOffset0 + 3] = p1;
buffer[dOffset1 + 0] = - p1;
buffer[dOffset2 + 3] = p0;
buffer[dOffset3 + 0] = p0;
bOffset -= 8;
eOffset -= 8;
dOffset0 += 4;
dOffset2 += 4;
dOffset1 -= 4;
dOffset3 -= 4;
}
}
}
// the following were split out into separate functions while optimizing;
// they could be pushed back up but eh. __forceinline showed no change;
// they're probably already being inlined.
static inline function step3Iter0Loop(n:Int, e:Vector<Float>, i_off:Int, k_off:Int, a:Vector<Float>)
{
var eeOffset0 = i_off; // e
var eeOffset2 = i_off + k_off; // e
var aOffset = 0;
var i = (n >> 2) + 1;
while (--i > 0) {
var k00_20 = e[eeOffset0 + 0] - e[eeOffset2 + 0];
var k01_21 = e[eeOffset0 + -1] - e[eeOffset2 + -1];
e[eeOffset0 + 0] += e[eeOffset2 + 0];//e[eeOffset0 + 0] = e[eeOffset0 + 0] + e[eeOffset2 + 0];
e[eeOffset0 + -1] += e[eeOffset2 + -1];//e[eeOffset0 + -1] = e[eeOffset0 + -1] + e[eeOffset2 + -1];
e[eeOffset2 + 0] = k00_20 * a[aOffset + 0] - k01_21 * a[aOffset + 1];
e[eeOffset2 + -1] = k01_21 * a[aOffset + 0] + k00_20 * a[aOffset + 1];
aOffset += 8;
k00_20 = e[eeOffset0 + -2] - e[eeOffset2 + -2];
k01_21 = e[eeOffset0 + -3] - e[eeOffset2 + -3];
e[eeOffset0 + -2] += e[eeOffset2 + -2];//e[eeOffset0 + -2] = e[eeOffset0 + -2] + e[eeOffset2 + -2];
e[eeOffset0 + -3] += e[eeOffset2 + -3];//e[eeOffset0 + -3] = e[eeOffset0 + -3] + e[eeOffset2 + -3];
e[eeOffset2 + -2] = k00_20 * a[aOffset + 0] - k01_21 * a[aOffset + 1];
e[eeOffset2 + -3] = k01_21 * a[aOffset + 0] + k00_20 * a[aOffset + 1];
aOffset += 8;
k00_20 = e[eeOffset0 + -4] - e[eeOffset2 + -4];
k01_21 = e[eeOffset0 + -5] - e[eeOffset2 + -5];
e[eeOffset0 + -4] += e[eeOffset2 + -4];//e[eeOffset0 + -4] = e[eeOffset0 + -4] + e[eeOffset2 + -4];
e[eeOffset0 + -5] += e[eeOffset2 + -5];//e[eeOffset0 + -5] = e[eeOffset0 + -5] + e[eeOffset2 + -5];
e[eeOffset2 + -4] = k00_20 * a[aOffset + 0] - k01_21 * a[aOffset + 1];
e[eeOffset2 + -5] = k01_21 * a[aOffset + 0] + k00_20 * a[aOffset + 1];
aOffset += 8;
k00_20 = e[eeOffset0 + -6] - e[eeOffset2 + -6];
k01_21 = e[eeOffset0 + -7] - e[eeOffset2 + -7];
e[eeOffset0 + -6] += e[eeOffset2 + -6];//e[eeOffset0 + -6] = e[eeOffset0 + -6] + e[eeOffset2 + -6];
e[eeOffset0 + -7] += e[eeOffset2 + -7];//e[eeOffset0 + -7] = e[eeOffset0 + -7] + e[eeOffset2 + -7];
e[eeOffset2 + -6] = k00_20 * a[aOffset + 0] - k01_21 * a[aOffset + 1];
e[eeOffset2 + -7] = k01_21 * a[aOffset + 0] + k00_20 * a[aOffset + 1];
aOffset += 8;
eeOffset0 -= 8;
eeOffset2 -= 8;
}
}
static inline function step3InnerRLoop(lim:Int, e:Vector<Float>, d0:Int, k_off:Int, a:Vector<Float>, k1:Int) {
var aOffset = 0;
var eOffset0 = d0; //e
var eOffset2 = d0 + k_off; //e
var i = (lim >> 2) + 1;
while (--i > 0) {
var k00_20 = e[eOffset0 + -0] - e[eOffset2 + -0];
var k01_21 = e[eOffset0 + -1] - e[eOffset2 + -1];
e[eOffset0 + -0] += e[eOffset2 + -0];//e[eOffset0 + -0] = e[eOffset0 + -0] + e[eOffset2 + -0];
e[eOffset0 + -1] += e[eOffset2 + -1];//e[eOffset0 + -1] = e[eOffset0 + -1] + e[eOffset2 + -1];
e[eOffset2 + -0] = (k00_20)*a[aOffset + 0] - (k01_21) * a[aOffset + 1];
e[eOffset2 + -1] = (k01_21)*a[aOffset + 0] + (k00_20) * a[aOffset + 1];
aOffset += k1;
k00_20 = e[eOffset0 + -2] - e[eOffset2 + -2];
k01_21 = e[eOffset0 + -3] - e[eOffset2 + -3];
e[eOffset0 + -2] += e[eOffset2 + -2];//e[eOffset0 + -2] = e[eOffset0 + -2] + e[eOffset2 + -2];
e[eOffset0 + -3] += e[eOffset2 + -3];//e[eOffset0 + -3] = e[eOffset0 + -3] + e[eOffset2 + -3];
e[eOffset2 + -2] = (k00_20)*a[aOffset + 0] - (k01_21) * a[aOffset + 1];
e[eOffset2 + -3] = (k01_21)*a[aOffset + 0] + (k00_20) * a[aOffset + 1];
aOffset += k1;
k00_20 = e[eOffset0 + -4] - e[eOffset2 + -4];
k01_21 = e[eOffset0 + -5] - e[eOffset2 + -5];
e[eOffset0 + -4] += e[eOffset2 + -4];//e[eOffset0 + -4] = e[eOffset0 + -4] + e[eOffset2 + -4];
e[eOffset0 + -5] += e[eOffset2 + -5];//e[eOffset0 + -5] = e[eOffset0 + -5] + e[eOffset2 + -5];
e[eOffset2 + -4] = (k00_20)*a[aOffset + 0] - (k01_21) * a[aOffset + 1];
e[eOffset2 + -5] = (k01_21)*a[aOffset + 0] + (k00_20) * a[aOffset + 1];
aOffset += k1;
k00_20 = e[eOffset0 + -6] - e[eOffset2 + -6];
k01_21 = e[eOffset0 + -7] - e[eOffset2 + -7];
e[eOffset0 + -6] += e[eOffset2 + -6];//e[eOffset0 + -6] = e[eOffset0 + -6] + e[eOffset2 + -6];
e[eOffset0 + -7] += e[eOffset2 + -7];//e[eOffset0 + -7] = e[eOffset0 + -7] + e[eOffset2 + -7];
e[eOffset2 + -6] = (k00_20)*a[aOffset + 0] - (k01_21) * a[aOffset + 1];
e[eOffset2 + -7] = (k01_21)*a[aOffset + 0] + (k00_20) * a[aOffset + 1];
eOffset0 -= 8;
eOffset2 -= 8;
aOffset += k1;
}
}
static inline function step3InnerSLoop(n:Int, e:Vector<Float>, i_off:Int, k_off:Int, a:Vector<Float>, aOffset0:Int, aOffset1:Int, k0:Int)
{
var A0 = a[aOffset0];
var A1 = a[aOffset0 + 1];
var A2 = a[aOffset0 + aOffset1];
var A3 = a[aOffset0 + aOffset1 + 1];
var A4 = a[aOffset0 + aOffset1 * 2+0];
var A5 = a[aOffset0 + aOffset1 * 2+1];
var A6 = a[aOffset0 + aOffset1 * 3+0];
var A7 = a[aOffset0 + aOffset1 * 3+1];
var eeOffset0 = i_off; // e
var eeOffset2 = i_off + k_off; // e
var i = n + 1;
while (--i > 0) {
var k00 = e[eeOffset0 + 0] - e[eeOffset2 + 0];
var k11 = e[eeOffset0 + -1] - e[eeOffset2 + -1];
e[eeOffset0 + 0] = e[eeOffset0 + 0] + e[eeOffset2 + 0];
e[eeOffset0 + -1] = e[eeOffset0 + -1] + e[eeOffset2 + -1];
e[eeOffset2 + 0] = (k00) * A0 - (k11) * A1;
e[eeOffset2 + -1] = (k11) * A0 + (k00) * A1;
k00 = e[eeOffset0 + -2] - e[eeOffset2 + -2];
k11 = e[eeOffset0 + -3] - e[eeOffset2 + -3];
e[eeOffset0 + -2] = e[eeOffset0 + -2] + e[eeOffset2 + -2];
e[eeOffset0 + -3] = e[eeOffset0 + -3] + e[eeOffset2 + -3];
e[eeOffset2 + -2] = (k00) * A2 - (k11) * A3;
e[eeOffset2 + -3] = (k11) * A2 + (k00) * A3;
k00 = e[eeOffset0 + -4] - e[eeOffset2 + -4];
k11 = e[eeOffset0 + -5] - e[eeOffset2 + -5];
e[eeOffset0 + -4] = e[eeOffset0 + -4] + e[eeOffset2 + -4];
e[eeOffset0 + -5] = e[eeOffset0 + -5] + e[eeOffset2 + -5];
e[eeOffset2 + -4] = (k00) * A4 - (k11) * A5;
e[eeOffset2 + -5] = (k11) * A4 + (k00) * A5;
k00 = e[eeOffset0 + -6] - e[eeOffset2 + -6];
k11 = e[eeOffset0 + -7] - e[eeOffset2 + -7];
e[eeOffset0 + -6] = e[eeOffset0 + -6] + e[eeOffset2 + -6];
e[eeOffset0 + -7] = e[eeOffset0 + -7] + e[eeOffset2 + -7];
e[eeOffset2 + -6] = (k00) * A6 - (k11) * A7;
e[eeOffset2 + -7] = (k11) * A6 + (k00) * A7;
eeOffset0 -= k0;
eeOffset2 -= k0;
}
}
static inline function iter54(e:Vector<Float>, zOffset:Int)
{
var t0 = e[zOffset + 0];
var t1 = e[zOffset + -4];
var k00 = t0 - t1;
var y0 = t0 + t1;
t0 = e[zOffset + -2];
t1 = e[zOffset + -6];
var y2 = t0 + t1;
var k22 = t0 - t1;
e[zOffset + -0] = y0 + y2; // z0 + z4 + z2 + z6
e[zOffset + -2] = y0 - y2; // z0 + z4 - z2 - z6
// done with y0,y2
var k33 = e[zOffset + -3] - e[zOffset + -7];
e[zOffset + -4] = k00 + k33; // z0 - z4 + z3 - z7
e[zOffset + -6] = k00 - k33; // z0 - z4 - z3 + z7
// done with k33
t0 = e[zOffset + -1];
t1 = e[zOffset + -5];
var k11 = t0 - t1;
var y1 = t0 + t1;
var y3 = e[zOffset + -3] + e[zOffset + -7];
e[zOffset + -1] = y1 + y3; // z1 + z5 + z3 + z7
e[zOffset + -3] = y1 - y3; // z1 + z5 - z3 - z7
e[zOffset + -5] = k11 - k22; // z1 - z5 + z2 - z6
e[zOffset + -7] = k11 + k22; // z1 - z5 - z2 + z6
}
static inline function step3InnerSLoopLd654(n:Int, e:Vector<Float>, i_off:Int, a:Vector<Float>, baseN:Int)
{
var A2 = a[baseN >> 3];
var zOffset = i_off; // e
var baseOffset = i_off - 16 * n; //e
while (zOffset > baseOffset) {
var t0 = e[zOffset];
var t1 = e[zOffset + -8];
e[zOffset + -8] = t0 - t1;
e[zOffset + -0] = t0 + t1;
t0 = e[zOffset + -1];
t1 = e[zOffset + -9];
e[zOffset + -9] = t0 - t1;
e[zOffset + -1] = t0 + t1;
t0 = e[zOffset + -2];
t1 = e[zOffset + -10];
var k00 = t0 - t1;
e[zOffset + -2] = t0 + t1;
t0 = e[zOffset + -3];
t1 = e[zOffset + -11];
var k11 = t0 - t1;
e[zOffset + -3] = t0 + t1;
e[zOffset + -10] = (k00+k11) * A2;
e[zOffset + -11] = (k11-k00) * A2;
t0 = e[zOffset + -4];
t1 = e[zOffset + -12];
k00 = t1 - t0; // reverse to avoid a unary negation
e[zOffset + -4] = t0 + t1;
t0 = e[zOffset + -5];
t1 = e[zOffset + -13];
k11 = t0 - t1;
e[zOffset + -5] = t0 + t1;
e[zOffset + -12] = k11;
e[zOffset + -13] = k00;
t0 = e[zOffset + -6];
t1 = e[zOffset + -14];
k00 = t1 - t0; // reverse to avoid a unary negation
e[zOffset + -6] = t0 + t1;
t0 = e[zOffset + -7];
t1 = e[zOffset + -15];
k11 = t0 - t1;
e[zOffset + -7] = t0 + t1;
e[zOffset + -14] = (k00+k11) * A2;
e[zOffset + -15] = (k00-k11) * A2;
iter54(e, zOffset);
iter54(e, zOffset - 8);
zOffset -= 16;
}
}
}

View File

@ -0,0 +1,160 @@
package kha.audio2.ogg.vorbis;
import haxe.io.BytesOutput;
import haxe.io.Output;
import haxe.io.StringInput;
import kha.audio2.ogg.tools.Mdct;
import kha.audio2.ogg.vorbis.data.Floor;
import kha.audio2.ogg.vorbis.data.Mapping;
import kha.audio2.ogg.vorbis.data.Mode;
import kha.audio2.ogg.vorbis.data.Header;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import haxe.io.Eof;
import haxe.io.Input;
import haxe.PosInfos;
#if sys
import sys.FileSystem;
import sys.io.File;
import sys.io.FileInput;
#end
/**
* public domain ogg reader.
* @author shohei909
*/
class Reader {
public var decoder(default, null):VorbisDecoder;
public var header(get, never):Header;
function get_header():Header {
return decoder.header;
}
public var totalSample(get, never):Int;
function get_totalSample():Int {
return decoder.totalSample;
}
public var totalMillisecond(get, never):Float;
function get_totalMillisecond():Float {
return sampleToMillisecond(decoder.totalSample);
}
public var currentSample(get, set):Int;
function get_currentSample():Int {
return decoder.currentSample;
}
function set_currentSample(value:Int):Int {
decoder.seek(seekFunc, inputLength, value);
return decoder.currentSample;
}
public var currentMillisecond(get, set):Float;
function get_currentMillisecond():Float
{
return sampleToMillisecond(currentSample);
}
function set_currentMillisecond(value:Float):Float {
currentSample = millisecondToSample(value);
return currentMillisecond;
}
public var loopStart:Null<Int>;
public var loopLength:Null<Int>;
var seekFunc:Int->Void;
var inputLength:Int;
function new (input:Input, seekFunc:Int->Void, inputLength:Int) {
this.seekFunc = seekFunc;
this.inputLength = inputLength;
decoder = VorbisDecoder.start(input);
decoder.setupSampleNumber(seekFunc, inputLength);
loopStart = header.comment.loopStart;
loopLength = header.comment.loopLength;
}
public static function openFromBytes(bytes:Bytes) {
var input = new BytesInput(bytes);
return new Reader(input, seekBytes.bind(input), bytes.length);
}
static function seekBytes(bytes:BytesInput, pos:Int) {
bytes.position = pos;
}
#if sys
public static function openFromFile(fileName:String):Reader {
var file = File.read(fileName, true);
var stat = FileSystem.stat(fileName);
return new Reader(file, file.seek.bind(_, SeekBegin), stat.size);
}
#end
public static function readAll(bytes:Bytes, output:Output, useFloat:Bool = false):Header {
var input = new BytesInput(bytes);
var decoder = VorbisDecoder.start(input);
decoder.setupSampleNumber(seekBytes.bind(input), bytes.length);
var header = decoder.header;
var count = 0;
var bufferSize = 4096;
var buffer = new kha.arrays.Float32Array(bufferSize * header.channel);
while (true) {
var n = decoder.read(buffer, bufferSize, header.channel, header.sampleRate, useFloat);
for (i in 0...n * header.channel) {
output.writeFloat(buffer[i]);
}
if (n == 0) { break; }
count += n;
}
return decoder.header;
}
public function read(output:kha.arrays.Float32Array, ?samples:Int, ?channels:Int, ?sampleRate:Int, useFloat:Bool = false) {
decoder.ensurePosition(seekFunc);
if (samples == null) {
samples = decoder.totalSample;
}
if (channels == null) {
channels = header.channel;
}
if (sampleRate == null) {
sampleRate = header.sampleRate;
}
return decoder.read(output, samples, channels, sampleRate, useFloat);
}
public function clone():Reader {
var reader = Type.createEmptyInstance(Reader);
reader.seekFunc = seekFunc;
reader.inputLength = inputLength;
reader.decoder = decoder.clone(seekFunc);
reader.loopStart = loopStart;
reader.loopLength = loopLength;
return reader;
}
public inline function sampleToMillisecond(samples:Int) {
return samples / header.sampleRate * 1000;
}
public inline function millisecondToSample(millseconds:Float) {
return Math.floor(millseconds / 1000 * header.sampleRate);
}
}
private typedef InitData = {
input:Input,
seekFunc:Int->Void,
inputLength:Int,
}

View File

@ -0,0 +1,857 @@
package kha.audio2.ogg.vorbis;
import haxe.ds.Vector;
import haxe.Int64;
import haxe.io.Bytes;
import haxe.io.Eof;
import haxe.io.Input;
import haxe.io.Output;
import kha.audio2.ogg.tools.Crc32;
import kha.audio2.ogg.tools.MathTools;
import kha.audio2.ogg.vorbis.data.Codebook;
import kha.audio2.ogg.vorbis.data.Floor.Floor1;
import kha.audio2.ogg.vorbis.data.Header;
import kha.audio2.ogg.vorbis.data.Mode;
import kha.audio2.ogg.vorbis.data.Page;
import kha.audio2.ogg.vorbis.data.ProbedPage;
import kha.audio2.ogg.vorbis.data.ReaderError;
import kha.audio2.ogg.vorbis.data.Page;
import kha.audio2.ogg.vorbis.data.Residue;
import kha.audio2.ogg.vorbis.data.Setting;
import kha.audio2.ogg.vorbis.VorbisDecoder.DecodeInitialResult;
/**
* ...
* @author shohei909
*/
class VorbisDecodeState
{
public static inline var INVALID_BITS = -1;
public var page(default, null):Page;
public var eof(default, null):Bool;
public var pFirst(default, null):ProbedPage;
public var pLast(default, null):ProbedPage;
public var validBits(default, null):Int = 0;
public var inputPosition(default, null):Int;
public var input(default, null):Input;
public var discardSamplesDeferred:Int;
public var segments(default, null):Vector<Int>;
public var bytesInSeg:Int = 0; // uint8
// decode buffer
public var channelBuffers:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
public var channelBufferStart:Int;
public var channelBufferEnd:Int;
public var currentSample(default, null):Int;
public var previousWindow:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
public var previousLength:Int;
public var finalY:Vector<Array<Int>>; // [STB_VORBIS_MAX_CHANNELS];
var firstDecode:Bool = false;
var nextSeg:Int = 0;
var acc:UInt;
var lastSeg:Bool; // flag that we're on the last decodeState
var lastSegWhich:Int; // what was the decodeState number of the l1ast seg?
var endSegWithKnownLoc:Int;
var knownLocForPacket:Int;
var error:ReaderError;
var currentLoc:Int; //uint32 sample location of next frame to decode
var currentLocValid:Int;
var firstAudioPageOffset:UInt;
public function new(input:Input)
{
this.input = input;
inputPosition = 0;
page = new Page();
Crc32.init();
}
public function setup(loc0:Int, loc1:Int) {
var segmentCount = readByte();
this.segments = read(segmentCount);
// assume we Don't_ know any the sample position of any segments
this.endSegWithKnownLoc = -2;
if (loc0 != 0xFFFFFFFF || loc1 != 0xFFFFFFFF) {
var i:Int = segmentCount - 1;
while (i >= 0) {
if (segments.get(i) < 255) {
break;
}
if (i >= 0) {
this.endSegWithKnownLoc = i;
this.knownLocForPacket = loc0;
}
i--;
}
}
if (firstDecode) {
var i:Int = 0;
var len:Int = 0;
var p = new ProbedPage();
for (i in 0...segmentCount) {
len += segments.get(i);
}
len += 27 + segmentCount;
p.pageStart = firstAudioPageOffset;
p.pageEnd = p.pageStart + len;
p.firstDecodedSample = 0;
p.lastDecodedSample = loc0;
pFirst = p;
}
nextSeg = 0;
}
public function clone(seekFunc:Int->Void)
{
var state = Type.createEmptyInstance(VorbisDecodeState);
seekFunc(inputPosition);
state.input = input;
// primitive
state.eof = eof;
state.validBits = validBits;
state.discardSamplesDeferred = discardSamplesDeferred;
state.firstDecode = firstDecode;
state.nextSeg = nextSeg;
state.bytesInSeg = bytesInSeg;
state.acc = state.acc;
state.lastSeg = lastSeg;
state.lastSegWhich = lastSegWhich;
state.currentLoc = currentLoc;
state.currentLocValid = currentLocValid;
state.inputPosition = inputPosition;
state.firstAudioPageOffset = firstAudioPageOffset;
// sharrow copy
state.error = error;
state.segments = segments;
state.pFirst = pFirst;
state.pLast = pLast;
// deep copy
state.page = page.clone();
return state;
}
// nextSegment
public function next():Int {
if (lastSeg) {
return 0;
}
if (nextSeg == -1) {
lastSegWhich = segments.length - 1; // in case startPage fails
try {
page.start(this);
} catch(e:ReaderError) {
lastSeg = true;
error = e;
return 0;
}
if ((page.flag & PageFlag.CONTINUED_PACKET) == 0) {
throw new ReaderError(ReaderErrorType.CONTINUED_PACKET_FLAG_INVALID);
}
}
var len = segments.get(nextSeg++);
if (len < 255) {
lastSeg = true;
lastSegWhich = nextSeg - 1;
}
if (nextSeg >= segments.length) {
nextSeg = -1;
}
VorbisTools.assert(bytesInSeg == 0);
bytesInSeg = len;
return len;
}
public function startPacket() {
while (nextSeg == -1) {
page.start(this);
if ((page.flag & PageFlag.CONTINUED_PACKET) != 0) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
}
lastSeg = false;
validBits = 0;
bytesInSeg = 0;
}
public function maybeStartPacket():Bool
{
if (nextSeg == -1) {
var eof = false;
var x = try {
readByte();
} catch (e:Eof) {
eof = true;
0;
}
if (eof) {
return false; // EOF at page boundary is not an error!
}
if (x != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
page.startWithoutCapturePattern(this);
}
startPacket();
return true;
}
// public inline function readBits(n:Int):Int
public function readBits(n:Int):Int // Kha: reduce output size
{
if (validBits < 0) {
return 0;
} else if (validBits < n) {
if (n > 24) {
// the accumulator technique below would not work correctly in this case
return readBits(24) + ((readBits(n - 24) << 24));
} else {
if (validBits == 0) {
acc = 0;
}
do {
if (bytesInSeg == 0 && (lastSeg || next() == 0)) {
validBits = INVALID_BITS;
break;
} else {
bytesInSeg--;
acc += (readByte() << validBits);
validBits += 8;
}
} while (validBits < n);
if (validBits < 0) {
return 0;
} else {
var z = acc & ((1 << n) - 1);
acc >>>= n;
validBits -= n;
return z;
}
}
} else {
var z = acc & ((1 << n) - 1);
acc >>>= n;
validBits -= n;
return z;
}
}
inline function readPacketRaw():Int {
return if (bytesInSeg == 0 && (lastSeg || next() == 0)) { // CLANG!
VorbisTools.EOP;
} else {
//VorbisTools.assert(bytesInSeg > 0);
bytesInSeg--;
readByte();
}
}
public inline function readPacket():Int
{
var x = readPacketRaw();
validBits = 0;
return x;
}
public inline function flushPacket():Void {
while (bytesInSeg != 0 || (!lastSeg && next() != 0)) {
bytesInSeg--;
readByte();
}
}
public inline function vorbisValidate() {
var header = Bytes.alloc(6);
for (i in 0...6) {
header.set(i, readPacket());
}
if (header.toString() != "vorbis") {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "vorbis header");
}
}
public function firstPageValidate()
{
if (segments.length != 1) {
throw new ReaderError(INVALID_FIRST_PAGE, "segmentCount");
}
if (segments.get(0) != 30) {
throw new ReaderError(INVALID_FIRST_PAGE, "decodeState head");
}
}
public function startFirstDecode()
{
firstAudioPageOffset = inputPosition;
firstDecode = true;
}
public inline function capturePattern()
{
if (readByte() != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
}
inline function skip(len:Int)
{
read(len);
}
function prepHuffman()
{
if (validBits <= 24) {
if (validBits == 0) {
acc = 0;
}
do {
if (bytesInSeg == 0 && (lastSeg || next() == 0)) { // CLANG!
return;
} else {
bytesInSeg--;
acc += readByte() << validBits;
validBits += 8;
}
} while (validBits <= 24);
}
}
public inline function decode(c:Codebook):Int {
var val = decodeRaw(c);
if (c.sparse) {
val = c.sortedValues[val];
}
return val;
}
public inline function decodeRaw(c:Codebook)
{
if (validBits < Setting.FAST_HUFFMAN_LENGTH){
prepHuffman();
}
// fast huffman table lookup
var i = c.fastHuffman[acc & Setting.FAST_HUFFMAN_TABLE_MASK];
return if (i >= 0) {
var l = c.codewordLengths[i];
acc >>>= l;
validBits -= l;
if (validBits < 0) {
validBits = 0;
-1;
} else {
i;
}
} else {
decodeScalarRaw(c);
}
}
public inline function isLastByte()
{
return bytesInSeg == 0 && lastSeg;
}
public function finishDecodePacket(previousLength:Int, n:Int, r:DecodeInitialResult)
{
var left = r.left.start;
var currentLocValid = false;
var n2 = n >> 1;
if (firstDecode) {
// assume we start so first non-discarded sample is sample 0
// this isn't to spec, but spec would require us to read ahead
// and decode the size of all current frames--could be done,
// but presumably it's not a commonly used feature
currentLoc = -n2; // start of first frame is positioned for discard
// we might have to discard samples "from" the next frame too,
// if we're lapping a large block then a small at the start?
discardSamplesDeferred = n - r.right.end;
currentLocValid = true;
firstDecode = false;
} else if (discardSamplesDeferred != 0) {
r.left.start += discardSamplesDeferred;
left = r.left.start;
discardSamplesDeferred = 0;
} else if (previousLength == 0 && currentLocValid) {
// we're recovering from a seek... that means we're going to discard
// the samples from this packet even though we know our position from
// the last page header, so we need to update the position based on
// the discarded samples here
// but wait, the code below is going to add this in itself even
// on a discard, so we don't need to do it here...
}
// check if we have ogg information about the sample # for this packet
if (lastSegWhich == endSegWithKnownLoc) {
// if we have a valid current loc, and this is final:
if (currentLocValid && (page.flag & PageFlag.LAST_PAGE) != 0) {
var currentEnd = knownLocForPacket - (n - r.right.end);
// then let's infer the size of the (probably) short final frame
if (currentEnd < currentLoc + r.right.end) {
var len = if (currentEnd < currentLoc) {
// negative truncation, that's impossible!
0;
} else {
currentEnd - currentLoc;
}
len += r.left.start;
currentLoc += len;
return {
len : len,
left : left,
right : r.right.start,
}
}
}
// otherwise, just set our sample loc
// guess that the ogg granule pos refers to the Middle_ of the
// last frame?
// set currentLoc to the position of leftStart
currentLoc = knownLocForPacket - (n2-r.left.start);
currentLocValid = true;
}
if (currentLocValid) {
currentLoc += (r.right.start - r.left.start);
}
// if (alloc.allocBuffer)
//assert(alloc.allocBufferLengthInBytes == tempOffset);
return {
len : r.right.end,
left : left,
right : r.right.start,
}
}
public inline function readInt32():Int
{
inputPosition += 4;
return input.readInt32();
}
public inline function readByte():Int
{
inputPosition += 1;
return input.readByte();
}
public inline function read(n:Int):Vector<Int> {
inputPosition += n;
var vec = new Vector(n);
for (i in 0...n) {
vec[i] = input.readByte();
}
return vec;
}
public inline function readBytes(n:Int):Bytes {
inputPosition += n;
return input.read(n);
}
public inline function readString(n:Int):String
{
inputPosition += n;
return input.readString(n);
}
public function getSampleNumber(seekFunc:Int->Void, inputLength:UInt):Int {
// first, store the current decode position so we can restore it
var restoreOffset = inputPosition;
// now we want to seek back 64K from the end (the last page must
// be at most a little less than 64K, but let's allow a little slop)
var previousSafe = if (inputLength >= 65536 && inputLength - 65536 >= firstAudioPageOffset) {
inputLength - 65536;
} else {
firstAudioPageOffset;
}
setInputOffset(seekFunc, previousSafe);
// previousSafe is now our candidate 'earliest known place that seeking
// to will lead to the final page'
var end = 0;
var last = false;
switch (findPage(seekFunc, inputLength)) {
case Found(e, l):
end = e;
last = l;
case NotFound:
throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
}
// check if there are more pages
var lastPageLoc = inputPosition;
// stop when the lastPage flag is set, not when we reach eof;
// this allows us to stop short of a 'fileSection' end without
// explicitly checking the length of the section
while (!last) {
setInputOffset(seekFunc, end);
switch (findPage(seekFunc, inputLength)) {
case Found(e, l):
end = e;
last = l;
case NotFound:
// the last page we found didn't have the 'last page' flag
// set. whoops!
break;
}
previousSafe = lastPageLoc + 1;
lastPageLoc = inputPosition;
}
setInputOffset(seekFunc, lastPageLoc);
// parse the header
var vorbisHeader = read(6);
// extract the absolute granule position
var lo = readInt32();
var hi = readInt32();
if (lo == 0xffffffff && hi == 0xffffffff || hi > 0) {
throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
}
pLast = new ProbedPage();
pLast.pageStart = lastPageLoc;
pLast.pageEnd = end;
pLast.lastDecodedSample = lo;
pLast.firstDecodedSample = null;
pLast.afterPreviousPageStart = previousSafe;
setInputOffset(seekFunc, restoreOffset);
return lo;
}
public inline function forcePageResync()
{
nextSeg = -1;
}
public inline function setInputOffset(seekFunc:Int->Void, n:Int)
{
seekFunc(inputPosition = n);
}
public function findPage(seekFunc:Int->Void, inputLength:Int):FindPageResult {
try {
while (true) {
var n = readByte();
if (n == 0x4f) { // page header
var retryLoc = inputPosition;
// check if we're off the end of a fileSection stream
if (retryLoc - 25 > inputLength) {
return FindPageResult.NotFound;
}
if (readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
continue;
}
var header = new Vector<UInt>(27);
header[0] = 0x4f;
header[1] = 0x67;
header[2] = 0x67;
header[3] = 0x53;
for (i in 4...27) {
header[i] = readByte();
}
if (header[4] != 0) {
setInputOffset(seekFunc, retryLoc);
continue;
}
var goal:UInt = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24);
for (i in 22...26) {
header[i] = 0;
}
var crc:UInt = 0;
for (i in 0...27){
crc = Crc32.update(crc, header[i]);
}
var len = 0;
try {
for (i in 0...header[26]) {
var s = readByte();
crc = Crc32.update(crc, s);
len += s;
}
for (i in 0...len) {
crc = Crc32.update(crc, readByte());
}
} catch (e:Eof) {
return FindPageResult.NotFound;
}
// finished parsing probable page
if (crc == goal) {
// we could now check that it's either got the last
// page flag set, OR it's followed by the capture
// pattern, but I guess TECHNICALLY you could have
// a file with garbage between each ogg page and recover
// from it automatically? So even though that paranoia
// might decrease the chance of an invalid decode by
// another 2^32, not worth it since it would hose those
// invalid-but-useful files?
var end = inputPosition;
setInputOffset(seekFunc, retryLoc - 1);
return FindPageResult.Found(end, (header[5] & 0x04 != 0));
}
}
}
} catch (e:Eof) {
return FindPageResult.NotFound;
}
}
public function analyzePage(seekFunc:Int->Void, h:Header)
{
var z:ProbedPage = new ProbedPage();
var packetType = new Vector<Bool>(255);
// record where the page starts
z.pageStart = inputPosition;
// parse the header
var pageHeader = read(27);
VorbisTools.assert(pageHeader.get(0) == 0x4f && pageHeader.get(1) == 0x67 && pageHeader.get(2) == 0x67 && pageHeader.get(3) == 0x53);
var lacing = read(pageHeader.get(26));
// determine the length of the payload
var len = 0;
for (i in 0...pageHeader.get(26)){
len += lacing.get(i);
}
// this implies where the page ends
z.pageEnd = z.pageStart + 27 + pageHeader.get(26) + len;
// read the last-decoded sample out of the data
z.lastDecodedSample = pageHeader.get(6) + (pageHeader.get(7) << 8) + (pageHeader.get(8) << 16) + (pageHeader.get(9) << 16);
if ((pageHeader.get(5) & 4) != 0) {
// if this is the last page, it's not possible to work
// backwards to figure out the first sample! whoops! fuck.
z.firstDecodedSample = null;
setInputOffset(seekFunc, z.pageStart);
return z;
}
// scan through the frames to determine the sample-count of each one...
// our goal is the sample # of the first fully-decoded sample on the
// page, which is the first decoded sample of the 2nd packet
var numPacket = 0;
var packetStart = ((pageHeader.get(5) & 1) == 0);
var modeCount = h.modes.length;
for (i in 0...pageHeader.get(26)) {
if (packetStart) {
if (lacing.get(i) == 0) {
setInputOffset(seekFunc, z.pageStart);
return null; // trying to read from zero-length packet
}
var n = readByte();
// if bottom bit is non-zero, we've got corruption
if (n & 1 != 0) {
setInputOffset(seekFunc, z.pageStart);
return null;
}
n >>= 1;
var b = MathTools.ilog(modeCount - 1);
n &= (1 << b) - 1;
if (n >= modeCount) {
setInputOffset(seekFunc, z.pageStart);
return null;
}
packetType[numPacket++] = h.modes[n].blockflag;
skip(lacing.get(i)-1);
} else {
skip(lacing.get(i));
}
packetStart = (lacing.get(i) < 255);
}
// now that we know the sizes of all the pages, we can start determining
// how much sample data there is.
var samples = 0;
// for the last packet, we step by its whole length, because the definition
// is that we encoded the end sample loc of the 'last packet completed',
// where 'completed' refers to packets being split, and we are left to guess
// what 'end sample loc' means. we assume it means ignoring the fact that
// the last half of the data is useless without windowing against the next
// packet... (so it's not REALLY complete in that sense)
if (numPacket > 1) {
samples += packetType[numPacket-1] ? h.blocksize1 : h.blocksize0;
}
var i = numPacket - 2;
while (i >= 1) {
i--;
// now, for this packet, how many samples do we have that
// do not overlap the following packet?
if (packetType[i]) {
if (packetType[i + 1]) {
samples += h.blocksize1 >> 1;
} else {
samples += ((h.blocksize1 - h.blocksize0) >> 2) + (h.blocksize0 >> 1);
}
} else {
samples += h.blocksize0 >> 1;
}
i--;
}
// now, at this point, we've rewound to the very beginning of the
// Second_ packet. if we entirely discard the first packet after
// a seek, this will be exactly the right sample number. HOWEVER!
// we can't as easily compute this number for the LAST page. The
// only way to get the sample offset of the LAST page is to use
// the end loc from the previous page. But what that returns us
// is _exactly_ the place where we get our first non-overlapped
// sample. (I think. Stupid spec for being ambiguous.) So for
// consistency it's better to do that here, too. However, that
// will then require us to NOT discard all of the first frame we
// decode, in some cases, which means an even weirder frame size
// and extra code. what a fucking pain.
// we're going to discard the first packet if we
// start the seek here, so we don't care about it. (we could actually
// do better; if the first packet is long, and the previous packet
// is short, there's actually data in the first half of the first
// packet that doesn't need discarding... but not worth paying the
// effort of tracking that of that here and in the seeking logic)
// except crap, if we infer it from the Previous_ packet's end
// location, we DO need to use that definition... and we HAVE to
// infer the start loc of the LAST packet from the previous packet's
// end location. fuck you, ogg vorbis.
z.firstDecodedSample = z.lastDecodedSample - samples;
// restore file state to where we were
setInputOffset(seekFunc, z.pageStart);
return z;
}
function decodeScalarRaw(c:Codebook):Int
{
prepHuffman();
VorbisTools.assert(c.sortedCodewords != null || c.codewords != null);
// cases to use binary search: sortedCodewords && !codewords
var codewordLengths = c.codewordLengths;
var codewords = c.codewords;
var sortedCodewords = c.sortedCodewords;
if (c.entries > 8 ? (sortedCodewords != null) : codewords != null) {
// binary search
var code = VorbisTools.bitReverse(acc);
var x = 0;
var n = c.sortedEntries;
while (n > 1) {
// invariant: sc[x] <= code < sc[x+n]
var m = x + (n >> 1);
if (sortedCodewords[m] <= code) {
x = m;
n -= (n>>1);
} else {
n >>= 1;
}
}
// x is now the sorted index
if (!c.sparse) {
x = c.sortedValues[x];
}
// x is now sorted index if sparse, or symbol otherwise
var len = codewordLengths[x];
if (validBits >= len) {
acc >>>= len;
validBits -= len;
return x;
}
validBits = 0;
return -1;
}
// if small, linear search
VorbisTools.assert(!c.sparse);
for (i in 0...c.entries) {
var cl = codewordLengths[i];
if (cl == Codebook.NO_CODE) {
continue;
}
if (codewords[i] == (acc & ((1 << cl)-1))) {
if (validBits >= cl) {
acc >>>= cl;
validBits -= cl;
return i;
}
validBits = 0;
return -1;
}
}
error = new ReaderError(INVALID_STREAM);
validBits = 0;
return -1;
}
}
private enum FindPageResult {
Found(end:Int, last:Bool);
NotFound;
}

View File

@ -0,0 +1,784 @@
package kha.audio2.ogg.vorbis;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import haxe.io.Input;
import haxe.io.Output;
import kha.audio2.ogg.tools.MathTools;
import kha.audio2.ogg.tools.Mdct;
import kha.audio2.ogg.vorbis.data.Codebook;
import kha.audio2.ogg.vorbis.data.Floor.Floor1;
import kha.audio2.ogg.vorbis.data.Header;
import kha.audio2.ogg.vorbis.data.Mode;
import kha.audio2.ogg.vorbis.data.ProbedPage;
import kha.audio2.ogg.vorbis.data.ReaderError;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class VorbisDecoder
{
var previousWindow:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
var previousLength:Int;
var finalY:Vector<Array<Int>>; // [STB_VORBIS_MAX_CHANNELS];
// twiddle factors
var a:Vector<Vector<Float>>; // var * [2]
var b:Vector<Vector<Float>>; // var * [2]
var c:Vector<Vector<Float>>; // var * [2]
var window:Vector<Vector<Float>>; //var * [2];
var bitReverseData:Vector<Vector<Int>>; //uint16 * [2]
// decode buffer
var channelBuffers:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
var channelBufferStart:Int;
var channelBufferEnd:Int;
public var header(default, null):Header;
public var currentSample(default, null):Int;
public var totalSample(default, null):Null<Int>;
var decodeState:VorbisDecodeState;
function new(header:Header, decodeState:VorbisDecodeState) {
this.header = header;
this.decodeState = decodeState;
totalSample = null;
currentSample = 0;
//Channel
previousLength = 0;
channelBuffers = new Vector(header.channel);
previousWindow = new Vector(header.channel);
finalY = new Vector(header.channel);
for (i in 0...header.channel) {
channelBuffers[i] = VorbisTools.emptyFloatVector(header.blocksize1);
previousWindow[i] = VorbisTools.emptyFloatVector(Std.int(header.blocksize1 / 2));
finalY[i] = new Array();
}
a = new Vector(2);
b = new Vector(2);
c = new Vector(2);
window = new Vector(2);
bitReverseData = new Vector(2);
initBlocksize(0, header.blocksize0);
initBlocksize(1, header.blocksize1);
}
public static function start(input:Input) {
var decodeState = new VorbisDecodeState(input);
var header = Header.read(decodeState);
var decoder = new VorbisDecoder(header, decodeState);
decodeState.startFirstDecode();
decoder.pumpFirstFrame();
return decoder;
}
public function read(output:kha.arrays.Float32Array, samples:Int, channels:Int, sampleRate:Int, useFloat:Bool) {
if (sampleRate % header.sampleRate != 0) {
throw 'Unsupported sampleRate : can\'t convert ${header.sampleRate} to $sampleRate';
}
if (channels % header.channel != 0) {
throw 'Unsupported channels : can\'t convert ${header.channel} to $channels';
}
var sampleRepeat = Std.int(sampleRate / header.sampleRate);
var channelRepeat = Std.int(channels / header.channel);
var n = 0;
var len = Math.floor(samples / sampleRepeat);
if (totalSample != null && len > totalSample - currentSample) {
len = totalSample - currentSample;
}
var index = 0;
while (n < len) {
var k = channelBufferEnd - channelBufferStart;
if (k >= len - n) k = len - n;
for (j in channelBufferStart...(channelBufferStart + k)) {
for (sr in 0...sampleRepeat) {
for (i in 0...header.channel) {
for (cr in 0...channelRepeat) {
var value = channelBuffers[i][j];
if (value > 1) {
value = 1;
} else if (value < -1) {
value = -1;
}
if (useFloat) {
//output.writeFloat(value);
output[index] = value;
++index;
} else {
//output.writeInt16(Math.floor(value * 0x7FFF));
}
}
}
}
}
n += k;
channelBufferStart += k;
if (n == len || getFrameFloat() == 0) {
break;
}
}
for (j in n...len) {
for (sr in 0...sampleRepeat) {
for (i in 0...header.channel) {
for (cr in 0...channelRepeat) {
if (useFloat) {
//output.writeFloat(0);
output[index] = 0;
++index;
} else {
//output.writeInt16(0);
}
}
}
}
}
currentSample += len;
return len * sampleRepeat;
}
public function skipSamples(len:Int) {
var n = 0;
if (totalSample != null && len > totalSample - currentSample) {
len = totalSample - currentSample;
}
while (n < len) {
var k = channelBufferEnd - channelBufferStart;
if (k >= len - n) k = len - n;
n += k;
channelBufferStart += k;
if (n == len || getFrameFloat() == 0) {
break;
}
}
currentSample += len;
return len;
}
public function setupSampleNumber(seekFunc:Int->Void, inputLength:Int) {
if (totalSample == null) {
totalSample = decodeState.getSampleNumber(seekFunc, inputLength);
}
}
public function seek(seekFunc:Int->Void, inputLength:UInt, sampleNumber:Int) {
if (currentSample == sampleNumber) {
return;
}
// do we know the location of the last page?
if (totalSample == null) {
setupSampleNumber(seekFunc, inputLength);
if (totalSample == 0) {
throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
}
}
if (sampleNumber < 0) {
sampleNumber = 0;
}
var p0 = decodeState.pFirst;
var p1 = decodeState.pLast;
if (sampleNumber >= p1.lastDecodedSample) {
sampleNumber = p1.lastDecodedSample - 1;
}
if (sampleNumber < p0.lastDecodedSample) {
seekFrameFromPage(seekFunc, p0.pageStart, 0, sampleNumber);
} else {
var attempts = 0;
while (p0.pageEnd < p1.pageStart) {
// copy these into local variables so we can tweak them
// if any are unknown
var startOffset:UInt = p0.pageEnd;
var endOffset:UInt = p1.afterPreviousPageStart; // an address known to seek to page p1
var startSample = p0.lastDecodedSample;
var endSample = p1.lastDecodedSample;
// currently there is no such tweaking logic needed/possible?
if (startSample == null || endSample == null) {
throw new ReaderError(SEEK_FAILED);
}
// now we want to lerp between these for the target samples...
// step 1: we need to bias towards the page start...
if (startOffset + 4000 < endOffset) {
endOffset -= 4000;
}
// now compute an interpolated search loc
var probe:UInt = startOffset + Math.floor((endOffset - startOffset) / (endSample - startSample) * (sampleNumber - startSample));
// next we need to bias towards binary search...
// code is a little wonky to allow for full 32-bit unsigned values
if (attempts >= 4) {
var probe2:UInt = startOffset + ((endOffset - startOffset) >> 1);
probe = if (attempts >= 8) {
probe2;
} else if (probe < probe2) {
probe + ((probe2 - probe) >>> 1);
} else {
probe2 + ((probe - probe2) >>> 1);
}
}
++attempts;
decodeState.setInputOffset(seekFunc, probe);
switch (decodeState.findPage(seekFunc, inputLength)) {
case NotFound:
throw new ReaderError(SEEK_FAILED);
case Found(_):
}
var q:ProbedPage = decodeState.analyzePage(seekFunc, header);
if (q == null) {
throw new ReaderError(SEEK_FAILED);
}
q.afterPreviousPageStart = probe;
// it's possible we've just found the last page again
if (q.pageStart == p1.pageStart) {
p1 = q;
continue;
}
if (sampleNumber < q.lastDecodedSample) {
p1 = q;
} else {
p0 = q;
}
}
if (p0.lastDecodedSample <= sampleNumber && sampleNumber < p1.lastDecodedSample) {
seekFrameFromPage(seekFunc, p1.pageStart, p0.lastDecodedSample, sampleNumber);
} else {
throw new ReaderError(SEEK_FAILED);
}
}
}
public function seekFrameFromPage(seekFunc:Int->Void, pageStart:Int, firstSample:Int, targetSample:Int) {
var frame = 0;
var frameStart:Int = firstSample;
// firstSample is the sample # of the first sample that doesn't
// overlap the previous page... note that this requires us to
// Partially_ discard the first packet! bleh.
decodeState.setInputOffset(seekFunc, pageStart);
decodeState.forcePageResync();
// frame start is where the previous packet's last decoded sample
// was, which corresponds to leftEnd... EXCEPT if the previous
// packet was long and this packet is short? Probably a bug here.
// now, we can start decoding frames... we'll only FAKE decode them,
// until we find the frame that contains our sample; then we'll rewind,
// and try again
var leftEnd = 0;
var leftStart = 0;
var prevState = null;
var lastState = null;
while (true) {
prevState = lastState;
lastState = decodeState.clone(seekFunc);
var initialResult = decodeInitial();
if (initialResult == null) {
lastState = prevState;
break;
}
leftStart = initialResult.left.start;
leftEnd = initialResult.left.end;
var start = if (frame == 0) {
leftEnd;
} else{
leftStart;
}
// the window starts at leftStart; the last valid sample we generate
// before the next frame's window start is rightStart-1
if (targetSample < frameStart + initialResult.right.start - start) {
break;
}
decodeState.flushPacket();
frameStart += initialResult.right.start - start;
++frame;
}
decodeState = lastState;
seekFunc(decodeState.inputPosition);
previousLength = 0;
pumpFirstFrame();
currentSample = frameStart;
skipSamples(targetSample - frameStart);
}
public function clone(seekFunc:Int->Void) {
var decoder = Type.createEmptyInstance(VorbisDecoder);
decoder.currentSample = currentSample;
decoder.totalSample = totalSample;
decoder.previousLength = previousLength;
decoder.channelBufferStart = channelBufferStart;
decoder.channelBufferEnd = channelBufferEnd;
// sharrow copy
decoder.a = a;
decoder.b = b;
decoder.c = c;
decoder.window = window;
decoder.bitReverseData = bitReverseData;
decoder.header = header;
// deep copy
decoder.decodeState = decodeState.clone(seekFunc);
decoder.channelBuffers = new Vector(header.channel);
decoder.previousWindow = new Vector(header.channel);
decoder.finalY = new Vector(header.channel);
for (i in 0...header.channel) {
decoder.channelBuffers[i] = VorbisTools.copyVector(channelBuffers[i]);
decoder.previousWindow[i] = VorbisTools.copyVector(previousWindow[i]);
decoder.finalY[i] = Lambda.array(finalY[i]);
}
return decoder;
}
public function ensurePosition(seekFunc:Int->Void) {
seekFunc(decodeState.inputPosition);
}
function getFrameFloat() {
var result = decodePacket();
if (result == null) {
channelBufferStart = channelBufferEnd = 0;
return 0;
}
var len = finishFrame(result);
channelBufferStart = result.left;
channelBufferEnd = result.left + len;
return len;
}
function pumpFirstFrame() {
finishFrame(decodePacket());
}
function finishFrame(r:DecodePacketResult):Int {
var len = r.len;
var right = r.right;
var left = r.left;
// we use right&left (the start of the right- and left-window sin()-regions)
// to determine how much to return, rather than inferring from the rules
// (same result, clearer code); 'left' indicates where our sin() window
// starts, therefore where the previous window's right edge starts, and
// therefore where to start mixing from the previous buffer. 'right'
// indicates where our sin() ending-window starts, therefore that's where
// we start saving, and where our returned-data ends.
// mixin from previous window
if (previousLength != 0) {
var n = previousLength;
var w = getWindow(n);
for (i in 0...header.channel) {
var cb = channelBuffers[i];
var pw = previousWindow[i];
for (j in 0...n) {
cb[left+j] = cb[left+j] * w[j] + pw[j] * w[n-1-j];
}
}
}
var prev = previousLength;
// last half of this data becomes previous window
previousLength = len - right;
// @OPTIMIZE: could avoid this copy by double-buffering the
// output (flipping previousWindow with channelBuffers), but
// then previousWindow would have to be 2x as large, and
// channelBuffers couldn't be temp mem (although they're NOT
// currently temp mem, they could be (unless we want to level
// performance by spreading out the computation))
for (i in 0...header.channel) {
var pw = previousWindow[i];
var cb = channelBuffers[i];
for (j in 0...(len - right)) {
pw[j] = cb[right + j];
}
}
if (prev == 0) {
// there was no previous packet, so this data isn't valid...
// this isn't entirely true, only the would-have-overlapped data
// isn't valid, but this seems to be what the spec requires
return 0;
}
// truncate a short frame
if (len < right) {
right = len;
}
return right - left;
}
function getWindow(len:Int)
{
len <<= 1;
return if (len == header.blocksize0) {
window[0];
} else if (len == header.blocksize1) {
window[1];
} else {
VorbisTools.assert(false);
null;
}
}
function initBlocksize(bs:Int, n:Int)
{
var n2 = n >> 1, n4 = n >> 2, n8 = n >> 3;
a[bs] = new Vector(n2);
b[bs] = new Vector(n2);
c[bs] = new Vector(n4);
window[bs] = new Vector(n2);
bitReverseData[bs] = new Vector(n8);
VorbisTools.computeTwiddleFactors(n, a[bs], b[bs], c[bs]);
VorbisTools.computeWindow(n, window[bs]);
VorbisTools.computeBitReverse(n, bitReverseData[bs]);
}
function inverseMdct(buffer:Vector<Float>, n:Int, blocktype:Bool) {
var bt = blocktype ? 1 : 0;
Mdct.inverseTransform(buffer, n, a[bt], b[bt], c[bt], bitReverseData[bt]);
}
function decodePacket():DecodePacketResult
{
var result = decodeInitial();
if (result == null) {
return null;
}
var rest = decodePacketRest(result);
return rest;
}
function decodeInitial():DecodeInitialResult
{
channelBufferStart = channelBufferEnd = 0;
do {
if (!decodeState.maybeStartPacket()) {
return null;
}
// check packet type
if (decodeState.readBits(1) != 0) {
while (VorbisTools.EOP != decodeState.readPacket()) {};
continue;
}
break;
} while (true);
var i = decodeState.readBits(MathTools.ilog(header.modes.length - 1));
if (i == VorbisTools.EOP || i >= header.modes.length) {
throw new ReaderError(ReaderErrorType.SEEK_FAILED);
}
var m = header.modes[i];
var n, prev, next;
if (m.blockflag) {
n = header.blocksize1;
prev = decodeState.readBits(1);
next = decodeState.readBits(1);
} else {
prev = next = 0;
n = header.blocksize0;
}
// WINDOWING
var windowCenter = n >> 1;
return {
mode : i,
left : if (m.blockflag && prev == 0) {
start : (n - header.blocksize0) >> 2,
end : (n + header.blocksize0) >> 2,
} else {
start : 0,
end : windowCenter,
},
right : if (m.blockflag && next == 0) {
start : (n * 3 - header.blocksize0) >> 2,
end : (n * 3 + header.blocksize0) >> 2,
} else {
start : windowCenter,
end : n,
},
}
}
function decodePacketRest(r:DecodeInitialResult):DecodePacketResult
{
var len = 0;
var m = header.modes[r.mode];
var zeroChannel = new Vector<Bool>(256);
var reallyZeroChannel = new Vector<Bool>(256);
// WINDOWING
var n = m.blockflag ? header.blocksize1 : header.blocksize0;
var map = header.mapping[m.mapping];
// FLOORS
var n2 = n >> 1;
VorbisTools.stbProf(1);
var rangeList = [256, 128, 86, 64];
var codebooks = header.codebooks;
for (i in 0...header.channel) {
var s = map.chan[i].mux;
zeroChannel[i] = false;
var floor = header.floorConfig[map.submapFloor[s]];
if (floor.type == 0) {
throw new ReaderError(INVALID_STREAM);
} else {
var g:Floor1 = floor.floor1;
if (decodeState.readBits(1) != 0) {
var fy = new Array<Int>();
var step2Flag = new Vector<Bool>(256);
var range = rangeList[g.floor1Multiplier-1];
var offset = 2;
fy = finalY[i];
fy[0] = decodeState.readBits(MathTools.ilog(range)-1);
fy[1] = decodeState.readBits(MathTools.ilog(range)-1);
for (j in 0...g.partitions) {
var pclass = g.partitionClassList[j];
var cdim = g.classDimensions[pclass];
var cbits = g.classSubclasses[pclass];
var csub = (1 << cbits) - 1;
var cval = 0;
if (cbits != 0) {
var c = codebooks[g.classMasterbooks[pclass]];
cval = decodeState.decode(c);
}
var books = g.subclassBooks[pclass];
for (k in 0...cdim) {
var book = books[cval & csub];
cval >>= cbits;
fy[offset++] = if (book >= 0) {
decodeState.decode(codebooks[book]);
} else {
0;
}
}
}
if (decodeState.validBits == VorbisDecodeState.INVALID_BITS) {
zeroChannel[i] = true;
continue;
}
step2Flag[0] = step2Flag[1] = true;
var naighbors = g.neighbors;
var xlist = g.xlist;
for (j in 2...g.values) {
var low = naighbors[j][0];
var high = naighbors[j][1];
var lowroom = VorbisTools.predictPoint(xlist[j], xlist[low], xlist[high], fy[low], fy[high]);
var val = fy[j];
var highroom = range - lowroom;
var room = if (highroom < lowroom){
highroom * 2;
}else{
lowroom * 2;
}
if (val != 0) {
step2Flag[low] = step2Flag[high] = true;
step2Flag[j] = true;
if (val >= room){
if (highroom > lowroom){
fy[j] = val - lowroom + lowroom;
}else{
fy[j] = lowroom - val + highroom - 1;
}
} else {
if (val & 1 != 0){
fy[j] = lowroom - ((val+1)>>1);
} else{
fy[j] = lowroom + (val>>1);
}
}
} else {
step2Flag[j] = false;
fy[j] = lowroom;
}
}
// defer final floor computation until _after_ residue
for (j in 0...g.values) {
if (!step2Flag[j]){
fy[j] = -1;
}
}
} else {
zeroChannel[i] = true;
}
// So we just defer everything else to later
// at this point we've decoded the floor into buffer
}
}
VorbisTools.stbProf(0);
// at this point we've decoded all floors
//if (alloc.allocBuffer) {
// assert(alloc.allocBufferLengthInBytes == tempOffset);
//}
// re-enable coupled channels if necessary
for (i in 0...header.channel) {
reallyZeroChannel[i] = zeroChannel[i];
}
for (i in 0...map.couplingSteps) {
if (!zeroChannel[map.chan[i].magnitude] || !zeroChannel[map.chan[i].angle]) {
zeroChannel[map.chan[i].magnitude] = zeroChannel[map.chan[i].angle] = false;
}
}
// RESIDUE DECODE
for (i in 0...map.submaps) {
var residueBuffers = new Vector<Vector<Float>>(header.channel);
var doNotDecode = new Vector<Bool>(256);
var ch = 0;
for (j in 0...header.channel) {
if (map.chan[j].mux == i) {
if (zeroChannel[j]) {
doNotDecode[ch] = true;
residueBuffers[ch] = null;
} else {
doNotDecode[ch] = false;
residueBuffers[ch] = channelBuffers[j];
}
++ch;
}
}
var r = map.submapResidue[i];
var residue = header.residueConfig[r];
residue.decode(decodeState,header, residueBuffers, ch, n2, doNotDecode, channelBuffers);
}
// INVERSE COUPLING
VorbisTools.stbProf(14);
var i = map.couplingSteps;
var n2 = n >> 1;
while (--i >= 0) {
var m = channelBuffers[map.chan[i].magnitude];
var a = channelBuffers[map.chan[i].angle];
for (j in 0...n2) {
var a2, m2;
if (m[j] > 0) {
if (a[j] > 0) {
m2 = m[j];
a2 = m[j] - a[j];
} else {
a2 = m[j];
m2 = m[j] + a[j];
}
} else {
if (a[j] > 0) {
m2 = m[j];
a2 = m[j] + a[j];
} else {
a2 = m[j];
m2 = m[j] - a[j];
}
}
m[j] = m2;
a[j] = a2;
}
}
// finish decoding the floors
VorbisTools.stbProf(15);
for (i in 0...header.channel) {
if (reallyZeroChannel[i]) {
for(j in 0...n2) {
channelBuffers[i][j] = 0;
}
} else {
map.doFloor(header.floorConfig, i, n, channelBuffers[i], finalY[i], null);
}
}
// INVERSE MDCT
VorbisTools.stbProf(16);
for (i in 0...header.channel) {
inverseMdct(channelBuffers[i], n, m.blockflag);
}
VorbisTools.stbProf(0);
// this shouldn't be necessary, unless we exited on an error
// and want to flush to get to the next packet
decodeState.flushPacket();
return decodeState.finishDecodePacket(previousLength, n, r);
}
}
typedef DecodePacketResult = {
var len : Int;
var left : Int;
var right : Int;
}
typedef DecodeInitialResult = {
var mode : Int;
var left : Range;
var right : Range;
}
private typedef Range = {
var start : Int;
var end : Int;
}

View File

@ -0,0 +1,291 @@
package kha.audio2.ogg.vorbis;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.Input;
import haxe.PosInfos;
import kha.audio2.ogg.vorbis.data.IntPoint;
import kha.audio2.ogg.vorbis.data.ReaderError;
import kha.audio2.ogg.tools.MathTools;
/**
* ...
* @author shohei909
*/
class VorbisTools
{
static public inline var EOP = -1;
static public var integerDivideTable:Vector<Vector<Int>>;
static inline var M__PI = 3.14159265358979323846264;
static inline var DIVTAB_NUMER = 32;
static inline var DIVTAB_DENOM = 64;
static public var INVERSE_DB_TABLE = [
1.0649863e-07, 1.1341951e-07, 1.2079015e-07, 1.2863978e-07,
1.3699951e-07, 1.4590251e-07, 1.5538408e-07, 1.6548181e-07,
1.7623575e-07, 1.8768855e-07, 1.9988561e-07, 2.1287530e-07,
2.2670913e-07, 2.4144197e-07, 2.5713223e-07, 2.7384213e-07,
2.9163793e-07, 3.1059021e-07, 3.3077411e-07, 3.5226968e-07,
3.7516214e-07, 3.9954229e-07, 4.2550680e-07, 4.5315863e-07,
4.8260743e-07, 5.1396998e-07, 5.4737065e-07, 5.8294187e-07,
6.2082472e-07, 6.6116941e-07, 7.0413592e-07, 7.4989464e-07,
7.9862701e-07, 8.5052630e-07, 9.0579828e-07, 9.6466216e-07,
1.0273513e-06, 1.0941144e-06, 1.1652161e-06, 1.2409384e-06,
1.3215816e-06, 1.4074654e-06, 1.4989305e-06, 1.5963394e-06,
1.7000785e-06, 1.8105592e-06, 1.9282195e-06, 2.0535261e-06,
2.1869758e-06, 2.3290978e-06, 2.4804557e-06, 2.6416497e-06,
2.8133190e-06, 2.9961443e-06, 3.1908506e-06, 3.3982101e-06,
3.6190449e-06, 3.8542308e-06, 4.1047004e-06, 4.3714470e-06,
4.6555282e-06, 4.9580707e-06, 5.2802740e-06, 5.6234160e-06,
5.9888572e-06, 6.3780469e-06, 6.7925283e-06, 7.2339451e-06,
7.7040476e-06, 8.2047000e-06, 8.7378876e-06, 9.3057248e-06,
9.9104632e-06, 1.0554501e-05, 1.1240392e-05, 1.1970856e-05,
1.2748789e-05, 1.3577278e-05, 1.4459606e-05, 1.5399272e-05,
1.6400004e-05, 1.7465768e-05, 1.8600792e-05, 1.9809576e-05,
2.1096914e-05, 2.2467911e-05, 2.3928002e-05, 2.5482978e-05,
2.7139006e-05, 2.8902651e-05, 3.0780908e-05, 3.2781225e-05,
3.4911534e-05, 3.7180282e-05, 3.9596466e-05, 4.2169667e-05,
4.4910090e-05, 4.7828601e-05, 5.0936773e-05, 5.4246931e-05,
5.7772202e-05, 6.1526565e-05, 6.5524908e-05, 6.9783085e-05,
7.4317983e-05, 7.9147585e-05, 8.4291040e-05, 8.9768747e-05,
9.5602426e-05, 0.00010181521, 0.00010843174, 0.00011547824,
0.00012298267, 0.00013097477, 0.00013948625, 0.00014855085,
0.00015820453, 0.00016848555, 0.00017943469, 0.00019109536,
0.00020351382, 0.00021673929, 0.00023082423, 0.00024582449,
0.00026179955, 0.00027881276, 0.00029693158, 0.00031622787,
0.00033677814, 0.00035866388, 0.00038197188, 0.00040679456,
0.00043323036, 0.00046138411, 0.00049136745, 0.00052329927,
0.00055730621, 0.00059352311, 0.00063209358, 0.00067317058,
0.00071691700, 0.00076350630, 0.00081312324, 0.00086596457,
0.00092223983, 0.00098217216, 0.0010459992, 0.0011139742,
0.0011863665, 0.0012634633, 0.0013455702, 0.0014330129,
0.0015261382, 0.0016253153, 0.0017309374, 0.0018434235,
0.0019632195, 0.0020908006, 0.0022266726, 0.0023713743,
0.0025254795, 0.0026895994, 0.0028643847, 0.0030505286,
0.0032487691, 0.0034598925, 0.0036847358, 0.0039241906,
0.0041792066, 0.0044507950, 0.0047400328, 0.0050480668,
0.0053761186, 0.0057254891, 0.0060975636, 0.0064938176,
0.0069158225, 0.0073652516, 0.0078438871, 0.0083536271,
0.0088964928, 0.009474637, 0.010090352, 0.010746080,
0.011444421, 0.012188144, 0.012980198, 0.013823725,
0.014722068, 0.015678791, 0.016697687, 0.017782797,
0.018938423, 0.020169149, 0.021479854, 0.022875735,
0.024362330, 0.025945531, 0.027631618, 0.029427276,
0.031339626, 0.033376252, 0.035545228, 0.037855157,
0.040315199, 0.042935108, 0.045725273, 0.048696758,
0.051861348, 0.055231591, 0.058820850, 0.062643361,
0.066714279, 0.071049749, 0.075666962, 0.080584227,
0.085821044, 0.091398179, 0.097337747, 0.10366330,
0.11039993, 0.11757434, 0.12521498, 0.13335215,
0.14201813, 0.15124727, 0.16107617, 0.17154380,
0.18269168, 0.19456402, 0.20720788, 0.22067342,
0.23501402, 0.25028656, 0.26655159, 0.28387361,
0.30232132, 0.32196786, 0.34289114, 0.36517414,
0.38890521, 0.41417847, 0.44109412, 0.46975890,
0.50028648, 0.53279791, 0.56742212, 0.60429640,
0.64356699, 0.68538959, 0.72993007, 0.77736504,
0.82788260, 0.88168307, 0.9389798, 1.0
];
public static inline function assert(b:Bool, ?p:PosInfos) {
#if debug
if (!b) {
throw new ReaderError(ReaderErrorType.OTHER, "", p);
}
#end
}
public static inline function neighbors(x:Vector<Int>, n:Int)
{
var low = -1;
var high = 65536;
var plow = 0;
var phigh = 0;
for (i in 0...n) {
if (x[i] > low && x[i] < x[n]) { plow = i; low = x[i]; }
if (x[i] < high && x[i] > x[n]) { phigh = i; high = x[i]; }
}
return {
low : plow,
high : phigh,
}
}
public static inline function floatUnpack(x:UInt):Float
{
// from the specification
var mantissa:Float = x & 0x1fffff;
var sign:Int = x & 0x80000000;
var exp:Int = (x & 0x7fe00000) >>> 21;
var res:Float = (sign != 0) ? -mantissa : mantissa;
return res * Math.pow(2, exp - 788);
}
public static inline function bitReverse(n:UInt):UInt
{
n = ((n & 0xAAAAAAAA) >>> 1) | ((n & 0x55555555) << 1);
n = ((n & 0xCCCCCCCC) >>> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xF0F0F0F0) >>> 4) | ((n & 0x0F0F0F0F) << 4);
n = ((n & 0xFF00FF00) >>> 8) | ((n & 0x00FF00FF) << 8);
return (n >>> 16) | (n << 16);
}
public static inline function pointCompare(a:IntPoint, b:IntPoint) {
return if (a.x < b.x) -1 else if (a.x > b.x) 1 else 0;
}
public static function uintAsc(a:UInt, b:UInt) {
return if (a < b) {
-1;
} else if (a == b){
0;
} else {
1;
}
}
public static function lookup1Values(entries:Int, dim:Int)
{
var r = Std.int(Math.exp(Math.log(entries) / dim));
if (Std.int(Math.pow(r + 1, dim)) <= entries) {
r++;
}
assert(Math.pow(r+1, dim) > entries);
assert(Std.int(Math.pow(r, dim)) <= entries); // (int),floor() as above
return r;
}
public static function computeWindow(n:Int, window:Vector<Float>)
{
var n2 = n >> 1;
for (i in 0...n2) {
window[i] = Math.sin(0.5 * M__PI * square(Math.sin((i - 0 + 0.5) / n2 * 0.5 * M__PI)));
}
}
public static function square(f:Float) {
return f * f;
}
public static function computeBitReverse(n:Int, rev:Vector<Int>)
{
var ld = MathTools.ilog(n) - 1;
var n8 = n >> 3;
for (i in 0...n8) {
rev[i] = (bitReverse(i) >>> (32 - ld + 3)) << 2;
}
}
public static function computeTwiddleFactors(n:Int, af:Vector<Float>, bf:Vector<Float>, cf:Vector<Float>)
{
var n4 = n >> 2;
var n8 = n >> 3;
var k2 = 0;
for (k in 0...n4) {
af[k2] = Math.cos(4*k*M__PI/n);
af[k2 + 1] = -Math.sin(4*k*M__PI/n);
bf[k2] = Math.cos((k2+1)*M__PI/n/2) * 0.5;
bf[k2 + 1] = Math.sin((k2 + 1) * M__PI / n / 2) * 0.5;
k2 += 2;
}
var k2 = 0;
for (k in 0...n8) {
cf[k2 ] = Math.cos(2*(k2+1) * M__PI/n);
cf[k2+1] = -Math.sin(2*(k2+1) * M__PI/n);
k2 += 2;
}
}
public static function drawLine(output:Vector<Float>, x0:Int, y0:Int, x1:Int, y1:Int, n:Int)
{
if (integerDivideTable == null) {
integerDivideTable = new Vector(DIVTAB_NUMER);
for (i in 0...DIVTAB_NUMER) {
integerDivideTable[i] = new Vector(DIVTAB_DENOM);
for (j in 1...DIVTAB_DENOM) {
integerDivideTable[i][j] = Std.int(i / j);
}
}
}
var dy = y1 - y0;
var adx = x1 - x0;
var ady = dy < 0 ? -dy : dy;
var base:Int;
var x = x0;
var y = y0;
var err = 0;
var sy = if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) {
if (dy < 0) {
base = -integerDivideTable[ady][adx];
base - 1;
} else {
base = integerDivideTable[ady][adx];
base + 1;
}
} else {
base = Std.int(dy / adx);
if (dy < 0) {
base - 1;
} else {
base + 1;
}
}
ady -= (base < 0 ? -base : base) * adx;
if (x1 > n) {
x1 = n;
}
output[x] *= INVERSE_DB_TABLE[y];
for (i in (x + 1)...x1) {
err += ady;
if (err >= adx) {
err -= adx;
y += sy;
} else {
y += base;
}
output[i] *= INVERSE_DB_TABLE[y];
}
}
public macro static inline function stbProf(i:Int)
{
return macro null;// macro trace($v { i }, channelBuffers[0][0], channelBuffers[0][1]);
}
public static inline function predictPoint(x:Int, x0:Int, x1:Int, y0:Int, y1:Int):Int
{
var dy = y1 - y0;
var adx = x1 - x0;
// @OPTIMIZE: force int division to round in the right direction... is this necessary on x86?
var err = Math.abs(dy) * (x - x0);
var off = Std.int(err / adx);
return dy < 0 ? (y0 - off) : (y0 + off);
}
public static inline function emptyFloatVector(len:Int) {
var vec = new Vector<Float>(len);
#if neko
for (i in 0...len) {
vec[i] = 0;
}
#end
return vec;
}
static public function copyVector(source:Vector<Float>):Vector<Float> {
var dest:Vector<Float> = new Vector<Float>(source.length);
for (i in 0...source.length) {
dest[i] = source[i];
}
return dest;
}
}

View File

@ -0,0 +1,594 @@
package kha.audio2.ogg.vorbis.data;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.Input;
import kha.audio2.ogg.tools.MathTools;
import kha.audio2.ogg.vorbis.data.ReaderError.ReaderErrorType;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class Codebook
{
static public inline var NO_CODE = 255;
public var dimensions:Int;
public var entries:Int;
public var codewordLengths:Vector<Int>; //uint8*
public var minimumValue:Float;
public var deltaValue:Float;
public var valueBits:Int; //uint8
public var lookupType:Int; //uint8
public var sequenceP:Bool; //uint8
public var sparse:Bool; //uint8
public var lookupValues:UInt; //uint32
public var multiplicands:Vector<Float>; // codetype *
public var codewords:Vector<UInt>; //uint32*
public var fastHuffman:Vector<Int>; //[FAST_HUFFMAN_TABLE_SIZE];
public var sortedCodewords:Array<UInt>; //uint32*
public var sortedValues:Vector<Int>;
public var sortedEntries:Int;
public function new () {
}
static public function read(decodeState:VorbisDecodeState):Codebook {
var c = new Codebook();
if (decodeState.readBits(8) != 0x42 || decodeState.readBits(8) != 0x43 || decodeState.readBits(8) != 0x56) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP);
}
var x = decodeState.readBits(8);
c.dimensions = (decodeState.readBits(8) << 8) + x;
var x = decodeState.readBits(8);
var y = decodeState.readBits(8);
c.entries = (decodeState.readBits(8) << 16) + (y << 8) + x;
var ordered = decodeState.readBits(1);
c.sparse = (ordered != 0) ? false : (decodeState.readBits(1) != 0);
var lengths = new Vector(c.entries);
if (!c.sparse) {
c.codewordLengths = lengths;
}
var total = 0;
if (ordered != 0) {
var currentEntry = 0;
var currentLength = decodeState.readBits(5) + 1;
while (currentEntry < c.entries) {
var limit = c.entries - currentEntry;
var n = decodeState.readBits(MathTools.ilog(limit));
if (currentEntry + n > c.entries) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "codebook entrys");
}
for (i in 0...n) {
lengths.set(currentEntry + i, currentLength);
}
currentEntry += n;
currentLength++;
}
} else {
for (j in 0...c.entries) {
var present = (c.sparse) ? decodeState.readBits(1) : 1;
if (present != 0) {
lengths.set(j, decodeState.readBits(5) + 1);
total++;
} else {
lengths.set(j, NO_CODE);
}
}
}
if (c.sparse && total >= (c.entries >> 2)) {
c.codewordLengths = lengths;
c.sparse = false;
}
c.sortedEntries = if (c.sparse) {
total;
} else {
var sortedCount = 0;
for (j in 0...c.entries) {
var l = lengths.get(j);
if (l > Setting.FAST_HUFFMAN_LENGTH && l != NO_CODE) {
++sortedCount;
}
}
sortedCount;
}
var values:Vector<UInt> = null;
if (!c.sparse) {
c.codewords = new Vector<UInt>(c.entries);
} else {
if (c.sortedEntries != 0) {
c.codewordLengths = new Vector(c.sortedEntries);
c.codewords = new Vector<UInt>(c.entries);
values = new Vector<UInt>(c.entries);
}
var size:Int = c.entries + (32 + 32) * c.sortedEntries;
}
if (!c.computeCodewords(lengths, c.entries, values)) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "compute codewords");
}
if (c.sortedEntries != 0) {
// allocate an extra slot for sentinels
c.sortedCodewords = [];
// allocate an extra slot at the front so that sortedValues[-1] is defined
// so that we can catch that case without an extra if
c.sortedValues = new Vector<Int>(c.sortedEntries);
c.computeSortedHuffman(lengths, values);
}
if (c.sparse) {
values = null;
c.codewords = null;
lengths = null;
}
c.computeAcceleratedHuffman();
c.lookupType = decodeState.readBits(4);
if (c.lookupType > 2) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "codebook lookup type");
}
if (c.lookupType > 0) {
c.minimumValue = VorbisTools.floatUnpack(decodeState.readBits(32));
c.deltaValue = VorbisTools.floatUnpack(decodeState.readBits(32));
c.valueBits = decodeState.readBits(4) + 1;
c.sequenceP = (decodeState.readBits(1) != 0);
if (c.lookupType == 1) {
c.lookupValues = VorbisTools.lookup1Values(c.entries, c.dimensions);
} else {
c.lookupValues = c.entries * c.dimensions;
}
var mults = new Vector<Int>(c.lookupValues);
for (j in 0...c.lookupValues) {
var q = decodeState.readBits(c.valueBits);
if (q == VorbisTools.EOP) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "fail lookup");
}
mults[j] = q;
}
{
c.multiplicands = new Vector(c.lookupValues);
//STB_VORBIS_CODEBOOK_FLOATS = true
for (j in 0...c.lookupValues) {
c.multiplicands[j] = mults[j] * c.deltaValue + c.minimumValue;
}
}
//STB_VORBIS_CODEBOOK_FLOATS = true
if (c.lookupType == 2 && c.sequenceP) {
for (j in 1...c.lookupValues) {
c.multiplicands[j] = c.multiplicands[j - 1];
}
c.sequenceP = false;
}
}
return c;
}
inline function addEntry(huffCode:UInt, symbol:Int, count:Int, len:Int, values:Vector<UInt>)
{
if (!sparse) {
codewords[symbol] = huffCode;
} else {
codewords[count] = huffCode;
codewordLengths.set(count, len);
values[count] = symbol;
}
}
inline function includeInSort(len:Int)
{
return if (sparse) {
VorbisTools.assert(len != NO_CODE);
true;
} else if (len == NO_CODE) {
false;
} else if (len > Setting.FAST_HUFFMAN_LENGTH) {
true;
} else {
false;
}
}
function computeCodewords(len:Vector<Int>, n:Int, values:Vector<UInt>)
{
var available = new Vector<UInt>(32);
for (x in 0...32) available[x] = 0;
// find the first entry
var k = 0;
while (k < n) {
if (len.get(k) < NO_CODE) {
break;
}
k++;
}
if (k == n) {
VorbisTools.assert(sortedEntries == 0);
return true;
}
var m = 0;
// add to the list
addEntry(0, k, m++, len.get(k), values);
// add all available leaves
var i = 0;
while (++i <= len.get(k)) {
available[i] = (1:UInt) << ((32 - i):UInt);
}
// note that the above code treats the first case specially,
// but it's really the same as the following code, so they
// could probably be combined (except the initial code is 0,
// and I use 0 in available[] to mean 'empty')
i = k;
while (++i < n) {
var z = len.get(i);
if (z == NO_CODE) continue;
// find lowest available leaf (should always be earliest,
// which is what the specification calls for)
// note that this property, and the fact we can never have
// more than one free leaf at a given level, isn't totally
// trivial to prove, but it seems true and the assert never
// fires, so!
while (z > 0 && available[z] == 0) --z;
if (z == 0) {
return false;
}
var res:UInt = available[z];
available[z] = 0;
addEntry(VorbisTools.bitReverse(res), i, m++, len.get(i), values);
// propogate availability up the tree
if (z != len.get(i)) {
var y = len.get(i);
while (y > z) {
VorbisTools.assert(available[y] == 0);
available[y] = res + (1 << (32 - y));
y--;
}
}
}
return true;
}
function computeSortedHuffman(lengths:Vector<Int>, values:Vector<UInt>)
{
// build a list of all the entries
// OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN.
// this is kind of a frivolous optimization--I don't see any performance improvement,
// but it's like 4 extra lines of code, so.
if (!sparse) {
var k = 0;
for (i in 0...entries) {
if (includeInSort(lengths.get(i))) {
sortedCodewords[k++] = VorbisTools.bitReverse(codewords[i]);
}
}
VorbisTools.assert(k == sortedEntries);
} else {
for (i in 0...sortedEntries) {
sortedCodewords[i] = VorbisTools.bitReverse(codewords[i]);
}
}
sortedCodewords[sortedEntries] = 0xffffffff;
sortedCodewords.sort(VorbisTools.uintAsc);
var len = sparse ? sortedEntries : entries;
// now we need to indicate how they correspond; we could either
// #1: sort a different data structure that says who they correspond to
// #2: for each sorted entry, search the original list to find who corresponds
// #3: for each original entry, find the sorted entry
// #1 requires extra storage, #2 is slow, #3 can use binary search!
for (i in 0...len) {
var huffLen = sparse ? lengths.get(values[i]) : lengths.get(i);
if (includeInSort(huffLen)) {
var code = VorbisTools.bitReverse(codewords[i]);
var x = 0;
var n = sortedEntries;
while (n > 1) {
// invariant: sc[x] <= code < sc[x+n]
var m = x + (n >> 1);
if (sortedCodewords[m] <= code) {
x = m;
n -= (n>>1);
} else {
n >>= 1;
}
}
//VorbisTools.assert(sortedCodewords[x] == code);
if (sparse) {
sortedValues[x] = values[i];
codewordLengths.set(x, huffLen);
} else {
sortedValues[x] = i;
}
}
}
}
function computeAcceleratedHuffman()
{
fastHuffman = new Vector(Setting.FAST_HUFFMAN_TABLE_SIZE);
fastHuffman[0] = -1;
for (i in 0...(Setting.FAST_HUFFMAN_TABLE_SIZE)) {
fastHuffman[i] = -1;
}
var len = (sparse) ? sortedEntries : entries;
//STB_VORBIS_FAST_HUFFMAN_SHORT
//if (len > 32767) len = 32767; // largest possible value we can encode!
for (i in 0...len) {
if (codewordLengths[i] <= Setting.FAST_HUFFMAN_LENGTH) {
var z:Int = (sparse) ? VorbisTools.bitReverse(sortedCodewords[i]) : codewords[i];
// set table entries for all bit combinations in the higher bits
while (z < Setting.FAST_HUFFMAN_TABLE_SIZE) {
fastHuffman[z] = i;
z += 1 << codewordLengths[i];
}
}
}
}
function codebookDecode(decodeState:VorbisDecodeState, output:Vector<Float>, offset:Int, len:Int)
{
var z = decodeStart(decodeState);
var lookupValues = this.lookupValues;
var sequenceP = this.sequenceP;
var multiplicands = this.multiplicands;
var minimumValue = this.minimumValue;
if (z < 0) {
return false;
}
if (len > dimensions) {
len = dimensions;
}
// STB_VORBIS_DIVIDES_IN_CODEBOOK = true
if (lookupType == 1) {
var div = 1;
var last = 0.0;
for (i in 0...len) {
var off = Std.int(z / div) % lookupValues;
var val = multiplicands[off] + last;
output[offset + i] += val;
if (sequenceP) {
last = val + minimumValue;
}
div *= lookupValues;
}
return true;
}
z *= dimensions;
if (sequenceP) {
var last = 0.0;
for (i in 0...len) {
var val = multiplicands[z + i] + last;
output[offset + i] += val;
last = val + minimumValue;
}
} else {
var last = 0.0;
for (i in 0...len) {
output[offset + i] += multiplicands[z + i] + last;
}
}
return true;
}
function codebookDecodeStep(decodeState:VorbisDecodeState, output:Vector<Float>, offset:Int, len:Int, step:Int)
{
var z = decodeStart(decodeState);
var last = 0.0;
if (z < 0) {
return false;
}
if (len > dimensions) {
len = dimensions;
}
var lookupValues = this.lookupValues;
var sequenceP = this.sequenceP;
var multiplicands = this.multiplicands;
// STB_VORBIS_DIVIDES_IN_CODEBOOK = true
if (lookupType == 1) {
var div = 1;
for (i in 0...len) {
var off = Std.int(z / div) % lookupValues;
var val = multiplicands[off] + last;
output[offset + i * step] += val;
if (sequenceP) {
last = val;
}
div *= lookupValues;
}
return true;
}
z *= dimensions;
for (i in 0...len) {
var val = multiplicands[z + i] + last;
output[offset + i * step] += val;
if (sequenceP) {
last = val;
}
}
return true;
}
inline function decodeStart(decodeState:VorbisDecodeState)
{
return decodeState.decode(this);
//var z = -1;
//// type 0 is only legal in a scalar context
//if (lookupType == 0) {
// throw new ReaderError(INVALID_STREAM);
//} else {
// z = decodeState.decode(this);
// //if (sparse) VorbisTools.assert(z < sortedEntries);
// if (z < 0) { // check for VorbisTools.EOP
// if (decodeState.isLastByte()) {
// return z;
// } else {
// throw new ReaderError(INVALID_STREAM);
// }
// } else {
// return z;
// }
//}
}
static var delay = 0;
public function decodeDeinterleaveRepeat(decodeState:VorbisDecodeState, residueBuffers:Vector<Vector<Float>>, ch:Int, cInter:Int, pInter:Int, len:Int, totalDecode:Int)
{
var effective = dimensions;
// type 0 is only legal in a scalar context
if (lookupType == 0) {
throw new ReaderError(INVALID_STREAM);
}
var multiplicands = this.multiplicands;
var sequenceP = this.sequenceP;
var lookupValues = this.lookupValues;
while (totalDecode > 0) {
var last = 0.0;
var z = decodeState.decode(this);
if (z < 0) {
if (decodeState.isLastByte()) {
return null;
}
throw new ReaderError(INVALID_STREAM);
}
// if this will take us off the end of the buffers, stop short!
// we check by computing the length of the virtual interleaved
// buffer (len*ch), our current offset within it (pInter*ch)+(cInter),
// and the length we'll be using (effective)
if (cInter + pInter * ch + effective > len * ch) {
effective = len * ch - (pInter * ch - cInter);
}
if (lookupType == 1) {
var div = 1;
if (sequenceP) {
for (i in 0...effective) {
var off = Std.int(z / div) % lookupValues;
var val = multiplicands[off] + last;
residueBuffers[cInter][pInter] += val;
if (++cInter == ch) {
cInter = 0;
++pInter;
}
last = val;
div *= lookupValues;
}
} else {
for (i in 0...effective) {
var off = Std.int(z / div) % lookupValues;
var val = multiplicands[off] + last;
residueBuffers[cInter][pInter] += val;
if (++cInter == ch) {
cInter = 0;
++pInter;
}
div *= lookupValues;
}
}
} else {
z *= dimensions;
if (sequenceP) {
for (i in 0...effective) {
var val = multiplicands[z + i] + last;
residueBuffers[cInter][pInter] += val;
if (++cInter == ch) {
cInter = 0;
++pInter;
}
last = val;
}
} else {
for (i in 0...effective) {
var val = multiplicands[z + i] + last;
residueBuffers[cInter][pInter] += val;
if (++cInter == ch) {
cInter = 0;
++pInter;
}
}
}
}
totalDecode -= effective;
}
return {
cInter : cInter,
pInter : pInter
}
}
public function residueDecode(decodeState:VorbisDecodeState, target:Vector<Float>, offset:Int, n:Int, rtype:Int)
{
if (rtype == 0) {
var step = Std.int(n / dimensions);
for (k in 0...step) {
if (!codebookDecodeStep(decodeState, target, offset + k, n-offset-k, step)) {
return false;
}
}
} else {
var k = 0;
while(k < n) {
if (!codebookDecode(decodeState, target, offset, n-k)) {
return false;
}
k += dimensions;
offset += dimensions;
}
}
return true;
}
}

View File

@ -0,0 +1,130 @@
package kha.audio2.ogg.vorbis.data;
/**
* ...
* @author shohei909
*/
class Comment {
public var data(default, null):Map<String, Array<String>>;
public var title(get, never):String;
function get_title() {
return getString("title");
}
public var loopStart(get, never):Null<Int>;
function get_loopStart() {
return Std.parseInt(getString("loopstart"));
}
public var loopLength(get, never):Null<Int>;
function get_loopLength() {
return Std.parseInt(getString("looplength"));
}
public var version(get, never):String;
function get_version() {
return getString("version");
}
public var album(get, never):String;
function get_album() {
return getString("album");
}
public var organization(get, never):String;
function get_organization() {
return getString("organization");
}
public var tracknumber(get, never):String;
function get_tracknumber() {
return getString("tracknumber");
}
public var performer(get, never):String;
function get_performer() {
return getString("performer");
}
public var copyright(get, never):String;
function get_copyright() {
return getString("copyright");
}
public var license(get, never):String;
function get_license() {
return getString("license");
}
public var artist(get, never):String;
function get_artist() {
return getString("artist");
}
public var description(get, never):String;
function get_description() {
return getString("description");
}
public var genre(get, never):String;
function get_genre() {
return getString("genre");
}
public var date(get, never):String;
function get_date() {
return getString("date");
}
public var location(get, never):String;
function get_location() {
return getString("location");
}
public var contact(get, never):String;
function get_contact() {
return getString("contact");
}
public var isrc(get, never):String;
function get_isrc() {
return getString("isrc");
}
public var artists(get, never):Array<String>;
function get_artists() {
return getArray("artist");
}
public function new() {
data = new Map();
}
public function add(key:String, value:String) {
key = key.toLowerCase();
if (data.exists(key)) {
data[key].push(value);
} else {
data[key] = [value];
}
}
public function getString(key:String) {
key = key.toLowerCase();
return if (data.exists(key)) {
data[key][0];
} else {
null;
}
}
public function getArray(key:String) {
key = key.toLowerCase();
return if (data.exists(key)) {
data[key];
} else {
null;
}
}
}

View File

@ -0,0 +1,151 @@
package kha.audio2.ogg.vorbis.data;
import haxe.ds.Vector;
import haxe.io.Input;
import kha.audio2.ogg.vorbis.data.ReaderError;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class Floor
{
public var floor0:Floor0;
public var floor1:Floor1;
public var type:Int;
function new()
{
}
public static function read(decodeState:VorbisDecodeState, codebooks:Vector<Codebook>):Floor
{
var floor = new Floor();
floor.type = decodeState.readBits(16);
if (floor.type > 1) {
throw new ReaderError(INVALID_SETUP);
}
if (floor.type == 0) {
var g = floor.floor0 = new Floor0();
g.order = decodeState.readBits(8);
g.rate = decodeState.readBits(16);
g.barkMapSize = decodeState.readBits(16);
g.amplitudeBits = decodeState.readBits(6);
g.amplitudeOffset = decodeState.readBits(8);
g.numberOfBooks = decodeState.readBits(4) + 1;
for (j in 0...g.numberOfBooks) {
g.bookList[j] = decodeState.readBits(8);
}
throw new ReaderError(FEATURE_NOT_SUPPORTED);
} else {
var p = new Array<IntPoint>();
var g = floor.floor1 = new Floor1();
var maxClass = -1;
g.partitions = decodeState.readBits(5);
g.partitionClassList = new Vector(g.partitions);
for (j in 0...g.partitions) {
g.partitionClassList[j] = decodeState.readBits(4);
if (g.partitionClassList[j] > maxClass) {
maxClass = g.partitionClassList[j];
}
}
g.classDimensions = new Vector(maxClass + 1);
g.classMasterbooks = new Vector(maxClass + 1);
g.classSubclasses = new Vector(maxClass + 1);
g.subclassBooks = new Vector(maxClass + 1);
for (j in 0...(maxClass + 1)) {
g.classDimensions[j] = decodeState.readBits(3) + 1;
g.classSubclasses[j] = decodeState.readBits(2);
if (g.classSubclasses[j] != 0) {
g.classMasterbooks[j] = decodeState.readBits(8);
if (g.classMasterbooks[j] >= codebooks.length) {
throw new ReaderError(INVALID_SETUP);
}
}
var kl = (1 << g.classSubclasses[j]);
g.subclassBooks[j] = new Vector(kl);
for (k in 0...kl) {
g.subclassBooks[j][k] = decodeState.readBits(8)-1;
if (g.subclassBooks[j][k] >= codebooks.length) {
throw new ReaderError(INVALID_SETUP);
}
}
}
g.floor1Multiplier = decodeState.readBits(2) + 1;
g.rangebits = decodeState.readBits(4);
g.xlist = new Vector(31*8+2);
g.xlist[0] = 0;
g.xlist[1] = 1 << g.rangebits;
g.values = 2;
for (j in 0...g.partitions) {
var c = g.partitionClassList[j];
for (k in 0...g.classDimensions[c]) {
g.xlist[g.values] = decodeState.readBits(g.rangebits);
g.values++;
}
}
// precompute the sorting
for (j in 0...g.values) {
p.push(new IntPoint());
p[j].x = g.xlist[j];
p[j].y = j;
}
p.sort(VorbisTools.pointCompare);
g.sortedOrder = new Vector(g.values);
for (j in 0...g.values) {
g.sortedOrder[j] = p[j].y;
}
g.neighbors = new Vector(g.values);
// precompute the neighbors
for (j in 2...g.values) {
var ne = VorbisTools.neighbors(g.xlist, j);
g.neighbors[j] = new Vector(g.values);
g.neighbors[j][0] = ne.low;
g.neighbors[j][1] = ne.high;
}
}
return floor;
}
}
class Floor0
{
public var order:Int; //uint8
public var rate:Int; //uint16
public var barkMapSize:Int; //uint16
public var amplitudeBits:Int; //uint8
public var amplitudeOffset:Int; //uint8
public var numberOfBooks:Int; //uint8
public var bookList:Vector<UInt>; //uint8 [16] varies
public function new() {
}
}
class Floor1
{
public var partitions:Int; // uint8
public var partitionClassList:Vector<Int>; // uint8 varies
public var classDimensions:Vector<Int>; // uint8 [16] varies
public var classSubclasses:Vector<Int>; // uint8 [16] varies
public var classMasterbooks:Vector<Int>; // uint8 [16] varies
public var subclassBooks:Vector<Vector<Int>>; //int 16 [16][8] varies
public var xlist:Vector<Int>; //uint16 [31*8+2] varies
public var sortedOrder:Vector<Int>; //uint8 [31 * 8 + 2];
public var neighbors:Vector<Vector<Int>>; //uint8[31 * 8 + 2][2];
public var floor1Multiplier:Int;
public var rangebits:Int;
public var values:Int;
public function new() {
}
}

View File

@ -0,0 +1,213 @@
package kha.audio2.ogg.vorbis.data;
import haxe.ds.Vector;
import haxe.io.BytesInput;
import haxe.io.BytesOutput;
import haxe.io.Input;
import haxe.io.Output;
import kha.audio2.ogg.vorbis.data.Comment;
import kha.audio2.ogg.vorbis.data.Page.PageFlag;
import kha.audio2.ogg.vorbis.data.ReaderError.ReaderErrorType;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class Header {
static public inline var PACKET_ID = 1;
static public inline var PACKET_COMMENT = 3;
static public inline var PACKET_SETUP = 5;
public var maximumBitRate(default, null):UInt;
public var nominalBitRate(default, null):UInt;
public var minimumBitRate(default, null):UInt;
public var sampleRate(default, null):UInt;
public var channel(default, null):Int;
public var blocksize0(default, null):Int;
public var blocksize1(default, null):Int;
public var codebooks(default, null):Vector<Codebook>;
public var floorConfig(default, null):Vector<Floor>;
public var residueConfig(default, null):Vector<Residue>;
public var mapping(default, null):Vector<Mapping>;
public var modes(default, null):Vector<Mode>; // [64] varies
public var comment(default, null):Comment;
public var vendor(default, null):String;
function new() {
}
static public function read(decodeState:VorbisDecodeState):Header {
var page = decodeState.page;
page.start(decodeState);
if ((page.flag & PageFlag.FIRST_PAGE) == 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "not firstPage");
}
if ((page.flag & PageFlag.LAST_PAGE) != 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "lastPage");
}
if ((page.flag & PageFlag.CONTINUED_PACKET) != 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "continuedPacket");
}
decodeState.firstPageValidate();
if (decodeState.readByte() != PACKET_ID) {
throw new ReaderError(INVALID_FIRST_PAGE, "decodeState head");
}
// vorbis header
decodeState.vorbisValidate();
// vorbisVersion
var version = decodeState.readInt32();
if (version != 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "vorbis version : " + version);
}
var header = new Header();
header.channel = decodeState.readByte();
if (header.channel == 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "no channel");
} else if (header.channel > Setting.MAX_CHANNELS) {
throw new ReaderError(TOO_MANY_CHANNELS, "too many channels");
}
header.sampleRate = decodeState.readInt32();
if (header.sampleRate == 0) {
throw new ReaderError(INVALID_FIRST_PAGE, "no sampling rate");
}
header.maximumBitRate = decodeState.readInt32();
header.nominalBitRate = decodeState.readInt32();
header.minimumBitRate = decodeState.readInt32();
var x = decodeState.readByte();
var log0 = x & 15;
var log1 = x >> 4;
header.blocksize0 = 1 << log0;
header.blocksize1 = 1 << log1;
if (log0 < 6 || log0 > 13) {
throw new ReaderError(INVALID_SETUP);
}
if (log1 < 6 || log1 > 13) {
throw new ReaderError(INVALID_SETUP);
}
if (log0 > log1) {
throw new ReaderError(INVALID_SETUP);
}
// framingFlag
var x = decodeState.readByte();
if (x & 1 == 0) {
throw new ReaderError(INVALID_FIRST_PAGE);
}
// comment fields
decodeState.page.start(decodeState);
decodeState.startPacket();
var len = 0;
var output = new BytesOutput();
while((len = decodeState.next()) != 0) {
output.write(decodeState.readBytes(len));
decodeState.bytesInSeg = 0;
}
{
var packetInput = new BytesInput(output.getBytes());
packetInput.readByte();
packetInput.read(6);
var vendorLength:UInt = packetInput.readInt32();
header.vendor = packetInput.readString(vendorLength);
header.comment = new Comment();
var commentCount = packetInput.readInt32();
for (i in 0...commentCount) {
var n = packetInput.readInt32();
var str = packetInput.readString(n);
var splitter = str.indexOf("=");
if (splitter != -1) {
header.comment.add(str.substring(0, splitter), str.substring(splitter + 1));
}
}
var x = packetInput.readByte();
if (x & 1 == 0) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP);
}
}
// third packet!
decodeState.startPacket();
if (decodeState.readPacket() != PACKET_SETUP) {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "setup packet");
}
decodeState.vorbisValidate();
// codebooks
var codebookCount = decodeState.readBits(8) + 1;
header.codebooks = new Vector(codebookCount);
for (i in 0...codebookCount) {
header.codebooks[i] = Codebook.read(decodeState);
}
// time domain transfers (notused)
x = decodeState.readBits(6) + 1;
for (i in 0...x) {
if (decodeState.readBits(16) != 0) {
throw new ReaderError(INVALID_SETUP);
}
}
// Floors
var floorCount = decodeState.readBits(6) + 1;
header.floorConfig = new Vector(floorCount);
for (i in 0...floorCount) {
header.floorConfig[i] = Floor.read(decodeState, header.codebooks);
}
// Residue
var residueCount = decodeState.readBits(6) + 1;
header.residueConfig = new Vector(residueCount);
for (i in 0...residueCount) {
header.residueConfig[i] = Residue.read(decodeState, header.codebooks);
}
//Mapping
var mappingCount = decodeState.readBits(6) + 1;
header.mapping = new Vector(mappingCount);
for (i in 0...mappingCount) {
var map = Mapping.read(decodeState, header.channel);
header.mapping[i] = map;
for (j in 0...map.submaps) {
if (map.submapFloor[j] >= header.floorConfig.length) {
throw new ReaderError(INVALID_SETUP);
}
if (map.submapResidue[j] >= header.residueConfig.length) {
throw new ReaderError(INVALID_SETUP);
}
}
}
var modeCount = decodeState.readBits(6) + 1;
header.modes = new Vector(modeCount);
for (i in 0...modeCount) {
var mode = Mode.read(decodeState);
header.modes[i] = mode;
if (mode.mapping >= header.mapping.length) {
throw new ReaderError(INVALID_SETUP);
}
}
decodeState.flushPacket();
return header;
}
}

View File

@ -0,0 +1,15 @@
package kha.audio2.ogg.vorbis.data;
/**
* ...
* @author shohei909
*/
class IntPoint
{
public var x:Int;
public var y:Int;
public function new() {
}
}

View File

@ -0,0 +1,127 @@
package kha.audio2.ogg.vorbis.data;
import haxe.ds.Vector;
import haxe.io.Input;
import kha.audio2.ogg.tools.MathTools;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
class Mapping
{
public var couplingSteps:Int; // uint16
public var chan:Vector<MappingChannel>;
public var submaps:Int; // uint8
public var submapFloor:Vector<Int>; // uint8 varies
public var submapResidue:Vector<Int>; // uint8 varies
public function new() {
}
public static function read(decodeState:VorbisDecodeState, channels:Int):Mapping
{
var m = new Mapping();
var mappingType = decodeState.readBits(16);
if (mappingType != 0) {
throw new ReaderError(INVALID_SETUP, "mapping type " + mappingType);
}
m.chan = new Vector(channels);
for (j in 0...channels) {
m.chan[j] = new MappingChannel();
}
if (decodeState.readBits(1) != 0) {
m.submaps = decodeState.readBits(4)+1;
} else {
m.submaps = 1;
}
//if (m.submaps > maxSubmaps) {
// maxSubmaps = m.submaps;
//}
if (decodeState.readBits(1) != 0) {
m.couplingSteps = decodeState.readBits(8)+1;
for (k in 0...m.couplingSteps) {
m.chan[k].magnitude = decodeState.readBits(MathTools.ilog(channels-1));
m.chan[k].angle = decodeState.readBits(MathTools.ilog(channels-1));
if (m.chan[k].magnitude >= channels) {
throw new ReaderError(INVALID_SETUP);
}
if (m.chan[k].angle >= channels) {
throw new ReaderError(INVALID_SETUP);
}
if (m.chan[k].magnitude == m.chan[k].angle) {
throw new ReaderError(INVALID_SETUP);
}
}
} else {
m.couplingSteps = 0;
}
// reserved field
if (decodeState.readBits(2) != 0) {
throw new ReaderError(INVALID_SETUP);
}
if (m.submaps > 1) {
for (j in 0...channels) {
m.chan[j].mux = decodeState.readBits(4);
if (m.chan[j].mux >= m.submaps) {
throw new ReaderError(INVALID_SETUP);
}
}
} else {
for (j in 0...channels) {
m.chan[j].mux = 0;
}
}
m.submapFloor = new Vector(m.submaps);
m.submapResidue = new Vector(m.submaps);
for (j in 0...m.submaps) {
decodeState.readBits(8); // discard
m.submapFloor[j] = decodeState.readBits(8);
m.submapResidue[j] = decodeState.readBits(8);
}
return m;
}
public function doFloor(floors:Vector<Floor>, i:Int, n:Int, target:Vector<Float>, finalY:Array<Int>, step2Flag:Vector<Bool>)
{
var n2 = n >> 1;
var s = chan[i].mux, floor;
var floor = floors[submapFloor[s]];
if (floor.type == 0) {
throw new ReaderError(INVALID_STREAM);
} else {
var g = floor.floor1;
var lx = 0, ly = finalY[0] * g.floor1Multiplier;
for (q in 1...g.values) {
var j = g.sortedOrder[q];
if (finalY[j] >= 0)
{
var hy = finalY[j] * g.floor1Multiplier;
var hx = g.xlist[j];
VorbisTools.drawLine(target, lx, ly, hx, hy, n2);
lx = hx;
ly = hy;
}
}
if (lx < n2) {
// optimization of: drawLine(target, lx,ly, n,ly, n2);
for (j in lx...n2) {
target[j] *= VorbisTools.INVERSE_DB_TABLE[ly];
}
}
}
}
}
class MappingChannel
{
public var magnitude:Int; // uint8
public var angle:Int; // uint8
public var mux:Int; // uint8
public function new() {
}
}

View File

@ -0,0 +1,29 @@
package kha.audio2.ogg.vorbis.data;
import haxe.io.Input;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
class Mode
{
public var blockflag:Bool; // uint8
public var mapping:Int; // uint8
public var windowtype:Int; // uint16
public var transformtype:Int; // uint16
public function new() {
}
public static function read(decodeState:VorbisDecodeState) {
var m = new Mode();
m.blockflag = (decodeState.readBits(1) != 0);
m.windowtype = decodeState.readBits(16);
m.transformtype = decodeState.readBits(16);
m.mapping = decodeState.readBits(8);
if (m.windowtype != 0) {
throw new ReaderError(INVALID_SETUP);
}
if (m.transformtype != 0) {
throw new ReaderError(INVALID_SETUP);
}
return m;
}
}

View File

@ -0,0 +1,60 @@
package kha.audio2.ogg.vorbis.data;
import haxe.io.Bytes;
import haxe.io.Input;
import kha.audio2.ogg.vorbis.data.ReaderError.ReaderErrorType;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class Page {
public var flag(default, null):Int;
public function new () {
}
public function clone() {
var page = new Page();
page.flag = flag;
return page;
}
// startPage
public function start(decodeState:VorbisDecodeState) {
decodeState.capturePattern();
startWithoutCapturePattern(decodeState);
}
// startPageNoCapturePattern
public function startWithoutCapturePattern(decodeState:VorbisDecodeState) {
var version = decodeState.readByte();
if (version != 0) {
throw new ReaderError(ReaderErrorType.INVALID_STREAM_STRUCTURE_VERSION, "" + version);
}
this.flag = decodeState.readByte();
var loc0 = decodeState.readInt32();
var loc1 = decodeState.readInt32();
// input serial number -- vorbis doesn't interleave, so discard
decodeState.readInt32();
//if (this.serial != get32(f)) throw new ReaderError(ReaderErrorType.incorrectStreamSerialNumber);
// page sequence number
decodeState.readInt32();
// CRC32
decodeState.readInt32();
// pageSegments
decodeState.setup(loc0, loc1);
}
}
class PageFlag {
static public inline var CONTINUED_PACKET = 1;
static public inline var FIRST_PAGE = 2;
static public inline var LAST_PAGE = 4;
}

View File

@ -0,0 +1,18 @@
package kha.audio2.ogg.vorbis.data;
/**
* ...
* @author shohei909
*/
class ProbedPage
{
public var pageStart:Int;
public var pageEnd:Int;
public var afterPreviousPageStart:Int;
public var firstDecodedSample:Null<Int>;
public var lastDecodedSample:Null<Int>;
public function new() {
}
}

View File

@ -0,0 +1,53 @@
package kha.audio2.ogg.vorbis.data;
import haxe.PosInfos;
/**
* ...
* @author shohei909
*/
class ReaderError
{
public var type(default, null):ReaderErrorType;
public var message(default, null):String;
public var posInfos(default, null):PosInfos;
public function new(type:ReaderErrorType, ?message:String = "", ?posInfos:PosInfos) {
this.type = type;
this.message = message;
this.posInfos = posInfos;
}
}
enum ReaderErrorType
{
NEED_MORE_DATA; // not a real error
INVALID_API_MIXING; // can't mix API modes
OUTOFMEM; // not enough memory
FEATURE_NOT_SUPPORTED; // uses floor 0
TOO_MANY_CHANNELS; // STB_VORBIS_MAX_CHANNELS is too small
FILE_OPEN_FAILURE; // fopen() failed
SEEK_WITHOUT_LENGTH; // can't seek in unknown-length file
UNEXPECTED_EOF; // file is truncated?
SEEK_INVALID; // seek past EOF
// decoding errors (corrupt/invalid input) -- you probably
// don't care about the exact details of these
// vorbis errors:
INVALID_SETUP;
INVALID_STREAM;
// ogg errors:
MISSING_CAPTURE_PATTERN;
INVALID_STREAM_STRUCTURE_VERSION;
CONTINUED_PACKET_FLAG_INVALID;
INCORRECT_STREAM_SERIAL_NUMBER;
INVALID_FIRST_PAGE;
BAD_PACKET_TYPE;
CANT_FIND_LAST_PAGE;
SEEK_FAILED;
OTHER;
}

View File

@ -0,0 +1,298 @@
package kha.audio2.ogg.vorbis.data;
import haxe.ds.Vector;
import haxe.io.Input;
import kha.audio2.ogg.vorbis.VorbisDecodeState;
/**
* ...
* @author shohei909
*/
class Residue
{
public var begin(default, null):UInt; // uint32
public var end(default, null):UInt; // uint32
public var partSize(default, null):UInt; // uint32
public var classifications(default, null):Int; // uint8
public var classbook(default, null):Int; // uint8
public var classdata(default, null):Vector<Vector<Int>>; //uint8 **
public var residueBooks(default, null):Vector<Vector<Int>>; //int16 (*)[8]
public var type(default, null):Int;
public function new() {
}
public static function read(decodeState:VorbisDecodeState, codebooks:Vector<Codebook>):Residue
{
var r = new Residue();
r.type = decodeState.readBits(16);
if (r.type > 2) {
throw new ReaderError(INVALID_SETUP);
}
var residueCascade = new Vector<Int>(64);
r.begin = decodeState.readBits(24);
r.end = decodeState.readBits(24);
r.partSize = decodeState.readBits(24)+1;
var classifications = r.classifications = decodeState.readBits(6)+1;
r.classbook = decodeState.readBits(8);
for (j in 0...r.classifications) {
var highBits = 0;
var lowBits = decodeState.readBits(3);
if (decodeState.readBits(1) != 0){
highBits = decodeState.readBits(5);
}
residueCascade[j] = highBits * 8 + lowBits;
}
r.residueBooks = new Vector(r.classifications);
for (j in 0...r.classifications) {
r.residueBooks[j] = new Vector(8);
for (k in 0...8) {
if (residueCascade[j] & (1 << k) != 0) {
r.residueBooks[j][k] = decodeState.readBits(8);
if (r.residueBooks[j][k] >= codebooks.length) {
throw new ReaderError(INVALID_SETUP);
}
} else {
r.residueBooks[j][k] = -1;
}
}
}
// precompute the classifications[] array to avoid inner-loop mod/divide
// call it 'classdata' since we already have classifications
var el = codebooks[r.classbook].entries;
var classwords = codebooks[r.classbook].dimensions;
r.classdata = new Vector(el);
for (j in 0...el) {
var temp = j;
var k = classwords;
var cd = r.classdata[j] = new Vector(classwords);
while (--k >= 0) {
cd[k] = temp % classifications;
temp = Std.int(temp / classifications);
}
}
return r;
}
public function decode(decodeState:VorbisDecodeState, header:Header, residueBuffers:Vector<Vector<Float>>, ch:Int, n:Int, doNotDecode:Vector<Bool>, channelBuffers:Vector<Vector<Float>>)
{
// STB_VORBIS_DIVIDES_IN_RESIDUE = true
var codebooks = header.codebooks;
var classwords = codebooks[classbook].dimensions;
var nRead = end - begin;
var partSize = this.partSize;
var partRead = Std.int(nRead / partSize);
var classifications = new Vector<Int>(header.channel * partRead + 1); // + 1 is a hack for a possible crash in line 268 with some ogg files
VorbisTools.stbProf(2);
for (i in 0...ch) {
if (!doNotDecode[i]) {
var buffer = residueBuffers[i];
for (j in 0...buffer.length) {
buffer[j] = 0;
}
}
}
if (type == 2 && ch != 1) {
for (j in 0...ch) {
if (!doNotDecode[j]) {
break;
} else if (j == ch - 1) {
return;
}
}
VorbisTools.stbProf(3);
for (pass in 0...8) {
var pcount = 0, classSet = 0;
if (ch == 2) {
VorbisTools.stbProf(13);
while (pcount < partRead) {
var z = begin + pcount * partSize;
var cInter = (z & 1);
var pInter = z >> 1;
if (pass == 0) {
var c:Codebook = codebooks[classbook];
var q = decodeState.decode(c);
if (q == VorbisTools.EOP) {
return;
}
var i = classwords;
while (--i >= 0) {
classifications[i + pcount] = q % this.classifications;
q = Std.int(q / this.classifications);
}
}
VorbisTools.stbProf(5);
for (i in 0...classwords) {
if (pcount >= partRead) {
break;
}
var z = begin + pcount*partSize;
var c = classifications[pcount];
var b = residueBooks[c][pass];
if (b >= 0) {
var book = codebooks[b];
VorbisTools.stbProf(20); // accounts for X time
var result = book.decodeDeinterleaveRepeat(decodeState, residueBuffers, ch, cInter, pInter, n, partSize);
if (result == null) {
return;
} else {
cInter = result.cInter;
pInter = result.pInter;
}
VorbisTools.stbProf(7);
} else {
z += partSize;
cInter = z & 1;
pInter = z >> 1;
}
++pcount;
}
VorbisTools.stbProf(8);
}
} else if (ch == 1) {
while (pcount < partRead) {
var z = begin + pcount*partSize;
var cInter = 0;
var pInter = z;
if (pass == 0) {
var c:Codebook = codebooks[classbook];
var q = decodeState.decode(c);
if (q == VorbisTools.EOP) return;
var i = classwords;
while (--i >= 0) {
classifications[i + pcount] = q % this.classifications;
q = Std.int(q / this.classifications);
}
}
for (i in 0...classwords) {
if (pcount >= partRead) {
break;
}
var z = begin + pcount * partSize;
var b = residueBooks[classifications[pcount]][pass];
if (b >= 0) {
var book:Codebook = codebooks[b];
VorbisTools.stbProf(22);
var result = book.decodeDeinterleaveRepeat(decodeState, residueBuffers, ch, cInter, pInter, n, partSize);
if (result == null) {
return;
} else {
cInter = result.cInter;
pInter = result.pInter;
}
VorbisTools.stbProf(3);
} else {
z += partSize;
cInter = 0;
pInter = z;
}
++pcount;
}
}
} else {
while (pcount < partRead) {
var z = begin + pcount * partSize;
var cInter = z % ch;
var pInter = Std.int(z / ch);
if (pass == 0) {
var c:Codebook = codebooks[classbook];
var q = decodeState.decode(c);
if (q == VorbisTools.EOP) {
return;
}
var i = classwords;
while (--i >= 0) {
classifications[i+pcount] = q % this.classifications;
q = Std.int(q / this.classifications);
}
}
for (i in 0...classwords) {
if (pcount >= partRead) {
break;
}
var z = begin + pcount * partSize;
var b = residueBooks[classifications[pcount]][pass];
if (b >= 0) {
var book = codebooks[b];
VorbisTools.stbProf(22);
var result = book.decodeDeinterleaveRepeat(decodeState, residueBuffers, ch, cInter, pInter, n, partSize);
if (result == null) {
return;
} else {
cInter = result.cInter;
pInter = result.pInter;
}
VorbisTools.stbProf(3);
} else {
z += partSize;
cInter = z % ch;
pInter = Std.int(z / ch);
}
++pcount;
}
}
}
}
return;
}
VorbisTools.stbProf(9);
for (pass in 0...8) {
var pcount = 0;
var classSet = 0;
while (pcount < partRead) {
if (pass == 0) {
for (j in 0...ch) {
if (!doNotDecode[j]) {
var c:Codebook = codebooks[classbook];
var temp = decodeState.decode(c);
if (temp == VorbisTools.EOP) {
return;
}
var i = classwords;
while (--i >= 0) {
classifications[j * partRead + i + pcount] = temp % this.classifications;
temp = Std.int(temp / this.classifications);
}
}
}
}
for (i in 0...classwords) {
if (pcount >= partRead) {
break;
}
for (j in 0...ch) {
if (!doNotDecode[j]) {
var c = classifications[j * partRead + pcount];
var b = residueBooks[c][pass];
if (b >= 0) {
var target = residueBuffers[j];
var offset = begin + pcount * partSize;
var n = partSize;
var book = codebooks[b];
if (!book.residueDecode(decodeState, target, offset, n, type)) {
return;
}
}
}
}
++pcount;
}
}
}
}
}

View File

@ -0,0 +1,15 @@
package kha.audio2.ogg.vorbis.data;
/**
* ...
* @author shohei909
*/
class Setting
{
static public inline var MAX_CHANNELS = 16;
static public inline var PUSHDATA_CRC_COUNT = 4;
static public inline var FAST_HUFFMAN_LENGTH = 10;
static public inline var FAST_HUFFMAN_TABLE_SIZE = (1 << FAST_HUFFMAN_LENGTH);
static public inline var FAST_HUFFMAN_TABLE_MASK = FAST_HUFFMAN_TABLE_SIZE - 1;
}

View File

@ -0,0 +1,8 @@
package kha.capture;
import kha.audio2.Buffer;
extern class AudioCapture {
public static var audioCallback: Int->Buffer->Void;
public static function init(initialized: Void->Void, error: Void->Void): Void;
}

View File

@ -0,0 +1,5 @@
package kha.capture;
extern class VideoCapture {
public static function init(initialized: kha.Video->Void, error: Void->Void): Void;
}

View File

@ -0,0 +1,7 @@
package kha.compute;
enum abstract Access(Int) to Int {
var Read = 0;
var Write = 1;
var ReadWrite = 2;
}

View File

@ -0,0 +1,41 @@
package kha.compute;
import kha.arrays.Float32Array;
import kha.Image;
import kha.FastFloat;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.graphics4.CubeMap;
import kha.graphics4.TextureAddressing;
import kha.graphics4.TextureFilter;
import kha.graphics4.MipMapFilter;
extern class Compute {
public static function setBool(location: ConstantLocation, value: Bool): Void;
public static function setInt(location: ConstantLocation, value: Int): Void;
public static function setFloat(location: ConstantLocation, value: FastFloat): Void;
public static function setFloat2(location: ConstantLocation, value1: FastFloat, value2: FastFloat): Void;
public static function setFloat3(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void;
public static function setFloat4(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void;
public static function setFloats(location: ConstantLocation, values: Float32Array): Void;
public static function setVector2(location: ConstantLocation, value: FastVector2): Void;
public static function setVector3(location: ConstantLocation, value: FastVector3): Void;
public static function setVector4(location: ConstantLocation, value: FastVector4): Void;
public static function setMatrix(location: ConstantLocation, value: FastMatrix4): Void;
public static function setMatrix3(location: ConstantLocation, value: FastMatrix3): Void;
public static function setBuffer(buffer: ShaderStorageBuffer, index: Int): Void;
public static function setTexture(unit: TextureUnit, texture: Image, access: Access): Void;
public static function setSampledTexture(unit: TextureUnit, texture: Image): Void;
public static function setSampledDepthTexture(unit: TextureUnit, texture: Image): Void;
public static function setSampledCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
public static function setSampledDepthCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
public static function setTextureParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
public static function setTexture3DParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
public static function setShader(shader: Shader): Void;
public static function compute(x: Int, y: Int, z: Int): Void;
}

View File

@ -0,0 +1,3 @@
package kha.compute;
extern class ConstantLocation {}

View File

@ -0,0 +1,10 @@
package kha.compute;
import kha.Blob;
extern class Shader {
public function new(sources: Array<Blob>, files: Array<String>);
public function delete(): Void;
public function getConstantLocation(name: String): ConstantLocation;
public function getTextureUnit(name: String): TextureUnit;
}

View File

@ -0,0 +1,12 @@
package kha.compute;
import kha.graphics4.VertexData;
extern class ShaderStorageBuffer {
public function new(indexCount: Int, type: VertexData);
public function delete(): Void;
public function lock(): Array<Int>;
public function unlock(): Void;
public function set(): Void;
public function count(): Int;
}

View File

@ -0,0 +1,3 @@
package kha.compute;
extern class TextureUnit {}

View File

@ -0,0 +1,115 @@
package kha.deprecated;
import kha.Canvas;
import kha.Image;
import kha.math.FastMatrix3;
class Painter {
var backbuffer: Image;
var opacities: Array<Float>;
public function new(width: Int, height: Int) {
this.backbuffer = Image.createRenderTarget(width, height);
opacities = new Array<Float>();
opacities.push(1);
}
public function drawImage(img: Image, x: Float, y: Float): Void {
backbuffer.g2.drawImage(img, x, y);
}
public function drawImage2(image: Image, sx: Float, sy: Float, sw: Float, sh: Float, dx: Float, dy: Float, dw: Float, dh: Float, angle: Float = 0,
ox: Float = 0, oy: Float = 0): Void {
if (angle != 0) {
backbuffer.g2.pushTransformation(FastMatrix3.translation(ox, oy).multmat(FastMatrix3.rotation(angle)).multmat(FastMatrix3.translation(-ox, -oy)));
backbuffer.g2.drawScaledSubImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
backbuffer.g2.popTransformation();
}
else {
backbuffer.g2.drawScaledSubImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
public function setColor(color: Color): Void {
backbuffer.g2.color = color;
}
public function drawRect(x: Float, y: Float, width: Float, height: Float, strength: Float = 1.0): Void {
backbuffer.g2.drawRect(x, y, width, height, strength);
}
public function fillRect(x: Float, y: Float, width: Float, height: Float): Void {
backbuffer.g2.fillRect(x, y, width, height);
}
public function setFont(font: Font): Void {
backbuffer.g2.font = font;
}
public function drawChars(text: String, offset: Int, length: Int, x: Float, y: Float): Void {
drawString(text.substr(offset, length), x, y);
}
public function drawString(text: String, x: Float, y: Float, scaleX: Float = 1.0, scaleY: Float = 1.0, scaleCenterX: Float = 0.0,
scaleCenterY: Float = 0.0): Void {
if (scaleX != 1 || scaleY != 1) {
backbuffer.g2.pushTransformation(FastMatrix3.translation(scaleCenterX, scaleCenterY)
.multmat(FastMatrix3.scale(scaleX, scaleY))
.multmat(FastMatrix3.translation(-scaleCenterX, -scaleCenterY)));
backbuffer.g2.drawString(text, x, y);
backbuffer.g2.popTransformation();
}
else {
backbuffer.g2.drawString(text, x, y);
}
}
public function drawLine(x1: Float, y1: Float, x2: Float, y2: Float, strength: Float = 1.0): Void {
backbuffer.g2.drawLine(x1, y1, x2, y2, strength);
}
public function drawVideo(video: Video, x: Float, y: Float, width: Float, height: Float): Void {
backbuffer.g2.drawVideo(video, x, y, width, height);
}
public function fillTriangle(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float): Void {
backbuffer.g2.fillTriangle(x1, y1, x2, y2, x3, y3);
}
public function translate(x: Float, y: Float): Void {}
public function clear(): Void {
fillRect(0, 0, System.windowWidth(), System.windowHeight());
}
public function begin(): Void {
backbuffer.g2.begin();
}
public function end(): Void {
backbuffer.g2.end();
}
public var opacity(get, set): Float;
function get_opacity(): Float {
return opacities[opacities.length - 1];
}
function set_opacity(value: Float): Float {
backbuffer.g2.opacity = value;
return opacities[opacities.length - 1] = value;
}
public function pushOpacity(): Void {
opacities.push(opacity);
}
public function popOpacity(): Void {
opacities.pop();
}
public function render(canvas: Canvas): Void {
Scaler.scale(backbuffer, canvas, System.screenRotation);
}
}

View File

@ -0,0 +1,26 @@
package kha.graphics1;
import kha.Color;
/**
* Basic graphical interface.<br>
* Represent old devices with only pixel pushing operations.
*/
interface Graphics {
/**
* Begin the graphic operations.
* You MUST call this.
*/
function begin(): Void;
/**
* Terminate all graphical operations and apply them.
* You MUST call this at the end.
*/
function end(): Void;
/**
* Set the pixel color at a specific position.
*/
function setPixel(x: Int, y: Int, color: Color): Void;
}

View File

@ -0,0 +1,296 @@
package kha.graphics1;
import kha.arrays.Float32Array;
import kha.arrays.Int32Array;
import kha.Blob;
import kha.Color;
import kha.FastFloat;
import kha.Image;
import kha.graphics4.ConstantLocation;
import kha.graphics4.IndexBuffer;
import kha.graphics4.MipMapFilter;
import kha.graphics4.PipelineState;
import kha.graphics4.TextureFilter;
import kha.graphics4.TextureAddressing;
import kha.graphics4.TextureUnit;
import kha.graphics4.VertexBuffer;
import kha.graphics4.VertexData;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.Video;
class Graphics4 implements kha.graphics4.Graphics {
var canvas: Canvas;
var g1: kha.graphics1.Graphics;
var indexBuffer: IndexBuffer;
var vertexBuffer: VertexBuffer;
var pipeline: PipelineState;
public function new(canvas: Canvas) {
this.canvas = canvas;
}
public function begin(additionalRenderTargets: Array<Canvas> = null): Void {
this.g1 = canvas.g1;
g1.begin();
}
public function beginFace(face: Int): Void {}
public function beginEye(eye: Int): Void {}
public function end(): Void {
g1.end();
}
public function vsynced(): Bool {
return true;
}
public function refreshRate(): Int {
return 60;
}
public function clear(?color: Color, ?depth: Float, ?stencil: Int): Void {}
public function viewport(x: Int, y: Int, width: Int, height: Int): Void {}
public function scissor(x: Int, y: Int, width: Int, height: Int): Void {}
public function disableScissor(): Void {}
public function setVertexBuffer(vertexBuffer: VertexBuffer): Void {
this.vertexBuffer = vertexBuffer;
}
public function setVertexBuffers(vertexBuffers: Array<kha.graphics4.VertexBuffer>): Void {}
public function setIndexBuffer(indexBuffer: IndexBuffer): Void {
this.indexBuffer = indexBuffer;
}
public function setTexture(unit: TextureUnit, texture: Image): Void {}
public function setTextureDepth(unit: TextureUnit, texture: Image): Void {}
public function setTextureArray(unit: TextureUnit, texture: Image): Void {}
public function setVideoTexture(unit: TextureUnit, texture: Video): Void {}
public function setImageTexture(unit: TextureUnit, texture: Image): Void {}
public function setTextureParameters(texunit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {}
public function setTexture3DParameters(texunit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {}
public function setTextureCompareMode(texunit: TextureUnit, enabled: Bool): Void {}
public function setCubeMapCompareMode(texunit: TextureUnit, enabled: Bool): Void {}
public function setCubeMap(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {}
public function setCubeMapDepth(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {}
public function renderTargetsInvertedY(): Bool {
return false;
}
public function instancedRenderingAvailable(): Bool {
return false;
}
public function setPipeline(pipeline: PipelineState): Void {
this.pipeline = pipeline;
}
public function setStencilReferenceValue(value: Int): Void {}
public function setBool(location: ConstantLocation, value: Bool): Void {}
public function setInt(location: ConstantLocation, value: Int): Void {}
public function setInt2(location: ConstantLocation, value1: Int, value2: Int): Void {}
public function setInt3(location: ConstantLocation, value1: Int, value2: Int, value3: Int): Void {}
public function setInt4(location: ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void {}
public function setInts(location: ConstantLocation, ints: Int32Array): Void {}
public function setFloat(location: ConstantLocation, value: FastFloat): Void {}
public function setFloat2(location: ConstantLocation, value1: FastFloat, value2: FastFloat): Void {}
public function setFloat3(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void {}
public function setFloat4(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void {}
public function setFloats(location: ConstantLocation, floats: Float32Array): Void {}
public function setFloat4s(location: ConstantLocation, float4s: Float32Array): Void {}
public function setVector2(location: ConstantLocation, value: FastVector2): Void {}
public function setVector3(location: ConstantLocation, value: FastVector3): Void {}
public function setVector4(location: ConstantLocation, value: FastVector4): Void {}
public function setMatrix(location: ConstantLocation, value: FastMatrix4): Void {}
public function setMatrix3(location: ConstantLocation, value: FastMatrix3): Void {}
static inline function min(a: FastFloat, b: FastFloat, c: FastFloat): FastFloat {
var min1 = a < b ? a : b;
return min1 < c ? min1 : c;
}
static inline function max(a: FastFloat, b: FastFloat, c: FastFloat): FastFloat {
var max1 = a > b ? a : b;
return max1 > c ? max1 : c;
}
inline function xtopixel(x: FastFloat): Int {
return Std.int((x + 1) / 2 * canvas.width);
}
inline function ytopixel(y: FastFloat): Int {
return Std.int((y + 1) / 2 * canvas.height);
}
public function drawIndexedVertices(start: Int = 0, count: Int = -1): Void {
#if js
// var vertexShaderSource = "output.gl_Position = new vec4(input.pos.x,input.pos.y,0.5,1.0);";
// var vertexShader = untyped __js__("new Function([\"input\", \"output\", \"vec4\"], vertexShaderSource)");
// var vertexShader = untyped __js__("window[this.pipeline.vertexShader.name]");
var vertexShader = untyped __js__("shader_vert");
// var fragmentShaderSource = "output.gl_FragColor = new vec4(1.0, 0.0, 0.0, 1.0);";
// var fragmentShader = untyped __js__("new Function([\"input\", \"output\", \"vec4\"], fragmentShaderSource)");
// var fragmentShader = untyped __js__("window[this.pipeline.fragmentShader.name]");
var fragmentShader = untyped __js__("shader_frag");
var index = 0;
while (index < indexBuffer._data.length) {
var indices = [
indexBuffer._data[index + 0],
indexBuffer._data[index + 1],
indexBuffer._data[index + 2]
];
var layout = pipeline.inputLayout[0];
var vertexStride = Std.int(layout.byteSize() / 4);
var offsets = [indices[0] * vertexStride, indices[1] * vertexStride, indices[2] * vertexStride];
var vsinputs = new Array<Dynamic>();
for (index in 0...3) {
var vsinput: Dynamic = {};
var vindex = 0;
for (element in layout.elements) {
switch (element.data) {
case VertexData.Float1:
var data1 = vertexBuffer._data.get(offsets[index] + vindex + 0);
untyped vsinput[element.name] = data1;
vindex += 1;
case VertexData.Float2:
var data2 = [
vertexBuffer._data.get(offsets[index] + vindex + 0),
vertexBuffer._data.get(offsets[index] + vindex + 1)
];
untyped vsinput[element.name] = data2;
vindex += 2;
case VertexData.Float3:
var data3 = [
vertexBuffer._data.get(offsets[index] + vindex + 0),
vertexBuffer._data.get(offsets[index] + vindex + 1),
vertexBuffer._data.get(offsets[index] + vindex + 2)
];
untyped vsinput[element.name] = data3;
vindex += 3;
case VertexData.Float4:
var data4 = [
vertexBuffer._data.get(offsets[index] + vindex + 0),
vertexBuffer._data.get(offsets[index] + vindex + 1),
vertexBuffer._data.get(offsets[index] + vindex + 2),
vertexBuffer._data.get(offsets[index] + vindex + 3)
];
untyped vsinput[element.name] = data4;
vindex += 4;
case VertexData.Float4x4:
case Short2Norm:
case Short4Norm:
}
}
vsinputs.push(vsinput);
}
var vsoutputs: Array<Dynamic> = [{}, {}, {}, {}];
for (i in 0...3)
vertexShader(vsinputs[i], vsoutputs[i], vec2, vec3, vec4, mat4);
var positions: Array<Array<FastFloat>> = [vsoutputs[0].gl_Position, vsoutputs[1].gl_Position, vsoutputs[2].gl_Position];
var minx = min(positions[0][0], positions[1][0], positions[2][0]);
var maxx = max(positions[0][0], positions[1][0], positions[2][0]);
var miny = min(positions[0][1], positions[1][1], positions[2][1]);
var maxy = max(positions[0][1], positions[1][1], positions[2][1]);
var minxp = xtopixel(minx);
var maxxp = xtopixel(maxx);
var minyp = ytopixel(miny);
var maxyp = ytopixel(maxy);
for (y in minyp...maxyp)
for (x in minxp...maxxp) {
var bc_screen: FastVector3 = barycentric(xtopixel(positions[0][0]), ytopixel(positions[0][1]), xtopixel(positions[1][0]),
ytopixel(positions[1][1]), xtopixel(positions[2][0]), ytopixel(positions[2][1]), x, y);
if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0)
continue;
var fsoutput: Dynamic = {};
fragmentShader({}, fsoutput, vec2, vec3, vec4, mat4);
var color: Array<FastFloat> = fsoutput.gl_FragColor;
g1.setPixel(x, y, Color.fromFloats(color[2], color[1], color[0], color[3]));
}
index += 3;
}
#end
}
static function vec2(x: FastFloat, y: FastFloat): Array<FastFloat> {
return [x, y];
}
static function vec3(x: FastFloat, y: FastFloat, z: FastFloat): Array<FastFloat> {
return [x, y, z];
}
static function vec4(x: FastFloat, y: FastFloat, z: FastFloat, w: FastFloat): Array<FastFloat> {
return [x, y, z, w];
}
static function mat4(x: FastFloat, y: FastFloat): Array<FastFloat> {
return [x, y];
}
static inline function barycentric(_1x: Int, _1y: Int, _2x: Int, _2y: Int, _3x: Int, _3y: Int, x: Int, y: Int): FastVector3 {
var a = new FastVector3(_3x - _1x, _2x - _1x, _1x - x);
var b = new FastVector3(_3y - _1y, _2y - _1y, _1y - y);
var u: FastVector3 = a.cross(b);
if (Math.abs(u.z) < 1)
return new FastVector3(-1, 1, 1); // degenerate
return new FastVector3(1.0 - (u.x + u.y) / u.z, u.y / u.z, u.x / u.z);
}
public function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1): Void {}
public function flush(): Void {}
public function maxBoundTextures(): Int {
return 16;
}
}

View File

@ -0,0 +1,257 @@
package kha.graphics2;
import kha.Color;
import kha.FastFloat;
import kha.Font;
import kha.graphics4.PipelineState;
import kha.Image;
import kha.math.FastMatrix3;
class Graphics {
public function begin(clear: Bool = true, clearColor: Color = null): Void {}
public function end(): Void {}
public function flush(): Void {}
// scale-filtering
// draw/fillPolygon
public function clear(color: Color = null): Void {}
public function drawImage(img: Image, x: FastFloat, y: FastFloat): Void {
drawSubImage(img, x, y, 0, 0, img.width, img.height);
}
/**
* `sx, sy, sw, sh` arguments is the sub-rectangle of the source `img` image
*/
public function drawSubImage(img: Image, x: FastFloat, y: FastFloat, sx: FastFloat, sy: FastFloat, sw: FastFloat, sh: FastFloat): Void {
drawScaledSubImage(img, sx, sy, sw, sh, x, y, sw, sh);
}
/**
* `dx, dy, dw, dh` arguments is the rectangle to draw into the destination context
*/
public function drawScaledImage(img: Image, dx: FastFloat, dy: FastFloat, dw: FastFloat, dh: FastFloat): Void {
drawScaledSubImage(img, 0, 0, img.width, img.height, dx, dy, dw, dh);
}
/**
* `sx, sy, sw, sh` arguments is the sub-rectangle of the source `img` image
* `dx, dy, dw, dh` arguments is the rectangle to draw into the destination context
*/
public function drawScaledSubImage(img: Image, sx: FastFloat, sy: FastFloat, sw: FastFloat, sh: FastFloat, dx: FastFloat, dy: FastFloat, dw: FastFloat,
dh: FastFloat): Void {}
public function drawRect(x: Float, y: Float, width: Float, height: Float, strength: Float = 1.0): Void {}
public function fillRect(x: Float, y: Float, width: Float, height: Float): Void {}
/**
* Draw a single line of text with the current `color`, `font` and `fontSize` properties.
*
* When drawing into rendertargets, you might have to use a different shader than the default one
* - use the default shader when drawing into a transparent section of your rendertarget
* - use a shader with `alphaBlendSource = BlendOne` when drawing into a non-transparent section of your rendertarget
*/
public function drawString(text: String, x: Float, y: Float): Void {}
/**
* Draw a single line of characters with the current `color`, `font` and `fontSize` properties.
*
* When drawing into rendertargets, you might have to use a different shader than the default one
* - use the default shader when drawing into a transparent section of your rendertarget
* - use a shader with `alphaBlendSource = BlendOne` when drawing into a non-transparent section of your rendertarget
*/
public function drawCharacters(text: Array<Int>, start: Int, length: Int, x: Float, y: Float): Void {}
public function drawLine(x1: Float, y1: Float, x2: Float, y2: Float, strength: Float = 1.0): Void {}
public function drawVideo(video: Video, x: Float, y: Float, width: Float, height: Float): Void {}
public function fillTriangle(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float): Void {}
public var imageScaleQuality(get, set): ImageScaleQuality;
public var mipmapScaleQuality(get, set): ImageScaleQuality;
function get_imageScaleQuality(): ImageScaleQuality {
return ImageScaleQuality.Low;
}
function set_imageScaleQuality(value: ImageScaleQuality): ImageScaleQuality {
return ImageScaleQuality.High;
}
function get_mipmapScaleQuality(): ImageScaleQuality {
return ImageScaleQuality.Low;
}
function set_mipmapScaleQuality(value: ImageScaleQuality): ImageScaleQuality {
return ImageScaleQuality.High;
}
/**
The color value is used for geometric primitives, images, and text. Remember to set it back to white to draw images unaltered.
*/
public var color(get, set): Color;
function get_color(): Color {
return Color.Black;
}
function set_color(color: Color): Color {
return Color.Black;
}
public var font(get, set): Font;
function get_font(): Font {
return null;
}
function set_font(font: Font): Font {
return null;
}
public var fontSize(get, set): Int;
function get_fontSize(): Int {
return myFontSize;
}
function set_fontSize(value: Int): Int {
return myFontSize = value;
}
public static var fontGlyphs: Array<Int> = [for (i in 32...256) i];
// works on the top of the transformation stack
public var transformation(get, set): FastMatrix3;
inline function get_transformation(): FastMatrix3 {
return transformations[transformationIndex];
}
inline function set_transformation(transformation: FastMatrix3): FastMatrix3 {
setTransformation(transformation);
transformations[transformationIndex].setFrom(transformation);
return transformation;
}
public inline function pushTransformation(trans: FastMatrix3): Void {
transformationIndex++;
if (transformationIndex == transformations.length) {
transformations.push(FastMatrix3.identity());
}
transformations[transformationIndex].setFrom(trans);
setTransformation(get_transformation());
}
public function popTransformation(): FastMatrix3 {
transformationIndex--;
if (transformationIndex == -1)
throw "There is no transformation matrix to remove, check your push/popTransformation code";
setTransformation(get_transformation());
return transformations[transformationIndex + 1];
}
public function scale(x: FastFloat, y: FastFloat): Void {
transformation.setFrom(kha.math.FastMatrix3.scale(x, y).multmat(transformation));
}
public function pushScale(x: FastFloat, y: FastFloat): Void {
final mat = FastMatrix3.scale(x, y).multmat(transformation);
pushTransformation(mat);
}
inline function translation(tx: FastFloat, ty: FastFloat): FastMatrix3 {
return FastMatrix3.translation(tx, ty).multmat(transformation);
}
public function translate(tx: FastFloat, ty: FastFloat): Void {
transformation.setFrom(translation(tx, ty));
}
public function pushTranslation(tx: FastFloat, ty: FastFloat): Void {
pushTransformation(translation(tx, ty));
}
inline function rotation(angle: FastFloat, centerx: FastFloat, centery: FastFloat): FastMatrix3 {
return FastMatrix3.translation(centerx, centery)
.multmat(FastMatrix3.rotation(angle))
.multmat(FastMatrix3.translation(-centerx, -centery))
.multmat(transformation);
}
public function rotate(angle: FastFloat, centerx: FastFloat, centery: FastFloat): Void {
transformation.setFrom(rotation(angle, centerx, centery));
}
public function pushRotation(angle: FastFloat, centerx: FastFloat, centery: FastFloat): Void {
pushTransformation(rotation(angle, centerx, centery));
}
public var opacity(get, set): Float; // works on the top of the opacity stack
public function pushOpacity(opacity: Float): Void {
setOpacity(opacity);
opacities.push(opacity);
}
public function popOpacity(): Float {
var ret = opacities.pop();
setOpacity(get_opacity());
return ret;
}
function get_opacity(): Float {
return opacities[opacities.length - 1];
}
function set_opacity(opacity: Float): Float {
setOpacity(opacity);
return opacities[opacities.length - 1] = opacity;
}
public function scissor(x: Int, y: Int, width: Int, height: Int): Void {}
public function disableScissor(): Void {}
#if sys_g4
private var pipe: PipelineState;
public var pipeline(get, set): PipelineState;
private function get_pipeline(): PipelineState {
return pipe;
}
private function set_pipeline(pipeline: PipelineState): PipelineState {
setPipeline(pipeline);
return pipe = pipeline;
}
#end
var transformations: Array<FastMatrix3>;
var transformationIndex: Int;
var opacities: Array<Float>;
var myFontSize: Int;
public function new() {
transformations = [FastMatrix3.identity()];
transformationIndex = 0;
opacities = [1];
myFontSize = 12;
#if sys_g4
pipe = null;
#end
}
function setTransformation(transformation: FastMatrix3): Void {}
function setOpacity(opacity: Float): Void {}
function setPipeline(pipeline: PipelineState): Void {}
}

View File

@ -0,0 +1,40 @@
package kha.graphics2;
import haxe.io.Bytes;
import kha.Canvas;
import kha.Color;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
import kha.Image;
class Graphics1 implements kha.graphics1.Graphics {
var canvas: Canvas;
var texture: Image;
var pixels: Bytes;
public function new(canvas: Canvas) {
this.canvas = canvas;
}
public function begin(): Void {
if (texture == null || (texture.realWidth != canvas.width || texture.realHeight != canvas.height)) {
texture = Image.create(canvas.width, canvas.height, TextureFormat.RGBA32, Usage.ReadableUsage);
}
pixels = texture.lock();
}
public function end(): Void {
texture.unlock();
canvas.g2.begin(false);
canvas.g2.drawImage(texture, 0, 0);
canvas.g2.end();
}
public function setPixel(x: Int, y: Int, color: Color): Void {
#if (kha_html5 || kha_krom)
pixels.setInt32(y * texture.stride + x * 4, Color.fromBytes(color.Bb, color.Gb, color.Rb, color.Ab));
#else
pixels.setInt32(y * texture.stride + x * 4, color);
#end
}
}

View File

@ -0,0 +1,370 @@
package kha.graphics2;
import kha.math.Vector2;
import kha.math.FastVector2;
import kha.graphics2.Graphics;
import kha.graphics2.VerTextAlignment;
import kha.graphics2.HorTextAlignment;
/**
* Static extension functions for Graphics2.
* Usage: "using kha.graphics2.GraphicsExtension;"
*/
class GraphicsExtension {
/**
* Draws a arc.
* @param ccw (optional) Specifies whether the drawing should be counterclockwise.
* @param segments (optional) The amount of lines that should be used to draw the arc.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function drawArc(g2: Graphics, cx: Float, cy: Float, radius: Float, sAngle: Float, eAngle: Float, strength: Float = 1, ccw: Bool = false,
segments: Int = 0): Void {
#if kha_html5
if (kha.SystemImpl.gl == null) {
var g: kha.js.CanvasGraphics = cast g2;
radius -= strength / 2; // reduce radius to fit the line thickness within image width/height
g.drawArc(cx, cy, radius, sAngle, eAngle, strength, ccw);
return;
}
#end
sAngle = sAngle % (Math.PI * 2);
eAngle = eAngle % (Math.PI * 2);
if (ccw) {
if (eAngle > sAngle)
eAngle -= Math.PI * 2;
}
else if (eAngle < sAngle)
eAngle += Math.PI * 2;
radius += strength / 2;
if (segments <= 0)
segments = Math.floor(10 * Math.sqrt(radius));
var theta = (eAngle - sAngle) / segments;
var c = Math.cos(theta);
var s = Math.sin(theta);
var x = Math.cos(sAngle) * radius;
var y = Math.sin(sAngle) * radius;
for (n in 0...segments) {
var px = x + cx;
var py = y + cy;
var t = x;
x = c * x - s * y;
y = c * y + s * t;
drawInnerLine(g2, x + cx, y + cy, px, py, strength);
}
}
/**
* Draws a filled arc.
* @param ccw (optional) Specifies whether the drawing should be counterclockwise.
* @param segments (optional) The amount of lines that should be used to draw the arc.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function fillArc(g2: Graphics, cx: Float, cy: Float, radius: Float, sAngle: Float, eAngle: Float, ccw: Bool = false,
segments: Int = 0): Void {
#if kha_html5
if (kha.SystemImpl.gl == null) {
var g: kha.js.CanvasGraphics = cast g2;
g.fillArc(cx, cy, radius, sAngle, eAngle, ccw);
return;
}
#end
sAngle = sAngle % (Math.PI * 2);
eAngle = eAngle % (Math.PI * 2);
if (ccw) {
if (eAngle > sAngle)
eAngle -= Math.PI * 2;
}
else if (eAngle < sAngle)
eAngle += Math.PI * 2;
if (segments <= 0)
segments = Math.floor(10 * Math.sqrt(radius));
var theta = (eAngle - sAngle) / segments;
var c = Math.cos(theta);
var s = Math.sin(theta);
var x = Math.cos(sAngle) * radius;
var y = Math.sin(sAngle) * radius;
var sx = x + cx;
var sy = y + cy;
for (n in 0...segments) {
var px = x + cx;
var py = y + cy;
var t = x;
x = c * x - s * y;
y = c * y + s * t;
g2.fillTriangle(px, py, x + cx, y + cy, sx, sy);
}
}
/**
* Draws a circle.
* @param segments (optional) The amount of lines that should be used to draw the circle.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function drawCircle(g2: Graphics, cx: Float, cy: Float, radius: Float, strength: Float = 1, segments: Int = 0): Void {
#if kha_html5
if (kha.SystemImpl.gl == null) {
var g: kha.js.CanvasGraphics = cast g2;
radius -= strength / 2; // reduce radius to fit the line thickness within image width/height
g.drawCircle(cx, cy, radius, strength);
return;
}
#end
radius += strength / 2;
if (segments <= 0)
segments = Math.floor(10 * Math.sqrt(radius));
var theta = 2 * Math.PI / segments;
var c = Math.cos(theta);
var s = Math.sin(theta);
var x = radius;
var y = 0.0;
for (n in 0...segments) {
var px = x + cx;
var py = y + cy;
var t = x;
x = c * x - s * y;
y = c * y + s * t;
drawInnerLine(g2, x + cx, y + cy, px, py, strength);
}
}
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
static function drawInnerLine(g2: Graphics, x1: Float, y1: Float, x2: Float, y2: Float, strength: Float): Void {
var side = y2 > y1 ? 1 : 0;
if (y2 == y1)
side = x2 - x1 > 0 ? 1 : 0;
var vec = new FastVector2();
if (y2 == y1)
vec.setFrom(new FastVector2(0, -1));
else
vec.setFrom(new FastVector2(1, -(x2 - x1) / (y2 - y1)));
vec.length = strength;
var p1 = new FastVector2(x1 + side * vec.x, y1 + side * vec.y);
var p2 = new FastVector2(x2 + side * vec.x, y2 + side * vec.y);
var p3 = p1.sub(vec);
var p4 = p2.sub(vec);
g2.fillTriangle(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
g2.fillTriangle(p3.x, p3.y, p2.x, p2.y, p4.x, p4.y);
}
/**
* Draws a filled circle.
* @param segments (optional) The amount of lines that should be used to draw the circle.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function fillCircle(g2: Graphics, cx: Float, cy: Float, radius: Float, segments: Int = 0): Void {
#if kha_html5
if (kha.SystemImpl.gl == null) {
var g: kha.js.CanvasGraphics = cast g2;
g.fillCircle(cx, cy, radius);
return;
}
#end
if (segments <= 0) {
segments = Math.floor(10 * Math.sqrt(radius));
}
var theta = 2 * Math.PI / segments;
var c = Math.cos(theta);
var s = Math.sin(theta);
var x = radius;
var y = 0.0;
for (n in 0...segments) {
var px = x + cx;
var py = y + cy;
var t = x;
x = c * x - s * y;
y = c * y + s * t;
g2.fillTriangle(px, py, x + cx, y + cy, cx, cy);
}
}
/**
* Draws a convex polygon.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function drawPolygon(g2: Graphics, x: Float, y: Float, vertices: Array<Vector2>, strength: Float = 1) {
var iterator = vertices.iterator();
var v0 = iterator.next();
var v1 = v0;
while (iterator.hasNext()) {
var v2 = iterator.next();
g2.drawLine(v1.x + x, v1.y + y, v2.x + x, v2.y + y, strength);
v1 = v2;
}
g2.drawLine(v1.x + x, v1.y + y, v0.x + x, v0.y + y, strength);
}
/**
* Draws a filled convex polygon.
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function fillPolygon(g2: Graphics, x: Float, y: Float, vertices: Array<Vector2>) {
var iterator = vertices.iterator();
if (!iterator.hasNext())
return;
var v0 = iterator.next();
if (!iterator.hasNext())
return;
var v1 = iterator.next();
while (iterator.hasNext()) {
var v2 = iterator.next();
g2.fillTriangle(v0.x + x, v0.y + y, v1.x + x, v1.y + y, v2.x + x, v2.y + y);
v1 = v2;
}
}
/**
* Draws a cubic bezier using 4 pairs of points. If the x and y arrays have a length bigger then 4, the additional
* points will be ignored. With a length smaller of 4 a error will occur, there is no check for this.
* You can construct the curves visually in Inkscape with a path using default nodes.
* Provide x and y in the following order: startPoint, controlPoint1, controlPoint2, endPoint
* Reference: http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function drawCubicBezier(g2: Graphics, x: Array<Float>, y: Array<Float>, segments: Int = 20, strength: Float = 1.0): Void {
var t: Float;
var q0 = calculateCubicBezierPoint(0, x, y);
var q1: Array<Float>;
for (i in 1...(segments + 1)) {
t = i / segments;
q1 = calculateCubicBezierPoint(t, x, y);
g2.drawLine(q0[0], q0[1], q1[0], q1[1], strength);
q0 = q1;
}
}
/**
* Draws multiple cubic beziers joined by the end point. The minimum size is 4 pairs of points (a single curve).
*/
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
public static function drawCubicBezierPath(g2: Graphics, x: Array<Float>, y: Array<Float>, segments: Int = 20, strength: Float = 1.0): Void {
var i = 0;
var t: Float;
var q0: Array<Float> = null;
var q1: Array<Float> = null;
while (i < x.length - 3) {
if (i == 0)
q0 = calculateCubicBezierPoint(0, [x[i], x[i + 1], x[i + 2], x[i + 3]], [y[i], y[i + 1], y[i + 2], y[i + 3]]);
for (j in 1...(segments + 1)) {
t = j / segments;
q1 = calculateCubicBezierPoint(t, [x[i], x[i + 1], x[i + 2], x[i + 3]], [y[i], y[i + 1], y[i + 2], y[i + 3]]);
g2.drawLine(q0[0], q0[1], q1[0], q1[1], strength);
q0 = q1;
}
i += 3;
}
}
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
static function calculateCubicBezierPoint(t: Float, x: Array<Float>, y: Array<Float>): Array<Float> {
var u: Float = 1 - t;
var tt: Float = t * t;
var uu: Float = u * u;
var uuu: Float = uu * u;
var ttt: Float = tt * t;
// first term
var p: Array<Float> = [uuu * x[0], uuu * y[0]];
// second term
p[0] += 3 * uu * t * x[1];
p[1] += 3 * uu * t * y[1];
// third term
p[0] += 3 * u * tt * x[2];
p[1] += 3 * u * tt * y[2];
// fourth term
p[0] += ttt * x[3];
p[1] += ttt * y[3];
return p;
}
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
static public function drawAlignedString(g2: Graphics, text: String, x: Float, y: Float, horAlign: HorTextAlignment, verAlign: VerTextAlignment): Void {
var xoffset = 0.0;
if (horAlign == TextCenter || horAlign == TextRight) {
var width = g2.font.width(g2.fontSize, text);
if (horAlign == TextCenter) {
xoffset = -width * 0.5;
}
else {
xoffset = -width;
}
}
var yoffset = 0.0;
if (verAlign == TextMiddle || verAlign == TextBottom) {
var height = g2.font.height(g2.fontSize);
if (verAlign == TextMiddle) {
yoffset = -height * 0.5;
}
else {
yoffset = -height;
}
}
g2.drawString(text, x + xoffset, y + yoffset);
}
@:deprecated("GraphicsExtension will be removed. If you want to use it, simply copy it into your own project and then remove this message.")
static public function drawAlignedCharacters(g2: Graphics, text: Array<Int>, start: Int, length: Int, x: Float, y: Float, horAlign: HorTextAlignment,
verAlign: VerTextAlignment): Void {
var xoffset = 0.0;
if (horAlign == TextCenter || horAlign == TextRight) {
var width = g2.font.widthOfCharacters(g2.fontSize, text, start, length);
if (horAlign == TextCenter) {
xoffset = -width * 0.5;
}
else {
xoffset = -width;
}
}
var yoffset = 0.0;
if (verAlign == TextMiddle || verAlign == TextBottom) {
var height = g2.font.height(g2.fontSize);
if (verAlign == TextMiddle) {
yoffset = -height * 0.5;
}
else {
yoffset = -height;
}
}
g2.drawCharacters(text, start, length, x + xoffset, y + yoffset);
}
}

View File

@ -0,0 +1,7 @@
package kha.graphics2;
enum abstract HorTextAlignment(Int) {
var TextLeft;
var TextCenter;
var TextRight;
}

View File

@ -0,0 +1,6 @@
package kha.graphics2;
enum abstract ImageScaleQuality(Int) {
var Low; // usually point filter
var High; // usually bilinear filter
}

View File

@ -0,0 +1,7 @@
package kha.graphics2;
enum abstract VerTextAlignment(Int) {
var TextTop;
var TextMiddle;
var TextBottom;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
package kha.graphics4;
enum abstract BlendingFactor(Int) to Int {
var Undefined = 0;
var BlendOne = 1;
var BlendZero = 2;
var SourceAlpha = 3;
var DestinationAlpha = 4;
var InverseSourceAlpha = 5;
var InverseDestinationAlpha = 6;
var SourceColor = 7;
var DestinationColor = 8;
var InverseSourceColor = 9;
var InverseDestinationColor = 10;
}

View File

@ -0,0 +1,9 @@
package kha.graphics4;
enum abstract BlendingOperation(Int) to Int {
var Add = 0;
var Subtract = 1;
var ReverseSubtract = 2;
var Min = 3;
var Max = 4;
}

View File

@ -0,0 +1,12 @@
package kha.graphics4;
enum abstract CompareMode(Int) to Int {
var Always = 0;
var Never = 1;
var Equal = 2;
var NotEqual = 3;
var Less = 4;
var LessEqual = 5;
var Greater = 6;
var GreaterEqual = 7;
}

View File

@ -0,0 +1,3 @@
package kha.graphics4;
interface ConstantLocation {}

View File

@ -0,0 +1,19 @@
package kha.graphics4;
import haxe.io.Bytes;
extern class CubeMap implements Canvas implements Resource {
public static function createRenderTarget(size: Int, format: TextureFormat = TextureFormat.RGBA32,
depthStencil: DepthStencilFormat = NoDepthAndStencil): CubeMap;
public function unload(): Void;
public function lock(level: Int = 0): Bytes;
public function unlock(): Void;
public var width(get, null): Int;
public var height(get, null): Int;
public var g1(get, null): kha.graphics1.Graphics;
public var g2(get, null): kha.graphics2.Graphics;
public var g4(get, null): kha.graphics4.Graphics;
}

View File

@ -0,0 +1,7 @@
package kha.graphics4;
enum abstract CullMode(Int) to Int {
var Clockwise = 0;
var CounterClockwise = 1;
var None = 2;
}

View File

@ -0,0 +1,15 @@
package kha.graphics4;
enum abstract DepthStencilFormat(Int) to Int {
var NoDepthAndStencil = 0;
var DepthOnly = 1;
var DepthAutoStencilAuto = 2;
// This is platform specific, use with care!
var Depth24Stencil8 = 3;
var Depth32Stencil8 = 4;
var Depth16 = 5;
// var StencilOnlyIndex1 = 5;
// var StencilOnlyIndex4 = 6;
// var StencilOnlyIndex8 = 7;
// var StencilOnlyIndex16 = 8;
}

View File

@ -0,0 +1,13 @@
package kha.graphics4;
import kha.Blob;
extern class FragmentShader {
public function new(sources: Array<Blob>, files: Array<String>);
public function delete(): Void;
/**
Beware: This function is not portable.
**/
public static function fromSource(source: String): FragmentShader;
}

View File

@ -0,0 +1,16 @@
package kha.graphics4;
import kha.Blob;
#if cpp
extern class GeometryShader {
public function new(sources: Array<Blob>);
public function delete(): Void;
}
#else
class GeometryShader {
public function new(sources: Array<Blob>) {}
public function delete(): Void {}
}
#end

View File

@ -0,0 +1,77 @@
package kha.graphics4;
import kha.arrays.Float32Array;
import kha.arrays.Int32Array;
import kha.Color;
import kha.FastFloat;
import kha.Image;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.Video;
interface Graphics {
function begin(additionalRenderTargets: Array<Canvas> = null): Void;
function beginFace(face: Int): Void;
function beginEye(eye: Int): Void;
function end(): Void;
function vsynced(): Bool;
function refreshRate(): Int;
function clear(?color: Color, ?depth: Float, ?stencil: Int): Void;
function viewport(x: Int, y: Int, width: Int, height: Int): Void;
function scissor(x: Int, y: Int, width: Int, height: Int): Void;
function disableScissor(): Void;
function setVertexBuffer(vertexBuffer: VertexBuffer): Void;
function setVertexBuffers(vertexBuffers: Array<kha.graphics4.VertexBuffer>): Void;
function setIndexBuffer(indexBuffer: IndexBuffer): Void;
function setTexture(unit: TextureUnit, texture: Image): Void;
function setTextureDepth(unit: TextureUnit, texture: Image): Void;
function setTextureArray(unit: TextureUnit, texture: Image): Void;
function setVideoTexture(unit: TextureUnit, texture: Video): Void;
function setImageTexture(unit: TextureUnit, texture: Image): Void;
function setTextureParameters(texunit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, minificationFilter: TextureFilter,
magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
function setTexture3DParameters(texunit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, wAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
function setTextureCompareMode(texunit: TextureUnit, enabled: Bool): Void;
function setCubeMapCompareMode(texunit: TextureUnit, enabled: Bool): Void;
function setCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
function setCubeMapDepth(unit: TextureUnit, cubeMap: CubeMap): Void;
function maxBoundTextures(): Int;
// function maxTextureSize(): Int;
// function supportsNonPow2Textures(): Bool;
function setStencilReferenceValue(value: Int): Void;
function instancedRenderingAvailable(): Bool;
function setPipeline(pipeline: PipelineState): Void;
function setBool(location: ConstantLocation, value: Bool): Void;
function setInt(location: ConstantLocation, value: Int): Void;
function setInt2(location: ConstantLocation, value1: Int, value2: Int): Void;
function setInt3(location: ConstantLocation, value1: Int, value2: Int, value3: Int): Void;
function setInt4(location: ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void;
function setInts(location: ConstantLocation, ints: Int32Array): Void;
function setFloat(location: ConstantLocation, value: FastFloat): Void;
function setFloat2(location: ConstantLocation, value1: FastFloat, value2: FastFloat): Void;
function setFloat3(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void;
function setFloat4(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void;
function setFloats(location: ConstantLocation, floats: Float32Array): Void;
function setVector2(location: ConstantLocation, value: FastVector2): Void;
function setVector3(location: ConstantLocation, value: FastVector3): Void;
function setVector4(location: ConstantLocation, value: FastVector4): Void;
function setMatrix(location: ConstantLocation, value: FastMatrix4): Void;
function setMatrix3(location: ConstantLocation, value: FastMatrix3): Void;
function drawIndexedVertices(start: Int = 0, count: Int = -1): Void;
function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1): Void;
function flush(): Void;
}

Some files were not shown because too many files have changed in this diff Show More