diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/lukas/music/util/UIUtil.kt b/app/src/main/java/com/lukas/music/util/UIUtil.kt index e7ba28f..9e2c520 100644 --- a/app/src/main/java/com/lukas/music/util/UIUtil.kt +++ b/app/src/main/java/com/lukas/music/util/UIUtil.kt @@ -39,6 +39,18 @@ this.progress = initialProgress } +fun SeekBar.smartSetup( + min: Int, + max: Int, + target: KMutableProperty0, + callback: (Int) -> Unit +) { + setup(min, max, target.get()) { + target.set(it) + callback(it) + } +} + fun Button.setupToggle( target: KMutableProperty0, activeColor: Int, diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/lukas/music/util/UIUtil.kt b/app/src/main/java/com/lukas/music/util/UIUtil.kt index e7ba28f..9e2c520 100644 --- a/app/src/main/java/com/lukas/music/util/UIUtil.kt +++ b/app/src/main/java/com/lukas/music/util/UIUtil.kt @@ -39,6 +39,18 @@ this.progress = initialProgress } +fun SeekBar.smartSetup( + min: Int, + max: Int, + target: KMutableProperty0, + callback: (Int) -> Unit +) { + setup(min, max, target.get()) { + target.set(it) + callback(it) + } +} + fun Button.setupToggle( target: KMutableProperty0, activeColor: Int, diff --git a/app/src/main/res/layout/fragment_edit_envelope.xml b/app/src/main/res/layout/fragment_edit_envelope.xml new file mode 100644 index 0000000..9e45d05 --- /dev/null +++ b/app/src/main/res/layout/fragment_edit_envelope.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/lukas/music/util/UIUtil.kt b/app/src/main/java/com/lukas/music/util/UIUtil.kt index e7ba28f..9e2c520 100644 --- a/app/src/main/java/com/lukas/music/util/UIUtil.kt +++ b/app/src/main/java/com/lukas/music/util/UIUtil.kt @@ -39,6 +39,18 @@ this.progress = initialProgress } +fun SeekBar.smartSetup( + min: Int, + max: Int, + target: KMutableProperty0, + callback: (Int) -> Unit +) { + setup(min, max, target.get()) { + target.set(it) + callback(it) + } +} + fun Button.setupToggle( target: KMutableProperty0, activeColor: Int, diff --git a/app/src/main/res/layout/fragment_edit_envelope.xml b/app/src/main/res/layout/fragment_edit_envelope.xml new file mode 100644 index 0000000..9e45d05 --- /dev/null +++ b/app/src/main/res/layout/fragment_edit_envelope.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_instrument.xml b/app/src/main/res/layout/fragment_edit_instrument.xml index b5578fc..55e667b 100644 --- a/app/src/main/res/layout/fragment_edit_instrument.xml +++ b/app/src/main/res/layout/fragment_edit_instrument.xml @@ -14,8 +14,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".EditInstrumentFragment"> - - + app:layout_constraintTop_toBottomOf="@+id/editEnvelopeButton" /> + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d886c4..66c49ef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/lukas/music/util/UIUtil.kt b/app/src/main/java/com/lukas/music/util/UIUtil.kt index e7ba28f..9e2c520 100644 --- a/app/src/main/java/com/lukas/music/util/UIUtil.kt +++ b/app/src/main/java/com/lukas/music/util/UIUtil.kt @@ -39,6 +39,18 @@ this.progress = initialProgress } +fun SeekBar.smartSetup( + min: Int, + max: Int, + target: KMutableProperty0, + callback: (Int) -> Unit +) { + setup(min, max, target.get()) { + target.set(it) + callback(it) + } +} + fun Button.setupToggle( target: KMutableProperty0, activeColor: Int, diff --git a/app/src/main/res/layout/fragment_edit_envelope.xml b/app/src/main/res/layout/fragment_edit_envelope.xml new file mode 100644 index 0000000..9e45d05 --- /dev/null +++ b/app/src/main/res/layout/fragment_edit_envelope.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_instrument.xml b/app/src/main/res/layout/fragment_edit_instrument.xml index b5578fc..55e667b 100644 --- a/app/src/main/res/layout/fragment_edit_instrument.xml +++ b/app/src/main/res/layout/fragment_edit_instrument.xml @@ -14,8 +14,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".EditInstrumentFragment"> - - + app:layout_constraintTop_toBottomOf="@+id/editEnvelopeButton" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_voice.xml b/app/src/main/res/layout/fragment_edit_voice.xml index 5380d7d..d9f8eaf 100644 --- a/app/src/main/res/layout/fragment_edit_voice.xml +++ b/app/src/main/res/layout/fragment_edit_voice.xml @@ -16,7 +16,6 @@ android:layout_height="match_parent" tools:context=".ui.fragments.EditVoiceFragment"> - + diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp index 5505a35..d26ee2b 100644 --- a/app/src/main/cpp/JavaFunctions.cpp +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -76,4 +76,20 @@ listSet(audioHost->instruments->begin(), id, nullptr); delete getInstrument(id); } + +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_InternalInstrument_updateEnvelopeParameters(JNIEnv *env, + jobject thiz, jint id, + jfloat attack, + jfloat delay, + jfloat sustain, + jfloat release) { + Instrument *instrument = getInstrument((id)); + Envelope *envelope = instrument->envelope; + envelope->attack = attack; + envelope->delay = delay; + envelope->sustain = sustain; + envelope->release = release; + envelope->update(); +} } \ No newline at end of file diff --git a/app/src/main/cpp/effects/Envelope.cpp b/app/src/main/cpp/effects/Envelope.cpp index f8dfd61..5400548 100644 --- a/app/src/main/cpp/effects/Envelope.cpp +++ b/app/src/main/cpp/effects/Envelope.cpp @@ -3,6 +3,11 @@ #include "Envelope.h" void Envelope::initialize(AudioHost *host) { + this->host = host; + update(); +} + +void Envelope::update() { attackIncrement = 1 / attack / host->sampleRate; delayIncrement = 1 / delay / host->sampleRate; releaseIncrement = 1 / release / host->sampleRate; diff --git a/app/src/main/cpp/effects/Envelope.h b/app/src/main/cpp/effects/Envelope.h index 7076f8f..dbb5575 100644 --- a/app/src/main/cpp/effects/Envelope.h +++ b/app/src/main/cpp/effects/Envelope.h @@ -16,15 +16,16 @@ EnvelopePhase phase; float attackIncrement, delayIncrement, releaseIncrement; float value = 0; + AudioHost *host; public: - float attack = 0.05, delay = 0.2, sustain = 0.75, release = 1; + float attack = 0.05, delay = 0.05, sustain = 0.7, release = 0.1; void initialize(AudioHost *host); + void update(); + void startNote(); - void endNote(); - void doRender(uint32_t sampleCount); }; diff --git a/app/src/main/java/com/lukas/music/instruments/Envelope.kt b/app/src/main/java/com/lukas/music/instruments/Envelope.kt new file mode 100644 index 0000000..6603611 --- /dev/null +++ b/app/src/main/java/com/lukas/music/instruments/Envelope.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.instruments + +class Envelope(val instrument: Instrument) { + var attack: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var delay: Int = 50 + set(value) { + field = value + instrument.updateEnvelope() + } + + var sustain: Int = 70 + set(value) { + field = value + instrument.updateEnvelope() + } + + var release: Int = 100 + set(value) { + field = value + instrument.updateEnvelope() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 92a896c..3013335 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -16,6 +16,7 @@ abstract class Instrument(var name: String) { var voice: Voice = BassVoice(this) + var envelope = Envelope(this) abstract var waveform: Waveform abstract var volume: Float abstract var muted: Boolean @@ -24,6 +25,7 @@ abstract fun stop() abstract fun stopNote(note: Note) abstract fun destroy() + abstract fun updateEnvelope() companion object { val instruments = mutableListOf() diff --git a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt index b4c68a4..946e653 100644 --- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt @@ -13,7 +13,7 @@ import com.lukas.music.song.note.Note class InternalInstrument { - private val id = createInstrument() + val id = createInstrument() var note: Note? = null var waveform: Waveform = Waveform.SINE @@ -67,10 +67,27 @@ destroy(id) } + fun applyEnvelope(envelope: Envelope) { + updateEnvelopeParameters( + id, + envelope.attack.toFloat() / 1000f, + envelope.delay.toFloat() / 1000f, + envelope.sustain.toFloat() / 100f, + envelope.release.toFloat() / 1000f, + ) + } + private external fun createInstrument(): Int private external fun setInstrumentWaveform(id: Int, waveform: Int) private external fun startNote(id: Int, frequency: Double) private external fun endNote(id: Int) private external fun setVolume(id: Int, volume: Float) private external fun destroy(id: Int) + private external fun updateEnvelopeParameters( + id: Int, + attack: Float, + delay: Float, + sustain: Float, + release: Float + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt index 2b43524..a9b5c53 100644 --- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt @@ -53,4 +53,8 @@ override fun destroy() { internalInstrument.destroy() } + + override fun updateEnvelope() { + internalInstrument.applyEnvelope(envelope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt index 26e4fbb..8f055d0 100644 --- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt @@ -75,4 +75,10 @@ instrument.destroy() } } + + override fun updateEnvelope() { + for (instrument in internalInstruments) { + instrument.applyEnvelope(envelope) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt index bd7a3d9..f01d9e4 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditChordFragment.kt @@ -23,7 +23,7 @@ import com.lukas.music.song.chords.Interval import com.lukas.music.util.setup -class EditChordFragment(val chord: Chord, private val songFragment: SongFragment) : +class EditChordFragment(private val chord: Chord, private val songFragment: SongFragment) : DialogFragment() { lateinit var binding: FragmentEditChordBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt new file mode 100644 index 0000000..ea6a855 --- /dev/null +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditEnvelopeFragment.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Lukas Eisenhauer + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package com.lukas.music.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.lukas.music.databinding.FragmentEditEnvelopeBinding +import com.lukas.music.instruments.Envelope +import com.lukas.music.util.smartSetup + +class EditEnvelopeFragment(private val envelope: Envelope) : DialogFragment() { + lateinit var binding: FragmentEditEnvelopeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentEditEnvelopeBinding.inflate(inflater) + binding.attackSeek.smartSetup(5, 200, envelope::attack) { + binding.attackText.text = "Attack: $it ms" + } + binding.delaySeek.smartSetup(5, 200, envelope::delay) { + binding.delayText.text = "Delay: $it ms" + } + binding.sustainSeek.smartSetup(0, 100, envelope::sustain) { + binding.sustainText.text = "Sustain: $it%" + } + binding.releaseSeek.smartSetup(5, 200, envelope::release) { + binding.releaseText.text = "Release: $it ms" + } + binding.closeButton.setOnClickListener { + dismiss() + } + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt index 9c76f59..45d84b9 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/EditInstrumentFragment.kt @@ -64,6 +64,9 @@ binding.editVoiceButton.setOnClickListener { EditVoiceFragment(instrument.voice).showNow(childFragmentManager, "") } + binding.editEnvelopeButton.setOnClickListener { + EditEnvelopeFragment(instrument.envelope).showNow(childFragmentManager, "") + } binding.closeButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/lukas/music/util/UIUtil.kt b/app/src/main/java/com/lukas/music/util/UIUtil.kt index e7ba28f..9e2c520 100644 --- a/app/src/main/java/com/lukas/music/util/UIUtil.kt +++ b/app/src/main/java/com/lukas/music/util/UIUtil.kt @@ -39,6 +39,18 @@ this.progress = initialProgress } +fun SeekBar.smartSetup( + min: Int, + max: Int, + target: KMutableProperty0, + callback: (Int) -> Unit +) { + setup(min, max, target.get()) { + target.set(it) + callback(it) + } +} + fun Button.setupToggle( target: KMutableProperty0, activeColor: Int, diff --git a/app/src/main/res/layout/fragment_edit_envelope.xml b/app/src/main/res/layout/fragment_edit_envelope.xml new file mode 100644 index 0000000..9e45d05 --- /dev/null +++ b/app/src/main/res/layout/fragment_edit_envelope.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_instrument.xml b/app/src/main/res/layout/fragment_edit_instrument.xml index b5578fc..55e667b 100644 --- a/app/src/main/res/layout/fragment_edit_instrument.xml +++ b/app/src/main/res/layout/fragment_edit_instrument.xml @@ -14,8 +14,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".EditInstrumentFragment"> - - + app:layout_constraintTop_toBottomOf="@+id/editEnvelopeButton" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_voice.xml b/app/src/main/res/layout/fragment_edit_voice.xml index 5380d7d..d9f8eaf 100644 --- a/app/src/main/res/layout/fragment_edit_voice.xml +++ b/app/src/main/res/layout/fragment_edit_voice.xml @@ -16,7 +16,6 @@ android:layout_height="match_parent" tools:context=".ui.fragments.EditVoiceFragment"> - Add a new instrument Delete this instrument Edit voice + Edit envelope \ No newline at end of file