blob: 1adef911f866fb9dee3856037cabbba19cfa313c [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <utils/logging.h>
#include <thread>
#include "Game.h"
constexpr int32_t kChannelCount = 2;
Game::Game(AAssetManager &assetManager): mAssetManager(assetManager) {
}
void Game::start() {
// Load the RAW PCM data files for both the clap sound and backing track into memory.
std::shared_ptr<AAssetDataSource> mClapSource(AAssetDataSource::newFromAssetManager(mAssetManager,
"CLAP.raw",
oboe::ChannelCount::Stereo));
if (mClapSource == nullptr){
LOGE("Could not load source data for clap sound");
return;
}
mClap = std::make_shared<Player>(mClapSource);
std::shared_ptr<AAssetDataSource> mBackingTrackSource(AAssetDataSource::newFromAssetManager(mAssetManager,
"FUNKY_HOUSE.raw",
oboe::ChannelCount::Stereo));
if (mBackingTrackSource == nullptr){
LOGE("Could not load source data for backing track");
return;
}
mBackingTrack = std::make_shared<Player>(mBackingTrackSource);
mBackingTrack->setPlaying(true);
mBackingTrack->setLooping(true);
// Add the clap and backing track sounds to a mixer so that they can be played together
// simultaneously using a single audio stream.
mMixer.addTrack(mClap);
mMixer.addTrack(mBackingTrack);
mMixer.setChannelCount(kChannelCount);
// Add the audio frame numbers on which the clap sound should be played to the clap event queue.
// The backing track tempo is 120 beats per minute, which is 2 beats per second. At a sample
// rate of 48000 frames per second this means a beat occurs every 24000 frames, starting at
// zero. So the first 3 beats are: 0, 24000, 48000
mClapEvents.push(0);
mClapEvents.push(24000);
mClapEvents.push(48000);
// We want the user to tap on the screen exactly 4 beats after the first clap. 4 beats is
// 96000 frames, so we just add 96000 to the above frame numbers.
mClapWindows.push(96000);
mClapWindows.push(120000);
mClapWindows.push(144000);
// Create a builder
AudioStreamBuilder builder;
builder.setChannelCount(kChannelCount);
builder.setSampleRate(kSampleRateHz);
builder.setCallback(this);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);
Result result = builder.openStream(&mAudioStream);
if (result != Result::OK){
LOGE("Failed to open stream. Error: %s", convertToText(result));
return;
}
if (mAudioStream->getFormat() == AudioFormat::I16){
mConversionBuffer = std::make_unique<float[]>(mAudioStream->getBufferCapacityInFrames() *
kChannelCount);
}
// Reduce stream latency by setting the buffer size to a multiple of the burst size
auto setBufferSizeResult = mAudioStream->setBufferSizeInFrames(
mAudioStream->getFramesPerBurst() * kBufferSizeInBursts);
if (setBufferSizeResult != Result::OK){
LOGW("Failed to set buffer size. Error: %s", convertToText(setBufferSizeResult.error()));
}
result = mAudioStream->requestStart();
if (result != Result::OK){
LOGE("Failed to start stream. Error: %s", convertToText(result));
}
}
void Game::stop(){
if (mAudioStream != nullptr){
mAudioStream->close();
delete mAudioStream;
mAudioStream = nullptr;
}
}
void Game::tap(int64_t eventTimeAsUptime) {
mClap->setPlaying(true);
int64_t nextClapWindowFrame;
if (mClapWindows.pop(nextClapWindowFrame)){
int64_t frameDelta = nextClapWindowFrame - mCurrentFrame;
int64_t timeDelta = convertFramesToMillis(frameDelta, kSampleRateHz);
int64_t windowTime = mLastUpdateTime + timeDelta;
TapResult result = getTapResult(eventTimeAsUptime, windowTime);
mUiEvents.push(result);
}
}
void Game::tick(){
TapResult r;
if (mUiEvents.pop(r)) {
renderEvent(r);
} else {
SetGLScreenColor(kScreenBackgroundColor);
}
}
void Game::onSurfaceCreated() {
SetGLScreenColor(kScreenBackgroundColor);
}
void Game::onSurfaceChanged(int widthInPixels, int heightInPixels) {
}
void Game::onSurfaceDestroyed() {
}
DataCallbackResult Game::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
// If we're outputting in 16-bit we need to render into a separate buffer then convert that
// buffer to int16s
bool is16Bit = (oboeStream->getFormat() == AudioFormat::I16);
float *outputBuffer = (is16Bit) ? mConversionBuffer.get() : static_cast<float *>(audioData);
int64_t nextClapEvent;
for (int i = 0; i < numFrames; ++i) {
if (mClapEvents.peek(nextClapEvent) && mCurrentFrame == nextClapEvent){
mClap->setPlaying(true);
mClapEvents.pop(nextClapEvent);
}
mMixer.renderAudio(outputBuffer+(kChannelCount*i), 1);
mCurrentFrame++;
}
if (is16Bit){
oboe::convertFloatToPcm16(outputBuffer,
static_cast<int16_t*>(audioData),
numFrames * kChannelCount);
}
mLastUpdateTime = nowUptimeMillis();
return DataCallbackResult::Continue;
}