diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/here/HereConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/here/HereConstants.java index 53e0a60c..35915e07 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/here/HereConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/here/HereConstants.java @@ -22,5 +22,6 @@ public final class HereConstants { public static final UUID UUID_VOLUME = UUID.fromString(String.format(BASE_HERE_UUID, "e7")); // TODO: Put device in sleep mode, (byte)0x00 sleep, (byte)0x01 wake public static final UUID UUID_SLEEP = UUID.fromString(String.format(BASE_HERE_UUID, "eb")); + public static final UUID UUID_EQ = UUID.fromString(String.format(BASE_HERE_UUID, "e3")); public static final UUID UUID_EFFECTS = UUID.fromString(String.format(BASE_HERE_UUID, "e9")); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffect.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffect.java new file mode 100644 index 00000000..516d3b67 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffect.java @@ -0,0 +1,194 @@ +package nodomain.freeyourgadget.gadgetbridge.entities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.LoggingExceptionHandler; + +/** + * Created by Nicolò Balzarotti on 10/10/17. + */ + +public class AudioEffect implements Serializable { + private static final Logger LOG = LoggerFactory.getLogger(LoggingExceptionHandler.class); + + // Hopefully does not impact too much on performances. Params are usually less than 6 + private List params = new ArrayList(); + private AudioEffectType type; + boolean enable; + + public AudioEffect(AudioEffectType type, int volume) { + if (type == AudioEffectType.VOLUME) { + LOG.debug("Create volume control effect"); + this.type = type; + this.params.add(volume); + this.enable = true; + } else { + LOG.error("This constructor (effect, int) works only for volume!"); + LOG.debug("Effect passed: " + type.name()); + } + } + + public AudioEffect(AudioEffectType type, boolean enable, List params) { + this.type = type; + this.enable = enable; + this.params = params; + } + + public AudioEffect(AudioEffectType type, boolean enable) { + AudioEffect e = new AudioEffect(type, enable, getDefaultParams(type)); + this.type = e.type; + this.enable = e.enable; + this.params = e.params; + } + + public void addParameter(Object o) { + this.params.add(o); + } + + ; + + public AudioEffectType getType() { + return this.type; + } + + public byte[] toByteMessage() { + List message = new ArrayList<>(); + if (enable) { + if (type != AudioEffectType.VOLUME) { + message.add((byte) (type.getId() + 0x80)); + } + } else if (type != AudioEffectType.VOLUME) { + // Disable just by sending the effect id + message.add((byte) type.getId()); + return byteListToArray(message); + } + + LOG.debug("Converting parameters"); + for (Object param : params) { + if (param instanceof String && ((String) param).equals("3byte")) { + for (int i = 0; i < 3; ++i) { + message.add((byte) 0x00); + } + } else if (param instanceof Float) { + for (byte b : intToBytes(little2big((float) param))) { + message.add(b); + } + } else if (param instanceof Double) { + for (byte b : intToBytes24(float32toQ24((float) ((double) param)))) { + message.add(b); + } + } else if (param instanceof Integer && type == AudioEffectType.VOLUME) { + LOG.debug("The volume int value is " + param); + message.add((byte) ((int) param)); + } else { + LOG.error("Programming error, unknown parameter"); + } + } + + return byteListToArray(message); + } + + private static List getDefaultParams(AudioEffectType type) { + LOG.debug("Getting default parameters for effect of type " + type.name()); + + List params = new ArrayList(); + switch (type) { + case ECHO: + params.add("3byte"); + params.add(0.3f); // delay time + params.add(0.4f); // level + break; + case REVERB: + params.add("3byte"); + params.add(1.8f); + params.add(1.2f); + params.add(0.015f); + break; + case CHORUS: + params.add(0.5); // Q24 rate + params.add(0.02f); // depth + params.add(0.02f); // delay + params.add(0.7f); // wet + params.add(0.7f); // dry + break; + case NOISEMASK: + params.add("3byte"); + params.add(0.1f); // wet + params.add(0.0f); // dry + params.add(0.0f); // seed (random int) + break; + case FUZZ: + params.add("3byte"); + params.add(0.05f); // cutoff + params.add(0.02f); // wet + params.add(0.02f); // dry + params.add(60.0f); // ramp factor + break; + case FLANGE: + params.add(0.3625); // rate (double converted to FixedP24) + params.add(0.009f); // depth + params.add(0.0f); // delay + params.add(-0.7f); // wet (left) + // params.add(0.7f); // wet (right) // we'll set it differently when we'll manage + // L/R separately + params.add(1.0f); // dry + break; + case BASSBOOST: + params.add("3byte"); + params.add(-10.0f); // treble gain + params.add(10.0f); // global gain + break; + case BITCRUSHER: + params.add("3byte"); + params.add(65536.0f); // bit num + params.add(12000.0f); // bit freq + break; + default: + LOG.warn("Programming error! Enabled a non-existent effect (" + + type.name() + "!"); + } + return params; + } + + // ADD ONLY THE FIRST 3 byte! (24bit!) + private static byte[] intToBytes24(int i) { + return new byte[]{ + (byte) ((i >>> 0) & 0xff), + (byte) ((i >>> 8) & 0xff), // 8 and 16 might be inverted + (byte) ((i >>> 16) & 0xff) + }; + } + + private static byte[] intToBytes(int i) { + return new byte[]{ + (byte) (i >>> 24), + (byte) (i >>> 16), + (byte) (i >>> 8), + (byte) i + }; + } + + private static int little2big(float f) { + return Integer.reverseBytes(Float.floatToRawIntBits(f)); + } + + private static int float32toQ24(float f) { + int i = (int) (f * 16777216); // 2^24 + LOG.debug(Integer.toHexString(i)); + i = (i >> 8) | (i << 16); + return i; + } + + private byte[] byteListToArray(List message) { + byte[] byteMessage = new byte[message.size()]; + for (int i = 0; i < message.size(); ++i) { + byteMessage[i] = message.get(i); + } + return byteMessage; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffectType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffectType.java new file mode 100644 index 00000000..badc40fc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AudioEffectType.java @@ -0,0 +1,44 @@ +package nodomain.freeyourgadget.gadgetbridge.entities; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Nicolò Balzarotti on 10/9/17. + */ + +public enum AudioEffectType { + // Volume + VOLUME(0, 0), + // Equalizer + EQ(10, 0), + // Effects + ECHO(20, 1), REVERB(21, 2), NOISEMASK(22, 3), FUZZ(23, 4), + // TAPE(24, 5), // disabled in real app too, probably not implemented + FLANGE(26, 7), + CHORUS(27, 8), + BITCRUSHER(28, 9), + BASSBOOST(29, 10); + + private final int key; + private final int effect_id; + + public int getKey() { return key; } + + public int getId() { return effect_id; } + + AudioEffectType(int key, int effect_id) { + this.key = key; + this.effect_id = effect_id; + } + + public static AudioEffectType getByEffectId(int id) { + for (AudioEffectType e : AudioEffectType.values()) { + if (e.effect_id == id) { + return e; + } + } + return null;// not found + } +} +