mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-21 23:43:14 -04:00
262 lines
5.8 KiB
C++
262 lines
5.8 KiB
C++
|
|
// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/libs/
|
|
|
|
#include "Gb_Apu.h"
|
|
|
|
#include <string.h>
|
|
|
|
/* Copyright (C) 2003-2005 Shay Green. This module is free software; you
|
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
|
General Public License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version. This
|
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
more details. You should have received a copy of the GNU Lesser General
|
|
Public License along with this module; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
#include BLARGG_SOURCE_BEGIN
|
|
|
|
Gb_Apu::Gb_Apu()
|
|
{
|
|
square1.synth = &square_synth;
|
|
square2.synth = &square_synth;
|
|
square1.has_sweep = true;
|
|
wave.synth = &other_synth;
|
|
noise.synth = &other_synth;
|
|
|
|
oscs [0] = &square1;
|
|
oscs [1] = &square2;
|
|
oscs [2] = &wave;
|
|
oscs [3] = &noise;
|
|
|
|
volume( 1.0 );
|
|
reset();
|
|
}
|
|
|
|
Gb_Apu::~Gb_Apu()
|
|
{
|
|
}
|
|
|
|
void Gb_Apu::treble_eq( const blip_eq_t& eq )
|
|
{
|
|
square_synth.treble_eq( eq );
|
|
other_synth.treble_eq( eq );
|
|
}
|
|
|
|
void Gb_Apu::volume( double vol )
|
|
{
|
|
vol *= 0.60 / osc_count;
|
|
square_synth.volume( vol );
|
|
other_synth.volume( vol );
|
|
}
|
|
|
|
void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
|
{
|
|
for ( int i = 0; i < osc_count; i++ )
|
|
osc_output( i, center, left, right );
|
|
}
|
|
|
|
void Gb_Apu::reset()
|
|
{
|
|
next_frame_time = 0;
|
|
last_time = 0;
|
|
frame_count = 0;
|
|
stereo_found = false;
|
|
|
|
square1.reset();
|
|
square2.reset();
|
|
wave.reset();
|
|
noise.reset();
|
|
|
|
memset( regs, 0, sizeof regs );
|
|
}
|
|
|
|
void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
|
{
|
|
require( (unsigned) index < osc_count );
|
|
|
|
Gb_Osc& osc = *oscs [index];
|
|
if ( center && !left && !right )
|
|
{
|
|
// mono
|
|
left = center;
|
|
right = center;
|
|
}
|
|
else
|
|
{
|
|
// must be silenced or stereo
|
|
require( (!left && !right) || (left && right) );
|
|
}
|
|
osc.outputs [1] = right;
|
|
osc.outputs [2] = left;
|
|
osc.outputs [3] = center;
|
|
osc.output = osc.outputs [osc.output_select];
|
|
}
|
|
|
|
void Gb_Apu::run_until( gb_time_t end_time )
|
|
{
|
|
require( end_time >= last_time ); // end_time must not be before previous time
|
|
if ( end_time == last_time )
|
|
return;
|
|
|
|
while ( true )
|
|
{
|
|
gb_time_t time = next_frame_time;
|
|
if ( time > end_time )
|
|
time = end_time;
|
|
|
|
// run oscillators
|
|
for ( int i = 0; i < osc_count; ++i ) {
|
|
Gb_Osc& osc = *oscs [i];
|
|
if ( osc.output ) {
|
|
if ( osc.output != osc.outputs [3] )
|
|
stereo_found = true;
|
|
osc.run( last_time, time );
|
|
}
|
|
}
|
|
last_time = time;
|
|
|
|
if ( time == end_time )
|
|
break;
|
|
|
|
next_frame_time += 4194304 / 256; // 256 Hz
|
|
|
|
// 256 Hz actions
|
|
square1.clock_length();
|
|
square2.clock_length();
|
|
wave.clock_length();
|
|
noise.clock_length();
|
|
|
|
frame_count = (frame_count + 1) & 3;
|
|
if ( frame_count == 0 ) {
|
|
// 64 Hz actions
|
|
square1.clock_envelope();
|
|
square2.clock_envelope();
|
|
noise.clock_envelope();
|
|
}
|
|
|
|
if ( frame_count & 1 )
|
|
square1.clock_sweep(); // 128 Hz action
|
|
}
|
|
}
|
|
|
|
bool Gb_Apu::end_frame( gb_time_t end_time )
|
|
{
|
|
if ( end_time > last_time )
|
|
run_until( end_time );
|
|
|
|
assert( next_frame_time >= end_time );
|
|
next_frame_time -= end_time;
|
|
|
|
assert( last_time >= end_time );
|
|
last_time -= end_time;
|
|
|
|
bool result = stereo_found;
|
|
stereo_found = false;
|
|
return result;
|
|
}
|
|
|
|
void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data )
|
|
{
|
|
require( (unsigned) data < 0x100 );
|
|
|
|
int reg = addr - start_addr;
|
|
if ( (unsigned) reg >= register_count )
|
|
return;
|
|
|
|
run_until( time );
|
|
|
|
regs [reg] = data;
|
|
|
|
if ( addr < 0xff24 )
|
|
{
|
|
// oscillator
|
|
int index = reg / 5;
|
|
oscs [index]->write_register( reg - index * 5, data );
|
|
}
|
|
// added
|
|
else if ( addr == 0xff24 )
|
|
{
|
|
int global_volume = data & 7;
|
|
int old_volume = square1.global_volume;
|
|
if ( old_volume != global_volume )
|
|
{
|
|
int any_enabled = false;
|
|
for ( int i = 0; i < osc_count; i++ )
|
|
{
|
|
Gb_Osc& osc = *oscs [i];
|
|
if ( osc.enabled )
|
|
{
|
|
if ( osc.last_amp )
|
|
{
|
|
int new_amp = osc.last_amp * global_volume / osc.global_volume;
|
|
if ( osc.output )
|
|
square_synth.offset( time, new_amp - osc.last_amp, osc.output );
|
|
osc.last_amp = new_amp;
|
|
}
|
|
any_enabled |= osc.volume;
|
|
}
|
|
osc.global_volume = global_volume;
|
|
}
|
|
|
|
if ( !any_enabled && square1.outputs [3] )
|
|
square_synth.offset( time, (global_volume - old_volume) * 15 * 2, square1.outputs [3] );
|
|
}
|
|
}
|
|
|
|
else if ( addr == 0xff25 || addr == 0xff26 )
|
|
{
|
|
int mask = (regs [0xff26 - start_addr] & 0x80) ? ~0 : 0;
|
|
int flags = regs [0xff25 - start_addr] & mask;
|
|
|
|
// left/right assignments
|
|
for ( int i = 0; i < osc_count; i++ )
|
|
{
|
|
Gb_Osc& osc = *oscs [i];
|
|
osc.enabled &= mask;
|
|
int bits = flags >> i;
|
|
Blip_Buffer* old_output = osc.output;
|
|
osc.output_select = (bits >> 3 & 2) | (bits & 1);
|
|
osc.output = osc.outputs [osc.output_select];
|
|
if ( osc.output != old_output && osc.last_amp )
|
|
{
|
|
if ( old_output )
|
|
square_synth.offset( time, -osc.last_amp, old_output );
|
|
osc.last_amp = 0;
|
|
}
|
|
}
|
|
}
|
|
else if ( addr >= 0xff30 )
|
|
{
|
|
int index = (addr & 0x0f) * 2;
|
|
wave.wave [index] = data >> 4;
|
|
wave.wave [index + 1] = data & 0x0f;
|
|
}
|
|
}
|
|
|
|
int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr )
|
|
{
|
|
// function now takes actual address, i.e. 0xFFXX
|
|
require( start_addr <= addr && addr < end_addr );
|
|
|
|
run_until( time );
|
|
|
|
int data = regs [addr - start_addr];
|
|
|
|
if ( addr == 0xff26 )
|
|
{
|
|
data &= 0xf0;
|
|
for ( int i = 0; i < osc_count; i++ )
|
|
{
|
|
const Gb_Osc& osc = *oscs [i];
|
|
if ( osc.enabled && (osc.length || !osc.length_enabled) )
|
|
data |= 1 << i;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|