/* 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.HLE.modules; import static java.lang.Math.min; import java.nio.ByteBuffer; import java.nio.IntBuffer; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.CheckArgument; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.TPointer; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.types.IAction; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.hardware.Audio; import jpcsp.memory.IMemoryReader; import jpcsp.memory.IMemoryWriter; import jpcsp.memory.MemoryReader; import jpcsp.memory.MemoryWriter; import jpcsp.sound.AudioBlockingOutputAction; import jpcsp.sound.SoundChannel; import jpcsp.util.Utilities; import org.apache.log4j.Logger; import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL10; import org.lwjgl.openal.ALC10; import org.lwjgl.openal.ALC11; import org.lwjgl.openal.ALCdevice; public class sceAudio extends HLEModule { public static Logger log = Modules.getLogger("sceAudio"); public byte[] audioData; @Override public void start() { SoundChannel.init(); // The audio driver is capable of handling PCM and VAG (ADPCM) playback, // but it uses the same channels for this processing. // E.g.: Use channels 0 to 4 to playback 4 VAG files or use channels 0 to 2 // to playback raw PCM data. // Note: Currently, working with pspPCMChannels only is enough. pspPCMChannels = new SoundChannel[PSP_AUDIO_CHANNEL_MAX]; for (int channel = 0; channel < pspPCMChannels.length; channel++) { pspPCMChannels[channel] = new SoundChannel(channel); } pspSRC1Channel = new SoundChannel(8); // Use a special channel 8 to handle SRC functions (first channel). pspSRC2Channel = new SoundChannel(9); // Use a special channel 9 to handle SRC functions (second channel). super.start(); } @Override public void stop() { if (inputDevice != null) { ALC11.alcCaptureCloseDevice(inputDevice); inputDevice = null; } inputDeviceInitialized = false; captureBuffer = null; super.stop(); } protected static final int PSP_AUDIO_VOLUME_MAX = 0x8000; protected static final int PSP_AUDIO_CHANNEL_MAX = 8; protected static final int PSP_AUDIO_SAMPLE_MIN = 64; protected static final int PSP_AUDIO_SAMPLE_MAX = 65472; protected static final int PSP_AUDIO_FORMAT_STEREO = 0; protected static final int PSP_AUDIO_FORMAT_MONO = 0x10; protected SoundChannel[] pspPCMChannels; // Two different threads can output audio on the SRC channel // without interfering with each others. protected SoundChannel pspSRC1Channel; protected SoundChannel pspSRC2Channel; protected ALCdevice inputDevice; protected ByteBuffer captureBuffer; protected IntBuffer samplesBuffer; protected boolean inputDeviceInitialized; protected static class AudioBlockingInputAction implements IAction { private int threadId; private int addr; private int samples; private int frequency; public AudioBlockingInputAction(int threadId, int addr, int samples, int frequency) { this.threadId = threadId; this.addr = addr; this.samples = samples; this.frequency = frequency; } @Override public void execute() { Modules.sceAudioModule.hleAudioBlockingInput(threadId, addr, samples, frequency); } } protected static int doAudioOutput(SoundChannel channel, int pvoid_buf) { int ret = -1; if (channel.isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("doAudioOutput(%s, 0x%08X)", channel.toString(), pvoid_buf)); } int bytesPerSample = channel.isFormatStereo() ? 4 : 2; int nbytes = bytesPerSample * channel.getSampleLength(); byte[] data = new byte[nbytes]; IMemoryReader memoryReader = MemoryReader.getMemoryReader(pvoid_buf, nbytes, 2); if (channel.isFormatMono()) { int volume = Audio.getVolume(channel.getLeftVolume()); for (int i = 0; i < nbytes; i += 2) { short sample = (short) memoryReader.readNext(); sample = SoundChannel.adjustSample(sample, volume); SoundChannel.storeSample(sample, data, i); } } else { int leftVolume = Audio.getVolume(channel.getLeftVolume()); int rightVolume = Audio.getVolume(channel.getRightVolume()); for (int i = 0; i < nbytes; i += 4) { short lsample = (short) memoryReader.readNext(); short rsample = (short) memoryReader.readNext(); lsample = SoundChannel.adjustSample(lsample, leftVolume); rsample = SoundChannel.adjustSample(rsample, rightVolume); SoundChannel.storeSample(lsample, data, i); SoundChannel.storeSample(rsample, data, i + 2); } } Modules.sceAudioModule.audioData = data; channel.play(data); ret = channel.getSampleLength(); } else { log.warn("doAudioOutput: channel " + channel.getIndex() + " not reserved"); } return ret; } protected static void blockThreadOutput(SoundChannel channel, int addr, int leftVolume, int rightVolume) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; blockThreadOutput(threadMan.getCurrentThreadID(), channel, addr, leftVolume, rightVolume); threadMan.hleBlockCurrentThread(SceKernelThreadInfo.JPCSP_WAIT_AUDIO); channel.setBusy(true); } protected static void blockThreadOutput(int threadId, SoundChannel channel, int addr, int leftVolume, int rightVolume) { IAction action = new AudioBlockingOutputAction(threadId, channel, addr, leftVolume, rightVolume); int delayMicros = channel.getUnblockOutputDelayMicros(addr == 0); long schedule = Emulator.getClock().microTime() + delayMicros; if (log.isDebugEnabled()) { log.debug(String.format("blockThreadOutput micros=%d, schedule=%d", delayMicros, schedule)); } Emulator.getScheduler().addAction(schedule, action); } public void hleAudioBlockingOutput(int threadId, SoundChannel channel, int addr, int leftVolume, int rightVolume) { if (log.isDebugEnabled()) { log.debug(String.format("hleAudioBlockingOutput %s", channel.toString())); } if (addr == 0) { // If another thread is also sending audio data on this channel, // do not wait for the channel to be drained, unblock the thread now. ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo thread = threadMan.getThreadById(threadId); if (thread != null) { thread.cpuContext._v0 = channel.getSampleLength(); threadMan.hleUnblockThread(threadId); } channel.setBusy(false); } else if (!channel.isOutputBlocking()) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo thread = threadMan.getThreadById(threadId); if (thread != null) { changeChannelVolume(channel, leftVolume, rightVolume); int ret = doAudioOutput(channel, addr); thread.cpuContext._v0 = ret; threadMan.hleUnblockThread(threadId); } channel.setBusy(false); } else { blockThreadOutput(threadId, channel, addr, leftVolume, rightVolume); } } protected static int changeChannelVolume(SoundChannel channel, int leftvol, int rightvol) { int ret = -1; if (channel.isReserved()) { // Negative volume means no change if (leftvol >= 0) { channel.setLeftVolume(leftvol); } if (rightvol >= 0) { channel.setRightVolume(rightvol); } ret = 0; } return ret; } protected int hleAudioGetChannelRestLength(SoundChannel channel) { int len = channel.getRestLength(); // To avoid small "clicks" in the sound, simulate a rest length of 0 // when approaching the end of the buffered samples. // 2048 is an empirical value. if (len > 0 && len <= 2048) { if (log.isDebugEnabled()) { log.debug(String.format("hleAudioGetChannelRestLength truncating rest length %d to 0", len)); } len = 0; } if (log.isDebugEnabled()) { log.debug(String.format("hleAudioGetChannelRestLength(%d) = %d", channel.getIndex(), len)); } return len; } protected SoundChannel getFreeSRCChannel() { if (!pspSRC1Channel.isBusy()) { return pspSRC1Channel; } if (!pspSRC2Channel.isBusy()) { return pspSRC2Channel; } return null; } protected int hleAudioSRCChReserve(int sampleCount, int freq, int format) { if (pspSRC1Channel.isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("hleAudioSRCChReserve returning ERROR_AUDIO_CHANNEL_ALREADY_RESERVED")); } return SceKernelErrors.ERROR_AUDIO_CHANNEL_ALREADY_RESERVED; } // Reserve both SRC channels pspSRC1Channel.setSampleRate(freq); pspSRC1Channel.setReserved(true); pspSRC1Channel.setSampleLength(sampleCount); pspSRC1Channel.setFormat(format); pspSRC2Channel.setSampleRate(freq); pspSRC2Channel.setReserved(true); pspSRC2Channel.setSampleLength(sampleCount); pspSRC2Channel.setFormat(format); return 0; } public int checkChannel(int channel) { if (channel < 0 || channel >= PSP_AUDIO_CHANNEL_MAX) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid channel number %d", channel)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_INVALID_CHANNEL); } return channel; } public int checkReservedChannel(int channel) { channel = checkChannel(channel); if (!pspPCMChannels[channel].isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("Channel not reserved %d", channel)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_CHANNEL_NOT_INIT); } return channel; } public int checkSampleCount(int sampleCount) { if (sampleCount <= 0 || sampleCount > 0xFFC0 || (sampleCount & 0x3F) != 0) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid sampleCount 0x%X", sampleCount)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED); } return sampleCount; } public int checkSmallSampleCount(int sampleCount) { if (sampleCount < 17 || sampleCount >= 4095 + 17) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid small sampleCount 0x%X", sampleCount)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED); } return sampleCount; } public int checkReserveSampleCount(int sampleCount) { if (sampleCount < 17 || sampleCount >= 4095 + 17) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid reserve sampleCount 0x%X", sampleCount)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_SIZE); } return sampleCount; } public int checkVolume(int volume) { // Negative volume is allowed if (volume > 0xFFFF) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid volume 0x%X", volume)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_INVALID_VOLUME); } return volume; } public int checkVolume2(int volume) { if (volume < 0 || volume > 0xFFFFF) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid volume 0x%X", volume)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_INVALID_VOLUME); } return volume; } public int checkFormat(int format) { if (format != PSP_AUDIO_FORMAT_STEREO && format != PSP_AUDIO_FORMAT_MONO) { if (log.isDebugEnabled()) { log.debug(String.format("Invalid format 0x%X", format)); } throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_INVALID_FORMAT); } return format; } public int checkFrequency(int frequency) { // No change in frequency (e.g. default frequency 44100) is accepted if (pspSRC1Channel.getSampleRate() == frequency) { return frequency; } switch (frequency) { case 0: case 8000: case 11025: case 12000: case 16000: case 22050: case 24000: case 32000: case 48000: // OK break; default: throw new SceKernelErrorException(SceKernelErrors.ERROR_AUDIO_INVALID_FREQUENCY); } return frequency; } public int checkChannelCount(int channelCount) { if (channelCount != 2) { if (channelCount == 4) { throw new SceKernelErrorException(SceKernelErrors.ERROR_NOT_IMPLEMENTED); } throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_SIZE); } return channelCount; } protected void hleAudioBlockingInput(int threadId, int addr, int samples, int frequency) { int availableSamples = hleAudioGetInputLength(); if (log.isTraceEnabled()) { log.trace(String.format("hleAudioBlockingInput available samples: %d from %d", availableSamples, samples)); } int bufferBytes = samples << 1; if (inputDevice == null) { // No input device available, fake device input Memory.getInstance().memset(addr, (byte) 0, bufferBytes); Modules.ThreadManForUserModule.hleUnblockThread(threadId); } else if (availableSamples >= samples) { if (captureBuffer == null || captureBuffer.capacity() < bufferBytes) { captureBuffer = BufferUtils.createByteBuffer(bufferBytes); } else { captureBuffer.rewind(); } ALC11.alcCaptureSamples(inputDevice, captureBuffer, samples); captureBuffer.rewind(); IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(addr, samples, 2); for (int i = 0; i < samples; i++) { short sample = captureBuffer.getShort(); memoryWriter.writeNext(sample & 0xFFFF); } if (log.isTraceEnabled()) { log.trace(String.format("hleAudioBlockingInput returning %d samples: %s", samples, Utilities.getMemoryDump(addr, bufferBytes, 2, 16))); } Modules.ThreadManForUserModule.hleUnblockThread(threadId); } else { blockThreadInput(threadId, addr, samples, frequency, availableSamples); } } public int hleAudioGetInputLength() { if (inputDevice == null) { return 0; } if (samplesBuffer == null) { samplesBuffer = BufferUtils.createIntBuffer(1); } ALC10.alcGetInteger(inputDevice, ALC11.ALC_CAPTURE_SAMPLES, samplesBuffer); return samplesBuffer.get(0); } protected int getUnblockInputDelayMicros(int availableSamples, int samples, int frequency) { if (availableSamples >= samples) { return 0; } int missingSamples = samples - availableSamples; int delayMicros = (int) (missingSamples * 1000000L / frequency); return delayMicros; } protected void blockThreadInput(int addr, int samples, int frequency) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; int threadId = threadMan.getCurrentThreadID(); threadMan.hleBlockCurrentThread(SceKernelThreadInfo.JPCSP_WAIT_AUDIO); blockThreadInput(threadId, addr, samples, frequency, hleAudioGetInputLength()); } protected void blockThreadInput(int threadId, int addr, int samples, int frequency, int availableSamples) { int delayMicros = getUnblockInputDelayMicros(availableSamples, samples, frequency); if (log.isTraceEnabled()) { log.trace(String.format("blockThreadInput waiting %d micros", delayMicros)); } Emulator.getScheduler().addAction(Emulator.getClock().microTime() + delayMicros, new AudioBlockingInputAction(threadId, addr, samples, frequency)); } public int hleAudioInputBlocking(int maxSamples, int frequency, TPointer buffer) { if (!inputDeviceInitialized) { IntBuffer majorVersion = BufferUtils.createIntBuffer(1); IntBuffer minorVersion = BufferUtils.createIntBuffer(1); ALC10.alcGetInteger(null, ALC10.ALC_MAJOR_VERSION, majorVersion); ALC10.alcGetInteger(null, ALC10.ALC_MINOR_VERSION, minorVersion); log.info(String.format("OpenAL Version %d.%d, extensions %s", majorVersion.get(0), minorVersion.get(0), ALC10.alcGetString(null, ALC10.ALC_EXTENSIONS))); inputDevice = ALC11.alcCaptureOpenDevice(null, frequency, AL10.AL_FORMAT_MONO16, 10 * 1024); if (inputDevice != null) { ALC11.alcCaptureStart(inputDevice); } else { log.warn(String.format("No audio input device available, faking.")); } inputDeviceInitialized = true; } blockThreadInput(buffer.getAddress(), maxSamples, frequency); return 0; } @HLEUnimplemented @HLEFunction(nid = 0x80F1F7E0, version = 150, checkInsideInterrupt = true) public int sceAudioInit() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x210567F7, version = 150, checkInsideInterrupt = true) public int sceAudioEnd() { return 0; } @HLEFunction(nid = 0xA2BEAA6C, version = 150, checkInsideInterrupt = true) public int sceAudioSetFrequency(int frequency) { if (frequency != 44100 && frequency != 48000) { return SceKernelErrors.ERROR_AUDIO_INVALID_FREQUENCY; } for (int i = 0; i < pspPCMChannels.length; i++) { pspPCMChannels[i].setSampleRate(frequency); } return 0; } @HLEUnimplemented @HLEFunction(nid = 0xB61595C0, version = 150, checkInsideInterrupt = true) public int sceAudioLoopbackTest() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x927AC32B, version = 150, checkInsideInterrupt = true) public int sceAudioSetVolumeOffset() { return 0; } @HLEFunction(nid = 0x8C1009B2, version = 150, checkInsideInterrupt = true) public int sceAudioOutput(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkVolume") int vol, @CanBeNull TPointer pvoid_buf) { if (pspPCMChannels[channel].isOutputBlocking()) { return SceKernelErrors.ERROR_AUDIO_CHANNEL_BUSY; } changeChannelVolume(pspPCMChannels[channel], vol, vol); int result = doAudioOutput(pspPCMChannels[channel], pvoid_buf.getAddress()); Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); return result; } @HLEFunction(nid = 0x136CAF51, version = 150, checkInsideInterrupt = true) public int sceAudioOutputBlocking(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkVolume") int vol, @CanBeNull TPointer pvoid_buf) { int result = 0; if (pvoid_buf.isNull()) { if (!pspPCMChannels[channel].isDrained()) { if (log.isDebugEnabled()) { log.debug("sceAudioOutputBlocking[pvoid_buf==0] blocking " + pspPCMChannels[channel].toString()); } blockThreadOutput(pspPCMChannels[channel], pvoid_buf.getAddress(), vol, vol); } result = pspPCMChannels[channel].getSampleLength(); } else if (!pspPCMChannels[channel].isOutputBlocking()) { if (log.isDebugEnabled()) { log.debug("sceAudioOutputBlocking[not blocking] " + pspPCMChannels[channel].toString()); } changeChannelVolume(pspPCMChannels[channel], vol, vol); result = doAudioOutput(pspPCMChannels[channel], pvoid_buf.getAddress()); if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutputBlocking[not blocking] returning %d (%s)", result, pspPCMChannels[channel])); } Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); } else { if (log.isDebugEnabled()) { log.debug("sceAudioOutputBlocking[blocking] " + pspPCMChannels[channel].toString()); } blockThreadOutput(pspPCMChannels[channel], pvoid_buf.getAddress(), vol, vol); } return result; } @HLEFunction(nid = 0xE2D56B2D, version = 150, checkInsideInterrupt = true) public int sceAudioOutputPanned(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkVolume") int leftvol, @CheckArgument("checkVolume") int rightvol, @CanBeNull TPointer pvoid_buf) { if (pspPCMChannels[channel].isOutputBlocking()) { return SceKernelErrors.ERROR_AUDIO_CHANNEL_BUSY; } changeChannelVolume(pspPCMChannels[channel], leftvol, rightvol); int result = doAudioOutput(pspPCMChannels[channel], pvoid_buf.getAddress()); Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); return result; } @HLEFunction(nid = 0x13F592BC, version = 150, checkInsideInterrupt = true) public int sceAudioOutputPannedBlocking(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkVolume") int leftvol, @CheckArgument("checkVolume") int rightvol, @CanBeNull TPointer pvoid_buf) { int result = 0; if (leftvol == Integer.MIN_VALUE || rightvol == Integer.MIN_VALUE) { // In case of blocking panned output, 0x80000000 is not allowed. // In case of non-blocking panned output, 0x80000000 is allowed. return SceKernelErrors.ERROR_AUDIO_INVALID_VOLUME; } if (pvoid_buf.isNull()) { // Tested on PSP: // An output adress of 0 is actually a special code for the PSP. // It means that we must stall processing until all the previous // unplayed samples' data is output. if (!pspPCMChannels[channel].isDrained()) { if (log.isDebugEnabled()) { log.debug("sceAudioOutputPannedBlocking[pvoid_buf==0] blocking " + pspPCMChannels[channel].toString()); } blockThreadOutput(pspPCMChannels[channel], pvoid_buf.getAddress(), leftvol, rightvol); } result = pspPCMChannels[channel].getSampleLength(); } else if (!pspPCMChannels[channel].isOutputBlocking()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutputPannedBlocking[not blocking] leftVol=%d, rightVol=%d, channel=%s", leftvol, rightvol, pspPCMChannels[channel].toString())); } changeChannelVolume(pspPCMChannels[channel], leftvol, rightvol); result = doAudioOutput(pspPCMChannels[channel], pvoid_buf.getAddress()); Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); } else { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutputPannedBlocking[blocking] leftVol=%d, rightVol=%d, channel=%s", leftvol, rightvol, pspPCMChannels[channel].toString())); } blockThreadOutput(pspPCMChannels[channel], pvoid_buf.getAddress(), leftvol, rightvol); } return result; } @HLEFunction(nid = 0x5EC81C55, version = 150, checkInsideInterrupt = true) public int sceAudioChReserve(int channel, int sampleCount, int format) { if (channel >= 0) { channel = checkChannel(channel); if (pspPCMChannels[channel].isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioChReserve failed - channel %d already in use", channel)); } return SceKernelErrors.ERROR_AUDIO_INVALID_CHANNEL; } } else { // The PSP is searching for a free channel, starting with the highest channel number. for (int i = pspPCMChannels.length - 1; i >= 0; i--) { if (!pspPCMChannels[i].isReserved()) { channel = i; break; } } if (channel < 0) { log.debug("sceAudioChReserve failed - no free channels available"); return SceKernelErrors.ERROR_AUDIO_NO_CHANNELS_AVAILABLE; } } // The validity of the sampleCount and format parameters is only checked after the channel check sampleCount = checkSampleCount(sampleCount); format = checkFormat(format); pspPCMChannels[channel].setReserved(true); pspPCMChannels[channel].setSampleLength(sampleCount); pspPCMChannels[channel].setFormat(format); return channel; } @HLEUnimplemented @HLEFunction(nid = 0x41EFADE7, version = 150, checkInsideInterrupt = true) public int sceAudioOneshotOutput() { return 0; } @HLEFunction(nid = 0x6FC46853, version = 150, checkInsideInterrupt = true) public int sceAudioChRelease(@CheckArgument("checkChannel") int channel) { if (!pspPCMChannels[channel].isReserved()) { return SceKernelErrors.ERROR_AUDIO_CHANNEL_NOT_RESERVED; } pspPCMChannels[channel].release(); pspPCMChannels[channel].setReserved(false); return 0; } @HLEFunction(nid = 0xB011922F, version = 150, checkInsideInterrupt = true) public int sceAudioGetChannelRestLength(@CheckArgument("checkChannel") int channel) { return hleAudioGetChannelRestLength(pspPCMChannels[channel]); } @HLEFunction(nid = 0xCB2E439E, version = 150, checkInsideInterrupt = true) public int sceAudioSetChannelDataLen(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkSampleCount") int sampleCount) { pspPCMChannels[channel].setSampleLength(sampleCount); return 0; } @HLEFunction(nid = 0x95FD0C2D, version = 150, checkInsideInterrupt = true) public int sceAudioChangeChannelConfig(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkFormat") int format) { pspPCMChannels[channel].setFormat(format); return 0; } @HLEFunction(nid = 0xB7E1D8E7, version = 150, checkInsideInterrupt = true) public int sceAudioChangeChannelVolume(@CheckArgument("checkReservedChannel") int channel, @CheckArgument("checkVolume") int leftvol, @CheckArgument("checkVolume") int rightvol) { return changeChannelVolume(pspPCMChannels[channel], leftvol, rightvol); } @HLEFunction(nid = 0x01562BA3, version = 150, checkInsideInterrupt = true) public int sceAudioOutput2Reserve(@CheckArgument("checkReserveSampleCount") int sampleCount) { return hleAudioSRCChReserve(sampleCount, 44100, SoundChannel.FORMAT_STEREO); } @HLEFunction(nid = 0x43196845, version = 150, checkInsideInterrupt = true) public int sceAudioOutput2Release() { return sceAudioSRCChRelease(); } @HLEFunction(nid = 0x2D53F36E, version = 150, checkInsideInterrupt = true) public int sceAudioOutput2OutputBlocking(@CheckArgument("checkVolume2") int vol, @CanBeNull TPointer buf) { return sceAudioSRCOutputBlocking(vol, buf); } @HLEFunction(nid = 0x647CEF33, version = 150, checkInsideInterrupt = true) public int sceAudioOutput2GetRestSample() { if (!pspSRC1Channel.isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutput2GetRestSample returning ERROR_AUDIO_CHANNEL_NOT_RESERVED")); } return SceKernelErrors.ERROR_AUDIO_CHANNEL_NOT_RESERVED; } int rest1 = pspSRC1Channel.isBusy() ? pspSRC1Channel.getSampleLength() : 0; int rest2 = pspSRC2Channel.isBusy() ? pspSRC2Channel.getSampleLength() : 0; int rest = rest1 + rest2; if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutput2GetRestSample returning 0x%X (rest1=0x%X, rest2=0x%X)", rest, rest1, rest2)); } return rest; } @HLEFunction(nid = 0x63F2889C, version = 150, checkInsideInterrupt = true) public int sceAudioOutput2ChangeLength(@CheckArgument("checkSmallSampleCount") int sampleCount) { if (!pspSRC1Channel.isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioOutput2ChangeLength returning ERROR_AUDIO_CHANNEL_NOT_RESERVED")); } return SceKernelErrors.ERROR_AUDIO_CHANNEL_NOT_RESERVED; } pspSRC1Channel.setSampleLength(sampleCount); pspSRC2Channel.setSampleLength(sampleCount); return 0; } @HLEFunction(nid = 0x38553111, version = 150, checkInsideInterrupt = true) public int sceAudioSRCChReserve(@CheckArgument("checkReserveSampleCount") int sampleCount, @CheckArgument("checkFrequency") int freq, @CheckArgument("checkChannelCount") int format) { return hleAudioSRCChReserve(sampleCount, freq, format); } @HLEFunction(nid = 0x5C37C0AE, version = 150, checkInsideInterrupt = true) public int sceAudioSRCChRelease() { if (!pspSRC1Channel.isReserved()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioSRCChRelease returning ERROR_AUDIO_CHANNEL_NOT_RESERVED")); } return SceKernelErrors.ERROR_AUDIO_CHANNEL_NOT_RESERVED; } pspSRC1Channel.release(); pspSRC1Channel.setReserved(false); pspSRC2Channel.release(); pspSRC2Channel.setReserved(false); return 0; } @HLEFunction(nid = 0xE0727056, version = 150, checkInsideInterrupt = true) public int sceAudioSRCOutputBlocking(@CheckArgument("checkVolume2") int vol, @CanBeNull TPointer buf) { // Tested on PSP: any sound volume above MAX_VOLUME has the same effect as MAX_VOLUME. int channelVolume = min(SoundChannel.MAX_VOLUME, vol); SoundChannel pspSRCChannel = getFreeSRCChannel(); if (pspSRCChannel == null) { return SceKernelErrors.ERROR_AUDIO_CHANNEL_BUSY; } pspSRCChannel.setVolume(channelVolume); if (buf.isNull()) { // Tested on PSP: // SRC audio also delays when buf == 0, in order to drain all // audio samples from the audio driver. if (!pspSRCChannel.isDrained()) { if (log.isDebugEnabled()) { log.debug("sceAudioSRCOutputBlocking[buf==0] blocking " + pspSRCChannel); } // Do not update volume, it has already been updated above blockThreadOutput(pspSRCChannel, buf.getAddress(), -1, -1); } else { Modules.ThreadManForUserModule.hleYieldCurrentThread(); } } else if (!pspSRC1Channel.isReserved()) { // Channel is automatically reserved. The audio data (buf) is not used in this case. if (log.isDebugEnabled()) { log.debug(String.format("sceAudioSRCOutputBlocking automatically reserving channel %s", pspSRCChannel)); } pspSRC1Channel.setReserved(true); pspSRC2Channel.setReserved(true); } else { if (!pspSRCChannel.isOutputBlocking()) { if (log.isDebugEnabled()) { log.debug(String.format("sceAudioSRCOutputBlocking[not blocking] %s to %s", buf, pspSRCChannel.toString())); } Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); return doAudioOutput(pspSRCChannel, buf.getAddress()); } if (log.isDebugEnabled()) { log.debug(String.format("sceAudioSRCOutputBlocking[blocking] %s to %s", buf, pspSRCChannel.toString())); } // Do not update volume, it has already been updated above blockThreadOutput(pspSRCChannel, buf.getAddress(), -1, -1); } return 0; } @HLEUnimplemented @HLEFunction(nid = 0x086E5895, version = 150, checkInsideInterrupt = true) public int sceAudioInputBlocking(int maxSamples, int frequency, TPointer buffer) { if (frequency != 44100 && frequency != 22050 && frequency != 11025) { return SceKernelErrors.ERROR_AUDIO_INVALID_FREQUENCY; } return hleAudioInputBlocking(maxSamples, frequency, buffer); } @HLEUnimplemented @HLEFunction(nid = 0x6D4BEC68, version = 150, checkInsideInterrupt = true) public int sceAudioInput() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xA708C6A6, version = 150, checkInsideInterrupt = true) public int sceAudioGetInputLength() { return hleAudioGetInputLength(); } @HLEUnimplemented @HLEFunction(nid = 0x87B2E651, version = 150, checkInsideInterrupt = true) public int sceAudioWaitInputEnd() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x7DE61688, version = 150, checkInsideInterrupt = true) public int sceAudioInputInit() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xE926D3FB, version = 150, checkInsideInterrupt = true) public int sceAudioInputInitEx() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xA633048E, version = 150, checkInsideInterrupt = true) public int sceAudioPollInputEnd() { return 0; } @HLEFunction(nid = 0xE9D97901, version = 150, checkInsideInterrupt = true) public int sceAudioGetChannelRestLen(@CheckArgument("checkChannel") int channel) { return hleAudioGetChannelRestLength(pspPCMChannels[channel]); } @HLEFunction(nid = 0x9DB844C6, version = 500, checkInsideInterrupt = true) public int sceAudioSetFrequency500(int frequency) { return sceAudioSetFrequency(frequency); } }