2012-12-16 19:40:49 +00:00
|
|
|
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
|
|
|
|
// This program 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 General Public License 2.0 for more details.
|
|
|
|
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
|
|
|
|
// Official git repository and contact information can be found at
|
|
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This is not really hardware, it's a software audio mixer running on the Media Engine.
|
|
|
|
// From the perspective of a PSP app though, it might as well be.
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "../Globals.h"
|
2013-02-04 04:31:46 +00:00
|
|
|
#include "ChunkFile.h"
|
2012-12-16 19:40:49 +00:00
|
|
|
|
|
|
|
enum {
|
|
|
|
PSP_SAS_VOICES_MAX = 32,
|
|
|
|
|
|
|
|
PSP_SAS_PITCH_MIN = 1,
|
|
|
|
PSP_SAS_PITCH_BASE = 0x1000,
|
|
|
|
PSP_SAS_PITCH_MAX = 0x4000,
|
|
|
|
|
|
|
|
PSP_SAS_VOL_MAX = 0x1000,
|
|
|
|
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_LINEAR_INCREASE = 0,
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_LINEAR_DECREASE = 1,
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_LINEAR_BENT = 2,
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_EXPONENT_DECREASE = 3,
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_EXPONENT_INCREASE = 4,
|
|
|
|
PSP_SAS_ADSR_CURVE_MODE_DIRECT = 5,
|
|
|
|
|
|
|
|
PSP_SAS_ADSR_ATTACK = 1,
|
|
|
|
PSP_SAS_ADSR_DECAY = 2,
|
|
|
|
PSP_SAS_ADSR_SUSTAIN = 4,
|
|
|
|
PSP_SAS_ADSR_RELEASE = 8,
|
2012-12-17 17:05:10 +00:00
|
|
|
|
|
|
|
PSP_SAS_ENVELOPE_HEIGHT_MAX = 0x40000000,
|
|
|
|
PSP_SAS_ENVELOPE_FREQ_MAX = 0x7FFFFFFF,
|
2012-12-16 19:40:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct WaveformEffect
|
|
|
|
{
|
|
|
|
int type;
|
|
|
|
int delay;
|
|
|
|
int feedback;
|
|
|
|
int leftVol;
|
|
|
|
int rightVol;
|
|
|
|
int isDryOn;
|
|
|
|
int isWetOn;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum VoiceType {
|
2012-12-17 20:43:31 +00:00
|
|
|
VOICETYPE_OFF,
|
|
|
|
VOICETYPE_VAG, // default
|
|
|
|
VOICETYPE_PCM,
|
|
|
|
VOICETYPE_NOISE,
|
|
|
|
VOICETYPE_ATRAC3,
|
|
|
|
VOICETYPE_TRIWAVE, // are these used? there are functions for them (sceSetTriangularWave)
|
|
|
|
VOICETYPE_PULSEWAVE,
|
2012-12-16 19:40:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// VAG is a Sony ADPCM audio compression format, which goes all the way back to the PSX.
|
|
|
|
// It compresses 28 16-bit samples into a block of 16 bytes.
|
2012-12-26 10:36:47 +00:00
|
|
|
class VagDecoder {
|
2012-12-16 19:40:49 +00:00
|
|
|
public:
|
2012-12-17 20:43:31 +00:00
|
|
|
VagDecoder() : data_(0), read_(0) {}
|
2012-12-28 08:05:54 +00:00
|
|
|
void Start(u32 dataPtr, int vagSize, bool loopEnabled);
|
2012-12-16 19:40:49 +00:00
|
|
|
|
2012-12-17 17:05:10 +00:00
|
|
|
void GetSamples(s16 *outSamples, int numSamples);
|
|
|
|
|
2012-12-28 10:09:44 +00:00
|
|
|
void DecodeBlock(u8 *&readp);
|
2012-12-16 19:40:49 +00:00
|
|
|
bool End() const { return end_; }
|
|
|
|
|
2013-02-04 09:31:02 +00:00
|
|
|
void DoState(PointerWrap &p);
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
private:
|
2013-01-24 23:48:25 +00:00
|
|
|
int samples[28];
|
2012-12-16 19:40:49 +00:00
|
|
|
int curSample;
|
|
|
|
|
2012-12-28 10:09:44 +00:00
|
|
|
u32 data_;
|
|
|
|
u32 read_;
|
2012-12-17 17:05:10 +00:00
|
|
|
int curBlock_;
|
|
|
|
int loopStartBlock_;
|
2012-12-26 08:07:52 +00:00
|
|
|
int numBlocks_;
|
2012-12-16 19:40:49 +00:00
|
|
|
|
|
|
|
// rolling state. start at 0, should probably reset to 0 on loops?
|
2013-01-24 23:48:25 +00:00
|
|
|
int s_1;
|
|
|
|
int s_2;
|
2012-12-16 19:40:49 +00:00
|
|
|
|
2012-12-17 17:05:10 +00:00
|
|
|
bool loopEnabled_;
|
|
|
|
bool loopAtNextBlock_;
|
2012-12-16 19:40:49 +00:00
|
|
|
bool end_;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Max height: 0x40000000 I think
|
|
|
|
class ADSREnvelope
|
|
|
|
{
|
|
|
|
public:
|
2012-12-25 09:08:57 +00:00
|
|
|
ADSREnvelope();
|
2012-12-16 19:40:49 +00:00
|
|
|
void SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2);
|
|
|
|
|
2012-12-17 17:05:10 +00:00
|
|
|
void WalkCurve(int rate, int type);
|
|
|
|
|
|
|
|
void KeyOn();
|
|
|
|
void KeyOff();
|
|
|
|
|
|
|
|
void Step();
|
|
|
|
|
|
|
|
int GetHeight() const {
|
2012-12-25 09:08:57 +00:00
|
|
|
return height_ > PSP_SAS_ENVELOPE_HEIGHT_MAX ? PSP_SAS_ENVELOPE_HEIGHT_MAX : height_;
|
2012-12-17 17:05:10 +00:00
|
|
|
}
|
|
|
|
bool HasEnded() const {
|
|
|
|
return state_ == STATE_OFF;
|
|
|
|
}
|
2012-12-16 19:40:49 +00:00
|
|
|
int attackRate;
|
|
|
|
int decayRate;
|
|
|
|
int sustainRate;
|
|
|
|
int releaseRate;
|
|
|
|
int attackType;
|
|
|
|
int decayType;
|
|
|
|
int sustainType;
|
|
|
|
int sustainLevel;
|
|
|
|
int releaseType;
|
2012-12-17 17:05:10 +00:00
|
|
|
|
2013-02-04 09:31:02 +00:00
|
|
|
void DoState(PointerWrap &p);
|
|
|
|
|
2012-12-17 17:05:10 +00:00
|
|
|
private:
|
|
|
|
enum ADSRState {
|
|
|
|
STATE_ATTACK,
|
|
|
|
STATE_DECAY,
|
|
|
|
STATE_SUSTAIN,
|
|
|
|
STATE_RELEASE,
|
|
|
|
STATE_OFF,
|
|
|
|
};
|
|
|
|
void SetState(ADSRState state);
|
|
|
|
|
|
|
|
ADSRState state_;
|
|
|
|
int steps_;
|
|
|
|
s64 height_; // s64 to avoid having to care about overflow when calculatimg. TODO: this should be fine as s32
|
2012-12-16 19:40:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// A SAS voice.
|
|
|
|
// TODO: Look into pre-decoding the VAG samples on SetVoice instead of decoding them on the fly.
|
|
|
|
// It's not very likely that games encode VAG dynamically.
|
|
|
|
struct SasVoice
|
|
|
|
{
|
2012-12-17 20:43:31 +00:00
|
|
|
SasVoice()
|
2013-02-16 12:02:53 +00:00
|
|
|
: playing(false),
|
|
|
|
paused(false),
|
|
|
|
on(false),
|
2012-12-25 09:08:57 +00:00
|
|
|
type(VOICETYPE_OFF),
|
2012-12-17 20:43:31 +00:00
|
|
|
vagAddr(0),
|
|
|
|
vagSize(0),
|
|
|
|
pcmAddr(0),
|
|
|
|
pcmSize(0),
|
|
|
|
sampleRate(44100),
|
2012-12-26 07:53:19 +00:00
|
|
|
sampleFrac(0),
|
2012-12-17 20:43:31 +00:00
|
|
|
pitch(PSP_SAS_PITCH_BASE),
|
2013-01-31 06:51:57 +00:00
|
|
|
loop(true), // true = ignore VAG loop , false = process VAG loop
|
2012-12-17 20:43:31 +00:00
|
|
|
noiseFreq(0),
|
|
|
|
volumeLeft(0),
|
|
|
|
volumeRight(0),
|
|
|
|
volumeLeftSend(0),
|
2013-02-16 12:02:53 +00:00
|
|
|
volumeRightSend(0),
|
|
|
|
effectLeft(0),
|
|
|
|
effectRight(0) {
|
|
|
|
memset(resampleHist, 0, sizeof(resampleHist));
|
2012-12-25 09:08:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Reset();
|
|
|
|
void KeyOn();
|
|
|
|
void KeyOff();
|
|
|
|
void ChangedParams(bool changedVag);
|
2012-12-17 20:43:31 +00:00
|
|
|
|
2013-02-04 09:31:02 +00:00
|
|
|
void DoState(PointerWrap &p);
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
bool playing;
|
2012-12-17 17:05:10 +00:00
|
|
|
bool paused; // a voice can be playing AND paused. In that case, it won't play.
|
|
|
|
bool on; // key-on, key-off.
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
VoiceType type;
|
|
|
|
|
|
|
|
u32 vagAddr;
|
|
|
|
int vagSize;
|
|
|
|
u32 pcmAddr;
|
|
|
|
int pcmSize;
|
|
|
|
int sampleRate;
|
2012-12-17 17:05:10 +00:00
|
|
|
|
2012-12-26 07:53:19 +00:00
|
|
|
int sampleFrac;
|
2012-12-16 19:40:49 +00:00
|
|
|
int pitch;
|
2012-12-17 17:05:10 +00:00
|
|
|
bool loop;
|
2012-12-16 19:40:49 +00:00
|
|
|
|
|
|
|
int noiseFreq;
|
|
|
|
|
|
|
|
int volumeLeft;
|
|
|
|
int volumeRight;
|
|
|
|
int volumeLeftSend; // volume to "Send" (audio-lingo) to the effects processing engine, like reverb
|
|
|
|
int volumeRightSend;
|
2013-01-27 14:34:14 +00:00
|
|
|
int effectLeft;
|
|
|
|
int effectRight;
|
2012-12-16 20:20:16 +00:00
|
|
|
s16 resampleHist[2];
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
ADSREnvelope envelope;
|
|
|
|
|
|
|
|
VagDecoder vag;
|
|
|
|
};
|
|
|
|
|
|
|
|
class SasInstance
|
|
|
|
{
|
|
|
|
public:
|
2012-12-17 17:05:10 +00:00
|
|
|
SasInstance();
|
|
|
|
~SasInstance();
|
2012-12-16 19:40:49 +00:00
|
|
|
|
2012-12-28 11:54:16 +00:00
|
|
|
void ClearGrainSize();
|
2012-12-17 17:05:10 +00:00
|
|
|
void SetGrainSize(int newGrainSize);
|
2012-12-16 19:40:49 +00:00
|
|
|
int GetGrainSize() const { return grainSize; }
|
|
|
|
|
|
|
|
int maxVoices;
|
|
|
|
int sampleRate;
|
|
|
|
int outputMode;
|
|
|
|
|
|
|
|
int *mixBuffer;
|
2012-12-17 17:05:10 +00:00
|
|
|
int *sendBuffer;
|
|
|
|
s16 *resampleBuffer;
|
2012-12-16 19:40:49 +00:00
|
|
|
|
2013-01-25 17:36:05 +00:00
|
|
|
FILE *audioDump;
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
void Mix(u32 outAddr);
|
|
|
|
|
2012-12-28 08:05:54 +00:00
|
|
|
void DoState(PointerWrap &p);
|
|
|
|
|
2012-12-17 17:05:10 +00:00
|
|
|
SasVoice voices[PSP_SAS_VOICES_MAX];
|
|
|
|
WaveformEffect waveformEffect;
|
|
|
|
|
2012-12-16 19:40:49 +00:00
|
|
|
private:
|
|
|
|
int grainSize;
|
|
|
|
};
|