/*
This file is part of jpcsp.
Jpcsp 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, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.sound;
import org.apache.log4j.Level;
import jpcsp.HLE.Modules;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.IMemoryWriter;
import jpcsp.memory.MemoryReader;
import jpcsp.memory.MemoryWriter;
/**
* @author gid15
*
*/
public class Utils {
/**
* Clamp a mono audio sample to a valid range [-0x8000..0x7FFF].
*
* @param value a signed 32-bit value
* @return 0x7FFF if the input value is larger than 0x7FFF
* -0x8000 if the input value is smaller than -0x8000
* otherwise the input value itself.
*/
public static int clampMono(int value) {
return Math.max(Math.min(value, 0x7FFF), -0x8000);
}
/**
* Mix (i.e. add) 2 mono audio values into a new mono audio value.
* A mono audio value is a signed 16-bit value in the range [-0x8000..0x7FFF].
*
* @param value1 a value in the range [-0x8000..0x7FFF]
* @param value2 a value in the range [-0x8000..0x7FFF]
* @return the sum of value1 and value2, clamped to the range [-0x8000..0x7FFF]
*/
public static int mixMono(int value1, int value2) {
return clampMono(value1 + value2);
}
/**
* Retrieve the right mono value from a stereo sample value.
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param value the stereo sample
* @return the mono value for the right channel (0xRRRR), in the range [-0x8000..0x7FFF]
*/
public static int getRightStereo(int value) {
return value >> 16;
}
/**
* Retrieve the right mono value from a stereo sample value.
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param value the stereo sample
* @return the mono value for the left channel (0xLLLL), in the range [-0x8000..0x7FFF]
*/
public static int getLeftStereo(int value) {
return (short) value;
}
/**
* Build a stereo sample value based on 2 mono values.
* Each mono value is assumed to be in the range [-0x8000..0x7FFF].
* An unexpected is returned if this is not the case.
*
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param leftValue the mono value for the left channel (0xLLLL), in the range [-0x8000..0x7FFF]
* @param rightValue the mono value for the right channel (0xRRRR), in the range [-0x8000..0x7FFF]
* @return the stereo sample value (0xRRRRLLLL)
*/
public static int getStereoWithoutClamp(int leftValue, int rightValue) {
return (rightValue << 16) | (leftValue & 0xFFFF);
}
/**
* Build a stereo sample value based on 2 mono values.
* Each mono value will be clamped to the range [-0x8000..0x7FFF] before processing.
*
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param leftValue the mono value for the left channel (0xLLLL)
* @param rightValue the mono value for the right channel (0xRRRR)
* @return the stereo sample value (0xRRRRLLLL)
*/
public static int getStereo(int leftValue, int rightValue) {
return getStereoWithoutClamp(clampMono(leftValue), clampMono(rightValue));
}
/**
* Multiply a stereo sample value by a volume value.
* The volume value has to be in the range [0..1],
* otherwise an unexpected result is returned.
* Each left and right channel can have different volume values.
*
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param stereoValue the base stereo value
* @param leftVolume the volume value for the left channel value,
* in the range [0..1]
* @param rightVolume the volume value for the right channel value,
* in the range [0..1]
* @return the base stereo value multiplied by the volume values
*/
public static int getStereo(int stereoValue, float leftVolume, float rightVolume) {
return getStereoWithoutClamp((int) (getLeftStereo(stereoValue) * leftVolume),
(int) (getRightStereo(stereoValue) * rightVolume));
}
/**
* Mix (i.e. add) 2 stereo audio samples into a new stereo audio sample.
*
* A stereo sample has the format
* 0xRRRRLLLL
* where 0xRRRR is the signed 16-bit mono value for the right channel
* and 0xLLLL is the signed 16-bit mono value for the left channel.
* Each mono value is in the range [-0x8000..0x7FFF].
*
* @param value1 the first stereo sample
* @param value2 the second stereo sample
* @return the sum of the first and second stereo samples,
* the mono values being clamped to a valid range.
*/
public static int mixStereo(int value1, int value2) {
return getStereoWithoutClamp(mixMono(getLeftStereo(value1), getLeftStereo(value2)),
mixMono(getRightStereo(value1), getRightStereo(value2)));
}
/**
* Mix stereo samples in memory: add one stereo sample stream to another
* stereo sample stream.
*
* @param inAddr the start address of the input stereo sample stream
* @param inOutAddr the start address of the stereo sample being updated
* @param samples the number of stereo samples
*/
public static void mixStereoInMemory(int inAddr, int inOutAddr, int samples) {
int length = samples << 2;
IMemoryReader inReader = MemoryReader.getMemoryReader(inAddr, length, 4);
IMemoryReader inOutReader = MemoryReader.getMemoryReader(inOutAddr, length, 4);
IMemoryWriter inOutWriter = MemoryWriter.getMemoryWriter(inOutAddr, length, 4);
for (int i = 0; i < samples; i++) {
int inStereoValue = inReader.readNext();
if (inStereoValue == 0) {
// InOut unchanged for this sample
inOutReader.skip(1);
inOutWriter.skip(1);
} else {
int inOutStereoValue = inOutReader.readNext();
inOutStereoValue = mixStereo(inStereoValue, inOutStereoValue);
inOutWriter.writeNext(inOutStereoValue);
}
}
inOutWriter.flush();
}
/**
* Mix stereo samples in memory: add one stereo sample stream (multiplied by
* a given volume value) to another stereo sample stream.
*
* @param inAddr the start address of the input stereo sample stream
* @param inOutAddr the start address of the stereo sample being updated
* @param samples the number of stereo samples
* @param inLeftVolume the volume value for the input left channel stream,
* in the range [0..1]
* @param inRightVolume the volume value for the input right channel stream,
* in the range [0..1]
*/
public static void mixStereoInMemory(int inAddr, int inOutAddr, int samples, float inLeftVolume, float inRightVolume) {
if (Math.abs(inLeftVolume) < 0.0001f) {
inLeftVolume = 0.f;
}
if (Math.abs(inRightVolume) < 0.0001f) {
inRightVolume = 0.f;
}
if (inLeftVolume == 0.f && inRightVolume == 0.f) {
// Nothing to do
return;
}
if (inLeftVolume == 1.f && inRightVolume == 1.f) {
// Simple case, without inVolume
mixStereoInMemory(inAddr, inOutAddr, samples);
return;
}
if (inLeftVolume < 0.f || inLeftVolume > 1.f) {
if (Modules.log.isEnabledFor(Level.WARN)) {
Modules.log.warn(String.format("Utils.mixStereoInMemory left volume outside range %f", inLeftVolume));
}
}
if (inRightVolume < 0.f || inRightVolume > 1.f) {
if (Modules.log.isEnabledFor(Level.WARN)) {
Modules.log.warn(String.format("Utils.mixStereoInMemory right volume outside range %f", inRightVolume));
}
}
int length = samples << 2;
IMemoryReader inReader = MemoryReader.getMemoryReader(inAddr, length, 4);
IMemoryReader inOutReader = MemoryReader.getMemoryReader(inOutAddr, length, 4);
IMemoryWriter inOutWriter = MemoryWriter.getMemoryWriter(inOutAddr, length, 4);
for (int i = 0; i < samples; i++) {
int inStereoValue = inReader.readNext();
if (inStereoValue == 0) {
// InOut unchanged for this sample
inOutReader.skip(1);
inOutWriter.skip(1);
} else {
inStereoValue = getStereo(inStereoValue, inLeftVolume, inRightVolume);
int inOutStereoValue = inOutReader.readNext();
inOutStereoValue = mixStereo(inStereoValue, inOutStereoValue);
inOutWriter.writeNext(inOutStereoValue);
}
}
inOutWriter.flush();
}
}