diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/app/src/main/java/com/lukas/music/song/note/Note.kt b/app/src/main/java/com/lukas/music/song/note/Note.kt
index d3a588d..17bfb2b 100644
--- a/app/src/main/java/com/lukas/music/song/note/Note.kt
+++ b/app/src/main/java/com/lukas/music/song/note/Note.kt
@@ -28,6 +28,22 @@
return this + (-other)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Note
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+
companion object {
val NOTES = Array(128) { Note(it) }
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/app/src/main/java/com/lukas/music/song/note/Note.kt b/app/src/main/java/com/lukas/music/song/note/Note.kt
index d3a588d..17bfb2b 100644
--- a/app/src/main/java/com/lukas/music/song/note/Note.kt
+++ b/app/src/main/java/com/lukas/music/song/note/Note.kt
@@ -28,6 +28,22 @@
return this + (-other)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Note
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+
companion object {
val NOTES = Array(128) { Note(it) }
diff --git a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
index 3990554..4706068 100644
--- a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
@@ -14,9 +14,16 @@
import com.lukas.music.song.note.Note
class BassVoice(instrument: Instrument) : Voice(instrument) {
- override val steps = listOf(1, 3)
+ override var noteActive: Array> = arrayOf(
+ arrayOf(true),
+ arrayOf(false),
+ arrayOf(true),
+ arrayOf(false)
+ )
- override fun step(root: Note, chord: Array) {
- instrument.startNote(chord[0] - 24)
+ override val noteCount: Int = 1
+
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return arrayOf(chordNotes[0] - 24)
}
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/app/src/main/java/com/lukas/music/song/note/Note.kt b/app/src/main/java/com/lukas/music/song/note/Note.kt
index d3a588d..17bfb2b 100644
--- a/app/src/main/java/com/lukas/music/song/note/Note.kt
+++ b/app/src/main/java/com/lukas/music/song/note/Note.kt
@@ -28,6 +28,22 @@
return this + (-other)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Note
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+
companion object {
val NOTES = Array(128) { Note(it) }
diff --git a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
index 3990554..4706068 100644
--- a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
@@ -14,9 +14,16 @@
import com.lukas.music.song.note.Note
class BassVoice(instrument: Instrument) : Voice(instrument) {
- override val steps = listOf(1, 3)
+ override var noteActive: Array> = arrayOf(
+ arrayOf(true),
+ arrayOf(false),
+ arrayOf(true),
+ arrayOf(false)
+ )
- override fun step(root: Note, chord: Array) {
- instrument.startNote(chord[0] - 24)
+ override val noteCount: Int = 1
+
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return arrayOf(chordNotes[0] - 24)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
index fc5362d..ab7117f 100644
--- a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
@@ -14,12 +14,15 @@
import com.lukas.music.song.note.Note
class ChordVoice(instrument: Instrument) : Voice(instrument) {
- override val steps: List = listOf(2, 4)
+ override var noteActive: Array> = arrayOf(
+ Array(3) { false },
+ Array(3) { true },
+ Array(3) { false },
+ Array(3) { true },
+ )
+ override val noteCount: Int = 3
- override fun step(root: Note, chord: Array) {
- instrument.stop()
- for (note in chord) {
- instrument.startNote(note)
- }
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return chordNotes
}
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/app/src/main/java/com/lukas/music/song/note/Note.kt b/app/src/main/java/com/lukas/music/song/note/Note.kt
index d3a588d..17bfb2b 100644
--- a/app/src/main/java/com/lukas/music/song/note/Note.kt
+++ b/app/src/main/java/com/lukas/music/song/note/Note.kt
@@ -28,6 +28,22 @@
return this + (-other)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Note
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+
companion object {
val NOTES = Array(128) { Note(it) }
diff --git a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
index 3990554..4706068 100644
--- a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
@@ -14,9 +14,16 @@
import com.lukas.music.song.note.Note
class BassVoice(instrument: Instrument) : Voice(instrument) {
- override val steps = listOf(1, 3)
+ override var noteActive: Array> = arrayOf(
+ arrayOf(true),
+ arrayOf(false),
+ arrayOf(true),
+ arrayOf(false)
+ )
- override fun step(root: Note, chord: Array) {
- instrument.startNote(chord[0] - 24)
+ override val noteCount: Int = 1
+
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return arrayOf(chordNotes[0] - 24)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
index fc5362d..ab7117f 100644
--- a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
@@ -14,12 +14,15 @@
import com.lukas.music.song.note.Note
class ChordVoice(instrument: Instrument) : Voice(instrument) {
- override val steps: List = listOf(2, 4)
+ override var noteActive: Array> = arrayOf(
+ Array(3) { false },
+ Array(3) { true },
+ Array(3) { false },
+ Array(3) { true },
+ )
+ override val noteCount: Int = 3
- override fun step(root: Note, chord: Array) {
- instrument.stop()
- for (note in chord) {
- instrument.startNote(note)
- }
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return chordNotes
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/voice/Voice.kt b/app/src/main/java/com/lukas/music/song/voice/Voice.kt
index f7fca94..4f56c2a 100644
--- a/app/src/main/java/com/lukas/music/song/voice/Voice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/Voice.kt
@@ -12,9 +12,36 @@
import com.lukas.music.instruments.Instrument
import com.lukas.music.song.note.Note
+import kotlin.reflect.KClass
abstract class Voice(val instrument: Instrument) {
- abstract val steps: List
+ abstract var noteActive: Array>
+ abstract val noteCount: Int
- abstract fun step(root: Note, chord: Array)
+ abstract fun getNotes(root: Note, chordNotes: Array): Array
+
+ fun step(root: Note, chordNotes: Array, beat: Int) {
+ if (instrument.muted) {
+ return
+ }
+ val activeNotes = noteActive[beat]
+ val notes = getNotes(root, chordNotes)
+ for ((index, active) in activeNotes.withIndex()) {
+ val note = notes[index]
+ if (!active) {
+ instrument.stopNote(note)
+ continue
+ }
+ instrument.startNote(note)
+ }
+ }
+
+ companion object {
+ val DEFAULT_VOICES = listOf>(
+ BassVoice::class,
+ ChordVoice::class,
+ )
+
+ val DEFAULT_VOICE_NAMES = listOf("Bass", "Chord")
+ }
}
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 97f8bf5..3d886c4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,8 @@
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 35173c4..97ec2a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,12 +56,12 @@
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20-Beta'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp
index dc80bd1..8e98b44 100644
--- a/app/src/main/cpp/AudioHost.cpp
+++ b/app/src/main/cpp/AudioHost.cpp
@@ -15,6 +15,9 @@
}
AudioHost *thiz = static_cast(userData);
for (auto const &instrument: *thiz->instruments) {
+ if (!instrument) {
+ continue;
+ }
instrument->render(buffer, sampleCount);
}
for (uint32_t i = 0; i < sampleCount; i++) {
diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp
index 6ebd2b0..5505a35 100644
--- a/app/src/main/cpp/JavaFunctions.cpp
+++ b/app/src/main/cpp/JavaFunctions.cpp
@@ -8,7 +8,7 @@
static AudioHost *audioHost;
-template
+template
void *listGet(_InputIterator iterator, uint32_t n) {
for (uint32_t i = 0; i < n; i++) {
iterator++;
@@ -16,6 +16,18 @@
return *iterator;
}
+Instrument *getInstrument(uint32_t id) {
+ return static_cast(listGet(audioHost->instruments->begin(), id));
+}
+
+template
+void listSet(_InputIterator iterator, uint32_t n, void *value) {
+ for (uint32_t i = 0; i < n; i++) {
+ iterator++;
+ }
+ *iterator = static_cast(value);
+}
+
extern "C" {
JNIEXPORT void JNICALL
@@ -23,7 +35,6 @@
audioHost = new AudioHost();
}
-extern "C"
JNIEXPORT jint JNICALL
Java_com_lukas_music_instruments_InternalInstrument_createInstrument(JNIEnv *env, jobject thiz) {
uint32_t result = audioHost->instruments->size();
@@ -32,43 +43,37 @@
return result;
}
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_lukas_music_instruments_InternalInstrument_setInstrumentActive(JNIEnv *env, jobject thiz,
- jint id, jboolean active) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->wave->amplitude = active ? 0.3 : 0.0;
-}
-}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_startNote(JNIEnv *env, jobject thiz,
jint id, jdouble frequency) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->startNote(frequency);
+ getInstrument(id)->startNote(frequency);
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_endNote(JNIEnv *env, jobject thiz,
jint id) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->endNote();
+ getInstrument(id)->endNote();
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_ui_fragments_PlayFragment_setMasterVolume(JNIEnv *env, jobject thiz,
jdouble volume) {
audioHost->masterVolume = volume;
}
-extern "C"
JNIEXPORT void JNICALL
Java_com_lukas_music_instruments_InternalInstrument_setInstrumentWaveform(JNIEnv *env, jobject thiz,
jint id, jint waveform) {
- Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(),
- id));
- instrument->setWaveform(static_cast(waveform));
+ getInstrument(id)->setWaveform(static_cast(waveform));
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_setVolume(JNIEnv *env, jobject thiz, jint id,
+ jfloat volume) {
+ getInstrument(id)->wave->amplitude = volume;
+}
+
+JNIEXPORT void JNICALL
+Java_com_lukas_music_instruments_InternalInstrument_destroy(JNIEnv *env, jobject thiz, jint id) {
+ listSet(audioHost->instruments->begin(), id, nullptr);
+ delete getInstrument(id);
+}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/EditVoiceFragment.kt b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
new file mode 100644
index 0000000..56f21e6
--- /dev/null
+++ b/app/src/main/java/com/lukas/music/EditVoiceFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * 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
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TableRow
+import androidx.core.view.setMargins
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
+import com.lukas.music.databinding.FragmentEditVoiceBinding
+import com.lukas.music.song.Song
+import com.lukas.music.song.voice.Voice
+import com.lukas.music.util.ArrayProperty
+import com.lukas.music.util.setupToggle
+
+class EditVoiceFragment(private val voice: Voice) : DialogFragment() {
+ private lateinit var binding: FragmentEditVoiceBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentEditVoiceBinding.inflate(inflater)
+ for (row in 0 until voice.noteCount) {
+ val rowLayout = TableRow(binding.root.context)
+ for (column in 0 until Song.currentSong.beats) {
+ val button = MaterialButton(binding.root.context)
+ button.layoutParams = buttonLayout
+ button.setupToggle(ArrayProperty(voice.noteActive[column], row), R.color.blue)
+ rowLayout.addView(button)
+ }
+ binding.noteGrid.addView(rowLayout)
+ }
+ binding.noteGrid.isStretchAllColumns = true
+ binding.closeButton.setOnClickListener {
+ dismiss()
+ }
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ companion object {
+ val buttonLayout = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT)
+
+ init {
+ buttonLayout.setMargins(5)
+ }
+ }
+}
\ 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 8ad90c6..92a896c 100644
--- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt
@@ -10,42 +10,22 @@
package com.lukas.music.instruments
-import com.lukas.music.databinding.FragmentInstrumentBinding
import com.lukas.music.song.note.Note
import com.lukas.music.song.voice.BassVoice
-import com.lukas.music.song.voice.ChordVoice
import com.lukas.music.song.voice.Voice
-abstract class Instrument(private var name: String) {
- private var active = false
+abstract class Instrument(var name: String) {
+ var voice: Voice = BassVoice(this)
abstract var waveform: Waveform
-
- fun applyToView(binding: FragmentInstrumentBinding) {
- binding.instrumentNameText.text = name
- binding.editInstrumentButton.setOnClickListener {
- println("click instrument $name")
- }
- binding.activeSwitch.setOnCheckedChangeListener { _, newActive ->
- active = newActive
- changeActive(newActive)
- }
- binding.activeSwitch.isChecked = active
- }
+ abstract var volume: Float
+ abstract var muted: Boolean
abstract fun startNote(note: Note)
abstract fun stop()
- abstract fun changeActive(newActive: Boolean)
+ abstract fun stopNote(note: Note)
+ abstract fun destroy()
companion object {
- val instruments =
- mutableListOf(
- MonoInstrument("Bass"),
- PolyInstrument("Chords"),
- )
-
- val voice = mutableListOf(
- BassVoice(instruments[0]),
- ChordVoice(instruments[1]),
- )
+ val instruments = mutableListOf()
}
}
\ No newline at end of file
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 dc64bcc..b4c68a4 100644
--- a/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/InternalInstrument.kt
@@ -10,33 +10,67 @@
package com.lukas.music.instruments
+import com.lukas.music.song.note.Note
+
class InternalInstrument {
private val id = createInstrument()
- var active: Boolean = false
- set(value) {
- field = value
- setInstrumentActive(id, value)
- }
+ var note: Note? = null
var waveform: Waveform = Waveform.SINE
set(value) {
field = value
setInstrumentWaveform(id, value.id)
- // this is to resend the setInstrumentActive for the new waveform in the internal c++ code
- active = active
+ refresh()
}
- fun startNote(frequency: Double) {
- startNote(id, frequency)
+ var volume: Float = 0.3f
+ set(value) {
+ field = value
+ if (!muted) {
+ actualVolume = value
+ }
+ }
+
+ var muted: Boolean = false
+ set(value) {
+ field = value
+ actualVolume = if (value) 0.0f else volume
+ }
+
+ private var actualVolume: Float = 1.0f
+ set(value) {
+ field = value
+ setVolume(id, value)
+ }
+
+ init {
+ refresh()
+ }
+
+ private fun refresh() {
+ // this is to resend the old information to the internal c++ code (when changing the waveform)
+ muted = muted
+ volume = volume
+ }
+
+ fun startNote(note: Note) {
+ this.note = note
+ startNote(id, note.frequency)
}
fun endNote() {
+ note = null
endNote(id)
}
+ fun destroy() {
+ destroy(id)
+ }
+
private external fun createInstrument(): Int
- private external fun setInstrumentActive(id: Int, isActive: Boolean)
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)
}
\ 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 e7b7464..2b43524 100644
--- a/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/MonoInstrument.kt
@@ -21,15 +21,36 @@
internalInstrument.waveform = value
}
- override fun startNote(note: Note) {
- internalInstrument.startNote(note.frequency)
- }
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ internalInstrument.volume = volume
+ }
- override fun changeActive(newActive: Boolean) {
- internalInstrument.active = newActive
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ internalInstrument.muted = value
+ }
+
+ override fun startNote(note: Note) {
+ if (note == internalInstrument.note) {
+ return
+ }
+ internalInstrument.startNote(note)
}
override fun stop() {
internalInstrument.endNote()
}
+
+ override fun stopNote(note: Note) {
+ if (note == internalInstrument.note) {
+ stop()
+ }
+ }
+
+ override fun destroy() {
+ internalInstrument.destroy()
+ }
}
\ 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 9d8b4f9..26e4fbb 100644
--- a/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
+++ b/app/src/main/java/com/lukas/music/instruments/PolyInstrument.kt
@@ -24,27 +24,55 @@
}
}
+ override var volume: Float = 1.0f
+ set(value) {
+ field = value
+ for (internalInstrument in internalInstruments) {
+ internalInstrument.volume = volume
+ }
+ }
+
+ override var muted: Boolean = false
+ set(value) {
+ field = value
+ for (instrument in internalInstruments) {
+ instrument.muted = value
+ }
+ }
+
override fun startNote(note: Note) {
for ((index, instrumentPlaying) in playing.withIndex()) {
if (!instrumentPlaying) {
- internalInstruments[index].startNote(note.frequency)
+ internalInstruments[index].startNote(note)
playing[index] = true
return
}
+ if (internalInstruments[index].note == note) {
+ return
+ }
}
throw IllegalStateException("cannot start another note with the current amount of oscillators")
}
- override fun changeActive(newActive: Boolean) {
- for (instrument in internalInstruments) {
- instrument.active = newActive
- }
- }
-
override fun stop() {
for ((i, instrument) in internalInstruments.withIndex()) {
instrument.endNote()
playing[i] = false
}
}
+
+ override fun stopNote(note: Note) {
+ for ((i, instrument) in internalInstruments.withIndex()) {
+ if (instrument.note == note) {
+ instrument.endNote()
+ playing[i] = false
+ }
+ }
+ }
+
+ override fun destroy() {
+ for (instrument in internalInstruments) {
+ instrument.destroy()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index a3123f2..fc5421b 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -16,10 +16,33 @@
import com.lukas.music.util.Cycle
class Song(
- var root: Note,
+ root: Note,
val beats: Int
) : Cycle(beats) {
val chordProgression = ChordProgression()
+ var soloInstrument: Instrument? = null
+ set(value) {
+ field = value
+ value?.let {
+ for (instrument in Instrument.instruments) {
+ if (instrument != value) {
+ instrument.stop()
+ }
+ }
+ }
+ }
+
+ var root: Note = root
+ set(value) {
+ field = value
+ stopAllInstruments()
+ }
+
+ private fun stopAllInstruments() {
+ for (instrument in Instrument.instruments) {
+ instrument.stop()
+ }
+ }
init {
for (i in 0 until beats) {
@@ -27,6 +50,7 @@
}
wraparoundListeners += {
chordProgression.step()
+ stopAllInstruments()
}
}
@@ -34,8 +58,12 @@
super.step()
val chord = chordProgression.currentItem?.currentItem ?: return index
val chordNotes = chord.getNotes(root)
- for (voice in Instrument.voice) {
- voice.step(root, chordNotes)
+ soloInstrument?.let {
+ it.voice.step(root, chordNotes, index)
+ } ?: run {
+ for (instrument in Instrument.instruments) {
+ instrument.voice.step(root, chordNotes, index)
+ }
}
return index
}
diff --git a/app/src/main/java/com/lukas/music/song/chords/Chord.kt b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
index db45fdd..4400dae 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Chord.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Chord.kt
@@ -19,6 +19,12 @@
interval = Interval(value)
}
var interval = Interval(note)
+ set(value) {
+ field = value
+ if (note != value.distance) {
+ note = value.distance
+ }
+ }
fun getNotes(root: Note): Array {
return Array(chordType.notes.size) { root + note + chordType.notes[it] }
diff --git a/app/src/main/java/com/lukas/music/song/chords/Interval.kt b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
index 2726787..d112805 100644
--- a/app/src/main/java/com/lukas/music/song/chords/Interval.kt
+++ b/app/src/main/java/com/lukas/music/song/chords/Interval.kt
@@ -10,7 +10,7 @@
package com.lukas.music.song.chords
-class Interval(private val distance: Int) {
+class Interval(val distance: Int) {
val name: IntervalName = when (distance) {
0 -> IntervalName.UNISON
1, 2 -> IntervalName.SECOND
@@ -32,7 +32,7 @@
return name.toString()
}
- enum class IntervalName(val distance: Int, val romanVersion: String) {
+ enum class IntervalName(private val distance: Int, val romanVersion: String) {
UNISON(0, "I"),
SECOND(1, "II"),
THIRD(3, "III"),
diff --git a/app/src/main/java/com/lukas/music/song/note/Note.kt b/app/src/main/java/com/lukas/music/song/note/Note.kt
index d3a588d..17bfb2b 100644
--- a/app/src/main/java/com/lukas/music/song/note/Note.kt
+++ b/app/src/main/java/com/lukas/music/song/note/Note.kt
@@ -28,6 +28,22 @@
return this + (-other)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Note
+
+ if (id != other.id) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+
companion object {
val NOTES = Array(128) { Note(it) }
diff --git a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
index 3990554..4706068 100644
--- a/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/BassVoice.kt
@@ -14,9 +14,16 @@
import com.lukas.music.song.note.Note
class BassVoice(instrument: Instrument) : Voice(instrument) {
- override val steps = listOf(1, 3)
+ override var noteActive: Array> = arrayOf(
+ arrayOf(true),
+ arrayOf(false),
+ arrayOf(true),
+ arrayOf(false)
+ )
- override fun step(root: Note, chord: Array) {
- instrument.startNote(chord[0] - 24)
+ override val noteCount: Int = 1
+
+ override fun getNotes(root: Note, chordNotes: Array): Array {
+ return arrayOf(chordNotes[0] - 24)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
index fc5362d..ab7117f 100644
--- a/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
+++ b/app/src/main/java/com/lukas/music/song/voice/ChordVoice.kt
@@ -14,12 +14,15 @@
import com.lukas.music.song.note.Note
class ChordVoice(instrument: Instrument) : Voice(instrument) {
- override val steps: List = listOf(2, 4)
+ override var noteActive: Array