diff --git a/app/src/main/java/linc/com/amplituda/Amplituda.java b/app/src/main/java/linc/com/amplituda/Amplituda.java index 2b3380c..9f80b84 100644 --- a/app/src/main/java/linc/com/amplituda/Amplituda.java +++ b/app/src/main/java/linc/com/amplituda/Amplituda.java @@ -1,408 +1,160 @@ package linc.com.amplituda; import android.content.Context; -import android.text.TextUtils; -import android.util.Log; import android.webkit.URLUtil; import java.io.File; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import linc.com.amplituda.callback.ErrorListener; -import linc.com.amplituda.callback.ListCallback; -import linc.com.amplituda.callback.StringCallback; + import linc.com.amplituda.exceptions.*; import linc.com.amplituda.exceptions.io.*; -import linc.com.amplituda.exceptions.processing.*; public final class Amplituda { - public static final int SINGLE_LINE_SEQUENCE_FORMAT = 0; - public static final int NEW_LINE_SEQUENCE_FORMAT = 1; - - public static final int SECONDS = 2; - public static final int MILLIS = 3; + private final FileManager fileManager; - private static final String OPERATION_PROCESSING = "Processing"; - private static final String OPERATION_PREPARING = "Preparing"; + public Amplituda(final Context context) { + fileManager = new FileManager(context); + } - private final ErrorListener errorListener; - private final FileManager fileManager; - private final List errors = new LinkedList<>(); - - private String amplitudes; - - private Amplituda( - ErrorListener errorListener, - FileManager fileManager, - final int priority, - final boolean enable - ) { - this.errorListener = errorListener; - this.fileManager = fileManager; - AmplitudaLogger.priority(priority); + /** + * Enable Amplituda logs for mor processing information + * @param priority - android Log constant. For example Log.DEBUG + * @param enable - turn on / off logs + */ + public Amplituda setLogConfig(final int priority, final boolean enable) { AmplitudaLogger.enable(enable); + AmplitudaLogger.priority(priority); + return this; } /** * Calculate amplitudes from file * @param audio - source file */ - public synchronized Amplituda fromFile(final File audio) { - // Clear previous data when Amplituda used repeatedly - clearPreviousAmplitudaData(); - + private synchronized AmplitudaResultJNI processFileJNI( + final File audio, + final InputAudio inputAudio + ) throws AmplitudaException { // Process audio if(!audio.exists()) { - throwException(new FileNotFoundException()); - } else { - if(!fileManager.isAudioFile(audio.getPath())) { - throwException(new FileOpenException()); - return this; - } + throw new FileNotFoundException(); + } - // Save start time - long startTime = System.currentTimeMillis(); + if(!fileManager.isAudioFile(audio.getPath())) { + throw new FileOpenException(); + } - // Save current audio path - fileManager.retainPath(audio.getPath()); + // Save start time + long startTime = System.currentTimeMillis(); - // Process input audio - AmplitudaResultJNI result = amplitudesFromAudioJNI(audio.getPath()); + // Process input audio + AmplitudaResultJNI result = amplitudesFromAudioJNI(audio.getPath()); - // Copy result data - amplitudes = result.getAmplitudes(); - errors.addAll(result.getErrors()); + // Save audio duration when file is valid and was processed + inputAudio.setDuration(fileManager.getAudioDuration(audio.getPath())); - // Emit all exceptions after subscribe - handleAmplitudaErrors(); + // Log operation time + AmplitudaLogger.logOperationTime(AmplitudaLogger.OPERATION_PROCESSING, startTime); + return result; + } - // Log operation time - AmplitudaLogger.logOperationTime(OPERATION_PROCESSING, startTime); + public AmplitudaProcessingOutput processAudio(final File audio) { + InputAudio inputAudio = new InputAudio<>(audio, InputAudio.Type.FILE); + try { + return new AmplitudaProcessingOutput<>(processFileJNI(audio, inputAudio), inputAudio); + } catch (AmplitudaException exception) { + // Handle processing error + return new AmplitudaProcessingOutput<>(exception, inputAudio); } - return this; } /** * Calculate amplitudes from file - * @param audio - path or url to input audio file - */ - public Amplituda fromFile(final String audio) { - if(URLUtil.isValidUrl(audio)) { - if(!fileManager.cacheNotNull()) { - throwException(new ExtendedProcessingDisabledException()); - return this; + * @param audio - local path or url to input audio file + */ + public AmplitudaProcessingOutput processAudio(final String audio) { + InputAudio inputAudio = new InputAudio<>(audio); + + try { + // When audio is local file - process as file + if(!URLUtil.isValidUrl(audio)) { + inputAudio.setType(InputAudio.Type.PATH); + return new AmplitudaProcessingOutput<>( + processFileJNI(new File(audio), inputAudio), + inputAudio + ); } + // When audio is URL + inputAudio.setType(InputAudio.Type.URL); // Save start time long startTime = System.currentTimeMillis(); // Copy audio from url to local storage File tempAudio = fileManager.getUrlFile(audio); + + // Check for success copy operation from url to local tmp if(tempAudio == null) { - throwException(new InvalidAudioUrlException()); - return this; + return new AmplitudaProcessingOutput<>( + new InvalidAudioUrlException(), + inputAudio + ); } // Log operation time - AmplitudaLogger.logOperationTime(OPERATION_PREPARING, startTime); + AmplitudaLogger.logOperationTime(AmplitudaLogger.OPERATION_PREPARING, startTime); // Process local audio - fromFile(tempAudio); + AmplitudaResultJNI result = processFileJNI(tempAudio, inputAudio); + + // Remove tmp file fileManager.deleteFile(tempAudio); - } else { - fromFile(new File(audio)); + + return new AmplitudaProcessingOutput<>(result, inputAudio); + } catch (AmplitudaException exception) { + // Handle processing error + return new AmplitudaProcessingOutput<>(exception, inputAudio); } - return this; } /** * Calculate amplitudes from file - * @param rawId - path to source file + * @param audio - path to res/raw source file */ - public Amplituda fromFile(final int rawId) { - if(!fileManager.cacheNotNull()) { - throwException(new ExtendedProcessingDisabledException()); - return this; - } + public AmplitudaProcessingOutput processAudio(final int audio) { + InputAudio inputAudio = new InputAudio<>(audio, InputAudio.Type.RESOURCE); // Save start time long startTime = System.currentTimeMillis(); // Copy raw to local file - File tempAudio = fileManager.getRawFile(rawId); + File tempAudio = fileManager.getRawFile(audio); + + // Check for success copy operation from res to local tmp if(tempAudio == null) { - throwException(new InvalidRawResourceException()); - return this; + return new AmplitudaProcessingOutput<>( + new InvalidRawResourceException(), + inputAudio + ); } // Log operation time - AmplitudaLogger.logOperationTime(OPERATION_PREPARING, startTime); - - // Process local raw file - fromFile(tempAudio); - fileManager.deleteFile(tempAudio); - - return this; - } - - /** - * Calculate amplitudes from file - * @param audio - path or url to input audio file - * Please use `fromFile(final String audio)` instead of this method - * - * ONLY TO SUPPORT WaveformSeekBar library 3.0.0 version with new Ampituda versions - * [https://github.com/massoudss/waveformSeekBar] - */ - @Deprecated - public Amplituda fromPath(final String audio) { - fromFile(audio); - return this; - } - - /** - * Convert result amplitudes to List - * @param listCallback - result callback - */ - public Amplituda amplitudesAsList(final ListCallback listCallback) { - if(amplitudes == null || amplitudes.isEmpty()) - return this; - - String[] log = amplitudes.split("\n"); - List amplitudes = new ArrayList<>(); - - for (String amplitude : log) { - if(amplitude.isEmpty()) { - break; - } - amplitudes.add(Integer.valueOf(amplitude)); - } - listCallback.call(amplitudes); - return this; - } - - /** - * Convert result amplitudes to JSON format - * @param jsonCallback - result callback - */ - public Amplituda amplitudesAsJson(final StringCallback jsonCallback) { - if(amplitudes == null || amplitudes.isEmpty()) - return this; - jsonCallback.call("[" + amplitudesToSingleLineSequence(amplitudes, ", ") + "]"); - return this; - } - - /** - * Overload for amplitudesAsSequence method. Use space (" ") as a default delimiter - * @param format - output format: single line or multiline output string - * @param stringCallback - result callback - */ - public Amplituda amplitudesAsSequence(final int format, final StringCallback stringCallback) { - if(amplitudes == null || amplitudes.isEmpty()) - return this; + AmplitudaLogger.logOperationTime(AmplitudaLogger.OPERATION_PREPARING, startTime); - amplitudesAsSequence(format, " ", stringCallback); - return this; - } - - /** - * Convert result amplitudes to single line string with custom delimiter and send result to user via stringCallback - * @param format - output format: single line or multiline output string - * @param singleLineDelimiter - delimiter between amplitudes. WARNING: this parameter will be ignored when NEW_LINE_SEQUENCE_FORMAT passed as a parameter - * @param stringCallback - result callback - */ - public Amplituda amplitudesAsSequence( - final int format, - final String singleLineDelimiter, - final StringCallback stringCallback - ) { - if(amplitudes == null || amplitudes.isEmpty()) - return this; - - switch (format) { - case SINGLE_LINE_SEQUENCE_FORMAT: stringCallback.call(amplitudesToSingleLineSequence( - amplitudes, - singleLineDelimiter - )); break; - case NEW_LINE_SEQUENCE_FORMAT: stringCallback.call(amplitudes); break; - default: throwException(new InvalidParameterFlagException()); break; - } - return this; - } + try { + // Process local raw file + AmplitudaResultJNI result = processFileJNI(tempAudio, inputAudio); - /** - * Extracts list of amplitudes per specific second - * @param second - specific second from input file - * @param listCallback - result callback - */ - public Amplituda amplitudesForSecond(final int second, final ListCallback listCallback) { - amplitudesAsList(new ListCallback() { - @Override - public void call(List data) { - int duration = (int) getDuration(SECONDS); - int aps = data.size() / duration; // amplitudes per second - // Use second as a map key - int currentSecond = 0; - // Map with format = Map - Map> amplitudes = new LinkedHashMap<>(); - // Temporary amplitudes list - List amplitudesPerSecond = new ArrayList<>(); - - for(int sampleIndex = 0; sampleIndex < data.size(); sampleIndex++) { - if(sampleIndex % aps == 0) { // Save all amplitudes when current frame index equals to aps - // Save amplitudes to map - amplitudes.put(currentSecond, new ArrayList<>(amplitudesPerSecond)); - // Clear temporary amplitudes - amplitudesPerSecond.clear(); - // Increase current second - currentSecond++; - } else { - // Add amplitude to temporary list - amplitudesPerSecond.add(data.get(sampleIndex)); - } - } - - if(second > duration) { - throwException(new SecondOutOfBoundsException(second, duration)); - } else { - listCallback.call(amplitudes.get(second)); - } - } - }); - return this; - } - - /** - * Merge result amplitudes according to samplesPerSecond - * @param samplesPerSecond - number of samples per audio second - * For example: - * audio duration = 200 seconds - * after Amplituda processing, 1 second contains 40 samples - * 200 seconds contains 200 * 40 = 8000 - * case 1: samplesPerSecond = 1, function will merge this 40 samples to 1. - * Output size will be 200 amplitudes - * case 2: samplesPerSecond = 20, function will merge this 40 samples to 20. - * Output size will be 4000 amplitudes - * Advantage: small output size - * Disadvantage: output quality - */ - public Amplituda compressAmplitudes(final int samplesPerSecond) { - amplitudesAsList(new ListCallback() { - @Override - public void call(List data) { - if(samplesPerSecond <= 0) { - throwException(new InvalidParameterFlagException()); - return; - } - - int duration = (int) getDuration(SECONDS); - int aps = data.size() / duration; - - if(samplesPerSecond > aps) { - throwException(new SampleOutOfBoundsException(aps, samplesPerSecond)); - return; - } - - if(aps == samplesPerSecond) { - return; - } - - int apsDivider = aps / samplesPerSecond; - int sum = 0; - StringBuilder compressed = new StringBuilder(); - - if(apsDivider < 2) { - apsDivider = 2; - } - - for(int sampleIndex = 0; sampleIndex < data.size(); sampleIndex++) { - if(sampleIndex % apsDivider == 0) { - compressed.append(sum / apsDivider); - compressed.append('\n'); - sum = 0; - } else { - sum += data.get(sampleIndex); - } - } - - amplitudes = compressed.toString(); - } - }); - return this; - } - - /** - * Returns duration from file in seconds or millis - * @param format - output time format: SECONDS or MILLIS - */ - public long getDuration(final int format) { - String inputAudioFile = fileManager.getCachePath(); - - if(inputAudioFile == null) { - throwException(new NoInputFileException()); - return 0; - } - - if(format != SECONDS && format != MILLIS) { - throwException(new InvalidParameterFlagException()); - return 0; - } - - long duration = fileManager.getAudioDuration(inputAudioFile); - - if (format == SECONDS) { - return duration / 1000; - } - return duration; - } - - /** - * Convert result amplitudes to single line string with delimiter - * @param amplitudes - result from native c++ code - * @param delimiter - amplitudes separator - * @return string from amplitudes with custom delimiter. Example -> 0, 1, 2 | delimiter = ", " - */ - private String amplitudesToSingleLineSequence(final String amplitudes, final String delimiter) { - String[] log = amplitudes.split("\n"); - return TextUtils.join(delimiter, log); - } - - /** - * Emit new exception event for listener - * @param exception - cause - */ - private void throwException(final AmplitudaException exception) { - if(errorListener == null) { - errors.add(exception); - return; - } - errorListener.call(exception); - } + // Delete tmp + fileManager.deleteFile(tempAudio); - /** - * Handle errors from ndk side - */ - private synchronized void handleAmplitudaErrors() { - if(errors.isEmpty()) - return; - for(final AmplitudaException exception : errors) { - throwException(exception); + return new AmplitudaProcessingOutput<>(result, inputAudio); + } catch (AmplitudaException exception) { + // Handle processing error + return new AmplitudaProcessingOutput<>(exception, inputAudio); } - errors.clear(); } - /** - * Clear local variables. Call this function when Amplituda object used repeatedly - */ - private void clearPreviousAmplitudaData() { - amplitudes = null; - errors.clear(); - fileManager.clearPath(); - } /** * NDK part @@ -413,52 +165,4 @@ private void clearPreviousAmplitudaData() { native AmplitudaResultJNI amplitudesFromAudioJNI(String pathToAudio); - public static final class Builder { - - private int logPriority = Log.DEBUG; - private boolean logEnable = false; - private ErrorListener errorListener = null; - private final FileManager fileManager = new FileManager(); - - /** - * Observe and handle errors - * @param errorListener - callback with exception as a parameter - */ - public Builder setErrorListener(final ErrorListener errorListener) { - this.errorListener = errorListener; - return this; - } - - /** - * Enable processing audio from url or res/raw - * @param context - this param used for initialization - * cache directory and resources for res/raw - */ - public Builder enableExtendedProcessing(final Context context) { - fileManager.initCache(context); - fileManager.initResources(context); - return this; - } - - /** - * Enable Amplituda logs for mor processing information - * @param priority - android Log constant. For example Log.DEBUG - * @param enable - turn on / off logs - */ - public Builder setLogConfig(final int priority, final boolean enable) { - this.logPriority = priority; - this.logEnable = enable; - return this; - } - - public Amplituda build() { - return new Amplituda( - this.errorListener, - this.fileManager, - this.logPriority, - this.logEnable - ); - } - } - } diff --git a/app/src/main/java/linc/com/amplituda/AmplitudaLogger.java b/app/src/main/java/linc/com/amplituda/AmplitudaLogger.java index 93d7017..3f5834c 100644 --- a/app/src/main/java/linc/com/amplituda/AmplitudaLogger.java +++ b/app/src/main/java/linc/com/amplituda/AmplitudaLogger.java @@ -4,11 +4,13 @@ import java.util.Locale; -public final class AmplitudaLogger { +final class AmplitudaLogger { + static final String OPERATION_PROCESSING = "Processing"; + static final String OPERATION_PREPARING = "Preparing"; private static final String LIB_TAG = "AMPLITUDA"; - private static int priority; - private static boolean enable; + private static int priority = Log.DEBUG; + private static boolean enable = false; /** * Print message to logcat @@ -46,7 +48,7 @@ synchronized static void enable(final boolean logEnable) { /** * Set log message priority - * @param - android Log priority constant. + * @param logPrior - android Log priority constant. */ synchronized static void priority(final int logPrior) { priority = logPrior; diff --git a/app/src/main/java/linc/com/amplituda/AmplitudaProcessingOutput.java b/app/src/main/java/linc/com/amplituda/AmplitudaProcessingOutput.java new file mode 100644 index 0000000..d92abe9 --- /dev/null +++ b/app/src/main/java/linc/com/amplituda/AmplitudaProcessingOutput.java @@ -0,0 +1,160 @@ +package linc.com.amplituda; + +import java.util.LinkedHashSet; +import java.util.List; + +import linc.com.amplituda.callback.AmplitudaErrorListener; +import linc.com.amplituda.callback.AmplitudaSuccessListener; +import linc.com.amplituda.exceptions.AmplitudaException; +import linc.com.amplituda.exceptions.processing.InvalidParameterFlagException; +import linc.com.amplituda.exceptions.processing.SampleOutOfBoundsException; + +public final class AmplitudaProcessingOutput { + + private final AmplitudaResult amplitudaResult; + private LinkedHashSet processingErrors = new LinkedHashSet<>(); + + private AmplitudaProcessingOutput( + final String amplitudes, + final InputAudio inputAudio + ) { + amplitudaResult = new AmplitudaResult<>(amplitudes, inputAudio); + } + + AmplitudaProcessingOutput( + final AmplitudaResultJNI processingData, + final InputAudio inputAudio + ) { + this( + processingData.getAmplitudes(), + inputAudio + ); + this.processingErrors.addAll(processingData.getErrors()); + } + + AmplitudaProcessingOutput(final AmplitudaException exception, final InputAudio inputAudio) { + this("", inputAudio); + this.processingErrors.add(exception); + } + + /** + * Merge result amplitudes according to samplesPerSecond + * @param preferredSamplesPerSecond - number of samples per audio second + * For example: + * audio duration = 200 seconds + * after Amplituda processing, 1 second contains 40 samples + * 200 seconds contains 200 * 40 = 8000 + * case 1: samplesPerSecond = 1, function will merge this 40 samples to 1. + * Output size will be 200 amplitudes + * case 2: samplesPerSecond = 20, function will merge this 40 samples to 20. + * Output size will be 4000 amplitudes + * Advantage: small output size + * Disadvantage: output quality + */ + public AmplitudaProcessingOutput compress(final int preferredSamplesPerSecond) { + List data = amplitudaResult.amplitudesAsList(); + + if(preferredSamplesPerSecond <= 0) { + throwException(new InvalidParameterFlagException(), null); + return this; + } + + int duration = (int) amplitudaResult.getAudioDuration(AmplitudaResult.DurationUnit.SECONDS); + int aps = data.size() / duration; + + if(preferredSamplesPerSecond > aps) { + throwException(new SampleOutOfBoundsException(aps, preferredSamplesPerSecond), null); + return this; + } + + if(aps == preferredSamplesPerSecond) { + return this; + } + + int apsDivider = aps / preferredSamplesPerSecond; + int sum = 0; + StringBuilder compressed = new StringBuilder(); + + if(apsDivider < 2) { + apsDivider = 2; + } + + for(int sampleIndex = 0; sampleIndex < data.size(); sampleIndex++) { + if(sampleIndex % apsDivider == 0) { + compressed.append(sum / apsDivider); + compressed.append('\n'); + sum = 0; + } else { + sum += data.get(sampleIndex); + } + } + + amplitudaResult.setAmplitudes(compressed.toString()); + return this; + } + + /** + * Get Amplituda processing result. This function returns result in callback + * @param successListener - success processing operation callback + * @param errorListener - processing error callback + */ + public void get( + final AmplitudaSuccessListener successListener, + final AmplitudaErrorListener errorListener + ) { + handleAmplitudaProcessingErrors(errorListener); + successListener.onSuccess(amplitudaResult); + } + + /** + * Get Amplituda processing result. This function returns result in callback + * @param successListener - success processing operation callback + */ + public void get(final AmplitudaSuccessListener successListener) { + get(successListener, null); + } + + /** + * Get Amplituda processing result + * @param errorListener - processing error callback + * @return AmplitudaResult object + */ + public AmplitudaResult get(final AmplitudaErrorListener errorListener) { + handleAmplitudaProcessingErrors(errorListener); + return amplitudaResult; + } + + /** + * Get Amplituda processing result + * @return AmplitudaResult object + */ + public AmplitudaResult get() { + return amplitudaResult; + } + + private void handleAmplitudaProcessingErrors(final AmplitudaErrorListener errorListener) { + if(processingErrors.isEmpty()){ + processingErrors = null; + return; + } + + for(final AmplitudaException exception : processingErrors) { + throwException(exception, errorListener); + } + + processingErrors.clear(); + processingErrors = null; + } + + private void throwException( + final AmplitudaException exception, + final AmplitudaErrorListener errorListener + ) { + if(errorListener == null) { + processingErrors.add(exception); + return; + } + errorListener.onError(exception); + } + +} diff --git a/app/src/main/java/linc/com/amplituda/AmplitudaResult.java b/app/src/main/java/linc/com/amplituda/AmplitudaResult.java new file mode 100644 index 0000000..226367a --- /dev/null +++ b/app/src/main/java/linc/com/amplituda/AmplitudaResult.java @@ -0,0 +1,172 @@ +package linc.com.amplituda; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class AmplitudaResult { + + private String amplitudes; + private final InputAudio inputAudio; + + AmplitudaResult( + final String amplitudes, + final InputAudio inputAudio + ) { + this.amplitudes = amplitudes; + this.inputAudio = inputAudio; + } + + /** + * Returns input audio source: String (path/url), File or Integer resource + */ + public T getAudioSource() { + return inputAudio.getSource(); + } + + /** + * Returns duration from file in seconds or millis + * @param unit - output time unit: SECONDS or MILLIS + */ + public long getAudioDuration(DurationUnit unit) { + if (unit == DurationUnit.SECONDS) { + return inputAudio.getDuration() / 1000; + } + return inputAudio.getDuration(); + } + + /** + * Returns input audio type: URL, RESOURCE or FILE + */ + public InputAudio.Type getInputAudioType() { + return inputAudio.getType(); + } + + /** + * Convert result amplitudes to List + */ + public List amplitudesAsList() { + if(amplitudes == null || amplitudes.isEmpty()) + return Collections.emptyList(); + + String[] log = amplitudes.split("\n"); + List amplitudes = new ArrayList<>(); + + for (String amplitude : log) { + if(amplitude.isEmpty()) { + break; + } + amplitudes.add(Integer.valueOf(amplitude)); + } + return amplitudes; + } + + /** + * Convert result amplitudes to JSON format + */ + public String amplitudesAsJson() { + if(amplitudes == null || amplitudes.isEmpty()) + return ""; + return Arrays.toString(amplitudesAsList().toArray()); + } + + /** + * Overload for amplitudesAsSequence method. Use space (" ") as a default delimiter + * @param format - output format: single line or multiline output string + */ + public String amplitudesAsSequence(final SequenceFormat format) { + if(amplitudes == null || amplitudes.isEmpty()) + return ""; + return amplitudesAsSequence(format, " "); + } + + /** + * Convert result amplitudes to single line string with custom delimiter and send result to user via stringCallback + * @param format - output format: single line or multiline output string + * @param singleLineDelimiter - delimiter between amplitudes. WARNING: this parameter will be ignored when NEW_LINE_SEQUENCE_FORMAT passed as a parameter + */ + public String amplitudesAsSequence( + final SequenceFormat format, + final String singleLineDelimiter + ) { + if(amplitudes == null || amplitudes.isEmpty()) + return ""; + + if (format == SequenceFormat.SINGLE_LINE) { + return amplitudesToSingleLineSequence( + amplitudes, + singleLineDelimiter + ); + } + return amplitudes; + } + + /** + * Extracts list of amplitudes per specific second + * @param second - specific second from input file + */ + public List amplitudesForSecond(final int second) { + List data = amplitudesAsList(); + + final int duration = (int) getAudioDuration(DurationUnit.SECONDS); + final int aps = data.size() / duration; // amplitudes per second + + // Use second as a map key + int currentSecond = 0; + + // Map with format = Map + Map> amplitudes = new LinkedHashMap<>(); + + // Temporary amplitudes list + List amplitudesPerSecond = new ArrayList<>(); + + for(int sampleIndex = 0; sampleIndex < data.size(); sampleIndex++) { + if(sampleIndex % aps == 0) { // Save all amplitudes when current frame index equals to aps + // Save amplitudes to map + amplitudes.put(currentSecond, new ArrayList<>(amplitudesPerSecond)); + // Clear temporary amplitudes + amplitudesPerSecond.clear(); + // Increase current second + currentSecond++; + } else { + // Add amplitude to temporary list + amplitudesPerSecond.add(data.get(sampleIndex)); + } + } + + return amplitudes.get(second); + } + + /** + * Convert result amplitudes to single line string with delimiter + * @param amplitudes - result from native c++ code + * @param delimiter - amplitudes separator + * @return string from amplitudes with custom delimiter. Example -> 0, 1, 2 | delimiter = ", " + */ + private String amplitudesToSingleLineSequence(final String amplitudes, final String delimiter) { + String[] log = amplitudes.split("\n"); + return TextUtils.join(delimiter, log); + } + + /** + * Update amplitudes data + * Call only from internal compress() + */ + void setAmplitudes(final String amplitudes) { + this.amplitudes = amplitudes; + } + + public enum DurationUnit { + SECONDS, MILLIS + } + + public enum SequenceFormat { + SINGLE_LINE, NEW_LINE + } + +} diff --git a/app/src/main/java/linc/com/amplituda/FileManager.java b/app/src/main/java/linc/com/amplituda/FileManager.java index 289569c..ee57791 100644 --- a/app/src/main/java/linc/com/amplituda/FileManager.java +++ b/app/src/main/java/linc/com/amplituda/FileManager.java @@ -18,30 +18,13 @@ final class FileManager { - private Resources resources; - private String cachePath; - private String cache; static final String RAW_TEMP = "amplituda_tmp_raw"; + private final Resources resources; + private final String cache; - /** - * Check not null for cache directory - */ - boolean cacheNotNull() { - return cache != null; - } - - /** - * Init cache directory path - */ - synchronized void initCache(final Context context) { - cache = context.getCacheDir().getPath() + File.separator; - } - - /** - * Init resources for res/raw decoding - */ - synchronized void initResources(final Context context) { + public FileManager(final Context context) { resources = context.getResources(); + cache = context.getCacheDir().getPath() + File.separator; } /** @@ -53,27 +36,6 @@ synchronized void deleteFile(final File file) { } } - /** - * Retain current audio path - */ - synchronized void retainPath(final String path) { - cachePath = path; - } - - /** - * Clear saved path - */ - synchronized void clearPath() { - cachePath = ""; - } - - /** - * Return stashed path - */ - synchronized String getCachePath() { - return cachePath; - } - /** * Validate audio file * @param path - audio file path @@ -106,8 +68,12 @@ synchronized long getAudioDuration(final String path) { */ synchronized File getRawFile(final int resource) { File temp = new File(cache, RAW_TEMP); - streamToFile(resources.openRawResource(resource), temp, 1024 * 4); - return guessAudioExtension(temp); + try { + streamToFile(resources.openRawResource(resource), temp, 1024 * 4); + return guessAudioExtension(temp); + } catch (Resources.NotFoundException ignored) { + return null; + } } /** diff --git a/app/src/main/java/linc/com/amplituda/InputAudio.java b/app/src/main/java/linc/com/amplituda/InputAudio.java new file mode 100644 index 0000000..01d889f --- /dev/null +++ b/app/src/main/java/linc/com/amplituda/InputAudio.java @@ -0,0 +1,51 @@ +package linc.com.amplituda; + +public final class InputAudio { + private final T source; + private long duration; + private InputAudio.Type type; + + InputAudio(final T source, long duration, final InputAudio.Type type) { + this.source = source; + this.type = type; + this.duration = duration; + } + + public InputAudio(T source, long duration) { + this.source = source; + this.duration = duration; + } + + public InputAudio(T source, Type type) { + this.source = source; + this.type = type; + } + + public InputAudio(T source) { + this.source = source; + } + + public T getSource() { + return source; + } + + public long getDuration() { + return duration; + } + + void setDuration(final long duration) { + this.duration = duration; + } + + public Type getType() { + return type; + } + + void setType(Type type) { + this.type = type; + } + + public enum Type { + FILE, PATH, URL, RESOURCE + } +} diff --git a/app/src/main/java/linc/com/amplituda/callback/AmplitudaCallback.java b/app/src/main/java/linc/com/amplituda/callback/AmplitudaCallback.java deleted file mode 100644 index ccaaffe..0000000 --- a/app/src/main/java/linc/com/amplituda/callback/AmplitudaCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package linc.com.amplituda.callback; - -/** - * Base Callback interface - */ -interface AmplitudaCallback { - void call(final T data); -} diff --git a/app/src/main/java/linc/com/amplituda/callback/ErrorListener.java b/app/src/main/java/linc/com/amplituda/callback/AmplitudaErrorListener.java similarity index 60% rename from app/src/main/java/linc/com/amplituda/callback/ErrorListener.java rename to app/src/main/java/linc/com/amplituda/callback/AmplitudaErrorListener.java index ff5682a..ee15f99 100644 --- a/app/src/main/java/linc/com/amplituda/callback/ErrorListener.java +++ b/app/src/main/java/linc/com/amplituda/callback/AmplitudaErrorListener.java @@ -6,5 +6,6 @@ /** * Callback interface for error events */ -public interface ErrorListener extends AmplitudaCallback { +public interface AmplitudaErrorListener { + void onError(final AmplitudaException exception); } diff --git a/app/src/main/java/linc/com/amplituda/callback/AmplitudaSuccessListener.java b/app/src/main/java/linc/com/amplituda/callback/AmplitudaSuccessListener.java new file mode 100644 index 0000000..31d8ce7 --- /dev/null +++ b/app/src/main/java/linc/com/amplituda/callback/AmplitudaSuccessListener.java @@ -0,0 +1,10 @@ +package linc.com.amplituda.callback; + +import linc.com.amplituda.AmplitudaResult; + +/** + * Callback interface for success processing event + */ +public interface AmplitudaSuccessListener { + void onSuccess(final AmplitudaResult result); +} diff --git a/app/src/main/java/linc/com/amplituda/callback/ListCallback.java b/app/src/main/java/linc/com/amplituda/callback/ListCallback.java deleted file mode 100644 index f72a84d..0000000 --- a/app/src/main/java/linc/com/amplituda/callback/ListCallback.java +++ /dev/null @@ -1,9 +0,0 @@ -package linc.com.amplituda.callback; - -import java.util.List; - -/** - * Callback interface for list output - */ -public interface ListCallback extends AmplitudaCallback> { -} diff --git a/app/src/main/java/linc/com/amplituda/callback/StringCallback.java b/app/src/main/java/linc/com/amplituda/callback/StringCallback.java deleted file mode 100644 index 8d747fc..0000000 --- a/app/src/main/java/linc/com/amplituda/callback/StringCallback.java +++ /dev/null @@ -1,7 +0,0 @@ -package linc.com.amplituda.callback; - -/** - * Callback interface for string output - */ -public interface StringCallback extends AmplitudaCallback { -} diff --git a/app/src/main/java/linc/com/amplituda/exceptions/io/ExtendedProcessingDisabledException.java b/app/src/main/java/linc/com/amplituda/exceptions/io/ExtendedProcessingDisabledException.java deleted file mode 100644 index 32db8b9..0000000 --- a/app/src/main/java/linc/com/amplituda/exceptions/io/ExtendedProcessingDisabledException.java +++ /dev/null @@ -1,9 +0,0 @@ -package linc.com.amplituda.exceptions.io; - -import static linc.com.amplituda.ErrorCode.EXTENDED_PROCESSING_DISABLED_IO_CODE; - -public class ExtendedProcessingDisabledException extends AmplitudaIOException { - public ExtendedProcessingDisabledException() { - super("You are trying to process audio from res/raw or url with disabled extended processing.\nPlease call `enableExtendedProcessing(final Context context)` while building Amplituda", EXTENDED_PROCESSING_DISABLED_IO_CODE); - } -} diff --git a/example/src/main/java/linc/com/example/MainActivity.java b/example/src/main/java/linc/com/example/MainActivity.java index bc6041b..5094958 100644 --- a/example/src/main/java/linc/com/example/MainActivity.java +++ b/example/src/main/java/linc/com/example/MainActivity.java @@ -2,16 +2,21 @@ import androidx.appcompat.app.AppCompatActivity; +import android.hardware.camera2.CameraManager; import android.os.Bundle; import android.util.Log; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; +import java.util.Locale; import linc.com.amplituda.Amplituda; -import linc.com.amplituda.exceptions.io.AmplitudaIOException; -import linc.com.amplituda.exceptions.processing.AmplitudaProcessingException; +import linc.com.amplituda.AmplitudaProcessingOutput; +import linc.com.amplituda.AmplitudaResult; +import linc.com.amplituda.InputAudio; +import linc.com.amplituda.exceptions.AmplitudaException; public class MainActivity extends AppCompatActivity { @@ -20,37 +25,38 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - Amplituda amplituda = new Amplituda.Builder() - .enableExtendedProcessing(this) - .setErrorListener(error -> { - if(error instanceof AmplitudaIOException) { - System.out.println("IO exception!"); - } else if(error instanceof AmplitudaProcessingException) { - System.out.println("Processing exception!"); - } - }) - .setLogConfig(Log.ERROR, true) - .build(); - - amplituda.fromFile("/storage/emulated/0/Music/Linc - Amplituda.mp3") - .compressAmplitudes(1) - .amplitudesAsJson(json -> { - System.out.println("As json: " + json); - }) - .amplitudesAsList(list -> { - System.out.print("As list: " + Arrays.toString(list.toArray())); - }) - .amplitudesAsSequence(Amplituda.SINGLE_LINE_SEQUENCE_FORMAT, defSeq -> { - System.out.println("As sequence default: " + defSeq); - }) - .amplitudesAsSequence(Amplituda.SINGLE_LINE_SEQUENCE_FORMAT, " * ", custSeq -> { - System.out.println("As sequence custom: " + custSeq); - }) - .amplitudesAsSequence(Amplituda.NEW_LINE_SEQUENCE_FORMAT, newLineSeq -> { - System.out.println("As new line sequence: " + newLineSeq); - }) - .amplitudesForSecond(1, amps -> { - System.out.print("For second: " + Arrays.toString(amps.toArray())); - }); + Amplituda amplituda = new Amplituda(this); + + amplituda.processAudio("/storage/emulated/0/Music/Linc - Amplituda.mp3") + .get(this::printResult, Throwable::printStackTrace); + } + + private void printResult(AmplitudaResult result) { + System.out.printf(Locale.US, + "Audio info:\n" + + "millis = %d\n" + + "seconds = %d\n\n" + + "source = %s\n" + + "source type = %s\n\n" + + "Amplitudes:\n" + + "size: = %d\n" + + "list: = %s\n" + + "json: = %s\n" + + "single line sequence = %s\n" + + "new line sequence = %s\n" + + "custom delimiter sequence = %s\n%n", + result.getAudioDuration(AmplitudaResult.DurationUnit.MILLIS), + result.getAudioDuration(AmplitudaResult.DurationUnit.SECONDS), + result.getInputAudioType() == InputAudio.Type.FILE ? ((File) result.getAudioSource()).getAbsolutePath() : result.getAudioSource(), + result.getInputAudioType().name(), + result.amplitudesAsList().size(), + Arrays.toString(result.amplitudesAsList().toArray()), + result.amplitudesAsJson(), + result.amplitudesAsSequence(AmplitudaResult.SequenceFormat.SINGLE_LINE), + result.amplitudesAsSequence(AmplitudaResult.SequenceFormat.NEW_LINE), + result.amplitudesAsSequence(AmplitudaResult.SequenceFormat.SINGLE_LINE, " * ") + ); + } + }