aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp')
-rw-r--r--externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp241
1 files changed, 241 insertions, 0 deletions
diff --git a/externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp b/externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp
new file mode 100644
index 0000000000..199f4cce14
--- /dev/null
+++ b/externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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 <cinttypes>
+
+#include "Game.h"
+
+Game::Game(AAssetManager &assetManager): mAssetManager(assetManager) {
+}
+
+void Game::load() {
+
+ if (!openStream()) {
+ mGameState = GameState::FailedToLoad;
+ return;
+ }
+
+ if (!setupAudioSources()) {
+ mGameState = GameState::FailedToLoad;
+ return;
+ }
+
+ scheduleSongEvents();
+
+ Result result = mAudioStream->requestStart();
+ if (result != Result::OK){
+ LOGE("Failed to start stream. Error: %s", convertToText(result));
+ mGameState = GameState::FailedToLoad;
+ return;
+ }
+
+ mGameState = GameState::Playing;
+}
+
+void Game::start() {
+
+ // async returns a future, we must store this future to avoid blocking. It's not sufficient
+ // to store this in a local variable as its destructor will block until Game::load completes.
+ mLoadingResult = std::async(&Game::load, this);
+}
+
+void Game::stop(){
+
+ if (mAudioStream){
+ mAudioStream->stop();
+ mAudioStream->close();
+ mAudioStream.reset();
+ }
+}
+
+void Game::tap(int64_t eventTimeAsUptime) {
+
+ if (mGameState != GameState::Playing){
+ LOGW("Game not in playing state, ignoring tap event");
+ } else {
+ mClap->setPlaying(true);
+
+ int64_t nextClapWindowTimeMs;
+ if (mClapWindows.pop(nextClapWindowTimeMs)){
+
+ // Convert the tap time to a song position
+ int64_t tapTimeInSongMs = mSongPositionMs + (eventTimeAsUptime - mLastUpdateTime);
+ TapResult result = getTapResult(tapTimeInSongMs, nextClapWindowTimeMs);
+ mUiEvents.push(result);
+ }
+ }
+}
+
+void Game::tick(){
+
+ switch (mGameState){
+ case GameState::Playing:
+ TapResult r;
+ if (mUiEvents.pop(r)) {
+ renderEvent(r);
+ } else {
+ SetGLScreenColor(kPlayingColor);
+ }
+ break;
+
+ case GameState::Loading:
+ SetGLScreenColor(kLoadingColor);
+ break;
+
+ case GameState::FailedToLoad:
+ SetGLScreenColor(kLoadingFailedColor);
+ break;
+ }
+}
+
+void Game::onSurfaceCreated() {
+ SetGLScreenColor(kLoadingColor);
+}
+
+void Game::onSurfaceChanged(int widthInPixels, int heightInPixels) {
+}
+
+void Game::onSurfaceDestroyed() {
+}
+
+DataCallbackResult Game::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
+
+ auto *outputBuffer = static_cast<float *>(audioData);
+
+ int64_t nextClapEventMs;
+
+ for (int i = 0; i < numFrames; ++i) {
+
+ mSongPositionMs = convertFramesToMillis(
+ mCurrentFrame,
+ mAudioStream->getSampleRate());
+
+ if (mClapEvents.peek(nextClapEventMs) && mSongPositionMs >= nextClapEventMs){
+ mClap->setPlaying(true);
+ mClapEvents.pop(nextClapEventMs);
+ }
+ mMixer.renderAudio(outputBuffer+(oboeStream->getChannelCount()*i), 1);
+ mCurrentFrame++;
+ }
+
+ mLastUpdateTime = nowUptimeMillis();
+
+ return DataCallbackResult::Continue;
+}
+
+void Game::onErrorAfterClose(AudioStream *audioStream, Result error) {
+ if (error == Result::ErrorDisconnected){
+ mGameState = GameState::Loading;
+ mAudioStream.reset();
+ mMixer.removeAllTracks();
+ mCurrentFrame = 0;
+ mSongPositionMs = 0;
+ mLastUpdateTime = 0;
+ start();
+ } else {
+ LOGE("Stream error: %s", convertToText(error));
+ }
+}
+
+/**
+ * Get the result of a tap
+ *
+ * @param tapTimeInMillis - The time the tap occurred in milliseconds
+ * @param tapWindowInMillis - The time at the middle of the "tap window" in milliseconds
+ * @return TapResult can be Early, Late or Success
+ */
+TapResult Game::getTapResult(int64_t tapTimeInMillis, int64_t tapWindowInMillis){
+ LOGD("Tap time %" PRId64 ", tap window time: %" PRId64, tapTimeInMillis, tapWindowInMillis);
+ if (tapTimeInMillis <= tapWindowInMillis + kWindowCenterOffsetMs) {
+ if (tapTimeInMillis >= tapWindowInMillis - kWindowCenterOffsetMs) {
+ return TapResult::Success;
+ } else {
+ return TapResult::Early;
+ }
+ } else {
+ return TapResult::Late;
+ }
+}
+
+bool Game::openStream() {
+
+ // Create an audio stream
+ AudioStreamBuilder builder;
+ builder.setFormat(AudioFormat::Float);
+ builder.setFormatConversionAllowed(true);
+ builder.setPerformanceMode(PerformanceMode::LowLatency);
+ builder.setSharingMode(SharingMode::Exclusive);
+ builder.setSampleRate(48000);
+ builder.setSampleRateConversionQuality(
+ SampleRateConversionQuality::Medium);
+ builder.setChannelCount(2);
+ builder.setDataCallback(this);
+ builder.setErrorCallback(this);
+ Result result = builder.openStream(mAudioStream);
+ if (result != Result::OK){
+ LOGE("Failed to open stream. Error: %s", convertToText(result));
+ return false;
+ }
+
+ mMixer.setChannelCount(mAudioStream->getChannelCount());
+
+ return true;
+}
+
+bool Game::setupAudioSources() {
+
+ // Set the properties of our audio source(s) to match that of our audio stream
+ AudioProperties targetProperties {
+ .channelCount = mAudioStream->getChannelCount(),
+ .sampleRate = mAudioStream->getSampleRate()
+ };
+
+ // Create a data source and player for the clap sound
+ std::shared_ptr<AAssetDataSource> mClapSource {
+ AAssetDataSource::newFromCompressedAsset(mAssetManager, kClapFilename, targetProperties)
+ };
+ if (mClapSource == nullptr){
+ LOGE("Could not load source data for clap sound");
+ return false;
+ }
+ mClap = std::make_unique<Player>(mClapSource);
+
+ // Create a data source and player for our backing track
+ std::shared_ptr<AAssetDataSource> backingTrackSource {
+ AAssetDataSource::newFromCompressedAsset(mAssetManager, kBackingTrackFilename, targetProperties)
+ };
+ if (backingTrackSource == nullptr){
+ LOGE("Could not load source data for backing track");
+ return false;
+ }
+ mBackingTrack = std::make_unique<Player>(backingTrackSource);
+ mBackingTrack->setPlaying(true);
+ mBackingTrack->setLooping(true);
+
+ // Add both players to a mixer
+ mMixer.addTrack(mClap.get());
+ mMixer.addTrack(mBackingTrack.get());
+
+ return true;
+}
+
+void Game::scheduleSongEvents() {
+
+ for (auto t : kClapEvents) mClapEvents.push(t);
+ for (auto t : kClapWindows) mClapWindows.push(t);
+}