From eb270ac76e8f83199e2d36b5131c85a565963861 Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Tue, 9 Jan 2024 18:38:42 +0100 Subject: [PATCH 1/6] Drum patterns are now also dynamic patterns --- .../mwengine/example/MWEngineActivity.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java index 1464f3d..e3cba17 100644 --- a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java +++ b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java @@ -223,6 +223,7 @@ private void init() { }); findViewById( R.id.PatternSwitchButton ).setOnClickListener(( View v ) -> { _patternIndex = _patternIndex == 0 ? 1 : 0; + createDrumPattern(); createBassPattern(); }); findViewById( R.id.RecordInputButton ).setOnTouchListener(( View v, MotionEvent event ) -> { @@ -315,12 +316,7 @@ protected void setupSong() { // STEP 2 : Sample events to play back a drum beat - createDrumEvent( "hat", 2 ); // hi-hat on the second 8th note after the first beat of the bar - createDrumEvent( "clap", 4 ); // clap sound on the second beat of the bar - createDrumEvent( "hat", 6 ); // hi-hat on the second 8th note after the second beat - createDrumEvent( "hat", 10 ); // hi-hat on the second 8th note after the third beat - createDrumEvent( "clap", 12 ); // clap sound on the third beat of the bar - createDrumEvent( "hat", 14 ); // hi-hat on the second 8th note after the fourth beat + createDrumPattern(); // Real-time synthesis events @@ -550,6 +546,21 @@ private void createSynthEvent( SynthInstrument synth, double frequency, int posi _synth2Events.add( event ); } + private void createDrumPattern() { + // clear any existing patterns (when switching) + for ( final SampleEvent event : _drumEvents ) { + event.dispose(); + } + int start = _patternIndex == 0 ? 2 : 0; + int incr = _patternIndex == 0 ? 4 : 1; + // depending on the pattern hi-hats occur on the off beats or every 16th note + for ( int i = start; i < 16; i += incr ) { + createDrumEvent( "hat", i ); + } + createDrumEvent( "clap", 4 ); // clap sound on the second beat of the bar + createDrumEvent( "clap", 12 ); // clap sound on the third beat of the bar + } + private void createBassPattern() { // clear any existing patterns (when switching) for ( final SynthEvent event : _synth1Events ) { From 2a2e51837b5adebdafc96c3d572bd77d38055b1e Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Wed, 10 Jan 2024 08:06:42 +0100 Subject: [PATCH 2/6] Update build configuration --- gradle.properties | 3 +++ gradle/wrapper/gradle-wrapper.properties | 2 +- mwengine/CMakeLists.txt | 2 +- mwengine/build.gradle | 7 ++++--- mwengine/src/main/AndroidManifest.xml | 3 +-- mwengine_example/build.gradle | 7 ++++--- mwengine_example/src/main/AndroidManifest.xml | 3 +-- .../java/nl/igorski/mwengine/example/MWEngineActivity.java | 3 ++- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/gradle.properties b/gradle.properties index 01b80d7..b08c9fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,3 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 91f26f5..3a542b5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1 +1 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip \ No newline at end of file diff --git a/mwengine/CMakeLists.txt b/mwengine/CMakeLists.txt index ade95c6..703fd23 100644 --- a/mwengine/CMakeLists.txt +++ b/mwengine/CMakeLists.txt @@ -34,7 +34,7 @@ set(target mwengine) # architecture-specific compiler flags if (${ANDROID_ABI} MATCHES "x86_64") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -msse4.2 -mpopcnt -m64") endif() # source folders diff --git a/mwengine/build.gradle b/mwengine/build.gradle index 8cbd894..d112fba 100644 --- a/mwengine/build.gradle +++ b/mwengine/build.gradle @@ -7,12 +7,13 @@ buildscript { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' + classpath 'com.android.tools.build:gradle:8.2.1' } } android { - compileSdkVersion 32 + namespace 'nl.igorski.mwengine' + compileSdk 33 defaultConfig { // NOTE: the value of minSdkVersion is used by the native build as the target APP_PLATFORM @@ -22,7 +23,7 @@ android { // thus set the value of minSdkVersion to whatever fits your use case. minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" diff --git a/mwengine/src/main/AndroidManifest.xml b/mwengine/src/main/AndroidManifest.xml index e7490cf..d6fa7c3 100644 --- a/mwengine/src/main/AndroidManifest.xml +++ b/mwengine/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/mwengine_example/build.gradle b/mwengine_example/build.gradle index aa259e5..3596733 100644 --- a/mwengine_example/build.gradle +++ b/mwengine_example/build.gradle @@ -19,12 +19,13 @@ buildscript { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' + classpath 'com.android.tools.build:gradle:8.2.1' } } android { - compileSdkVersion 32 + namespace 'nl.igorski.mwengine.example' + compileSdk 33 defaultConfig { // the sdk version range below matches the one of the library. You can change these as you see fit @@ -34,7 +35,7 @@ android { // thus set the value of minSdkVersion to whatever fits your use case. minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 1 versionName "1.0.0" diff --git a/mwengine_example/src/main/AndroidManifest.xml b/mwengine_example/src/main/AndroidManifest.xml index d4173b4..be9bf2d 100644 --- a/mwengine_example/src/main/AndroidManifest.xml +++ b/mwengine_example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java index e3cba17..d19eb80 100644 --- a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java +++ b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java @@ -551,9 +551,10 @@ private void createDrumPattern() { for ( final SampleEvent event : _drumEvents ) { event.dispose(); } + _drumEvents.clear(); // clear the old events so they can be garbage collected + // depending on the pattern hi-hats occur on the off beats or every 16th note int start = _patternIndex == 0 ? 2 : 0; int incr = _patternIndex == 0 ? 4 : 1; - // depending on the pattern hi-hats occur on the off beats or every 16th note for ( int i = start; i < 16; i += incr ) { createDrumEvent( "hat", i ); } From 19b95b0c069d847865ae9ab580af9ab36be3b515 Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Wed, 10 Jan 2024 22:04:02 +0100 Subject: [PATCH 3/6] Postpone audio event buffer destruction --- mwengine/src/main/cpp/events/baseaudioevent.cpp | 9 ++++++++- mwengine/src/main/cpp/events/baseaudioevent.h | 4 +++- mwengine/src/main/cpp/instruments/baseinstrument.cpp | 3 +++ mwengine/src/main/cpp/sequencer.cpp | 8 ++++---- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/mwengine/src/main/cpp/events/baseaudioevent.cpp b/mwengine/src/main/cpp/events/baseaudioevent.cpp index 7ad4450..6773d56 100755 --- a/mwengine/src/main/cpp/events/baseaudioevent.cpp +++ b/mwengine/src/main/cpp/events/baseaudioevent.cpp @@ -54,8 +54,15 @@ BaseAudioEvent::~BaseAudioEvent() void BaseAudioEvent::dispose() { + _disposed = true; removeFromSequencer(); - destroyBuffer(); +} + +void BaseAudioEvent::onRemove() +{ + if ( _disposed ) { + destroyBuffer(); + } } BaseInstrument* BaseAudioEvent::getInstrument() diff --git a/mwengine/src/main/cpp/events/baseaudioevent.h b/mwengine/src/main/cpp/events/baseaudioevent.h index 61c6ca1..a89d723 100755 --- a/mwengine/src/main/cpp/events/baseaudioevent.h +++ b/mwengine/src/main/cpp/events/baseaudioevent.h @@ -35,7 +35,8 @@ class BaseAudioEvent BaseAudioEvent(); virtual ~BaseAudioEvent(); - void dispose(); // invoke when removing the Event from the engine + void dispose(); // invoke when removing the Event from the engine + void onRemove(); // invoked by the Instrument once the event has safely been removed /** * event volume is in a percentile (0 - 1) range @@ -213,6 +214,7 @@ class BaseAudioEvent bool _removalEnqueued; bool _locked; bool _updateAfterUnlock; // use in update-methods when checking for lock + bool _disposed = false; // _destroyableBuffer indicates we can delete the buffer on destruction (true by default and // implies that this AudioEvent holds the only reference to its buffers diff --git a/mwengine/src/main/cpp/instruments/baseinstrument.cpp b/mwengine/src/main/cpp/instruments/baseinstrument.cpp index 6cbd0d6..3d1de97 100644 --- a/mwengine/src/main/cpp/instruments/baseinstrument.cpp +++ b/mwengine/src/main/cpp/instruments/baseinstrument.cpp @@ -175,6 +175,9 @@ bool BaseInstrument::removeEvent( BaseAudioEvent* audioEvent, bool isLiveEvent ) removeEventFromMeasureCache( audioEvent ); } } + if ( removed ) { + audioEvent->onRemove(); + } return removed; } diff --git a/mwengine/src/main/cpp/sequencer.cpp b/mwengine/src/main/cpp/sequencer.cpp index 97af091..6e3ba2d 100755 --- a/mwengine/src/main/cpp/sequencer.cpp +++ b/mwengine/src/main/cpp/sequencer.cpp @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - https://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -45,6 +45,7 @@ int Sequencer::registerInstrument( BaseInstrument* instrument ) for ( auto const & i : instruments ) { if ( i == instrument ) { wasPresent = true; + break; } } @@ -112,9 +113,9 @@ bool Sequencer::getAudioEvents( std::vector* channels, int buffer } } - if ( addLiveInstruments && instrument->hasLiveEvents() ) + if ( addLiveInstruments && instrument->hasLiveEvents() ) { collectLiveEvents( instrument ); - + } channels->push_back( instrumentChannel ); } } @@ -133,7 +134,6 @@ void Sequencer::collectSequencedEvents( BaseInstrument* instrument, int bufferPo if ( !instrument->hasEvents() ) { return; } - AudioChannel* channel = instrument->audioChannel; auto audioEvents = instrument->getEventsForMeasure( measure ); From 6aafa81bff8970f1f90be0c54650876d03c332e6 Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Wed, 10 Jan 2024 22:10:35 +0100 Subject: [PATCH 4/6] Add comments --- mwengine/src/main/cpp/events/baseaudioevent.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mwengine/src/main/cpp/events/baseaudioevent.cpp b/mwengine/src/main/cpp/events/baseaudioevent.cpp index 6773d56..ca49385 100755 --- a/mwengine/src/main/cpp/events/baseaudioevent.cpp +++ b/mwengine/src/main/cpp/events/baseaudioevent.cpp @@ -54,14 +54,15 @@ BaseAudioEvent::~BaseAudioEvent() void BaseAudioEvent::dispose() { - _disposed = true; + _disposed = true; // set the disposed flag as removal can be async when sequencer is running + removeFromSequencer(); } void BaseAudioEvent::onRemove() { if ( _disposed ) { - destroyBuffer(); + destroyBuffer(); // if event was removed as part of disposal routine, clean the buffer now } } From 8b13e1de9a06f9a0bba46ef95030fa6e4fbf9754 Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Sat, 13 Jan 2024 09:25:05 +0100 Subject: [PATCH 5/6] Added method that allows to postpone deallocation of resources when engine is idle --- gradle.properties | 1 - mwengine/src/main/cpp/audioengine.cpp | 14 ++++++- mwengine/src/main/cpp/audioengine.h | 5 ++- .../src/main/cpp/definitions/notifications.h | 3 +- .../src/main/cpp/events/baseaudioevent.cpp | 7 +++- mwengine/src/main/cpp/events/baseaudioevent.h | 2 +- mwengine/src/main/cpp/sequencercontroller.cpp | 7 +++- mwengine/src/main/cpp/sequencercontroller.h | 3 +- .../java/nl/igorski/mwengine/MWEngine.java | 39 ++++++++++++++++++- .../mwengine/example/MWEngineActivity.java | 8 +++- .../src/main/res/values/strings.xml | 2 +- 11 files changed, 78 insertions(+), 13 deletions(-) diff --git a/gradle.properties b/gradle.properties index b08c9fb..ac1c36f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false diff --git a/mwengine/src/main/cpp/audioengine.cpp b/mwengine/src/main/cpp/audioengine.cpp index c2412dc..d35b429 100755 --- a/mwengine/src/main/cpp/audioengine.cpp +++ b/mwengine/src/main/cpp/audioengine.cpp @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - https://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -77,6 +77,7 @@ namespace MWEngine { int AudioEngine::time_sig_beat_unit = 4; int AudioEngine::queuedTime_sig_beat_amount = time_sig_beat_amount; int AudioEngine::queuedTime_sig_beat_unit = time_sig_beat_unit; + bool AudioEngine::broadcast_idle = false; /* buffer read/write pointers */ @@ -745,6 +746,12 @@ namespace MWEngine { if ( recordingState.bouncing && AudioEngineProps::isRendering.load() && DriverAdapter::isAAudio() ) { render( amountOfSamples ); } + + if ( broadcast_idle ) { + broadcast_idle = false; + Notifier::broadcast( Notifications::ENGINE_IDLE ); + } + return AudioEngineProps::isRendering.load(); } @@ -762,6 +769,11 @@ namespace MWEngine { #endif } + void AudioEngine::notifyWhenIdle() + { + broadcast_idle = true; + } + void AudioEngine::handleTempoUpdate( float aQueuedTempo, bool broadcastUpdate ) { float ratio = 1; diff --git a/mwengine/src/main/cpp/audioengine.h b/mwengine/src/main/cpp/audioengine.h index e8bc4b9..9331540 100755 --- a/mwengine/src/main/cpp/audioengine.h +++ b/mwengine/src/main/cpp/audioengine.h @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - https://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -49,6 +49,7 @@ class AudioEngine static void start( Drivers::types audioDriver ); static void stop(); static void reset(); + static void notifyWhenIdle(); // request to be notified when engine reaches idle state after render (fires once per request) static AudioChannel* getInputChannel(); @@ -143,6 +144,8 @@ class AudioEngine static int min_step_position; // the lowest step in the current sequence static int max_step_position; // the maximum step in the current sequence (e.g. 15 for single measure using a 16 step sequencer - step starts at 0.) + static bool broadcast_idle; // whether to broadcast a notification when the engine is entering idle phase after render + /* buffer read/write pointers */ static int bufferPosition; // the current sequence position in samples ("playback head" offset) diff --git a/mwengine/src/main/cpp/definitions/notifications.h b/mwengine/src/main/cpp/definitions/notifications.h index 18d8b0b..b673a11 100644 --- a/mwengine/src/main/cpp/definitions/notifications.h +++ b/mwengine/src/main/cpp/definitions/notifications.h @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2015-2018 Igor Zinken - http://www.igorski.nl + * Copyright (c) 2015-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -52,6 +52,7 @@ class Notifications /* system messages */ STATUS_BRIDGE_CONNECTED, // JNI bridge connected + ENGINE_IDLE, // engine is currently in idle state (between render cycles), this is the time to safely clean up resources /* fatal errors */ diff --git a/mwengine/src/main/cpp/events/baseaudioevent.cpp b/mwengine/src/main/cpp/events/baseaudioevent.cpp index ca49385..699bf78 100755 --- a/mwengine/src/main/cpp/events/baseaudioevent.cpp +++ b/mwengine/src/main/cpp/events/baseaudioevent.cpp @@ -55,8 +55,13 @@ BaseAudioEvent::~BaseAudioEvent() void BaseAudioEvent::dispose() { _disposed = true; // set the disposed flag as removal can be async when sequencer is running + _enabled = false; - removeFromSequencer(); + if ( _instrument != nullptr && isSequenced ) { + _instrument->removeEvent( this, false ); + } else { + stop(); + } } void BaseAudioEvent::onRemove() diff --git a/mwengine/src/main/cpp/events/baseaudioevent.h b/mwengine/src/main/cpp/events/baseaudioevent.h index a89d723..9f2573a 100755 --- a/mwengine/src/main/cpp/events/baseaudioevent.h +++ b/mwengine/src/main/cpp/events/baseaudioevent.h @@ -35,7 +35,7 @@ class BaseAudioEvent BaseAudioEvent(); virtual ~BaseAudioEvent(); - void dispose(); // invoke when removing the Event from the engine + void dispose(); // invoke when removing the Event from the engine, should be done when ENGINE_IDLE is broadcast! void onRemove(); // invoked by the Instrument once the event has safely been removed /** diff --git a/mwengine/src/main/cpp/sequencercontroller.cpp b/mwengine/src/main/cpp/sequencercontroller.cpp index 8c08ef1..222b799 100644 --- a/mwengine/src/main/cpp/sequencercontroller.cpp +++ b/mwengine/src/main/cpp/sequencercontroller.cpp @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - https://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -86,6 +86,11 @@ void SequencerController::setVolume( float aVolume ) AudioEngine::volume = VolumeUtil::toLog( aVolume ); } +bool SequencerController::getPlaying() +{ + return Sequencer::playing; +} + void SequencerController::setPlaying( bool aIsPlaying ) { Sequencer::playing = aIsPlaying; diff --git a/mwengine/src/main/cpp/sequencercontroller.h b/mwengine/src/main/cpp/sequencercontroller.h index e2358ad..d71bbfa 100644 --- a/mwengine/src/main/cpp/sequencercontroller.h +++ b/mwengine/src/main/cpp/sequencercontroller.h @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - http://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - http://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -44,6 +44,7 @@ class SequencerController void setTempo ( float aTempo, int aTimeSigBeatAmount, int aTimeSigBeatUnit ); void setTempoNow( float aTempo, int aTimeSigBeatAmount, int aTimeSigBeatUnit ); void setVolume ( float aVolume ); + bool getPlaying (); void setPlaying ( bool aPlaying ); /** diff --git a/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java b/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java index 43d07e9..2672052 100644 --- a/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java +++ b/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2022 Igor Zinken - https://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -34,6 +34,10 @@ import android.util.Log; import android.view.WindowManager; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import nl.igorski.mwengine.core.*; public final class MWEngine @@ -77,6 +81,10 @@ public interface IObserver void handleNotification( int aNotificationId, int aNotificationValue ); } + public interface VoidInterface { + public void operation(); + } + private static MWEngine INSTANCE; // we only allow a single instance to be constructed for resource optimization private IObserver _observer; @@ -96,6 +104,8 @@ public interface IObserver private boolean _initialCreation = true; private boolean _isRunning = false; + private List _idleCallbacks = new ArrayList( Collections.emptyList()); + private static int ENGINE_IDLE_NOTIFICATION = Notifications.ids.ENGINE_IDLE.ordinal(); /** * The Java-side bridge to manage all native layer components @@ -373,6 +383,20 @@ public void saveRecordedSnippet( int snippetBufferIndex ) { AudioEngine.saveRecordedSnippet( snippetBufferIndex ); } + /** + * Delay execution of provided callback until the engine is idle, e.g. + * between render cycles. This can be used to safely dispose() of sequenced AudioEvents + * and deallocate referenced resources while the Sequencer is running. + */ + public void executeWhenIdle( VoidInterface callback ) { + if ( _sequencerController.getPlaying()) { + _idleCallbacks.add( callback ); + AudioEngine.notifyWhenIdle(); + } else { + callback.operation(); + } + } + public void reset() { AudioEngine.reset(); } @@ -465,7 +489,18 @@ public static void handleBridgeConnected( int aSomething ) { } public static void handleNotification( int aNotificationId ) { - if ( INSTANCE != null && INSTANCE._observer != null ) INSTANCE._observer.handleNotification( aNotificationId ); + if ( INSTANCE != null ) { + if ( ENGINE_IDLE_NOTIFICATION == aNotificationId && !INSTANCE._idleCallbacks.isEmpty() ) { + for ( VoidInterface callback : INSTANCE._idleCallbacks ) { + callback.operation(); + } + INSTANCE._idleCallbacks.clear(); + return; + } + if ( INSTANCE._observer != null ) { + INSTANCE._observer.handleNotification( aNotificationId ); + } + } } public static void handleNotificationWithData( int aNotificationId, int aNotificationData ) { diff --git a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java index d19eb80..77788c0 100644 --- a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java +++ b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java @@ -223,8 +223,12 @@ private void init() { }); findViewById( R.id.PatternSwitchButton ).setOnClickListener(( View v ) -> { _patternIndex = _patternIndex == 0 ? 1 : 0; - createDrumPattern(); - createBassPattern(); + // we allow to dispose events while the Sequencer is running, so we delay execution + // until the engine has notified that it is safe to do so (between render cycles) + _engine.executeWhenIdle(() -> { + createDrumPattern(); + createBassPattern(); + }); }); findViewById( R.id.RecordInputButton ).setOnTouchListener(( View v, MotionEvent event ) -> { switch( event.getAction()) { diff --git a/mwengine_example/src/main/res/values/strings.xml b/mwengine_example/src/main/res/values/strings.xml index 53bc2ea..dc8ba16 100755 --- a/mwengine_example/src/main/res/values/strings.xml +++ b/mwengine_example/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ You can add new audio on the fly too: hold down the first button to play a C note or hold down the second button to record audio from your Android device input (tip: use headphones to prevent feedback occurring when using the microphone on your phone at louder volume settings) - You can switch up the bass pattern by clicking the button below. One plays the "official" MWEngine loop and the other E1M1 by Bobby Prince, or whoever inspired him. + You can switch up the bass and drum patterns by clicking the button below. One plays the "official" MWEngine loop and the other E1M1 by Bobby Prince, or whoever inspired him. Audio driver Play Pause From dbcab6249afff745b16196eaba3fbfcbb27d5f59 Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Sun, 10 Mar 2024 23:29:45 +0100 Subject: [PATCH 6/6] ongoing --- mwengine/src/main/cpp/sequencercontroller.h | 2 +- mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java | 3 +-- .../java/nl/igorski/mwengine/example/MWEngineActivity.java | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mwengine/src/main/cpp/sequencercontroller.h b/mwengine/src/main/cpp/sequencercontroller.h index d71bbfa..f70260d 100644 --- a/mwengine/src/main/cpp/sequencercontroller.h +++ b/mwengine/src/main/cpp/sequencercontroller.h @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Copyright (c) 2013-2024 Igor Zinken - http://www.igorski.nl + * Copyright (c) 2013-2024 Igor Zinken - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java b/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java index 2672052..b39c3ef 100644 --- a/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java +++ b/mwengine/src/main/java/nl/igorski/mwengine/MWEngine.java @@ -490,12 +490,11 @@ public static void handleBridgeConnected( int aSomething ) { public static void handleNotification( int aNotificationId ) { if ( INSTANCE != null ) { - if ( ENGINE_IDLE_NOTIFICATION == aNotificationId && !INSTANCE._idleCallbacks.isEmpty() ) { + if ( ENGINE_IDLE_NOTIFICATION == aNotificationId ) { for ( VoidInterface callback : INSTANCE._idleCallbacks ) { callback.operation(); } INSTANCE._idleCallbacks.clear(); - return; } if ( INSTANCE._observer != null ) { INSTANCE._observer.handleNotification( aNotificationId ); diff --git a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java index 77788c0..70fb079 100644 --- a/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java +++ b/mwengine_example/src/main/java/nl/igorski/mwengine/example/MWEngineActivity.java @@ -223,8 +223,9 @@ private void init() { }); findViewById( R.id.PatternSwitchButton ).setOnClickListener(( View v ) -> { _patternIndex = _patternIndex == 0 ? 1 : 0; - // we allow to dispose events while the Sequencer is running, so we delay execution - // until the engine has notified that it is safe to do so (between render cycles) + // we allow users to dispose events while the Sequencer is running, as such we delay execution + // until the engine has notified that it has entered its idle phase between renders (meaning: + // it is safe to perform operations that potentially deallocate buffer memory) _engine.executeWhenIdle(() -> { createDrumPattern(); createBassPattern();