Sigvatr

Members
  • Content count

    124
  • Joined

  • Last visited

Community Reputation

203 Neutral

About Sigvatr

  • Rank
    Member
  1. No, they are added/averaged together to return a single output. It appears that the operators run from top to bottom. I'm not sure exactly how this particular synthesizer is working. Either these are modulators that modulate themselves (which I think is unlikely) or they are generating feedback. Yes, I was expecting something like this to be the case. I don't think I want to manually implement each one of those.
  2. G'day mates,   To preface, if you are unfamiliar with frequency modulation synthesis, have a quick skim of this Wikipedia article: https://en.wikipedia.org/wiki/Frequency_modulation_synthesis   I have made this thread because I am trying to solve a difficult programming problem. Forgive me for not hastily getting to the point/my question; there is quite a lot of assumed knowledge of both C++ and audio synthesis in general that will be required to help me with my problem, so I have written a detailed post covering the basics of frequency modulation before delving into the autistic guts of my problem. Even if you are unable to provide me with any assistance, I hope you enjoy reading the thread (because it was fun to write).   Finally and before I begin, if anyone is interested in audio synthesis and video game console sound chips in general, have a look at this fun post I made some time ago on the TIGSource forums: http://forums.tigsource.com/index.php?topic=28653.msg795584#msg795584   Anyway, I am working on a real-time audio synthesis library in C++ styled upon sound chips of the 16-bit video game console era (Sega Genesis/Mega Drive, Neo Geo, etc.). I have a preference for the audio synthesis philosophy and methodology of Yamaha, who are a major producer of sound chips and electronic keyboards, most of which make use of frequency modulation synthesis.   The primary key to understanding frequency modulation synthesis is the difference between carrier operators and modulation operators. An FM operator is in its simplest form an oscillator which in most cases generate a sine wave, although my synthesizer defines 19 different types of wave forms (which is more or less irrelevant in the present context) and implements the ability to receive prerecorded wave data as well. An FM operator can be either a modulator, carrier or both. A modulator is fed into other operators and adjusts their frequency regardless of whether or not the latter is a carrier or another modulator. Carrier operators are not fed into other operators, but output the resulting wave data. Multiple carriers may be blended together to create the final output, usually by simply averaging them together.   This diagram conveys the difference between carrier operators and modulation operators:     FM synthesizers require at least 2 operators, one of which must be a carrier. A single operator (which is inevitably a carrier) cannot realistically be considered as frequency modulated synthesis because it is not modulated by anything (unless it is being modulated by prerecorded wave data instead of another operator, although that is besides the point).   The secondary key to understanding frequency modulation synthesis the concept of 'algorithms'. An FM algorithm defines the way that multiple operators (carriers and modulators) interact with one another. The number of operators that a particular FM synthesizer makes use of (which has in the past been constantly defined depending on the design of the synthesizer or limitations of the hardware) sets an upper limit to the number of possible algorithms. Similarly and historically, the number of algorithms and their respective configurations have been constantly defined.   The audio synthesis library I am developing differs from previous generations of FM synthesizers in that the number of operators, number of algorithms and algorithm configurations are not constantly defined or restricted by hardware limitations (i.e. they are defined by the user).   The majority of video game console FM sound chips make use of 4 operators. Notably, the Yamaha YM2612 sound chip (famously incorporated into the Sega Genesis/Mega Drive) defines 8 algorithms for its 4 operators, which are displayed here:     Although the YM2612 defines only 8 algorithms, many more are possible. There is an upper limit to the number of possible algorithms based on 4 operators, but I have not determined what it is, and that is part of the problem I am trying to solve.   More advanced FM synthesizers (such as electronic keyboards and virtual instruments) often make use of more than 4 operators. The number of possible algorithms to an FM synthesizer increases exponentially with each additional operator. Typically, the designers of these FM synthesizers define a constant number of possible algorithms and fixed algorithm configurations based on a trial and error test case methodology to determine which algorithms output the most interesting and useful audio. That being said, the upper limit of possible algorithm configurations in the case of higher numbers of operators can defy imagination.   To convey exactly how mind-boggling the possibilities are, here is a small subset of predefined algorithms to an electronic keyboard utilizing 6 operators:   Now that I have established the fundamentals of frequency modulation synthesis, we can move on to the specifics of my code. I have yet to determine a method of mapping the possible algorithms based on the user defined number of operators in my synthesizer. I have already attempted to manually define all possible algorithms in the range of 1-5 operators, yet being a mere human being makes me prone to error. There are far too many variables to take into consideration, and with every additional operator I attempt to solve, another dimension is opened that I am not able to comprehend. In the meantime, I have declared a function pointer towards a function that calculates the output of a single frame iteration as so:   f32 (voice::*iterate_func)(f32**,s32,f32); When the user attempts to change the algorithm number (which I have attempted to predefine, yet missed the mark), this function is executed:   void voice::set_algorithm( s32 v ) { BDAPI_MUTEX( mutex ); switch( operator_amount ) { case 1: { algorithm = 0; iterate_func = &voice::iterate_1_0; return; } case 2: { switch( algorithm = v % 2 ) { case 0: iterate_func = &voice::iterate_2_0; return; case 1: iterate_func = &voice::iterate_2_1; return; } } // etc These iteration functions invoke the operators as modulators and carriers based upon their function parameters; an operator iteration that receives the output from another operator is modulated:   f32 voice::iterate_1_0( f32** o, s32 i, f32 p_0 ) { return o[0][i] = operators[0]->iterate( 0.f, p_0 ); } f32 voice::iterate_2_0( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( 0.f, p_0 ); return ( o[0][i] + o[1][i] ) / 2.f; } f32 voice::iterate_2_1( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate(); return o[1][i] = operators[1]->iterate( o[0][i], p_0 ); } // etc Now, when I crank the number of operators into overdrive (5 in this particular case) things quickly become hectic and confusing:   f32 voice::iterate_04_00( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( o[0][i] ); o[2][i] = operators[2]->iterate( o[1][i] ); o[3][i] = operators[3]->iterate( o[2][i] ); return o[4][i] = operators[4]->iterate( o[3][i], p_0 ); } f32 voice::iterate_04_01( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( o[0][i] ); o[2][i] = operators[2]->iterate( o[1][i] ); o[3][i] = operators[3]->iterate( ); return o[4][i] = operators[4]->iterate( ( o[2][i] + o[3][i] ) / 2.f, p_0 ); } f32 voice::iterate_04_02( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( o[0][i] ); o[2][i] = operators[2]->iterate( ); o[3][i] = operators[3]->iterate( o[2][i] ); return o[4][i] = operators[4]->iterate( ( o[1][i] + o[3][i] ) / 2.f, p_0 ); } f32 voice::iterate_04_03( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( o[0][i] ); o[2][i] = operators[2]->iterate( ); o[3][i] = operators[3]->iterate( ); return o[4][i] = operators[4]->iterate( ( o[1][i] + o[2][i] + o[3][i] ) / 3.f, p_0 ); } f32 voice::iterate_04_04( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate(); o[1][i] = operators[1]->iterate(); o[2][i] = operators[2]->iterate(); o[3][i] = operators[3]->iterate(); return o[4][i] = operators[4]->iterate( ( o[0][i] + o[1][i] + o[2][i] + o[3][i] ) / 4.f, p_0 ); } f32 voice::iterate_04_05( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate(); o[1][i] = operators[1]->iterate(); o[2][i] = operators[2]->iterate(); o[3][i] = operators[3]->iterate( ( o[0][i] + o[1][i] + o[2][i] ) / 3.f ); return o[4][i] = operators[4]->iterate( o[3][i], p_0 ); } f32 voice::iterate_04_06( f32** o, s32 i, f32 p_0 ) { o[0][i] = operators[0]->iterate( ); o[1][i] = operators[1]->iterate( ); o[2][i] = operators[2]->iterate( ( o[0][i] + o[1][i] ) / 2.f ); o[3][i] = operators[3]->iterate( o[2][i] ); return o[4][i] = operators[4]->iterate( o[3][i], p_0 ); } // etc My failure is that I am unable to account for all possible algorithm configurations when the number of operators is variable. Manually coding each and every algorithm I can think of up to 5 operators seems impossible, and I don't even want to begin thinking about the implications of 6 operators. However, many advanced FM synthesizers that have existed for decades (electronic keyboards, etc.) have made use of 6 operators and beyond. My aim is to create the most thorough and flexible frequency modulated synthesis system thus far and I feel that there is a way to harness the power of C++ to do so (which has so far eluded me). The code I have presented to you above will most likely be scrapped, but I think it does a sufficient job of conveying the need for some sort of C++ template metaprogramming or perhaps a simpler and more straight forward method. Performance is critical, so I want to minimize overhead as much as possible. For reference, here is the code for a few of the functions specificed above:   // iterate f32 operate::iterate( f32 modulation, f32 pulse ) { BDAPI_MUTEX( mutex ); operator_oscillator->set_frequency( interpolate_frequency() ); return last_frame = operator_oscillator->iterate( powf( 2.f, pulse ) * powf( 2.f, modulation ) * ( 1.f + multiplier ) ) * envelope->iterate() * velocity; } // private interpolate frequency f32 operate::interpolate_frequency() { if( envelope->get_state() != adsr::attack ) { return target_frequency; } else { return math::mix<f32>( previous_frequency, math::clamp<f32>( math::inv_lerp<f32>( last_adsr, envelope->get_last_frame(), envelope->get_attack_level() ), 1.f ), target_frequency ); } } // run raw& voice::run() { BDAPI_MUTEX( mutex ); if( voice_state == idle ) { return *output; } fori( samples ) { if( using_amp ) { if( using_pulse ) { output_data[i] = ( this->*iterate_func )( operator_data, i, lfo_pulse->iterate() ) * 1.f - ( lfo_amp->iterate() + 1.f ) * 0.5f; } else { lfo_pulse->skip(); output_data[i] = ( this->*iterate_func )( operator_data, i, 0.f ) * 1.f - ( lfo_amp->iterate() + 1.f ) * 0.5f; } } else { lfo_amp->skip(); if( using_pulse ) { output_data[i] = ( this->*iterate_func )( operator_data, i, lfo_pulse->iterate() ); } else { lfo_pulse->skip(); output_data[i] = ( this->*iterate_func )( operator_data, i, 0.f ); } } } if( voice_state == state::key_on && duration > 0.f ) { duration -= samples; if( duration <= 0.f ) { fori( operator_amount ) { operators[i]->key_off(); } voice_state = state::key_off; } } if( voice_state == state::key_off ) { bl set_idle = true; fori( operator_amount ) { operators[i]->update_state(); if( operators[i]->get_state() == idle ) { operators[i]->clear(); } else { set_idle = false; } } if( set_idle ) { voice_state = idle; output->clear(); } } return *output; } Any ideas whatsoever would be greatly appreciated. Thank you for your patience.
  3. This is deprecated code from my library (i.e. several years old and simply scrapped), but it might be useful to someone:   state.hpp /* BDAPI - Brain Damage API - Version 0.2.7.0 */ #ifndef BDAPI_UTILITY_STATE_HPP #define BDAPI_UTILITY_STATE_HPP #include "bdapi.hpp" /* includes */ // standard #include <deque> // bdapi #include "utility/container.hpp" // namespaces namespace bdapi { namespace utility { /* state class */ class state { result (*function_initialize)() = NULL; result (*function_resume )() = NULL; result (*function_iterate )() = NULL; result (*function_suspend )() = NULL; result (*function_kill )() = NULL; string name; bl initialized = false; bl active = false; s64 initialize_count = 0; s64 resume_count = 0; s64 iterate_count = 0; s64 suspend_count = 0; s64 kill_count = 0; public: // get inline const string& get_name () const { return name; }; inline const bl& is_initialized () const { return initialized; }; inline const bl& is_active () const { return active; }; inline const s64& get_initialize_count () const { return initialize_count; }; inline const s64& get_resume_count () const { return resume_count; }; inline const s64& get_iterate_count () const { return iterate_count; }; inline const s64& get_suspend_count () const { return suspend_count; }; inline const s64& get_kill_count () const { return kill_count; }; public: // initialization state () {}; state ( string name, result(*function_initialize)(), result(*function_resume)(), result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() ); state ( const state& copy ); ~state () {}; result initialize ( string name, result(*function_initialize)(), result(*function_resume)(), result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() ); public: // core result do_initialize (); result do_resume (); result do_iterate (); result do_suspend (); result do_kill (); }; /* state handler class */ class state_handler { typedef container< state*, std::deque > state_deque; state_deque states; state* current_state = NULL; public: // get inline const state_deque& get_states () const { return states; }; inline state* get_current_state () const { return current_state; }; public: // initialization state_handler () {}; state_handler ( state* initial_state ); state_handler ( const state_handler& copy ); ~state_handler () {}; result initialize ( state* initial_state ); public: // core result do_initialize (); result do_resume (); result do_iterate (); result do_suspend (); result do_kill (); result change_state ( state* new_state ); result end_state ( ); public: // query string get_name ( ) const; string get_name ( s32 i ) const; s64 get_initialize_count ( ) const; s64 get_initialize_count ( s32 i ) const; s64 get_resume_count ( ) const; s64 get_resume_count ( s32 i ) const; s64 get_iterate_count ( ) const; s64 get_iterate_count ( s32 i ) const; s64 get_suspend_count ( ) const; s64 get_suspend_count ( s32 i ) const; s64 get_kill_count ( ) const; s64 get_kill_count ( s32 i ) const; s32 get_size () const; bl is_initialized ( ) const; bl is_initialized ( s32 i ) const; bl is_active ( ) const; bl is_active ( s32 i ) const; bl is_empty () const; }; /* end */ } } #endif state.cpp /* BDAPI - Brain Damage API - Version 0.2.7.0 */ #ifndef BDAPI_UTILITY_STATE_UTILITY_CPP #define BDAPI_UTILITY_STATE_UTILITY_CPP #include "utility/state.hpp" /* includes */ // namespaces namespace bdapi { namespace utility { /* state class */ // constructors state::state( string name, result(*function_initialize)(), result(*function_resume)(), result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() ) { initialize( name, function_initialize, function_resume, function_iterate, function_suspend, function_kill ); } state::state( const state& copy ) { function_initialize = copy.function_initialize; function_resume = copy.function_resume; function_iterate = copy.function_iterate; function_suspend = copy.function_suspend; function_kill = copy.function_kill; name = copy.name; initialized = copy.initialized; active = copy.active; initialize_count = copy.initialize_count; resume_count = copy.resume_count; iterate_count = copy.iterate_count; suspend_count = copy.suspend_count; kill_count = copy.kill_count; } // initializers result state::initialize( string name, result(*function_initialize)(), result(*function_resume)(), result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() ) { this->function_initialize = function_initialize; this->function_resume = function_resume; this->function_iterate = function_iterate; this->function_suspend = function_suspend; this->function_kill = function_kill; this->name = name; return 1; } // do initialize result state::do_initialize() { if( !initialized ) { result initialize_result = (*function_initialize)(); if( !initialize_result ) { BD_WRITE_ERROR( "Failed to initialize state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } else { initialize_count++; initialized = true; return do_resume(); } } BD_WRITE_ERROR( "Failed to initialize state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it has already been initialized", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } // do resume result state::do_resume() { if( !initialized ) { BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it has not been initialized", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } if( !active ) { result resume_result = (*function_resume)(); if( !resume_result ) { BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } else { resume_count++; active = true; return resume_result; } } BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it is active", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } // do iterate result state::do_iterate() { if( !initialized ) { BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it has not been initialized", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } if( !active ) { BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it inactive", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } result iterate_result = (*function_iterate)(); if( !iterate_result ) { BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } else { iterate_count++; return iterate_result; } } // do suspend result state::do_suspend() { if( !initialized ) { BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it has not been initialized", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } if( !active ) { BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it inactive", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } result suspend_result = (*function_suspend)(); if( !suspend_result ) { BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } else { suspend_count++; active = false; return suspend_result; } } // do kill result state::do_kill() { if( !initialized ) { BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it has not been initialized", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } if( !do_suspend() ) { BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] " "~F7as it is active", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } result result_kill = (*function_kill)(); if( !result_kill ) { BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" " "~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]", name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count ); return 0; } else { kill_count++; initialized = false; active = false; return result_kill; } } /* state handler class */ // constructors state_handler::state_handler( state* initial_state ) { initialize( initial_state ); } state_handler::state_handler( const state_handler& copy ) { states = copy.states; current_state = copy.current_state; } // initializers result state_handler::initialize( state* initial_state ) { if( !initial_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } states.insert( initial_state ); current_state = states.get_back(); return 1; } // do initialize result state_handler::do_initialize() { if( !current_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } return current_state->do_initialize(); } // do resume result state_handler::do_resume() { if( !current_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } return current_state->do_resume(); } // do iterate result state_handler::do_iterate() { if( !current_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } return current_state->do_iterate(); } // do suspend result state_handler::do_suspend() { if( !current_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } return current_state->do_suspend(); } // do kill result state_handler::do_kill() { if( !current_state ) { BD_WRITE_ERROR( "Initial state has not been allocated" ); return 0; } return current_state->do_kill(); } // change state result state_handler::change_state( state* new_state ) { if( !new_state ) { BD_WRITE_ERROR( "New state has not been allocated" ); return 0; } if( !is_empty() ) { if( !do_suspend() ) { BD_WRITE_ERROR( "Failed to suspend current state" ); return 0; } for_i( states ) { if( new_state == *i ) { states.insert( new_state ); current_state = states.get_back(); return do_resume(); } } } states.insert( new_state ); current_state = states.get_back(); return do_initialize(); } // end state result state_handler::end_state() { if( is_empty() ) { BD_WRITE_ERROR( "State handler is empty" ); return 0; } if( !do_kill() ) { BD_WRITE_ERROR( "Failed to terminate current state" ); return 0; } states.pop_back(); if( is_empty() ) { current_state = NULL; return 1; } while( !states.get_back()->is_initialized() ) { states.pop_back(); if( is_empty() ) { current_state = NULL; return 1; } } current_state = states.get_back(); return do_resume(); } // get name string state_handler::get_name() const { if( !current_state ) { return ""; } else { return current_state->get_name(); } } string state_handler::get_name( s32 i ) const { if( is_empty() ) { return ""; } else { return states[i]->get_name(); } } // get initialize count s64 state_handler::get_initialize_count() const { if( !current_state ) { return -1; } else { return current_state->get_initialize_count(); } } s64 state_handler::get_initialize_count( s32 i ) const { if( is_empty() ) { return -1; } else { return states[i]->get_initialize_count(); } } // get resume count s64 state_handler::get_resume_count() const { if( !current_state ) { return -1; } else { return current_state->get_resume_count(); } } s64 state_handler::get_resume_count( s32 i ) const { if( is_empty() ) { return -1; } else { return states[i]->get_resume_count(); } } // get iterate count s64 state_handler::get_iterate_count() const { if( !current_state ) { return -1; } else { return current_state->get_iterate_count(); } } s64 state_handler::get_iterate_count( s32 i ) const { if( is_empty() ) { return -1; } else { return states[i]->get_iterate_count(); } } // get suspend count s64 state_handler::get_suspend_count() const { if( !current_state ) { return -1; } else { return current_state->get_suspend_count(); } } s64 state_handler::get_suspend_count( s32 i ) const { if( is_empty() ) { return -1; } else { return states[i]->get_suspend_count(); } } // get kill count s64 state_handler::get_kill_count() const { if( !current_state ) { return -1; } else { return current_state->get_kill_count(); } } s64 state_handler::get_kill_count( s32 i ) const { if( is_empty() ) { return -1; } else { return states[i]->get_kill_count(); } } // get size s32 state_handler::get_size() const { return states.get_size(); } // is initialized bl state_handler::is_initialized() const { if( !current_state ) { return false; } else { return current_state->is_initialized(); } } bl state_handler::is_initialized( s32 i ) const { if( is_empty() ) { return false; } else { return states[i]->is_initialized(); } } // is active bl state_handler::is_active() const { if( !current_state ) { return false; } else { return current_state->is_active(); } } bl state_handler::is_active( s32 i ) const { if( is_empty() ) { return false; } else { return states[i]->is_active(); } } // is empty bl state_handler::is_empty() const { return states.is_empty(); } /* end */ } } #endif I am aware that my younger coding style causes offense to some programmers, but please keep in mind that my framework no longer uses this code at all. Even though this code commits many cardinal sins against coding etiquette, it remains more or less comprehensible (I think).
  4.   Not necessarily. I imagine a main loop that iterates over multiple states could potentially hang in certain circumstances and this could be avoided by associating a thread with each particular state.   My thoughts being said, could you elaborate on what you mean by that?
  5. Threading is important, too.
  6. Generally speaking it is a bad idea because you may want to be making use of multiple states at the same time. Imagine a game where you can drop down a console to read logging info and enter commands while the game is running. Perhaps it could even have a game menu opened at the same time on top of all that other stuff. A good example of this is Valve's Source engine. Team Fortress 2 can have the game running while a console, title screen, server browser and options window are all open at the same time, and that's not the end of it.
  7. Excellent, I love weissbraus.   I discovered that the periodic repetition only worked properly when scaled to powers of two. This meant that I had to compromise a little bit, but I don't think this will really be a problem because the loss of scaling finesse can be solved simply by tinkering with higher depths of perlin noise.   As such, the final code for my function is:   f32 noise( vec2 scale, vec2 offset, f32 depth, f32 strength ) { depth = pow( 2.0, depth ); scale.x = pow( 2.0, floor( scale.x ) ); scale.y = pow( 2.0, floor( scale.y ) ); return perlin( vec3( gl_FragCoord.xy * scale + offset * cell_size * scale, ( seed * depth ) ) / depth, vec3( cell_size * scale, 0.0 ) / depth ) * strength; } I am limited at the moment to using scale ratios to the power of two, ie. 2:1, 4:1, 8:1 etc. but I think this is a fair compromise to the problem at least for the moment. I don't think I will be able to support scaling at arbitrary ratios without writing my own noise generation algorithm or severally modifying webgl-noise's source even more; I'm not a computer scientist or mathematician so I will leave that for another day or person to figure out. Perlin and simplex noise are tried and true and as a game developer I do not think it is my destiny to write replacements for them.  
  8. Damn, you guys suck. If you post some pictures of beer in this thread I will post the solution I discovered.
  9. I am making use of https://github.com/ashima/webgl-noise/wiki as part of a procedural texture generation routine in a GLSL fragment shader, specifically the pnoise() function as part of the fragment shader found here: https://github.com/ashima/webgl-noise/blob/master/src/classicnoise3D.glsl#L39, as follows:   // Classic Perlin noise float cnoise(vec3 P) { vec3 Pi0 = floor(P); // Integer part for indexing vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 Pi0 = mod289(Pi0); Pi1 = mod289(Pi1); vec3 Pf0 = fract(P); // Fractional part for interpolation vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); vec4 iy = vec4(Pi0.yy, Pi1.yy); vec4 iz0 = Pi0.zzzz; vec4 iz1 = Pi1.zzzz; vec4 ixy = permute(permute(ix) + iy); vec4 ixy0 = permute(ixy + iz0); vec4 ixy1 = permute(ixy + iz1); vec4 gx0 = ixy0 * (1.0 / 7.0); vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; gx0 = fract(gx0); vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); vec4 sz0 = step(gz0, vec4(0.0)); gx0 -= sz0 * (step(0.0, gx0) - 0.5); gy0 -= sz0 * (step(0.0, gy0) - 0.5); vec4 gx1 = ixy1 * (1.0 / 7.0); vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; gx1 = fract(gx1); vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); vec4 sz1 = step(gz1, vec4(0.0)); gx1 -= sz1 * (step(0.0, gx1) - 0.5); gy1 -= sz1 * (step(0.0, gy1) - 0.5); vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w; vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w; float n000 = dot(g000, Pf0); float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); float n111 = dot(g111, Pf1); vec3 fade_xyz = fade(Pf0); vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); return 2.2 * n_xyz; }   I am making use of the three-dimensional periodic perlin noise generation function to generate tiling two-dimensional texture data; the perlin noise seed is passed into the third dimension (z) and I can generate a new seed at will.   Since the third dimension of the periodic repetition accepts a noise seed instead of a coordinate on the z-axis, I have slightly modified the first few lines of the pnoise() function to discard the modulus of this third dimension. I replaced this:   vec3 Pi0 = floor(P); // Integer part for indexing vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1   With this:   vec3 Pi0   = mod( floor( P   ), rep );      Pi0.z =      floor( P.z );   vec3 Pi1   = mod( Pi0   + vec3(1.0), rep );      Pi1.z =      Pi0.z +      1.0;   That works just fine.   Anyway, what I am trying to do is scale the noise on the two x and y dimensions. This would be useful for generation textures such as wood panels and so forth; by stretching one of the two dimensions.   I have written a higher level function to accept global uniform variables as part of my own fragment shader that makes use of this modified pnoise() function, as here:   uniform vec2 cell_size; // draw operation width and height uniform s32 seed = 0; f32 noise( vec2 scale, vec2 offset, f32 depth, f32 strength ) { depth = pow( 2.0, depth ); return pnoise( vec3( gl_FragCoord.xy * scale + offset * cell_size * scale, ( seed * depth ) ) / depth, vec3( cell_size, 0.0 ) / depth ) * strength; }   Unfortunately, the scaling (as per vec2 scale parameter) is not working as I have intended. I have taken a screenshot of the scaled function so that you can see it is not tiling properly:     Anybody have any ideas on how to fix the scaling?   Cheers!
  10. Explicitly calling sleep functions is considered bad practice.
  11. I will check this out when I am not hungover. It looks promising, though.
  12. Do you know what the implemented (actually coded) contents of the toString() function are? Did you write this code?
  13. Start off intending on making an open world MMORPG and work from that. Through that process you will find your answer.
  14. Particle Collision

      Many tomes of ancient knowledge have been compiled regarding this diverse subject matter. Writing your own collision detection system will in most cases be inefficient, so see if you can find preexisting third party libraries that can efficiently handle this for you before you excrete hours of strainful programming poop from a height over your codebase. It really is a fun subject matter to jump into as a programmer by pleasure, but before you make any decisions you need to assess how important your priorities in programming are.