Jump to content

  • Log In with Google      Sign In   
  • Create Account

Fastcall's Development Blog

Selective quote

Posted by , 21 August 2016 - - - - - - · 892 views

Oops, four months just went by in a blink. I hear it gets worse when you get older. Scary.

 

Anyway, here’s a CSS rule to keep the new selective quote modal at the bottom right corner of the screen. I like to select-along while I read, and this pesky modal is always in my way:

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("www.gamedev.net") {

#ddk33_qpopup_popup {
	position: fixed !important;
	left:     unset !important;
	top:      unset !important;
	right:    0px   !important;
	bottom:   0px   !important;
}

}
And a screen shot:
Posted Image

 

That’s all for now.




It’s happening

Posted by , 26 April 2016 - - - - - - · 1,216 views

It’s happening

Oh boy, it’s happening.

http://i.imgur.com/fy3Usda.png (665 KB)

 

Learned how to cycles render. Basic water ‘n’ stuff-- still needs some work.




Doodle

Posted by , 25 April 2016 - - - - - - · 1,013 views

Doodle

Just a doodle.

 

http://i.imgur.com/DeyN9TZ.png (177.8KB)




Block Transfers, Buffers, and Audio

Posted by , 15 April 2016 - - - - - - · 984 views

Waiting for Visual Studio to update, so I thought I’d write something.

 

When working with streams such as file or audio processing, you usually run into the same problem: How to copy the most data without knowing how large the stream of data is. I solved a similar problem at work and in my audio synthesizer. In the case of the synthesizer, I wanted an echo effect that fed back N stored seconds of audio.

 

Here’s how a simple block transfer could look:

// assuming:
HANDLE src;
HANDLE dst;
int    src_size;


// and:
const int block_size = 8 * 1000; // 8 KB
char* block = alloc(block_size);


// then:
int remaining = src_size;
for ( int bytes_read = 0;
      bytes_read = read(src, block, block_size));
      remaining -= bytes_read )
{
    // (process block)

    write(dst, block, bytes_read);
    remaining -= bytes_read;
}
For a block_size of 100 and a src_size of 550, you would make 6 writes of sizes:

 

100 100 100 100 100 50

 

 

The echo effect was a bit different: I have two buffers of known length, but one of the buffers is circular and has a read and write head at two different positions within that buffer.

// assuming:
int    samples = 44100;
int    audio_buffer_len = samples / 100;
float* out_audio = new float[audio_buffer_len];   // 1-channel audio at 44.1KHz, 100ms buffer


// and some state:
struct delay_effect {
    float  delay_amp    = 0.25f;
    int    delay_stride = samples * 2 / 3;     // 667ms or 29400 samples
    int    buffer_size  = delay_stride + 320;  // some extra padding
    float* buffer       = new float[buffer_size];
    int    wdx          = 0;                   // write position within buffer
    int    rdx          = delay_stride;        // read position within buffer

    // the extra padding on buffer is needed to keep the write head from interfering 
    // with the read head and vice-versa

    void apply(float* out_audio, int audio_buffer_len);
};


// then:
void delay_effect::apply(float* out_audio, int audio_buffer_len) {
    float* out_ap = out_audio;

    int samples_remaining = audio_buffer_len;
    while ( samples_remaining ) {
        // find minimum number of samples needed such that:
        // 1. read head does not overflow buffer
        // 2. write head does not overflow buffer
        // 3. audio buffer is not overflowed
        int take = min(
            min(buffer_size - wdx, buffer_size - rdx),
            samples_remaining
        );

        // apply effect to out_audio
        // (SIMD candidate:  snap to 16-byte boundaries and lengths, use float4s instead)
        float* in_bp  = buffer + rdx;
        float* out_bp = buffer + wdx;
        for ( int idx = 0; idx < take; ++idx ) {
            // read from buffer, apply damping, accumulate into output audio,
            // and store the mixed audio into y
            float y = (*out_ap++ += *in_bp++ * damping);

            // feed audio back into buffer
            *out_bp++ = y;
        }

        // advance read and write position, wrapping around buffer length
        wdx = (wdx + take) % buffer_size;
        rdx = (rdx + take) % buffer_size;
        samples_remaining -= take;
    }
}
Stepping through, you might see something like this for an audio buffer sized 50, and an echo buffer sized 190, and a delay of 85 samples:
buf#  loop#  take  rdx  wdx
   1      1    50    0   85

   2      1    50   50  135

   3      1     5  100  185
   3      2    45  105    0

   4      1    40  150   45
   4      2    10    0   85

   5      1    50   10   95

   6      1    45   60  145
   6      2     5  105    0

   7      1    50  110    5
As you can see, the entire audio buffer is covered: sum(`take`) group by `buf#`
And neither rdx+take nor wdx+take extends past the echo buffer’s 190 samples.

 

 

Okay, that’s all for now.
See you around.




Maximized IPBoard Chat

Posted by , 21 February 2016 - - - - - - · 2,418 views

Hi there. It’s been a while. Spent some time today fixing up the stupid IPBoard chat. I somehow managed to monkey-patch IPBoard’s styles to maximize use of screen space:

Before:
Posted Image

After:
Posted Image
Posted Image
Posted Image

The style can be found below and can be used installed using the Stylish addon (Firefox/Chrome). Chrome users must remove the @-moz-document directive and surrounding braces.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document url("http://www.gamedev.net/chat/") {
* { box-sizing: border-box !important; }

body {
	padding: 0 !important;
}

html, body {
	height: 100%;
	width: 100%;
	
	overflow: hidden;
}

#ipbwrapper {
	max-width: initial;
	max-height: initial;
	height: 100%;
	height: 100%;
}

#chatters-online-wrap,
#scrollbar_container,
.ipsLayout_content,
#ipbwrapper > .ipsLayout {
	height: 100%;
}

.ipsLayout_content,
#ipbwrapper > .ipsLayout > .ipsLayout_right {
	height: 100%;
	float: right;
	display: table;
}

.ipsLayout_content > *,
#ipbwrapper > .ipsLayout > .ipsLayout_right > * {
	display: table-row;
}

#chatters-online,
#messages-display {
	position: absolute;
	left: 0px;
	top: 0x;
	width: 100%;
	height: 100%;
}
	
@media (max-width: 960px) {
	.ipsBox_container.ipsLayout_right {
		width: 250px !important;
		float: right !important;
	}
	
	.ipsBox_container.ipsLayout_content {
		overflow: hidden !important;
		height: 100% !important;
		width: initial !important;
	}
}

#chatters-online {
	position: absolute;
	width: 100%;
	height: 100%;
	overflow: scroll;
	
	left: 0px;
	top: 0px;
}

#chatters-online-wrap {
	overflow: hidden;
	position: relative;
	
	left: 0px;
	top: 0px;
}

#chatters-online-wrap + .ipsPad.right {
	float: initial;
	text-align: right;
}
}
Anywho, that’s all for now. See you around.


EDIT:
Quick fixed chatrooms, small screen sizes.




Quick Opaque Pointer Example

Posted by , in Uncategorized, C++ 31 December 2015 - - - - - - · 1,901 views

Hi there.

Someone on the chat today was asking about the pimpl idiom, and I thought I'd write up a quick little example. The purpose of an opaque pointer is to move the the implementation specific dependencies (types, macros, includes, and et. al.) away from the header and into the actual source where its used. Including "Foobar.h" from "Unrelated.cpp" also includes iostream, string, vector, windows.h (namespace polluting!), and the entirety of "expensive third party library". If "Foobar" used an opaque pointer, then "Unrelated.cpp" can use a Foobar, without having to know about "string", "vector", "windows.h", or the third party library.

Let's say, as a contrived example, you wanted to wrap std::vector<std::string>. Such a class might look something like:


// simple_list.h
#include <vector>
#include <string>

class simple_list {
public:
	void add(const std::string& str);
	void dump() const;

private:
	std::vector<std::string> _data;
};

// simple_list.cpp
#include "simple_list.h"

void simple_list::add(const std::string& str) {
	_data.push_back(str);
}
Notice how including "simple_list.h" will also include "vector" and "string".


An opaque pointer will hide the details of its implementation:
// simple_list.h
class simple_list {
public:
	simple_list();
	~simple_list();
	simple_list(const simple_list&) = delete;
	simple_list& operator = ( const simple_list& ) = delete;

public:
	void add(const char* str);
	void dump() const;

private:
	struct impl;
	impl* _impl;
};

// simple_list.cpp
#include "simple_list.h"
#include <vector>
#include <string>

struct simple_list::impl {
	std::vector<std::string> data;
};


simple_list::simple_list() {
	_impl = new impl;
}

simple_list::~simple_list() {
	delete impl;
}

simple_list::add(const char* str) {
	_impl->data->push_back(str);
}
Note how simple_list.h no longer requires the "vector" or "string" headers, and how the implementation structure is well contained in simple_list.cpp. Unrelated.cpp can use a "simple_list" without first knowing about "vector" or "string".


Also note how simple_list now has a dynamic allocation with it. This is one of the main disadvantages of an opaque pointer, but it can be avoided.

This is my favorite approach to the opaque pointer idiom: Just reserve some space for the implementation:
// simple_list.h
#pragma once

class simple_list {
public: // noncopyable
	simple_list();
	~simple_list();

	simple_list(const simple_list&) = delete;
	simple_list& operator= ( const simple_list& ) = delete;

public: // interface
	void add(const char* str);
	void dump() const;

private: // pimpl
	struct internal_impl;

	internal_impl& impl();
	const internal_impl& impl() const;

private: // data
	int _data[6];
};



// simple_list.cpp
#include "simple_list.h"
#include <vector>
#include <iterator>
#include <iostream>
#include <string>

struct simple_list::internal_impl {
	internal_impl() {}
	~internal_impl() {}

	std::vector<std::string> _data;
};



simple_list::simple_list() {
	static_assert(sizeof(internal_impl) < sizeof(_data), "simple_list::_data too small for internal_impl");
	new (_data) internal_impl;
}

simple_list::~simple_list() {
	impl().~internal_impl();
}



void simple_list::add(const char* str) {
	impl()._data.push_back(str);
}

void simple_list::dump() const {
	using namespace std;
	const auto& vec = impl()._data;

	cout << '{';
	if ( vec.size() ) {
		copy(vec.begin(), vec.end()-1, ostream_iterator<string>(cout, ", "));
		cout << vec.back();
	}
	cout << '}';
}



simple_list::internal_impl& simple_list::impl() {
	return *reinterpret_cast<internal_impl*>(_data);
}

const simple_list::internal_impl& simple_list::impl() const {
	return *reinterpret_cast<const internal_impl*>(_data);
}



// main.cpp
#include "simple_list.h"

int main(int, char*[]) {
	simple_list f;

	f.add("the");
	f.add("quick");
	f.add("brown");
	f.add("fox");

	f.dump();
}
The simple_list now reserves 6*sizeof(int) bytes for the implementation, backed by a static_assert on the constructor to make sure the buffer doesn't overrun. Memory alignment and cache lines considered, this might be an acceptable compromise: Trade off the double indirection with a possibility of some wasted space.


Though, the point is moot, because in this contrived example, the vector and all its strings will store its data on the heap anyways.

That's all for now. See you around.




Learning Blender

Posted by , in Graphics 29 November 2015 - - - - - - · 1,306 views

Learning Blender

Hi there! Long time no see.

 

Been busy, but learning Blender. I figured if I knew my way around various GNU programs (GNU screen, less, vim) and their keyboard shortcuts, how bad could Blender be? I've spent the past few days learning blender, and I am loving it! Though, compared to 3dsmax, the object modifier stack is a bit limiting. I originally set out to make a configurable flipper, such that I could adjust the detail as needed; number of sides to each round side of the flipper, bevel depth, and etc. Unfortunately, blender does not have a "select volume operator", nor could I adjust the number of sides of a cylinder after creating it. Oh well, I guess I'll have to make do-- make a template "flipper", which retains as many operators as possible, then make a duplicate, apply operators, and work with the clone.

 

Anyway, here's what I've been working on. The specular on the ground plane is a bit ridiculous.
Posted Image

 

In the long-term future, I'm planning on trying out the blender game engine and perhaps get a pinball game going. Depends on a lot of things.

 

Okay, bye.




Pongout 0.83 WIP

Posted by , in OpenGL, Lua, Moonscript, LÖVE, Pongout 26 September 2015 - - - - - - · 1,643 views
Pongout
Well, it's about time I posted some progress pics of the game I've been working on the past month or so. Posted Image

Pongout is a crossover of breakout and pong, written in moonscript. It originally started as a test to see how Box2D will behave in a breakout game, and then suddenly the rest of this happened. This week was spent bug fixing, polishing, and reworking the third level. Those who have been following my earlier alpha and beta versions in the chat will be delighted to know that the game now runs in borderless fullscreen.

The game currently doesn't have any in-game menus or in-game instructions, so I've included the instructions below.

Download
Platform-independent: pongout_0.83.love (0.11 MB), requires LÖVE
Prepackaged Windows x64: pongout_0.83_win_x64.zip (3.63 MB)


Feedback is appreciated. Posted Image
There is an odd issue on some machines (read: my machines) that causes the game to be extra choppy unless an application that uses a high-performance timer is running in the background. If you run into this issue as well, please let me know.


Controls
Mouse        Controls paddles
Left mouse   Launches balls
left/right   Apply torque
r            Reset
escape       Quit

Debug controls: (forfeits highscore)
]            Hit all bricks once
n            Next level


Objective
The goal of the game is to score as many points as possible by skillfully using the ball and paddle to destroy all bricks on the playing field.


Posted Image
Legend
(A) Ball
(B) Brick
(C​) Paddle
(D) Unbreakable brick
(E) Ghost
(F) Score earned and score earned from global multiplier
(G) Lives remaining
(H) Current score
(I) Global multiplier
(J) High score


Ball
A ball accumulates multiplier by hitting normal bricks, up to 10x multiplier. As it does so, its speed will increase up to 50%. Hitting a paddle will reset the ball's multiplier and speed.

Use torque to get your ball unstuck or skillfully launch your ball back into the playing field. Use with discretion, as each use applies an increased speed penalty.


Paddle
Can redirect balls depending on where on the paddle they hit. Use this to aim the ball in the right direction.


Brick
Hit these for points and multiplier. Unbreakable bricks are worth less and do not increment multiplier.

Once all the breakable bricks are destroyed, the field is cleared and a new level will begin.


Ghost
Occupies breakable bricks. Hitting a brick inhabited by a ghost will grant a small amount of global multiplier and the ghost will attempt to locate to another vacant brick.

If there are no remaining bricks to occupy or if all breakable bricks are occupied with ghosts, then the ghosts will begin to panic and bonus time begins.


Bonus Time
Panicked ghosts will consume their host bricks over a period of time. Destroy them quickly before the level ends to get up to 1500 bonus points each.


Scoring
There are four parts to scoring: Base score, multiplier, flat score, and global multiplier. Base score is affected by multiplier whereas flat score is not, and both are affected by global multiplier:

score += global*(flat + base*multiplier)

Normal brick: 10 base and 1 multiplier
Unbreakable brick: 1 base
Ghost: 1/16% global multiplier and/or between 100 and 1500 flat during bonus time.


Screenshots
Level 1
Posted Image

Level 3 rework
Posted Image


IPBoard Extensions v1.5

Posted by , 13 August 2015 - - - - - - · 898 views

Changelog:
Added new emotes.
Fixed literal backslash escaping.
Changed users command to link users to their profiles.
And various other things since version 1.0 that I've forgotten.

// ==UserScript==
// @name        IPBoard Extensions
// @namespace   fastcall22.com
// @include     http://www.gamedev.net/chat/*
// @version     1.5
// @grant       none
// ==/UserScript==

(function ipb_ext(){
try{
	var Chat = IPBoard.prototype.chat;

	// util
	Chat.ext_post_message = function(content_or_nodes,content_is_html) {
		var post = document.createElement('li');
		['post','chat-myown'].forEach(function(cl){
			post.classList.add(cl);
		});

		if ( typeof content_or_nodes == 'string' ) {
			var content = content_or_nodes;
			var target = content_is_html ? 'innerHTML' : 'textContent';
			post[target] = content;
		} else {
			var nodes = content_or_nodes;
			[].concat(nodes).forEach(function(elem){
				post.appendChild(elem);
			});
		}

		document.getElementById('storage_chatroom')
		.appendChild(post);

		post.scrollIntoView(false);
	};

	Chat.ext_search_users = function(query) {
		var result  = {};
		var q_reg = new RegExp(query||'^');

		for ( var id in Chat.forumIdMap._object ) {
			var name = Chat.nameFormatting._object[id][2];
			if ( q_reg.test(name) )
				result[name] = id;
		}

		return result;
	};

	Chat.ext_user_action = function(action,target_id){
		ipb.chat.sendMessageToChild(
			"server="+serverHost +
			"&path="+serverPath +
			"&room="+roomId +
			"&user="+userId +
			"&access_key=" + accessKey +
			"&action="+action +
			"&against="+target_id
		);
		ipb.chat.lastAction =
			parseInt(new Date().getTime().toString().substring(0, 10));
	};

	// config
	var null_f = function(){};
	Chat.ext_empty_event = {
		stopPropagation: null_f,
		preventDefault: null_f
	};
	Chat.ext_confirm_action = null;
	var make_action = function(action){
		return function(query) {
			if ( !query.trim() )
				return;

			var users = Chat.ext_search_users(query);
			Chat.ext_confirm_action = function() {
				for ( var k in users )
					Chat.ext_user_action(action,users[k]);
			};
			var str = '';
			for ( var k in users ) {
				if ( str )
					str += ', ';
				str += k;
			}
			Chat.ext_post_message("Send /confirm to "+action+": "+str+".  Send /cancel to ignore");
		};
	};
	Chat.ext_commands = {
		kick: make_action('kick'),
		ban: make_action('ban'),
		test: function() {Chat.test = !(Chat.test || false)},
		debug: function() {Chat.debug = !(Chat.debug || false)},
		users: function(query) {
			var users = {};
			var keys = [];
			[].slice.call(document.querySelectorAll('#chatters-online li'))
			.forEach(function(li){
				var a = li.querySelector('a.ipsUserPhotoLink');
				var user_id = li.getAttribute('id').replace(/^user_/,'');
				var user_profile = a.getAttribute('href');
				var user_name = Chat.nameFormatting._object[user_id][2];

				var out_a = document.createElement('a');
				out_a.setAttribute('href',user_profile);
				out_a.textContent = user_name;
				users[user_name] = out_a;
				keys.push(user_name);
			});
			var nodes = [];
			keys.sort().forEach(function(key) {
				if ( nodes.length )
					nodes.push(document.createTextNode(', '));
				nodes.push(users[key]);
			});

			Chat.ext_post_message(nodes);
		},
		confirm: function() {
			if ( !Chat.ext_confirm_action )
				return;
			Chat.ext_confirm_action();
			Chat.ext_confirm_action = null;
		},
		cancel: function() {
			Chat.ext_confirm_action = null;
		},
		mute: function() {
			document.getElementById('sound_toggle').click();
		},
		quit: function(){
			document.getElementById('leave_room').click();
		},
	};
	Chat.ext_escape = {
		'n': '\n',
		't': ' ', 
		'e': '​',
		'\\': '\\',
	};
	Chat.ext_emotes = {
		fear: ':f34r:',
		lenny: '( ͡° ͜ʖ ͡°)',
		wat: 'ಠ__ಠ',
		shrug: '¯\\_(ツ)_/¯',
		whee: 'ᕕ( ᐛ )ᕗ',
		flip: '(ノಠ益ಠ)ノ彡┻━━┻',
		why: 'ლ(ಠ益ಠლ)',
	};
	Chat.ext_bbc = {
		rick: function(param,content) {
			// never gonna give you up
			// never gonna let you down
			// never gonna run around
			// and desert you
		}
	};

	// helpers
	Chat.arg_parse = /\s*(?:(\w+?)(?:="(.+?)"))?/g;
	Chat.bbc_filter = function ext_bbc_filter(_,tag,param,content) {
		var f = Chat.ext_bbc[tag];
		if ( !f )
			return _;

		return f(param,content);
	};

	Chat.ext_filters = [
		/* commands */ [
		/^\/(\w+)\s*(.*)$/,
		function ext_command_filter(_,cmd,argstr) {
			var args = argstr.split(' ');
			var f = Chat.ext_commands[cmd];
			if ( !f )
				return _;
			f.apply(null,args);
			return '';
		}
	], /* emotes */ [
		/:(\w+):/g,
		function ext_emote_filter(_,tag) {
			return Chat.ext_emotes[tag] || _;
		}
	], /* backslash */ [
		/\\(?:(x[0-9a-fA-f]{4})|([a-z\\]))/g,
		function ext_backslash_filter(_,hex,code) {
			if ( hex )
				return '&#'+hex+';';
			return Chat.ext_escape[code] || _;
		}
	], /* BBC w/ content */ [
		/\[(\w+)(?:=(.+))?\](.*?)\[\/\1\]/g,
		Chat.bbc_filter
	], /* self-closing BBC tags */ [
		/\[(\w+)(?:=(.+))?\s*()\/\]/g,
		Chat.bbc_filter
	], /* general */ [
		/\t/g,
		' '
	] ];

	Chat.ext_filter_message = function(str) {
		Chat.ext_filters.forEach(function(f){
			if ( !str.length )
				return;

			if ( Chat.debug )
				console.log(f,str);
			str = String.prototype.replace.apply(str,f);
		});
		return str;
	};

	Chat.ext_send = function ext_send(e) {
		if ( e.preventDefault ) e.preventDefault();
		if ( e.stopPropagation ) e.stopPropagation();

		var message = (document.getElementById('message_textarea').value || '').trim();
		if ( Chat.debug )
			console.log('send',message);
		if ( !message ) 
			return;

		var filtered = Chat.ext_filter_message(message) || null;
		document.getElementById('message_textarea').value = filtered;
		if ( filtered )
			return Chat.ipb_sendChat.call(this,Chat.ext_empty_event);
	};


	Chat.ipb_sendChat = Chat.sendChat;
	Chat.sendChat = Chat.ext_send;
	Chat.ext_rewired = true;

	console.log('okay');
} catch ( ex ) {
	console.log("Error: " + ex);
	console.log((new Error).stack);
}
})();
commands: kick, ban, quit, and users
escaping: e, n, t, e, and \
emots: lenny, wat, shrug, whee, flip, and why



As usual, add this as a greasemonkey script or dump it into your javascript console


FM Synth and Plotting with PowerShell

Posted by , in Aduio, Synth, PowerShell, Terminal 02 August 2015 - - - - - - · 1,056 views
FM Synthesizer, PowerShell
Beep boop, making progress on my FM synthesizer. Had a nasty popping sound with my phase-feedback logic and needed some way to rapidly graph out the logic. Powershell to the rescue:

# plot-sample.ps1
$samples = 1024;
$width = 2048;
$height = 512;

[Reflection.Assembly]::LoadWithPartialName("System.Drawing");
$bmp = new-object Drawing.Bitmap $width, $height;
$g = [Drawing.Graphics]::FromImage($bmp);
$g.SmoothingMode = "AntiAlias";

$penAxis =  new-object Drawing.Pen 0xFF808080, 2;
$penLineA = new-object Drawing.Pen 0xFF0000F0, 2;
$penLineB = new-object Drawing.Pen 0xFFF00000, 2;

$ptsA = new-object Drawing.PointF[] $samples;
$ptsB = new-object Drawing.PointF[] $samples;
0..($samples-1) |% {
    $Q = 4*[Math]::pi/$samples;
    $feedback = 0;
} {
    $ampA = [Math]::sin($_*$Q + $feedback);  # A[t] = sin(t + 1.05*A[t-1])
    $ampB = [Math]::sin($_*$Q);              # B[t] = sin(t)
    $feedback = $ampA * 1.05;

    $x = $bmp.Width * $_/$samples;
    $yA = $bmp.Height * (-$ampA/2.1 + .5);
    $yB = $bmp.Height * (-$ampB/2.1 + .5);
    $ptsA[$_] = new-object Drawing.PointF $x, $yA;
    $ptsB[$_] = new-object Drawing.PointF $x, $yB;
} {
    $g.Clear([Drawing.Color]::White);
    $g.DrawLine($penAxis,0,$bmp.Height/2,$bmp.Width,$bmp.Height/2);
    $g.DrawLines($penLineB,$ptsB);
    $g.DrawLines($penLineA,$ptsA);

    $bmp.Save("$pwd\plot.png");
}
Attached Image
Looks like the logic should be sound. I did find that the phase-feedback wasn't being sin'd in the synthesizer, and that was causing the popping noise. And here's a sample of what it sounds like right now:
fm-sample.ogg (36.4KB)

And here's the instrument used to generate that sound:
/*
unit:
	mul        frequency multiplier
	ofs        additive frequency
	amp        volume
	feed       phase feedback

envelope:
	A          attack rate, in degrees
	D1         decay rate to sustain level, in degrees
	D2         decay rate to silence, in degrees
	R          release rate, in degrees
	S          sustain level

program:
	u          unit to render
	in         phase buffer input slot, 0 for null input
	out        phase buffer output slot, 0 for audio data
*/
instrument i;
int idx = 0; //       mul    ofs    amp   feed      A    D1    D2     R     S
i.units[idx++] = {  2.00f, 0.00f, 0.50f, 1.00f, {80.0, 20.0, 10.0, 45.0, 0.75 }};
i.units[idx++] = {  1.00f, 0.00f, 0.25f, 0.00f, {85.0, 45.0,  5.0, 60.0, 0.50 }};
i.units[idx++] = {  7.00f, 0.33f, 0.12f, 0.00f, {88.5, 60.0,  5.0, 60.0, 0.25 }};
i.units[idx++] = {  1.00f, 0.33f, 0.50f, 0.00f, {87.0, 30.0,  5.0, 60.0, 0.67 }};
i.ct_units = idx;


// program:
//   ┌──┐
//   │╔═▼╗ ╔══╗
//   └╢ 0╟─► 1╟─┐
//    ╚══╝ ╚══╝ │ ╔══╗
//              ├─► 3╟─► out
//         ╔══╗ │ ╚══╝
//         ║ 2╟─┘
//         ╚══╝
idx = 0; //          u  in out
i.program[idx++] = { 0, 0, 2 };
i.program[idx++] = { 1, 2, 1 };
i.program[idx++] = { 2, 0, 1 };
i.program[idx++] = { 3, 1, 0 };
i.ct_program = idx;
Hue.






January 2017 »

S M T W T F S
1234567
891011121314
151617 18 192021
22232425262728
293031    

Recent Entries

Recent Comments

Latest Visitors