Skip to content

Commit

Permalink
Merge pull request #54 from pserwylo/autoplay-melodies
Browse files Browse the repository at this point in the history
Add option to autoplay melodies by mashing keys
  • Loading branch information
pserwylo authored May 24, 2023
2 parents 9f65b3a + d42477f commit 5f03c2f
Show file tree
Hide file tree
Showing 14 changed files with 513 additions and 20 deletions.
35 changes: 25 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 31
compileSdkVersion 33
defaultConfig {
applicationId "com.nicobrailo.pianoli"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 33
versionCode 19
versionName "1.19"
}


buildTypes {
release {
minifyEnabled false
Expand All @@ -28,19 +29,33 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

lintOptions {
disable 'GoogleAppIndexingWarning'
packagingOptions {
jniLibs {
excludes += ['META-INF/*']
}
resources {
excludes += ['META-INF/*']
}
}

packagingOptions {
exclude 'META-INF/*'
namespace 'com.nicobrailo.pianoli'

lint {
disable 'MissingTranslation', 'GoogleAppIndexingWarning'
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.android.support:design:28.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.preference:preference:1.2.0'
implementation "androidx.annotation:annotation:1.6.0"
}

// Not sure why this started happening all of a sudden, but the buidl was failing
// doe to an error similar to this StackOverflow post:
// https://stackoverflow.com/questions/75274720/a-failure-occurred-while-executing-appcheckdebugduplicateclasses/75315276#75315276
configurations.implementation {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nicobrailo.pianoli">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
Expand Down
93 changes: 91 additions & 2 deletions app/src/main/java/com/nicobrailo/pianoli/Piano.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
import android.media.SoundPool;
import android.util.Log;

import com.nicobrailo.pianoli.melodies.Melody;
import com.nicobrailo.pianoli.melodies.MelodyPlayer;
import com.nicobrailo.pianoli.melodies.MultipleSongsMelodyPlayer;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

class Piano {
private static final double KEYS_FLAT_HEIGHT_RATIO = 0.55;
Expand All @@ -22,9 +28,10 @@ class Piano {
private final int keys_height;
private final int keys_flats_height;
private final int keys_count;
private boolean[] key_pressed;
private final boolean[] key_pressed;
private static SoundPool KeySound = null;
private int[] KeySoundIdx;
private MelodyPlayer melody = null;

Piano(final Context context, int screen_size_x, int screen_size_y, final String soundset) {
keys_height = screen_size_y;
Expand All @@ -41,6 +48,11 @@ class Piano {
key_pressed = new boolean[keys_count];
Arrays.fill(key_pressed, false);
selectSoundset(context, soundset);

if (Preferences.areMelodiesEnabled(context)) {
this.melody = new MultipleSongsMelodyPlayer(Preferences.selectedMelodies(context));
this.melody.reset();
}
}

int get_keys_flat_width() {
Expand Down Expand Up @@ -120,6 +132,74 @@ Key get_area_for_flat_key(int key_idx) {
return new Key(x_i, x_i + keys_flat_width, 0, keys_flats_height);
}

private static final Map<String, Integer> note_to_key_idx = new HashMap<>();

static {
note_to_key_idx.put("C1", 0);
note_to_key_idx.put("C#1", 1);
note_to_key_idx.put("Db1", 1);
note_to_key_idx.put("D♭1", 1);
note_to_key_idx.put("D1", 2);
note_to_key_idx.put("D#1", 3);
note_to_key_idx.put("Eb1", 3);
note_to_key_idx.put("E♭1", 3);
note_to_key_idx.put("E1", 4);

note_to_key_idx.put("F1", 6);
note_to_key_idx.put("F#1", 7);
note_to_key_idx.put("Gb1", 7);
note_to_key_idx.put("G♭1", 7);
note_to_key_idx.put("G1", 8);
note_to_key_idx.put("G#1", 9);
note_to_key_idx.put("Ab1", 9);
note_to_key_idx.put("A♭1", 9);
note_to_key_idx.put("A1", 10);
note_to_key_idx.put("A#1", 11);
note_to_key_idx.put("Bb1", 11);
note_to_key_idx.put("B♭1", 11);
note_to_key_idx.put("B1", 12);

note_to_key_idx.put("C2", 14);
note_to_key_idx.put("C#2", 15);
note_to_key_idx.put("Db2", 15);
note_to_key_idx.put("D♭2", 15);
note_to_key_idx.put("D2", 16);
note_to_key_idx.put("D#2", 17);
note_to_key_idx.put("Eb2", 17);
note_to_key_idx.put("E♭2", 17);
note_to_key_idx.put("E2", 18);

note_to_key_idx.put("F2", 20);
note_to_key_idx.put("F#2", 21);
note_to_key_idx.put("Gb2", 21);
note_to_key_idx.put("G♭2", 21);
note_to_key_idx.put("G2", 22);
note_to_key_idx.put("G#2", 23);
note_to_key_idx.put("Ab2", 23);
note_to_key_idx.put("A♭2", 23);
note_to_key_idx.put("A2", 24);
note_to_key_idx.put("A#2", 25);
note_to_key_idx.put("Bb2", 25);
note_to_key_idx.put("B♭2", 25);
note_to_key_idx.put("B2", 26);
}

int get_key_idx_from_note(String note) {

Integer key_idx = note_to_key_idx.get(note);
if (key_idx == null) {
Log.w("PianOli::Piano", "Could not find a key corresponding to the note \"" + note + "\".");

// 5 is designated as the special sound T.raw.no_note, so the app wont crash, but it wont
// play a noise either.
return 5;
}

return key_idx;
}



void selectSoundset(final Context context, String soundSetName) {
if (KeySound != null) {
KeySound.release();
Expand Down Expand Up @@ -167,12 +247,21 @@ void selectSoundset(final Context context, String soundSetName) {
}
}

private void play_sound(final int key_idx) {
private void play_sound(int key_idx) {
if (key_idx < 0 || key_idx >= KeySoundIdx.length) {
Log.d("PianOli::Piano", "This shouldn't happen: Sound out of range, key" + key_idx);
return;
}

if (this.melody != null) {
if (!this.melody.hasNextNote()) {
this.melody.reset();
}

String note = this.melody.nextNote();
key_idx = get_key_idx_from_note(note);
}

KeySound.play(KeySoundIdx[key_idx], 1, 1, 1, 0, 1f);
}

Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/com/nicobrailo/pianoli/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,48 @@
import android.preference.PreferenceManager;
import android.util.Log;

import com.nicobrailo.pianoli.melodies.Melody;
import com.nicobrailo.pianoli.melodies.SingleSongMelodyPlayer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Preferences {

private static final String TAG = "Preferences";
private static final String DEFAULT_SOUNDSET = "piano";
private final static String PREF_SELECTED_SOUND_SET = "selectedSoundSet";
private final static String PREF_SELECTED_MELODIES = "selectedMelodies";
private final static String PREF_ENABLE_MELODIES = "enableMelodies";

/**
* If none are selected, then we play all melodies.
* This is counter intuitive from a user perspective ("Why is it playing all the
* melodies when I deselected them all!"), however it is probably more counter intuitive
* than the alternative which is "Why did it not play any melodies when I selected
* 'enable melodies'?").
*/
public static List<Melody> selectedMelodies(Context context) {
final String[] defaultMelodies = context.getResources().getStringArray(R.array.default_selected_melodies);
Set<String> defaultMelodiesSet = new HashSet<>();
Collections.addAll(defaultMelodiesSet, defaultMelodies);
final Set<String> selectedMelodies = PreferenceManager.getDefaultSharedPreferences(context).getStringSet(PREF_SELECTED_MELODIES, defaultMelodiesSet);

final ArrayList<Melody> melodies = new ArrayList<>(selectedMelodies.size());
for (Melody melody : Melody.all) {
if (selectedMelodies.isEmpty() || selectedMelodies.contains(melody.getId())) {
melodies.add(melody);
}
}
return melodies;
}

public static boolean areMelodiesEnabled(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_ENABLE_MELODIES, false);
}

/**
* The sound set is the name of the folder in assets/sounds/soundset_[NAME]
Expand Down
43 changes: 42 additions & 1 deletion app/src/main/java/com/nicobrailo/pianoli/SettingsFragment.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nicobrailo.pianoli;


import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
Expand All @@ -9,8 +10,11 @@

import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.PreferenceFragmentCompat;

import com.nicobrailo.pianoli.melodies.Melody;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -28,12 +32,12 @@ public SettingsFragment() {
public void onCreatePreferences(Bundle savedzInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
loadSounds();
loadMelodies();
}

public void onAttach (@NonNull Context context) {
super.onAttach(context);
this.availableSoundsets = getAvailableSoundsets(context);
loadSounds();
}

private ArrayList<String> getAvailableSoundsets(Context context) {
Expand Down Expand Up @@ -68,6 +72,40 @@ private ArrayList<String> getAvailableSoundsets(Context context) {
return filtList;
}

void loadMelodies() {
MultiSelectListPreference melodies = findPreference("selectedMelodies");
if (melodies != null) {

boolean enabled = getPreferenceManager().getSharedPreferences().getBoolean("enableMelodies", false);
melodies.setEnabled(enabled);

findPreference("enableMelodies").setOnPreferenceChangeListener((preference, newValue) -> {
melodies.setEnabled((Boolean)newValue);
return true;
});

String[] melodyEntries = new String[Melody.all.length];
String[] melodyEntryValues = new String[Melody.all.length];

// Ideally we'd also call setDefaultValue() here too and pass a Set<String>
// containing each melody. However, the system invokes the "persist default valeus"
// before we get here, and thus it never gets respected. Instead that is hardcoded
// in a string-array and referenced directly in root_preferences.xml.

for (int i = 0; i < Melody.all.length; i ++) {
Melody melody = Melody.all[i];
melodyEntryValues[i] = melody.getId();

@SuppressLint("DiscouragedApi")
int stringId = getResources().getIdentifier("melody_" + melody.getId(), "string", requireContext().getPackageName());
melodyEntries[i] = stringId > 0 ? getString(stringId) : melody.getId();
}

melodies.setEntries(melodyEntries);
melodies.setEntryValues(melodyEntryValues);
}
}

void loadSounds() {
ListPreference soundsets = findPreference("selectedSoundSet");
if (soundsets != null) {
Expand All @@ -77,6 +115,8 @@ void loadSounds() {
soundsetEntryValues[i] = availableSoundsets.get(i);

String name = SettingsActivity.SOUNDSET_DIR_PREFIX + availableSoundsets.get(i);

@SuppressLint("DiscouragedApi")
int stringId = getResources().getIdentifier(name, "string", requireContext().getPackageName());
soundsetEntries[i] = stringId > 0 ? getString(stringId) : availableSoundsets.get(i);
}
Expand All @@ -85,4 +125,5 @@ void loadSounds() {
soundsets.setEntryValues(soundsetEntryValues);
}
}

}
Loading

0 comments on commit 5f03c2f

Please sign in to comment.