├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml ├── settings.gradle └── src ├── main └── java │ └── org │ └── sela │ ├── App.java │ ├── Decoder.java │ ├── Encoder.java │ ├── Player.java │ ├── WavPlayer.java │ ├── data │ ├── Chunk.java │ ├── DataSubChunk.java │ ├── FormatSubChunk.java │ ├── Frame.java │ ├── LpcDecodedData.java │ ├── LpcEncodedData.java │ ├── Progress.java │ ├── RiceDecodedData.java │ ├── RiceEncodedData.java │ ├── SelaFile.java │ ├── SubChunk.java │ ├── SubFrame.java │ ├── WavFile.java │ └── WavFrame.java │ ├── exception │ └── FileException.java │ ├── frame │ ├── FrameDecoder.java │ └── FrameEncoder.java │ ├── lpc │ ├── LinearPredictor.java │ ├── LookupTables.java │ ├── ResidueGenerator.java │ └── SampleGenerator.java │ ├── rice │ ├── RiceDecoder.java │ └── RiceEncoder.java │ └── utils │ └── ProgressPrinter.java └── test └── java └── org └── sela ├── AppTest.java ├── FrameTest.java ├── LpcTest.java └── RiceTest.java /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Java Gradle CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-java/ for more details 4 | # 5 | version: 2.1 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/openjdk:11-jdk 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/postgres:9.4 16 | 17 | working_directory: ~/repo 18 | 19 | environment: 20 | # Customize the JVM maximum heap limit 21 | JVM_OPTS: -Xmx3200m 22 | TERM: dumb 23 | 24 | steps: 25 | - checkout 26 | 27 | # Download and cache dependencies 28 | - restore_cache: 29 | keys: 30 | - v1-dependencies-{{ checksum "build.gradle" }} 31 | # fallback to using the latest cache if no exact match is found 32 | - v1-dependencies- 33 | 34 | - run: gradle build sonarqube 35 | 36 | - save_cache: 37 | paths: 38 | - ~/.gradle 39 | key: v1-dependencies-{{ checksum "build.gradle" }} 40 | 41 | workflows: 42 | main: 43 | jobs: 44 | - build: 45 | context: SonarCloud -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | 15 | [*.yml] 16 | indent_size = 2 17 | 18 | [*.sh] 19 | end_of_line = lf 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # When shell scripts end in CRLF, bash gives a cryptic error message 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Standard Maven .gitignore 3 | # 4 | target/ 5 | pom.xml.tag 6 | pom.xml.releaseBackup 7 | pom.xml.versionsBackup 8 | pom.xml.next 9 | release.properties 10 | dependency-reduced-pom.xml 11 | buildNumber.properties 12 | .mvn/timing.properties 13 | 14 | # 15 | # IntelliJ 16 | # 17 | *.iml 18 | .idea/* 19 | !.idea/runConfigurations/ 20 | 21 | # 22 | # Visual Studio Code 23 | # 24 | .settings/ 25 | .classpath 26 | .project 27 | .vscode/ 28 | 29 | 30 | .gradle 31 | bin 32 | /build/ 33 | 34 | # Ignore Gradle GUI config 35 | gradle-app.setting 36 | 37 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 38 | !gradle-wrapper.jar 39 | 40 | # Cache of project 41 | .gradletasknamecache 42 | 43 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 44 | # gradle/wrapper/gradle-wrapper.properties -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: openjdk11 3 | git: 4 | depth: false 5 | addons: 6 | sonarcloud: 7 | organization: "saharatul" 8 | token: 9 | secure: "bHfjjQSZ43qT1DTR0vb3WOAPSeGO20SXXhTS/0QcWUyg8c3xqHLQ0nUXmNvuOc+Sw3aRHXI9dlYqFkGAQ3+tcayVZeKqVCAgLjadJXg74y1dP0A0cwcAxdo6NzI8gEWCWOoIBWJG5eYutsgRfTZsrkEAOyEmhceDtcp/bKXlOIgrTXC6flrhRrIIbI+BpSx/BR08OaY0F2j3b3I7bYsZ9mxvc1GT4z6lZDR+7cu5z+ftbhe6SvgzLWYs26MynAkYwk/ldHZptn4WvUtZyasuBhd1rSirBjZ5JvPprtX5tfWSP1gaEswWv2PKwlzBfw6A6eyicJfPWxTjhuQcsf80N5raPV0wayYCVvgHpYY1Pqf+s61Cxoq/iUKmgJtwaQJJGnEUqx0Z+HnBLgInXjYO/v3veCLiXfE8QvAFTSoI0tDDLJ/TZ2nEuFAO1LIXgWN84l4MV0aMODuH3Z0D0esASe+pGUx8/5c9d4qnKDcwAzR7iz2uObPf6eGlKo53F/XI43A+aURXF3lIYT/zCPMzSgt9DJhVOKNoupahURrUlC+j7OI4kXYBTkvJU5u8vcvkEfbNOL6uQ5ZQ1fXayylKtOk+SzCWLBgT5KrP9gcaVcs8C/r8oZ5RD/tUN0RU0B2dfmHS9NcZAVyh9yspkIftCW0crKeg8+87EyC7rrJHS0E=" 10 | script: 11 | - ./gradlew sonarqube -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | ----------- 3 | 4 | Copyright (c) 2020 Ratul Saha 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SELA 2 | ### SimplE Lossless Audio 3 | Java rewrite of the original [sela project](https://github.com/sahaRatul/sela). 4 | 5 | [![CircleCI](https://circleci.com/gh/sahaRatul/sela-java/tree/master.svg?style=svg)](https://circleci.com/gh/sahaRatul/sela-java/tree/master) 6 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=sahaRatul_sela-java&metric=alert_status)](https://sonarcloud.io/dashboard?id=sahaRatul_sela-java) 7 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=sahaRatul_sela-java&metric=ncloc)](https://sonarcloud.io/dashboard?id=sahaRatul_sela-java) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-orange.svg)](https://opensource.org/licenses/MIT) 9 | 10 | ### Block Diagrams 11 | ![Encoder](https://cloud.githubusercontent.com/assets/12273725/8868411/c24585e6-31f5-11e5-937a-e3c11c632704.png) 12 | ![Decoder](https://cloud.githubusercontent.com/assets/12273725/8868418/cbb6a1dc-31f5-11e5-91f6-8290766baa34.png) 13 | 14 | ### Build requirements 15 | - Open/Oracle JDK >=11 16 | - Maven 17 | 18 | ### Build instructions 19 | - cd to the directory 20 | - type ```mvn package``` to build the project 21 | 22 | ### References 23 | - Linear Prediction 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Linear_prediction) 25 | - [Digital Signal Processing by John G. Proakis & Dimitris G. Monolakis](http://www.amazon.com/Digital-Signal-Processing-4th-Edition/dp/0131873741) 26 | - [A detailed pdf](http://www.ece.ucsb.edu/Faculty/Rabiner/ece259/digital%20speech%20processing%20course/lectures_new/Lecture%2013_winter_2012_6tp.pdf) 27 | - Golomb-Rice lossless compression algorithm 28 | - [Wikipedia](https://en.wikipedia.org/wiki/Golomb_coding) 29 | - [Here is an implementation](http://michael.dipperstein.com/rice/index.html) 30 | - [MPEG4 ALS overview](http://elvera.nue.tu-berlin.de/files/1216Liebchen2009.pdf) 31 | - [FLAC overview](https://xiph.org/flac/documentation_format_overview.html) 32 | - [Paper on shorten, the original open source lossless codec](ftp://svr-ftp.eng.cam.ac.uk/pub/reports/robinson_tr156.ps.Z) 33 | - ISO/IEC 14496 Part 3, Subpart 11 (Audio Lossless Coding) 34 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | */ 4 | 5 | plugins { 6 | id 'java' 7 | id 'jacoco' 8 | id 'maven-publish' 9 | id 'org.sonarqube' version '2.8' 10 | } 11 | 12 | jacocoTestReport { 13 | reports { 14 | xml.enabled true 15 | } 16 | } 17 | 18 | sonarqube { 19 | properties { 20 | property "sonar.projectKey", "sahaRatul_sela-java" 21 | property "sonar.organization", "saharatul" 22 | property "sonar.java.libraries", "build/libs" 23 | property "sonar.host.url", "https://sonarcloud.io" 24 | } 25 | } 26 | 27 | repositories { 28 | mavenLocal() 29 | maven { 30 | url = 'https://repo.maven.apache.org/maven2' 31 | } 32 | } 33 | 34 | dependencies { 35 | testImplementation 'junit:junit:4.12' 36 | } 37 | 38 | group = 'org.sela' 39 | version = '2.0.2' 40 | sourceCompatibility = '1.8' 41 | 42 | publishing { 43 | publications { 44 | maven(MavenPublication) { 45 | from(components.java) 46 | } 47 | } 48 | } 49 | 50 | tasks.withType(JavaCompile) { 51 | options.encoding = 'UTF-8' 52 | } 53 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahaRatul/sela-java/3fa9af60851d5efc01e1977c70deda7b7d254768/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.sela 5 | sela 6 | 2.0.2 7 | 8 | 11 9 | 11 10 | UTF-8 11 | 12 | 13 | 14 | 15 | junit 16 | junit 17 | 4.12 18 | test 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-jar-plugin 27 | 3.2.0 28 | 29 | 30 | 31 | true 32 | lib/ 33 | org.sela.App 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | */ 4 | 5 | rootProject.name = 'sela' 6 | -------------------------------------------------------------------------------- /src/main/java/org/sela/App.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import javax.sound.sampled.LineUnavailableException; 7 | 8 | import org.sela.data.SelaFile; 9 | import org.sela.data.WavFile; 10 | import org.sela.exception.FileException; 11 | 12 | public final class App { 13 | public static void main(final String[] args) { 14 | System.out.println("SimplE Lossless Audio. Released under MIT license"); 15 | if (args.length < 2) { 16 | printUsage(); 17 | } else { 18 | try { 19 | parseCommandLineArgs(args); 20 | } catch (final Exception e) { 21 | System.err.println(e.getMessage() + ". Aborting..."); 22 | e.printStackTrace(); 23 | } 24 | } 25 | } 26 | 27 | private static void parseCommandLineArgs(final String[] args) 28 | throws IOException, FileException, LineUnavailableException, InterruptedException { 29 | if (args[0].equals("-e") && args.length == 3) { 30 | final File inputFile = new File(args[1]); 31 | final File outputFile = new File(args[2]); 32 | System.out.println("Encoding: " + inputFile.getAbsolutePath()); 33 | encodeFile(inputFile, outputFile); 34 | } else if (args[0].equals("-d") && args.length == 3) { 35 | final File inputFile = new File(args[1]); 36 | final File outputFile = new File(args[2]); 37 | System.out.println("Decoding: " + inputFile.getAbsolutePath()); 38 | decodeFile(inputFile, outputFile); 39 | } else if (args[0].equals("-p") && args.length == 2) { 40 | final File inputFile = new File(args[1]); 41 | System.out.println("Playing: " + inputFile.getAbsolutePath()); 42 | playSelaFile(inputFile); 43 | System.out.println(""); 44 | } else if(args[0].equals("-w") && args.length == 2) { 45 | final File inputFile = new File(args[1]); 46 | System.out.println("Playing: " + inputFile.getAbsolutePath()); 47 | playWavFile(inputFile); 48 | System.out.println(""); 49 | } else { 50 | System.out.println("Invalid arguments..."); 51 | printUsage(); 52 | return; 53 | } 54 | System.out.println("Done"); 55 | } 56 | 57 | private static void encodeFile(final File inputFile, final File outputFile) throws IOException, FileException { 58 | final Encoder selaEncoder = new Encoder(inputFile, outputFile); 59 | final SelaFile selaFile = selaEncoder.process(); 60 | selaFile.writeToStream(); 61 | } 62 | 63 | private static void decodeFile(final File inputFile, final File outputFile) throws IOException, FileException { 64 | final Decoder selaDecoder = new Decoder(inputFile, outputFile); 65 | final WavFile wavFile = selaDecoder.process(); 66 | wavFile.writeToStream(); 67 | } 68 | 69 | private static void playSelaFile(final File inputFile) throws IOException, FileException, LineUnavailableException, InterruptedException { 70 | final Player selaPlayer = new Player(inputFile); 71 | selaPlayer.play(); 72 | } 73 | 74 | private static void playWavFile(final File inputFile) throws IOException, FileException, LineUnavailableException, InterruptedException { 75 | final WavPlayer wavPlayer = new WavPlayer(inputFile); 76 | wavPlayer.play(); 77 | } 78 | 79 | private static void printUsage() { 80 | System.out.println(""); 81 | System.out.println("Usage:"); 82 | System.out.println(""); 83 | System.out.println("Encoding a file:"); 84 | System.out.println("java -jar sela.jar -e path/to/input.wav path/to/output.sela"); 85 | System.out.println(""); 86 | System.out.println("Decoding a file:"); 87 | System.out.println("java -jar sela.jar -d path/to/input.sela path/to/output.wav"); 88 | System.out.println(""); 89 | System.out.println("Playing a sela file:"); 90 | System.out.println("java -jar sela.jar -p path/to/input.sela"); 91 | System.out.println(""); 92 | System.out.println("Playing a wav file:"); 93 | System.out.println("java -jar sela.jar -w path/to/input.wav"); 94 | System.out.println(""); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/sela/Decoder.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | import org.sela.data.*; 13 | import org.sela.exception.FileException; 14 | import org.sela.frame.FrameDecoder; 15 | 16 | public class Decoder { 17 | protected WavFile wavFile; 18 | protected SelaFile selaFile; 19 | private final File outputFile; 20 | 21 | public Decoder(final File inputFile, final File outputFile) throws FileNotFoundException { 22 | this.selaFile = new SelaFile(new FileInputStream(inputFile)); 23 | this.outputFile = outputFile; 24 | } 25 | 26 | private void readFrames() throws IOException, FileException { 27 | selaFile.readFromStream(); 28 | } 29 | 30 | protected List processFrames() throws IOException, FileException { 31 | readFrames(); 32 | 33 | // Decode frames in parallel 34 | final List wavFrames = selaFile.getFrames().parallelStream().map(x -> new FrameDecoder(x).process()) 35 | .collect(Collectors.toList()); 36 | 37 | // Sort decoded samples 38 | Collections.sort(wavFrames); 39 | 40 | return wavFrames; 41 | } 42 | 43 | public WavFile process() throws IOException, FileException { 44 | final List wavFrames = processFrames(); 45 | 46 | final WavFile wavFile = new WavFile(selaFile.getSampleRate(), selaFile.getBitsPerSample(), 47 | selaFile.getChannels(), wavFrames, new FileOutputStream(outputFile)); 48 | this.wavFile = wavFile; 49 | return wavFile; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/Encoder.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import org.sela.data.*; 12 | import org.sela.frame.FrameEncoder; 13 | import org.sela.exception.*; 14 | 15 | public class Encoder { 16 | private final WavFile wavFile; 17 | private final File outputFile; 18 | private List wavFrames; 19 | private int frameCount; 20 | private final int samplePerSubFrame = 2048; 21 | 22 | public Encoder(final File inputFile, final File outputFile) throws FileException, IOException { 23 | wavFile = new WavFile(inputFile); 24 | this.outputFile = outputFile; 25 | } 26 | 27 | private void readSamples() { 28 | final long sampleCount = wavFile.getSampleCount(); 29 | frameCount = (int) Math.ceil((double) sampleCount / (samplePerSubFrame * wavFile.getNumChannels())); 30 | wavFrames = new ArrayList<>(frameCount); 31 | 32 | for (int i = 0; i < frameCount; i++) { 33 | final int[][] samples = new int[wavFile.getNumChannels()][samplePerSubFrame]; 34 | wavFile.readFrames(samples, samplePerSubFrame); 35 | wavFrames.add(new WavFrame(i, samples, (byte)wavFile.getBitsPerSample())); 36 | } 37 | } 38 | 39 | public SelaFile process() throws IOException { 40 | readSamples(); 41 | 42 | // Encode samples in parallel 43 | final List frames = wavFrames.parallelStream() 44 | .map(x -> (new FrameEncoder(x)).process()).collect(Collectors.toList()); 45 | 46 | // Sort encoded frames 47 | Collections.sort(frames); 48 | 49 | return new SelaFile(wavFile.getSampleRate(), wavFile.getBitsPerSample(), (byte) wavFile.getNumChannels(), 50 | frames, new FileOutputStream(outputFile)); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/Player.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.List; 6 | 7 | import javax.sound.sampled.AudioFormat; 8 | import javax.sound.sampled.AudioSystem; 9 | import javax.sound.sampled.DataLine; 10 | import javax.sound.sampled.LineUnavailableException; 11 | import javax.sound.sampled.SourceDataLine; 12 | 13 | import org.sela.data.Progress; 14 | import org.sela.data.WavFrame; 15 | import org.sela.exception.FileException; 16 | import org.sela.utils.ProgressPrinter; 17 | 18 | public class Player { 19 | private final List wavFrames; 20 | private final Decoder decoder; 21 | 22 | public Player(final File inputFile) throws IOException, FileException { 23 | decoder = new Decoder(inputFile, null); 24 | wavFrames = decoder.processFrames(); 25 | } 26 | 27 | public void play() throws LineUnavailableException, InterruptedException { 28 | // Select audio format parameters 29 | final AudioFormat af = new AudioFormat(decoder.selaFile.getSampleRate(), decoder.selaFile.getBitsPerSample(), 30 | decoder.selaFile.getChannels(), true, false); 31 | final DataLine.Info info = new DataLine.Info(SourceDataLine.class, af); 32 | final SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info); 33 | 34 | // Prepare audio output 35 | line.open(af, 2048 * decoder.selaFile.getChannels()); 36 | line.start(); 37 | 38 | // Prepare print thread 39 | final Progress progress = new Progress(wavFrames.size()); 40 | final Thread printThread = new Thread(new ProgressPrinter(progress)); 41 | printThread.start(); 42 | 43 | // Output wave form repeatedly 44 | byte bytesPerSample = (byte) ((byte) decoder.selaFile.getBitsPerSample() / 8); 45 | for (int i = 0; i < wavFrames.size(); i++) { 46 | final byte[] bytes = wavFrames.get(i).getDemuxedSamplesInByteArray(bytesPerSample); 47 | line.write(bytes, 0, bytes.length); 48 | progress.setCurrent(progress.getCurrent() + 1); 49 | } 50 | printThread.join(); 51 | line.drain(); 52 | line.stop(); 53 | line.close(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/sela/WavPlayer.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import javax.sound.sampled.AudioFormat; 9 | import javax.sound.sampled.AudioSystem; 10 | import javax.sound.sampled.DataLine; 11 | import javax.sound.sampled.LineUnavailableException; 12 | import javax.sound.sampled.SourceDataLine; 13 | 14 | import org.sela.data.Progress; 15 | import org.sela.data.WavFile; 16 | import org.sela.data.WavFrame; 17 | import org.sela.exception.FileException; 18 | import org.sela.utils.ProgressPrinter; 19 | 20 | public class WavPlayer { 21 | private final WavFile wavFile; 22 | private List wavFrames; 23 | private int frameCount; 24 | private final int samplePerSubFrame = 2048; 25 | 26 | public WavPlayer(final File inputFile) throws IOException, FileException { 27 | wavFile = new WavFile(inputFile); 28 | } 29 | 30 | private void readSamples() { 31 | final long sampleCount = wavFile.getSampleCount(); 32 | frameCount = (int) Math.ceil((double) sampleCount / (samplePerSubFrame * wavFile.getNumChannels())); 33 | wavFrames = new ArrayList<>(frameCount); 34 | 35 | for (int i = 0; i < frameCount; i++) { 36 | final int[][] samples = new int[wavFile.getNumChannels()][samplePerSubFrame]; 37 | wavFile.readFrames(samples, samplePerSubFrame); 38 | wavFrames.add(new WavFrame(i, samples, (byte) wavFile.getBitsPerSample())); 39 | } 40 | } 41 | 42 | public void play() throws LineUnavailableException, InterruptedException, FileException, IOException { 43 | readSamples(); 44 | 45 | // Select audio format parameters 46 | final AudioFormat af = new AudioFormat(wavFile.getSampleRate(), wavFile.getBitsPerSample(), 47 | wavFile.getNumChannels(), true, false); 48 | final DataLine.Info info = new DataLine.Info(SourceDataLine.class, af); 49 | final SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info); 50 | 51 | // Prepare audio output 52 | line.open(af, 2048 * wavFile.getNumChannels()); 53 | line.start(); 54 | 55 | // Prepare print thread 56 | final Progress progress = new Progress(wavFrames.size()); 57 | final Thread printThread = new Thread(new ProgressPrinter(progress)); 58 | printThread.start(); 59 | 60 | // Output wave form repeatedly 61 | byte bytesPerSample = (byte) ((byte) wavFile.getBitsPerSample() / 8); 62 | for (int i = 0; i < wavFrames.size(); i++) { 63 | final byte[] bytes = wavFrames.get(i).getDemuxedSamplesInByteArray(bytesPerSample); 64 | line.write(bytes, 0, bytes.length); 65 | progress.setCurrent(progress.getCurrent() + 1); 66 | } 67 | printThread.join(); 68 | line.drain(); 69 | line.stop(); 70 | line.close(); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/Chunk.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | 6 | import org.sela.exception.*; 7 | 8 | class Chunk { 9 | public String chunkId; 10 | public int chunkSize; 11 | public String format; 12 | public ArrayList subChunks; 13 | 14 | public void validate() throws FileException { 15 | if(!chunkId.equals("RIFF")) { 16 | throw new FileException("Invalid ChunkId"); 17 | } 18 | else if(!format.equals("WAVE")) { 19 | throw new FileException("Invalid format"); 20 | } 21 | } 22 | 23 | public void write(final ByteBuffer buffer) { 24 | buffer.put((byte) 'R'); 25 | buffer.put((byte) 'I'); 26 | buffer.put((byte) 'F'); 27 | buffer.put((byte) 'F'); 28 | buffer.putInt(chunkSize); 29 | buffer.put((byte) 'W'); 30 | buffer.put((byte) 'A'); 31 | buffer.put((byte) 'V'); 32 | buffer.put((byte) 'E'); 33 | for (final SubChunk subChunk : subChunks) { 34 | subChunk.write(buffer); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/DataSubChunk.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import org.sela.exception.*; 4 | 5 | public class DataSubChunk extends SubChunk { 6 | public int[] samples; 7 | private final byte bitsPerSample; 8 | 9 | public DataSubChunk(final byte bitsPerSample) { 10 | this.bitsPerSample = bitsPerSample; 11 | } 12 | 13 | public void validate() throws FileException { 14 | if(!super.subChunkId.equals("data")) { 15 | throw new FileException("Invalid subChunkId for data"); 16 | } 17 | if(super.subChunkSize != (samples.length * (bitsPerSample / 8))) { 18 | throw new FileException("Invalid subChunkSize for data"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/FormatSubChunk.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import org.sela.exception.*; 4 | 5 | public class FormatSubChunk extends SubChunk { 6 | public short audioFormat; 7 | public short numChannels; 8 | public int sampleRate; 9 | public int byteRate; 10 | public short blockAlign; 11 | public short bitsPerSample; 12 | 13 | public void validate() throws FileException { 14 | if(!super.subChunkId.equals("fmt ")) { 15 | throw new FileException("Invalid subChunkId for fmt"); 16 | } 17 | else if(bitsPerSample != 16) { 18 | throw new FileException("Only 16bit LE PCM supported"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/Frame.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | 6 | public final class Frame implements Comparable { 7 | public final int syncWord = 0xAA55FF00; 8 | public ArrayList subFrames; 9 | private final byte bitsPerSample; //For internal reference, not to be written on output 10 | private final int index; // For internal sorting, not to be written to output 11 | 12 | public Frame(final ArrayList subFrames, final byte bitsPerSample, final int index) { 13 | this.subFrames = subFrames; 14 | this.bitsPerSample = bitsPerSample; 15 | this.index = index; 16 | } 17 | 18 | public Frame(final int index, final byte bitsPerSample) { 19 | this.index = index; 20 | this.bitsPerSample = bitsPerSample; 21 | } 22 | 23 | public int getIndex() { 24 | return index; 25 | } 26 | 27 | public byte getBitsPerSample() { 28 | return bitsPerSample; 29 | } 30 | 31 | @Override 32 | public int compareTo(final Frame frame) { 33 | return this.index - frame.index; 34 | } 35 | 36 | public int getByteCount() { 37 | int count = 4; 38 | for (final SubFrame subFrame : subFrames) { 39 | count += subFrame.getByteCount(); 40 | } 41 | return count; 42 | } 43 | 44 | public void write(final ByteBuffer buffer) { 45 | buffer.putInt(syncWord); 46 | for (final SubFrame subFrame : subFrames) { 47 | subFrame.write(buffer); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/LpcDecodedData.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | public final class LpcDecodedData { 4 | public final int[] samples; 5 | public final byte bitsPerSample; 6 | 7 | public LpcDecodedData(final int[] samples, final byte bitsPerSample) { 8 | this.samples = samples; 9 | this.bitsPerSample = bitsPerSample; 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/LpcEncodedData.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | public final class LpcEncodedData { 4 | public final byte optimalLpcOrder; 5 | public final byte bitsPerSample; 6 | public final int[] quantizedReflectionCoefficients; 7 | public final int[] residues; 8 | 9 | public LpcEncodedData(final byte optimalLpcOrder, final byte bitsPerSample, final int[] quantizedReflectionCoefficients, 10 | final int[] residues) { 11 | this.optimalLpcOrder = optimalLpcOrder; 12 | this.bitsPerSample = bitsPerSample; 13 | this.quantizedReflectionCoefficients = quantizedReflectionCoefficients; 14 | this.residues = residues; 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/Progress.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | // Data Class for keeping track of progress. Will be shared between audio thread 6 | // and print thread 7 | public class Progress { 8 | private AtomicInteger current; 9 | public final int total; 10 | 11 | public Progress(final int total) { 12 | current = new AtomicInteger(0); 13 | this.total = total; 14 | } 15 | 16 | public int getCurrent() { 17 | return current.get(); 18 | } 19 | 20 | public void setCurrent(int value) { 21 | current.set(value); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/RiceDecodedData.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | public final class RiceDecodedData { 4 | public int[] decodedData; 5 | 6 | public RiceDecodedData(final int[] decodedData) { 7 | this.decodedData = decodedData; 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/RiceEncodedData.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | public final class RiceEncodedData { 4 | public int optimumRiceParam; 5 | public int dataCount; 6 | public int[] encodedData; 7 | 8 | public RiceEncodedData(final int optimumRiceParam, final int dataCount, final int[] encodedData) { 9 | this.optimumRiceParam = optimumRiceParam; 10 | this.dataCount = dataCount; 11 | this.encodedData = encodedData; 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/SelaFile.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.DataInputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import org.sela.exception.FileException; 16 | 17 | public final class SelaFile { 18 | private final byte[] magicNumber = { 0x53, 0x65, 0x4c, 0x61 }; // SeLa in ASCII 19 | private int sampleRate; 20 | private short bitsPerSample; 21 | private byte channels; 22 | private int numFrames; 23 | private List frames; 24 | DataOutputStream outputStream; 25 | DataInputStream inputStream; 26 | 27 | public SelaFile(final int sampleRate, final short bitsPerSample, final byte channels, final List frames, 28 | final FileOutputStream fos) { 29 | this.sampleRate = sampleRate; 30 | this.bitsPerSample = bitsPerSample; 31 | this.channels = channels; 32 | this.numFrames = frames.size(); 33 | this.frames = frames; 34 | this.outputStream = new DataOutputStream(new BufferedOutputStream(fos)); 35 | } 36 | 37 | public SelaFile(final FileInputStream fis) { 38 | this.inputStream = new DataInputStream(new BufferedInputStream(fis)); 39 | } 40 | 41 | private void write(final ByteBuffer buffer) { 42 | buffer.put(magicNumber); 43 | buffer.putInt(sampleRate); 44 | buffer.putShort(bitsPerSample); 45 | buffer.put(channels); 46 | buffer.putInt(numFrames); 47 | 48 | for (final Frame frame : frames) { 49 | frame.write(buffer); 50 | } 51 | } 52 | 53 | private void read(final ByteBuffer buffer) throws FileException { 54 | final byte[] magicNumber = new byte[4]; 55 | for (int i = 0; i < magicNumber.length; i++) { 56 | magicNumber[i] = buffer.get(); 57 | } 58 | if (!(new String(magicNumber).equals("SeLa"))) { 59 | throw new FileException("Not a sela file"); 60 | } 61 | sampleRate = buffer.getInt(); 62 | bitsPerSample = buffer.getShort(); 63 | channels = buffer.get(); 64 | numFrames = buffer.getInt(); 65 | frames = new ArrayList<>(numFrames); 66 | 67 | // Read frames 68 | for (int i = 0; i < numFrames; i++) { 69 | // Read SyncWord 70 | final int sync = buffer.getInt(); 71 | if (sync != 0xAA55FF00) { 72 | break; 73 | } 74 | final Frame frame = new Frame(i, (byte)bitsPerSample); 75 | frame.subFrames = new ArrayList<>(2); 76 | 77 | // Read subframes 78 | for (int j = 0; j < channels; j++) { 79 | // Get channel 80 | final byte subFrameChannel = buffer.get(); 81 | final byte subFrameType = buffer.get(); 82 | final byte parentChannelNumber = buffer.get(); 83 | 84 | // Get Reflection data 85 | final byte reflectionCoefficientRiceParam = buffer.get(); 86 | final short reflectionCoefficientRequiredInts = buffer.getShort(); 87 | final byte optimumLpcOrder = buffer.get(); 88 | final int[] encodedReflectionCoefficients = new int[reflectionCoefficientRequiredInts]; 89 | 90 | for (int k = 0; k < encodedReflectionCoefficients.length; k++) { 91 | encodedReflectionCoefficients[k] = buffer.getInt(); 92 | } 93 | 94 | // Get Residue data 95 | final byte residueRiceParam = buffer.get(); 96 | final short residueRequiredInts = buffer.getShort(); 97 | final short samplesPerChannel = buffer.getShort(); 98 | final int[] encodedResidues = new int[residueRequiredInts]; 99 | for (int k = 0; k < encodedResidues.length; k++) { 100 | encodedResidues[k] = buffer.getInt(); 101 | } 102 | 103 | // Generate subframes 104 | final RiceEncodedData reflectionData = new RiceEncodedData(reflectionCoefficientRiceParam, 105 | optimumLpcOrder, encodedReflectionCoefficients); 106 | final RiceEncodedData residueData = new RiceEncodedData(residueRiceParam, samplesPerChannel, 107 | encodedResidues); 108 | final SubFrame subFrame = new SubFrame(subFrameChannel, subFrameType, parentChannelNumber, 109 | reflectionData, residueData); 110 | frame.subFrames.add(subFrame); 111 | } 112 | frames.add(frame); 113 | } 114 | } 115 | 116 | public void readFromStream() throws IOException, FileException { 117 | if (inputStream == null) { 118 | throw new FileException("inputStream is null"); 119 | } 120 | 121 | final byte[] inputBytes = new byte[inputStream.available()]; 122 | final int readCount = inputStream.read(inputBytes); 123 | if (!(readCount > 0)) { 124 | throw new FileException("InputStream read error"); 125 | } 126 | 127 | final ByteBuffer buffer = ByteBuffer.wrap(inputBytes); 128 | buffer.order(ByteOrder.LITTLE_ENDIAN); 129 | 130 | read(buffer); 131 | inputStream.close(); 132 | } 133 | 134 | public void writeToStream() throws IOException, FileException { 135 | if (outputStream == null) { 136 | throw new FileException("outputStream is null"); 137 | } 138 | int byteCount = 15; 139 | for (final Frame frame : frames) { 140 | byteCount += frame.getByteCount(); 141 | } 142 | 143 | final ByteBuffer buffer = ByteBuffer.allocate(byteCount); 144 | buffer.order(ByteOrder.LITTLE_ENDIAN); 145 | 146 | write(buffer); 147 | 148 | outputStream.write(buffer.array()); 149 | outputStream.close(); 150 | } 151 | 152 | public int getSampleRate() { 153 | return sampleRate; 154 | } 155 | 156 | public short getBitsPerSample() { 157 | return bitsPerSample; 158 | } 159 | 160 | public byte getChannels() { 161 | return channels; 162 | } 163 | 164 | public List getFrames() { 165 | return frames; 166 | } 167 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/SubChunk.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class SubChunk { 6 | protected String subChunkId; 7 | protected int subChunkSize; 8 | protected byte[] subChunkData; 9 | 10 | public void write(final ByteBuffer buffer) { 11 | final char[] chars = new char[4]; 12 | subChunkId.getChars(0, 4, chars, 0); 13 | for (final char c : chars) { 14 | buffer.put((byte)c); 15 | } 16 | buffer.putInt(subChunkSize); 17 | buffer.put(subChunkData); 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/SubFrame.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public final class SubFrame { 6 | // Audio Channel number - 1, 2, etc 7 | public byte channel; 8 | // 0 - Subframe is independent, 1 - SubFrame uses difference coding and is 9 | // dependent on another channel 10 | public byte subFrameType; 11 | // Incase subFrame uses difference coding, the channel from which difference is 12 | // generated will be stored here 13 | public byte parentChannelNumber; 14 | 15 | // Reflection coefficient data 16 | public byte reflectionCoefficientRiceParam; 17 | public short reflectionCoefficientRequiredInts; 18 | public byte optimumLpcOrder; 19 | public int[] encodedReflectionCoefficients; 20 | 21 | // Residue data 22 | public byte residueRiceParam; 23 | public short residueRequiredInts; 24 | public short samplesPerChannel; 25 | public int[] encodedResidues; 26 | 27 | public SubFrame(final byte channel, final byte subFrameType, final byte parentChannelNumber, 28 | final RiceEncodedData reflectionData, final RiceEncodedData residueData) { 29 | this.channel = channel; 30 | this.subFrameType = subFrameType; 31 | this.parentChannelNumber = parentChannelNumber; 32 | 33 | this.reflectionCoefficientRiceParam = (byte) reflectionData.optimumRiceParam; 34 | this.reflectionCoefficientRequiredInts = (short) reflectionData.encodedData.length; 35 | this.optimumLpcOrder = (byte) reflectionData.dataCount; 36 | this.encodedReflectionCoefficients = reflectionData.encodedData; 37 | 38 | this.residueRiceParam = (byte) residueData.optimumRiceParam; 39 | this.residueRequiredInts = (short) residueData.encodedData.length; 40 | this.samplesPerChannel = (short) residueData.dataCount; 41 | this.encodedResidues = residueData.encodedData; 42 | } 43 | 44 | public int getByteCount() { 45 | return 12 + (4 * (encodedReflectionCoefficients.length + encodedResidues.length)); 46 | } 47 | 48 | public void write(final ByteBuffer buffer) { 49 | buffer.put(channel); 50 | buffer.put(subFrameType); 51 | buffer.put(parentChannelNumber); 52 | 53 | buffer.put(reflectionCoefficientRiceParam); 54 | buffer.putShort(reflectionCoefficientRequiredInts); 55 | buffer.put(optimumLpcOrder); 56 | for (final int i : encodedReflectionCoefficients) { 57 | buffer.putInt(i); 58 | } 59 | 60 | buffer.put(residueRiceParam); 61 | buffer.putShort(residueRequiredInts); 62 | buffer.putShort(samplesPerChannel); 63 | for (final int i : encodedResidues) { 64 | buffer.putInt(i); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/data/WavFile.java: -------------------------------------------------------------------------------- 1 | //Note: This entire file needs to rewritten 2 | 3 | package org.sela.data; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.BufferedOutputStream; 7 | import java.io.DataInputStream; 8 | import java.io.DataOutputStream; 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | import java.nio.ByteBuffer; 14 | import java.nio.ByteOrder; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import org.sela.exception.*; 19 | 20 | public class WavFile { 21 | private Chunk chunk; 22 | private File inputFile; 23 | private ByteBuffer buffer; 24 | private int readOffset; 25 | private int[][] demuxedSamples; // Used for reading 26 | private List frames; // Used for writing 27 | private short bitsPerSample; 28 | private final String fmtNotFound = "fmt subchunk not found in wav"; 29 | private final String dataNotFound = "data subchunk not found in wav"; 30 | 31 | private DataOutputStream outputStream; 32 | 33 | public WavFile(final File inputFile) throws IOException, FileException { 34 | this.inputFile = inputFile; 35 | this.readOffset = 0; 36 | this.read(); 37 | } 38 | 39 | public WavFile(final int sampleRate, final short bitsPerSample, final byte channels, final List frames, 40 | final FileOutputStream fos) { 41 | this.outputStream = new DataOutputStream(new BufferedOutputStream(fos)); 42 | this.frames = frames; 43 | this.bitsPerSample = bitsPerSample; 44 | createChunk(); 45 | createFormatSubChunk(sampleRate, channels, bitsPerSample); 46 | createDataChunk(); 47 | } 48 | 49 | private void allocateBuffer() throws IOException { 50 | try (final DataInputStream inputStream = new DataInputStream( 51 | new BufferedInputStream(new FileInputStream(inputFile)))) { 52 | final byte[] bytes = new byte[(int) inputFile.length()]; 53 | inputStream.readFully(bytes); 54 | 55 | buffer = ByteBuffer.wrap(bytes); 56 | buffer.order(ByteOrder.LITTLE_ENDIAN); 57 | 58 | inputStream.close(); 59 | } 60 | } 61 | 62 | private void readChunk() throws FileException { 63 | chunk = new Chunk(); 64 | final byte[] chunkId = new byte[4]; 65 | final byte[] format = new byte[4]; 66 | buffer.get(chunkId, 0, 4); 67 | 68 | chunk.chunkId = new String(chunkId); 69 | chunk.chunkSize = buffer.getInt(); 70 | 71 | for (int i = 0; i < format.length; i++) { 72 | format[i] = buffer.get(); 73 | } 74 | chunk.format = new String(format); 75 | chunk.subChunks = new ArrayList<>(100); 76 | 77 | chunk.validate(); 78 | } 79 | 80 | private void createChunk() { 81 | chunk = new Chunk(); 82 | chunk.chunkId = "RIFF"; 83 | chunk.chunkSize = 36; 84 | chunk.format = "WAVE"; 85 | chunk.subChunks = new ArrayList<>(2); 86 | } 87 | 88 | private void readSubChunks() throws FileException { 89 | while (buffer.hasRemaining()) { 90 | final SubChunk subChunk = new SubChunk(); 91 | final byte[] subChunkId = new byte[4]; 92 | for (int i = 0; i < subChunkId.length; i++) { 93 | subChunkId[i] = buffer.get(); 94 | } 95 | subChunk.subChunkId = new String(subChunkId); 96 | subChunk.subChunkSize = buffer.getInt(); 97 | subChunk.subChunkData = new byte[subChunk.subChunkSize]; 98 | for (int i = 0; i < subChunk.subChunkData.length; i++) { 99 | subChunk.subChunkData[i] = buffer.get(); 100 | } 101 | chunk.subChunks.add(subChunk); 102 | } 103 | } 104 | 105 | private void generateFormatSubChunk() throws FileException { 106 | final SubChunk subChunk = chunk.subChunks.stream().filter(x -> x.subChunkId.equals("fmt ")).findFirst() 107 | .orElse(null); 108 | if (subChunk == null) { 109 | throw new FileException(fmtNotFound); 110 | } 111 | final int subChunkIndex = chunk.subChunks.indexOf(subChunk); 112 | 113 | final ByteBuffer buffer = ByteBuffer.wrap(subChunk.subChunkData); 114 | buffer.order(ByteOrder.LITTLE_ENDIAN); 115 | 116 | final FormatSubChunk formatSubChunk = new FormatSubChunk(); 117 | formatSubChunk.subChunkId = subChunk.subChunkId; 118 | formatSubChunk.subChunkSize = subChunk.subChunkSize; 119 | formatSubChunk.subChunkData = subChunk.subChunkData; 120 | formatSubChunk.audioFormat = buffer.getShort(); 121 | formatSubChunk.numChannels = buffer.getShort(); 122 | formatSubChunk.sampleRate = buffer.getInt(); 123 | formatSubChunk.byteRate = buffer.getInt(); 124 | formatSubChunk.blockAlign = buffer.getShort(); 125 | formatSubChunk.bitsPerSample = buffer.getShort(); 126 | 127 | formatSubChunk.validate(); 128 | 129 | chunk.subChunks.set(subChunkIndex, formatSubChunk); 130 | } 131 | 132 | private void createFormatSubChunk(final int sampleRate, final byte channels, final short bitsPerSample) { 133 | final SubChunk subChunk = new SubChunk(); 134 | subChunk.subChunkId = "fmt "; 135 | subChunk.subChunkSize = 16; 136 | final ByteBuffer buffer = ByteBuffer.allocate(16); 137 | buffer.order(ByteOrder.LITTLE_ENDIAN); 138 | buffer.putShort((short) 1); // AudioFormat 139 | buffer.putShort((short) channels); // Channels 140 | buffer.putInt(sampleRate); // SampleRate 141 | buffer.putInt((sampleRate * channels * bitsPerSample) / 8); // ByteRate 142 | buffer.putShort((short) ((channels * bitsPerSample) / 8)); // BlockAlign 143 | buffer.putShort(bitsPerSample); // BitsPerSample 144 | subChunk.subChunkData = buffer.array(); 145 | chunk.subChunks.add(subChunk); 146 | } 147 | 148 | private void generateDataChunk() throws FileException { 149 | // Get Data subChunk 150 | final SubChunk subChunk = chunk.subChunks.stream().filter(x -> x.subChunkId.equals("data")).findFirst() 151 | .orElse(null); 152 | if (subChunk == null) { 153 | throw new FileException(dataNotFound); 154 | } 155 | final int subChunkIndex = chunk.subChunks.indexOf(subChunk); 156 | 157 | // Get fmt subChunk 158 | final FormatSubChunk formatSubChunk = (FormatSubChunk) chunk.subChunks.stream() 159 | .filter(x -> x.subChunkId.equals("fmt ")).findFirst().orElse(null); 160 | if (formatSubChunk == null) { 161 | throw new FileException(fmtNotFound); 162 | } 163 | 164 | final DataSubChunk dataSubChunk = new DataSubChunk((byte) formatSubChunk.bitsPerSample); 165 | 166 | final ByteBuffer buffer = ByteBuffer.wrap(subChunk.subChunkData); 167 | buffer.order(ByteOrder.LITTLE_ENDIAN); 168 | dataSubChunk.subChunkId = subChunk.subChunkId; 169 | dataSubChunk.subChunkSize = subChunk.subChunkSize; 170 | dataSubChunk.subChunkData = subChunk.subChunkData; 171 | 172 | final int bytesPerSample = formatSubChunk.bitsPerSample / 8; 173 | final int sampleCount = dataSubChunk.subChunkSize / bytesPerSample; 174 | dataSubChunk.samples = new int[sampleCount]; 175 | 176 | if (formatSubChunk.bitsPerSample == 16) { 177 | for (int i = 0; i < sampleCount; i++) { 178 | dataSubChunk.samples[i] = buffer.getShort(); 179 | } 180 | } else { 181 | for (int i = 0; i < sampleCount; i++) { 182 | dataSubChunk.samples[i] = (buffer.get()) << 24 | (buffer.get() & 0xFF) << 16 183 | | (buffer.get() & 0xFF) << 8; 184 | } 185 | } 186 | dataSubChunk.validate(); 187 | chunk.subChunks.set(subChunkIndex, dataSubChunk); 188 | } 189 | 190 | private void createDataChunk() { 191 | final SubChunk subChunk = new SubChunk(); 192 | subChunk.subChunkId = "data"; 193 | subChunk.subChunkSize = 0; 194 | for (final WavFrame frame : frames) { 195 | subChunk.subChunkSize += frame.getSizeInBytes(); 196 | } 197 | subChunk.subChunkData = new byte[0]; 198 | chunk.subChunks.add(subChunk); 199 | } 200 | 201 | private void demuxSamples() throws FileException { 202 | final FormatSubChunk formatSubChunk = (FormatSubChunk) chunk.subChunks.stream() 203 | .filter(x -> x.subChunkId.equals("fmt ")).findFirst().orElse(null); 204 | final DataSubChunk dataSubChunk = (DataSubChunk) chunk.subChunks.stream() 205 | .filter(x -> x.subChunkId.equals("data")).findFirst().orElse(null); 206 | 207 | if (formatSubChunk == null) { 208 | throw new FileException(fmtNotFound); 209 | } 210 | if (dataSubChunk == null) { 211 | throw new FileException(dataNotFound); 212 | } 213 | 214 | final int[][] demuxedSamples = new int[formatSubChunk.numChannels][dataSubChunk.samples.length 215 | / formatSubChunk.numChannels]; 216 | for (int i = 0; i < demuxedSamples.length; i++) { 217 | for (int j = 0; j < demuxedSamples[i].length; j++) { 218 | demuxedSamples[i][j] = dataSubChunk.samples[demuxedSamples.length * j + i]; 219 | } 220 | } 221 | 222 | this.frames = new ArrayList<>(1); 223 | this.demuxedSamples = demuxedSamples; 224 | this.frames.add(new WavFrame(0, demuxedSamples, (byte) getBitsPerSample())); // Just for reference 225 | } 226 | 227 | public short getNumChannels() { 228 | final FormatSubChunk formatSubChunk = (FormatSubChunk) chunk.subChunks.get(0); 229 | return formatSubChunk.numChannels; 230 | } 231 | 232 | public int getSampleRate() { 233 | final FormatSubChunk formatSubChunk = (FormatSubChunk) chunk.subChunks.get(0); 234 | return formatSubChunk.sampleRate; 235 | } 236 | 237 | public short getBitsPerSample() { 238 | final FormatSubChunk formatSubChunk = (FormatSubChunk) chunk.subChunks.get(0); 239 | return formatSubChunk.bitsPerSample; 240 | } 241 | 242 | public int getSampleCount() { 243 | return demuxedSamples.length * demuxedSamples[0].length; 244 | } 245 | 246 | public List getFrames() { 247 | return frames; 248 | } 249 | 250 | private void read() throws IOException, FileException { 251 | allocateBuffer(); 252 | readChunk(); 253 | readSubChunks(); 254 | generateFormatSubChunk(); 255 | generateDataChunk(); 256 | demuxSamples(); 257 | chunk.subChunks.trimToSize(); 258 | } 259 | 260 | public void writeToStream() throws FileException, IOException { 261 | if (outputStream == null) { 262 | throw new FileException("outputStream is null"); 263 | } 264 | int byteCount = 44; 265 | for (final WavFrame frame : frames) { 266 | byteCount += frame.getSizeInBytes(); 267 | } 268 | 269 | final ByteBuffer buffer = ByteBuffer.allocate(byteCount); 270 | buffer.order(ByteOrder.LITTLE_ENDIAN); 271 | 272 | // Write chunk and subchunks 273 | chunk.chunkSize += chunk.subChunks.get(1).subChunkSize; 274 | chunk.write(buffer); 275 | 276 | // Write samples 277 | byte bytesPerSample = (byte) ((byte) bitsPerSample / 8); 278 | for (int i = 0; i < frames.size(); i++) { 279 | buffer.put(frames.get(i).getDemuxedSamplesInByteArray(bytesPerSample)); 280 | } 281 | 282 | outputStream.write(buffer.array()); 283 | outputStream.close(); 284 | } 285 | 286 | public void readFrames(final int[][] output, final int samplesPerChannel) { 287 | final int readLimit = (demuxedSamples[0].length - readOffset) > samplesPerChannel ? samplesPerChannel 288 | : (demuxedSamples[0].length - readOffset); 289 | for (int i = 0; i < demuxedSamples.length; i++) { 290 | for (int j = 0; j < readLimit; j++) { 291 | output[i][j] = demuxedSamples[i][readOffset + j]; 292 | } 293 | } 294 | readOffset += samplesPerChannel; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/main/java/org/sela/data/WavFrame.java: -------------------------------------------------------------------------------- 1 | package org.sela.data; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.ByteOrder; 5 | 6 | public class WavFrame implements Comparable { 7 | private final int index; 8 | private final byte bitsPerSample; 9 | public int[][] samples; 10 | 11 | public WavFrame(final int index, final int[][] samples, final byte bitsPerSample) { 12 | this.index = index; 13 | this.samples = samples; 14 | this.bitsPerSample = bitsPerSample; 15 | } 16 | 17 | public int getIndex() { 18 | return index; 19 | } 20 | 21 | public byte getBitsPerSample() { 22 | return bitsPerSample; 23 | } 24 | 25 | @Override 26 | public int compareTo(final WavFrame frame) { 27 | return this.index - frame.index; 28 | } 29 | 30 | public int getSizeInBytes() { 31 | return samples.length * samples[0].length * (bitsPerSample / 8); 32 | } 33 | 34 | public byte[] getDemuxedSamplesInByteArray(final byte bytesPerSample) { 35 | // Demux 36 | final int[] demuxed = new int[samples.length * samples[0].length]; 37 | for (int i = 0; i < samples.length; i++) { 38 | for (int j = 0; j < samples[i].length; j++) { 39 | demuxed[j * samples.length + i] = samples[i][j]; 40 | } 41 | } 42 | 43 | // Write to buffer 44 | final byte[] bytes = new byte[demuxed.length * bytesPerSample]; 45 | final ByteBuffer buffer = ByteBuffer.wrap(bytes); 46 | buffer.order(ByteOrder.LITTLE_ENDIAN); 47 | 48 | if (bytesPerSample == 2) { 49 | for (int i = 0; i < demuxed.length; i++) { 50 | buffer.putShort((short)demuxed[i]); 51 | } 52 | } 53 | if (bytesPerSample == 3) { 54 | for (int i = 0; i < demuxed.length; i++) { 55 | for (int j = bytesPerSample; j >= 1; j--) { 56 | buffer.put((byte)(demuxed[i] >>> (j * 8))); 57 | } 58 | } 59 | } 60 | return buffer.array(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/exception/FileException.java: -------------------------------------------------------------------------------- 1 | package org.sela.exception; 2 | 3 | public class FileException extends Exception { 4 | private static final long serialVersionUID = 1L; 5 | 6 | public FileException() { 7 | super(); 8 | } 9 | 10 | public FileException(String message) { 11 | super(message); 12 | } 13 | 14 | public FileException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public FileException(Throwable cause) { 19 | super(cause); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/frame/FrameDecoder.java: -------------------------------------------------------------------------------- 1 | package org.sela.frame; 2 | 3 | import org.sela.data.*; 4 | import org.sela.lpc.SampleGenerator; 5 | import org.sela.rice.RiceDecoder; 6 | 7 | public final class FrameDecoder { 8 | private final Frame frame; 9 | 10 | public FrameDecoder(final Frame frame) { 11 | this.frame = frame; 12 | } 13 | 14 | public WavFrame process() { 15 | final int[][] samples = new int[frame.subFrames.size()][]; 16 | 17 | // Foreach independent subFrame 18 | for (final SubFrame subFrame : frame.subFrames) { 19 | if (subFrame.subFrameType == 0) { 20 | // Stage 1 - Extract data from subFrame 21 | final byte channel = subFrame.channel; 22 | final byte optimumLpcOrder = subFrame.optimumLpcOrder; 23 | final RiceEncodedData reflectionData = new RiceEncodedData(subFrame.reflectionCoefficientRiceParam, 24 | subFrame.optimumLpcOrder, subFrame.encodedReflectionCoefficients); 25 | final RiceEncodedData residueData = new RiceEncodedData(subFrame.residueRiceParam, 26 | subFrame.samplesPerChannel, subFrame.encodedResidues); 27 | 28 | // Stage 2 - Decompress data 29 | final RiceDecodedData decodedReflectionData = (new RiceDecoder(reflectionData)).process(); 30 | final RiceDecodedData decodedResidueData = (new RiceDecoder(residueData)).process(); 31 | 32 | // Stage 3 - Generate Samples 33 | final LpcEncodedData encodedData = new LpcEncodedData(optimumLpcOrder, frame.getBitsPerSample(), 34 | decodedReflectionData.decodedData, decodedResidueData.decodedData); 35 | final LpcDecodedData decoded = (new SampleGenerator(encodedData)).process(); 36 | samples[channel] = decoded.samples; 37 | } 38 | } 39 | 40 | // Foreach dependent subFrame 41 | for (final SubFrame subFrame : frame.subFrames) { 42 | if (subFrame.subFrameType == 1) { 43 | // Stage 1 - Extract data from subFrame 44 | final byte channel = subFrame.channel; 45 | final byte parentChannelNumber = subFrame.parentChannelNumber; 46 | final byte optimumLpcOrder = subFrame.optimumLpcOrder; 47 | final RiceEncodedData reflectionData = new RiceEncodedData(subFrame.reflectionCoefficientRiceParam, 48 | subFrame.optimumLpcOrder, subFrame.encodedReflectionCoefficients); 49 | final RiceEncodedData residueData = new RiceEncodedData(subFrame.residueRiceParam, 50 | subFrame.samplesPerChannel, subFrame.encodedResidues); 51 | 52 | // Stage 2 - Decompress data 53 | final RiceDecodedData decodedReflectionData = (new RiceDecoder(reflectionData)).process(); 54 | final RiceDecodedData decodedResidueData = (new RiceDecoder(residueData)).process(); 55 | 56 | // Stage 3 - Generate Difference signal 57 | final LpcEncodedData encodedData = new LpcEncodedData(optimumLpcOrder, frame.getBitsPerSample(), 58 | decodedReflectionData.decodedData, decodedResidueData.decodedData); 59 | final LpcDecodedData difference = (new SampleGenerator(encodedData)) 60 | .process(); 61 | 62 | int[] decoded = new int[difference.samples.length]; 63 | 64 | // Stage 4 Generate samples 65 | for (int i = 0; i < decoded.length; i++) { 66 | decoded[i] = samples[parentChannelNumber][i] - difference.samples[i]; 67 | } 68 | samples[channel] = decoded; 69 | } 70 | } 71 | return new WavFrame(frame.getIndex(), samples, frame.getBitsPerSample()); 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/frame/FrameEncoder.java: -------------------------------------------------------------------------------- 1 | package org.sela.frame; 2 | 3 | import java.util.ArrayList; 4 | 5 | import org.sela.data.*; 6 | import org.sela.lpc.*; 7 | import org.sela.rice.*; 8 | 9 | public final class FrameEncoder { 10 | private final WavFrame wavFrame; 11 | 12 | public FrameEncoder(final WavFrame wavFrame) { 13 | this.wavFrame = wavFrame; 14 | } 15 | 16 | public Frame process() { 17 | final ArrayList subFrames = new ArrayList<>(wavFrame.samples.length); 18 | 19 | // Foreach channel 20 | for (byte i = 0; i < wavFrame.samples.length; i++) { 21 | if (i == 1 && ((i + 1) == wavFrame.samples.length)) { 22 | // Stage 1 - Generate difference signal 23 | int[] differenceSignal = new int[wavFrame.samples[i].length]; 24 | for (int j = 0; j < differenceSignal.length; j++) { 25 | differenceSignal[j] = wavFrame.samples[i - 1][j] - wavFrame.samples[i][j]; 26 | } 27 | 28 | // Stage 2 - Generate residues and reflection coefficients for difference as 29 | // well as actual signal 30 | final ResidueGenerator residueGeneratorDifference = new ResidueGenerator( 31 | new LpcDecodedData(differenceSignal, wavFrame.getBitsPerSample())); 32 | final LpcEncodedData residuesDifference = residueGeneratorDifference.process(); 33 | 34 | final ResidueGenerator residueGeneratorActual = new ResidueGenerator( 35 | new LpcDecodedData(wavFrame.samples[i], wavFrame.getBitsPerSample())); 36 | final LpcEncodedData residuesActual = residueGeneratorActual.process(); 37 | 38 | // Stage 3 - Compress residues and reflection coefficients for difference and 39 | // actual signal 40 | final RiceEncoder reflectionRiceEncoderDifference = new RiceEncoder( 41 | new RiceDecodedData(residuesDifference.quantizedReflectionCoefficients)); 42 | final RiceEncoder residueRiceEncoderDifference = new RiceEncoder( 43 | new RiceDecodedData(residuesDifference.residues)); 44 | final RiceEncodedData reflectionDataDifference = reflectionRiceEncoderDifference.process(); 45 | final RiceEncodedData residueDataDifference = residueRiceEncoderDifference.process(); 46 | 47 | final RiceEncoder reflectionRiceEncoderActual = new RiceEncoder( 48 | new RiceDecodedData(residuesActual.quantizedReflectionCoefficients)); 49 | final RiceEncoder residueRiceEncoderActual = new RiceEncoder( 50 | new RiceDecodedData(residuesActual.residues)); 51 | final RiceEncodedData reflectionDataActual = reflectionRiceEncoderActual.process(); 52 | final RiceEncodedData residueDataActual = residueRiceEncoderActual.process(); 53 | 54 | // Stage 4 - Compare sizes of both types and generate subFrame 55 | int differenceDataSize = reflectionDataDifference.encodedData.length 56 | + residueDataDifference.encodedData.length; 57 | int actualDataSize = reflectionDataActual.encodedData.length + residueDataActual.encodedData.length; 58 | if (differenceDataSize < actualDataSize) { 59 | final SubFrame subFrame = new SubFrame(i, (byte) 1, (byte) (i - 1), reflectionDataDifference, 60 | residueDataDifference); 61 | subFrames.add(subFrame); 62 | } else { 63 | final SubFrame subFrame = new SubFrame(i, (byte) 0, i, reflectionDataActual, residueDataActual); 64 | subFrames.add(subFrame); 65 | } 66 | } else { 67 | // Stage 1 - Generate residues and reflection coefficients 68 | final ResidueGenerator residueGenerator = new ResidueGenerator(new LpcDecodedData(wavFrame.samples[i], wavFrame.getBitsPerSample())); 69 | final LpcEncodedData residues = residueGenerator.process(); 70 | 71 | // Stage 2 - Compress residues and reflection coefficients 72 | final RiceEncoder reflectionRiceEncoder = new RiceEncoder( 73 | new RiceDecodedData(residues.quantizedReflectionCoefficients)); 74 | final RiceEncoder residueRiceEncoder = new RiceEncoder(new RiceDecodedData(residues.residues)); 75 | final RiceEncodedData reflectionData = reflectionRiceEncoder.process(); 76 | final RiceEncodedData residueData = residueRiceEncoder.process(); 77 | 78 | // Stage 3 - Generate Subframes 79 | final SubFrame subFrame = new SubFrame(i, (byte) 0, i, reflectionData, residueData); 80 | subFrames.add(subFrame); 81 | } 82 | } 83 | 84 | return new Frame(subFrames, wavFrame.getBitsPerSample(), wavFrame.getIndex()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/sela/lpc/LinearPredictor.java: -------------------------------------------------------------------------------- 1 | package org.sela.lpc; 2 | 3 | public class LinearPredictor { 4 | protected double[] reflectionCoefficients; 5 | protected int[] quantizedReflectionCoefficients; 6 | protected long[] linearPredictionCoefficients; 7 | 8 | protected byte optimalLpcOrder; 9 | 10 | protected final int maxLpcOrder = 100; 11 | protected final int correctionFactor = 35; 12 | 13 | public LinearPredictor() { 14 | this.reflectionCoefficients = new double[maxLpcOrder]; 15 | optimalLpcOrder = 1; 16 | } 17 | 18 | public LinearPredictor(final int[] quantizedReflectionCoefficients, final byte optimalLpcOrder) { 19 | this.reflectionCoefficients = new double[maxLpcOrder]; 20 | this.quantizedReflectionCoefficients = quantizedReflectionCoefficients; 21 | this.optimalLpcOrder = optimalLpcOrder; 22 | } 23 | 24 | protected void dequantizeReflectionCoefficients() { 25 | if (optimalLpcOrder <= 1) { 26 | reflectionCoefficients[0] = 0; 27 | return; 28 | } 29 | 30 | reflectionCoefficients[0] = LookupTables.firstOrderCoefficients[quantizedReflectionCoefficients[0] + 64]; 31 | reflectionCoefficients[1] = LookupTables.secondOrderCoefficients[quantizedReflectionCoefficients[1] + 64]; 32 | for (int i = 2; i < optimalLpcOrder; i++) { 33 | reflectionCoefficients[i] = LookupTables.higherOrderCoefficients[quantizedReflectionCoefficients[i] + 64]; 34 | } 35 | } 36 | 37 | protected void generatelinearPredictionCoefficients() { 38 | linearPredictionCoefficients = new long[optimalLpcOrder + 1]; 39 | final double[][] linearPredictionCoefficientMatrix = new double[optimalLpcOrder][optimalLpcOrder]; 40 | final double[] lpcTmp = new double[optimalLpcOrder]; 41 | final long correction = (long) 1 << correctionFactor; 42 | 43 | // Generate LPC matrix 44 | for (int i = 0; i < optimalLpcOrder; i++) { 45 | lpcTmp[i] = reflectionCoefficients[i]; 46 | final int i2 = i >> 1; 47 | int j = 0; 48 | for (j = 0; j < i2; j++) { 49 | final double tmp = lpcTmp[j]; 50 | lpcTmp[j] += reflectionCoefficients[i] * lpcTmp[i - 1 - j]; 51 | lpcTmp[i - 1 - j] += reflectionCoefficients[i] * tmp; 52 | } 53 | if (i % 2 == 1) { 54 | lpcTmp[j] += lpcTmp[j] * reflectionCoefficients[i]; 55 | } 56 | 57 | for (j = 0; j <= i; j++) { 58 | linearPredictionCoefficientMatrix[i][j] = -lpcTmp[j]; 59 | } 60 | } 61 | 62 | // Select optimum order row from matrix 63 | for (int i = 0; i < optimalLpcOrder; i++) { 64 | linearPredictionCoefficients[i 65 | + 1] = (long) (correction * linearPredictionCoefficientMatrix[optimalLpcOrder - 1][i]); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/lpc/LookupTables.java: -------------------------------------------------------------------------------- 1 | package org.sela.lpc; 2 | 3 | public final class LookupTables { 4 | protected static final double[] firstOrderCoefficients = { -1, -0.9998779296875, -0.99951171875, -0.9989013671875, 5 | -0.998046875, -0.9969482421875, -0.99560546875, -0.9940185546875, -0.9921875, -0.9901123046875, 6 | -0.98779296875, -0.9852294921875, -0.982421875, -0.9793701171875, -0.97607421875, -0.9725341796875, 7 | -0.96875, -0.9647216796875, -0.96044921875, -0.9559326171875, -0.951171875, -0.9461669921875, 8 | -0.94091796875, -0.9354248046875, -0.9296875, -0.9237060546875, -0.91748046875, -0.9110107421875, 9 | -0.904296875, -0.8973388671875, -0.89013671875, -0.8826904296875, -0.875, -0.8670654296875, -0.85888671875, 10 | -0.8504638671875, -0.841796875, -0.8328857421875, -0.82373046875, -0.8143310546875, -0.8046875, 11 | -0.7947998046875, -0.78466796875, -0.7742919921875, -0.763671875, -0.7528076171875, -0.74169921875, 12 | -0.7303466796875, -0.71875, -0.7069091796875, -0.69482421875000011, -0.6824951171875, -0.669921875, 13 | -0.6571044921875, -0.64404296875, -0.63073730468750011, -0.6171875, -0.6033935546875, -0.58935546875, 14 | -0.57507324218750011, -0.56054687500000011, -0.5457763671875, -0.53076171875, -0.5155029296875, 15 | -0.50000000000000011, -0.48425292968750011, -0.46826171875, -0.4520263671875, -0.43554687500000011, 16 | -0.41882324218750011, -0.40185546875000011, -0.38464355468750011, -0.3671875, -0.34948730468750011, 17 | -0.33154296875000011, -0.31335449218750011, -0.29492187500000022, -0.2762451171875, -0.25732421875000011, 18 | -0.23815917968750011, -0.21875000000000011, -0.19909667968750022, -0.17919921875, -0.15905761718750011, 19 | -0.13867187500000011, -0.11804199218750011, -0.097167968750000222, -0.076049804687500222, 20 | -0.054687500000000111, -0.033081054687500111, -0.011230468750000111, 0.010864257812499778, 0.033203125, 21 | 0.055786132812499778, 0.07861328125, 0.1016845703125, 0.12499999999999978, 0.1485595703125, 22 | 0.17236328124999978, 0.1964111328125, 0.22070312499999956, 0.24523925781249978, 0.27001953125, 23 | 0.29504394531249978, 0.3203125, 0.34582519531249956, 0.37158203124999978, 0.3975830078125, 24 | 0.42382812499999978, 0.4503173828125, 0.47705078124999956, 0.50402832031249978, 0.53125, 25 | 0.55871582031249978, 0.58642578125, 0.61437988281249956, 0.64257812499999978, 0.6710205078125, 26 | 0.69970703124999956, 0.7286376953125, 0.75781249999999956, 0.78723144531249978, 0.81689453125, 27 | 0.84680175781249956, 0.876953125, 0.90734863281249956, 0.93798828124999978, 0.9688720703125 }; 28 | 29 | protected static final double[] secondOrderCoefficients = { -0.50000000000000011, 0.9998779296875, 0.99951171875, 30 | 0.9989013671875, 0.998046875, 0.9969482421875, 0.99560546875, 0.9940185546875, 0.9921875, 0.9901123046875, 31 | 0.98779296875, 0.9852294921875, 0.982421875, 0.9793701171875, 0.97607421875, 0.9725341796875, 0.96875, 32 | 0.9647216796875, 0.96044921875, 0.9559326171875, 0.951171875, 0.9461669921875, 0.94091796875, 33 | 0.9354248046875, 0.9296875, 0.9237060546875, 0.91748046875, 0.9110107421875, 0.904296875, 0.8973388671875, 34 | 0.89013671875, 0.8826904296875, 0.875, 0.8670654296875, 0.85888671875, 0.8504638671875, 0.841796875, 35 | 0.8328857421875, 0.82373046875, 0.8143310546875, 0.8046875, 0.7947998046875, 0.78466796875, 0.7742919921875, 36 | 0.763671875, 0.7528076171875, 0.74169921875, 0.7303466796875, 0.71875, 0.7069091796875, 0.69482421875000011, 37 | 0.6824951171875, 0.669921875, 0.6571044921875, 0.64404296875, 0.63073730468750011, 0.6171875, 38 | 0.6033935546875, 0.58935546875, 0.57507324218750011, 0.56054687500000011, 0.5457763671875, 0.53076171875, 39 | 0.5155029296875, 0.50000000000000011, 0.48425292968750011, 0.46826171875, 0.4520263671875, 40 | 0.43554687500000011, 0.41882324218750011, 0.40185546875000011, 0.38464355468750011, 0.3671875, 41 | 0.34948730468750011, 0.33154296875000011, 0.31335449218750011, 0.29492187500000022, 0.2762451171875, 42 | 0.25732421875000011, 0.23815917968750011, 0.21875000000000011, 0.19909667968750022, 0.17919921875, 43 | 0.15905761718750011, 0.13867187500000011, 0.11804199218750011, 0.097167968750000222, 0.076049804687500222, 44 | 0.054687500000000111, 0.033081054687500111, 0.011230468750000111, -0.010864257812499778, -0.033203125, 45 | -0.055786132812499778, -0.07861328125, -0.1016845703125, -0.12499999999999978, -0.1485595703125, 46 | -0.17236328124999978, -0.1964111328125, -0.22070312499999956, -0.24523925781249978, -0.27001953125, 47 | -0.29504394531249978, -0.3203125, -0.34582519531249956, -0.37158203124999978, -0.3975830078125, 48 | -0.42382812499999978, -0.4503173828125, -0.47705078124999956, -0.50402832031249978, -0.53125, 49 | -0.55871582031249978, -0.58642578125, -0.61437988281249956, -0.64257812499999978, -0.6710205078125, 50 | -0.69970703124999956, -0.7286376953125, -0.75781249999999956, -0.78723144531249978, -0.81689453125, 51 | -0.84680175781249956, -0.876953125, -0.90734863281249956, -0.93798828124999978, -0.9688720703125 }; 52 | 53 | protected static final double[] higherOrderCoefficients = { -1, -0.984375, -0.96875, -0.953125, -0.9375, -0.921875, 54 | -0.90625, -0.890625, -0.875, -0.859375, -0.84375, -0.828125, -0.8125, -0.796875, -0.78125, -0.765625, -0.75, 55 | -0.734375, -0.71875, -0.703125, -0.6875, -0.671875, -0.65625, -0.640625, -0.625, -0.609375, -0.59375, 56 | -0.578125, -0.5625, -0.546875, -0.53125, -0.515625, -0.5, -0.484375, -0.46875, -0.453125, -0.4375, 57 | -0.421875, -0.40625, -0.390625, -0.375, -0.359375, -0.34375, -0.328125, -0.3125, -0.296875, -0.28125, 58 | -0.265625, -0.25, -0.234375, -0.21875, -0.203125, -0.1875, -0.171875, -0.15625, -0.140625, -0.125, 59 | -0.109375, -0.09375, -0.078125, -0.0625, -0.046875, -0.03125, -0.015625, 0, 0.015625, 0.03125, 0.046875, 60 | 0.0625, 0.078125, 0.09375, 0.109375, 0.125, 0.140625, 0.15625, 0.171875, 0.1875, 0.203125, 0.21875, 61 | 0.234375, 0.25, 0.265625, 0.28125, 0.296875, 0.3125, 0.328125, 0.34375, 0.359375, 0.375, 0.390625, 0.40625, 62 | 0.421875, 0.4375, 0.453125, 0.46875, 0.484375, 0.5, 0.515625, 0.53125, 0.546875, 0.5625, 0.578125, 0.59375, 63 | 0.609375, 0.625, 0.640625, 0.65625, 0.671875, 0.6875, 0.703125, 0.71875, 0.734375, 0.75, 0.765625, 0.78125, 64 | 0.796875, 0.8125, 0.828125, 0.84375, 0.859375, 0.875, 0.890625, 0.90625, 0.921875, 0.9375, 0.953125, 65 | 0.96875, 0.984375 }; 66 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/lpc/ResidueGenerator.java: -------------------------------------------------------------------------------- 1 | package org.sela.lpc; 2 | 3 | import org.sela.data.LpcDecodedData; 4 | import org.sela.data.LpcEncodedData; 5 | 6 | public final class ResidueGenerator { 7 | private final int[] samples; 8 | private final int[] residues; 9 | private final double[] quantizedSamples; 10 | private final double[] autocorrelationFactors; 11 | private final LinearPredictor linearPredictor; 12 | private final byte bitsPerSample; 13 | 14 | private final int quantizationFactor; 15 | private final double sqrt2 = 1.4142135623730950488016887242096; 16 | 17 | public ResidueGenerator(final LpcDecodedData data) { 18 | this.linearPredictor = new LinearPredictor(); 19 | this.samples = data.samples; 20 | this.quantizedSamples = new double[samples.length]; 21 | this.autocorrelationFactors = new double[linearPredictor.maxLpcOrder + 1]; 22 | this.residues = new int[samples.length]; 23 | this.bitsPerSample = data.bitsPerSample; 24 | this.quantizationFactor = data.bitsPerSample == 16 ? 32767 : 2147483647; 25 | } 26 | 27 | private void quantizeSamples() { 28 | for (int i = 0; i < samples.length; i++) { 29 | quantizedSamples[i] = (double) samples[i] / quantizationFactor; 30 | } 31 | } 32 | 33 | private void generateAutoCorrelation() { 34 | double sum = 0, mean = 0; 35 | 36 | // Generate Mean of samples 37 | for (int i = 0; i < quantizedSamples.length; i++) { 38 | sum += quantizedSamples[i]; 39 | } 40 | mean = sum / quantizedSamples.length; 41 | 42 | // Generate autocorrelation coefficients 43 | for (int i = 0; i <= linearPredictor.maxLpcOrder; i++) { 44 | autocorrelationFactors[i] = 0.0; 45 | for (int j = i; j < quantizedSamples.length; j++) { 46 | autocorrelationFactors[i] += (quantizedSamples[j] - mean) * (quantizedSamples[j - i] - mean); 47 | } 48 | } 49 | 50 | // Normalise the coefficients 51 | for (int i = 1; i <= linearPredictor.maxLpcOrder; i++) { 52 | autocorrelationFactors[i] /= autocorrelationFactors[0]; 53 | } 54 | autocorrelationFactors[0] = 1.0; 55 | } 56 | 57 | private void generateReflectionCoefficients() { 58 | double error; 59 | final double[][] gen = new double[2][linearPredictor.maxLpcOrder]; 60 | 61 | for (int i = 0; i < linearPredictor.maxLpcOrder; i++) { 62 | gen[0][i] = gen[1][i] = autocorrelationFactors[i + 1]; 63 | } 64 | 65 | error = autocorrelationFactors[0]; 66 | linearPredictor.reflectionCoefficients[0] = -gen[1][0] / error; 67 | error += gen[1][0] * linearPredictor.reflectionCoefficients[0]; 68 | 69 | for (int i = 1; i < linearPredictor.maxLpcOrder; i++) { 70 | for (int j = 0; j < linearPredictor.maxLpcOrder - i; j++) { 71 | gen[1][j] = gen[1][j + 1] + linearPredictor.reflectionCoefficients[i - 1] * gen[0][j]; 72 | gen[0][j] = gen[1][j + 1] * linearPredictor.reflectionCoefficients[i - 1] + gen[0][j]; 73 | } 74 | linearPredictor.reflectionCoefficients[i] = -gen[1][0] / error; 75 | error += gen[1][0] * linearPredictor.reflectionCoefficients[i]; 76 | } 77 | } 78 | 79 | private void generateoptimalLpcOrder() { 80 | for (int i = linearPredictor.maxLpcOrder - 1; i >= 0; i--) { 81 | if (Math.abs(linearPredictor.reflectionCoefficients[i]) > 0.05) { 82 | linearPredictor.optimalLpcOrder = (byte) (i + 1); 83 | break; 84 | } 85 | } 86 | } 87 | 88 | private void quantizeReflectionCoefficients() { 89 | linearPredictor.quantizedReflectionCoefficients = new int[linearPredictor.optimalLpcOrder]; 90 | 91 | if (linearPredictor.quantizedReflectionCoefficients.length > 0) { 92 | linearPredictor.quantizedReflectionCoefficients[0] = (int) Math 93 | .floor(64 * (-1 + (sqrt2 * Math.sqrt(linearPredictor.reflectionCoefficients[0] + 1)))); 94 | } 95 | if (linearPredictor.quantizedReflectionCoefficients.length > 1) { 96 | linearPredictor.quantizedReflectionCoefficients[1] = (int) Math 97 | .floor(64 * (-1 + (sqrt2 * Math.sqrt(-linearPredictor.reflectionCoefficients[1] + 1)))); 98 | } 99 | for (int i = 2; i < linearPredictor.optimalLpcOrder; i++) { 100 | linearPredictor.quantizedReflectionCoefficients[i] = (int) Math 101 | .floor(64 * linearPredictor.reflectionCoefficients[i]); 102 | } 103 | } 104 | 105 | private void generateResidues() { 106 | final long correction = (long) 1 << (linearPredictor.correctionFactor - 1); 107 | 108 | residues[0] = samples[0]; 109 | 110 | for (int i = 1; i <= linearPredictor.optimalLpcOrder; i++) { 111 | long temp = correction; 112 | for (int j = 1; j <= i; j++) { 113 | temp += linearPredictor.linearPredictionCoefficients[j] * samples[i - j]; 114 | } 115 | residues[i] = samples[i] - (int) (temp >> linearPredictor.correctionFactor); 116 | } 117 | 118 | for (int i = linearPredictor.optimalLpcOrder + 1; i < samples.length; i++) { 119 | long temp = correction; 120 | for (int j = 0; j <= linearPredictor.optimalLpcOrder; j++) { 121 | temp += (linearPredictor.linearPredictionCoefficients[j] * samples[i - j]); 122 | } 123 | residues[i] = samples[i] - (int) (temp >> linearPredictor.correctionFactor); 124 | } 125 | } 126 | 127 | public LpcEncodedData process() { 128 | quantizeSamples(); 129 | generateAutoCorrelation(); 130 | generateReflectionCoefficients(); 131 | generateoptimalLpcOrder(); 132 | quantizeReflectionCoefficients(); 133 | linearPredictor.dequantizeReflectionCoefficients(); 134 | linearPredictor.generatelinearPredictionCoefficients(); 135 | generateResidues(); 136 | return new LpcEncodedData(linearPredictor.optimalLpcOrder, bitsPerSample, 137 | linearPredictor.quantizedReflectionCoefficients, residues); 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/lpc/SampleGenerator.java: -------------------------------------------------------------------------------- 1 | package org.sela.lpc; 2 | 3 | import org.sela.data.*; 4 | 5 | public final class SampleGenerator { 6 | private final int[] residues; 7 | private final byte bitsPerSample; 8 | private final int[] samples; 9 | private final LinearPredictor linearPredictor; 10 | 11 | public SampleGenerator(final LpcEncodedData encodedData) { 12 | this.linearPredictor = new LinearPredictor(encodedData.quantizedReflectionCoefficients, encodedData.optimalLpcOrder); 13 | this.residues = encodedData.residues; 14 | this.samples = new int[residues.length]; 15 | this.bitsPerSample = encodedData.bitsPerSample; 16 | } 17 | 18 | private void generateSamples() { 19 | final long correction = (long) 1 << (linearPredictor.correctionFactor - 1); 20 | 21 | samples[0] = residues[0]; 22 | 23 | for (int i = 1; i <= linearPredictor.optimalLpcOrder; i++) { 24 | long temp = correction; 25 | for (int j = 1; j <= i; j++) { 26 | temp -= linearPredictor.linearPredictionCoefficients[j] * samples[i - j]; 27 | } 28 | samples[i] = residues[i] - (int) (temp >> linearPredictor.correctionFactor); 29 | } 30 | 31 | for (int i = linearPredictor.optimalLpcOrder + 1; i < residues.length; i++) { 32 | long temp = correction; 33 | for (int j = 0; j <= linearPredictor.optimalLpcOrder; j++) 34 | temp -= (linearPredictor.linearPredictionCoefficients[j] * samples[i - j]); 35 | samples[i] = residues[i] - (int) (temp >> linearPredictor.correctionFactor); 36 | } 37 | } 38 | 39 | public LpcDecodedData process() { 40 | linearPredictor.dequantizeReflectionCoefficients(); 41 | linearPredictor.generatelinearPredictionCoefficients(); 42 | generateSamples(); 43 | return new LpcDecodedData(samples, bitsPerSample); 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/org/sela/rice/RiceDecoder.java: -------------------------------------------------------------------------------- 1 | package org.sela.rice; 2 | 3 | import org.sela.data.*; 4 | 5 | public final class RiceDecoder { 6 | private final int[] input; 7 | private int[] bitInput; 8 | private final int dataCount; 9 | private final int optimumRiceParam; 10 | private final long[] unsignedOutput; 11 | private final int[] output; 12 | 13 | public RiceDecoder(final RiceEncodedData encodedData) { 14 | this.input = encodedData.encodedData; 15 | this.dataCount = encodedData.dataCount; 16 | this.optimumRiceParam = encodedData.optimumRiceParam; 17 | this.unsignedOutput = new long[dataCount]; 18 | this.output = new int[dataCount]; 19 | } 20 | 21 | private void generateEncodedBits() { 22 | bitInput = new int[input.length * 32]; 23 | for (int i = 0; i < input.length; i++) { 24 | for (int j = 0; j < 32; j++) { 25 | bitInput[i * 32 + j] = input[i] >> j & 0b1; 26 | } 27 | } 28 | } 29 | 30 | private void generateDecodedUnsignedInts() { 31 | int count = 0, temp = 0, i = 0; 32 | int bitReadCounter = 0; 33 | while (count < dataCount) { 34 | // Count 1s until a zero is encountered 35 | temp = 0; 36 | while (bitInput[bitReadCounter++] == 1) { 37 | temp++; 38 | } 39 | unsignedOutput[count] = temp << optimumRiceParam; 40 | // Read the last 'optimumRiceParam' number of bits and add them to output 41 | for (i = 1; i < (optimumRiceParam + 1); i++) { 42 | unsignedOutput[count] = unsignedOutput[count] 43 | | ((long) bitInput[bitReadCounter++] << (optimumRiceParam - i)); 44 | } 45 | count++; 46 | } 47 | } 48 | 49 | private void convertUnsignedToSigned() { 50 | for (int i = 0; i < output.length; i++) { 51 | output[i] = (int) (((unsignedOutput[i] & 0x01) == 0x01) ? -((unsignedOutput[i] + 1) >> 1) 52 | : (unsignedOutput[i] >> 1)); 53 | } 54 | } 55 | 56 | public RiceDecodedData process() { 57 | generateEncodedBits(); 58 | generateDecodedUnsignedInts(); 59 | convertUnsignedToSigned(); 60 | return new RiceDecodedData(output); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/sela/rice/RiceEncoder.java: -------------------------------------------------------------------------------- 1 | package org.sela.rice; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | 6 | import org.sela.data.*; 7 | 8 | public final class RiceEncoder { 9 | private final int[] input; 10 | private final long[] unsignedInput; 11 | private int[] output; 12 | private int[] bitOutput; 13 | private final ArrayList bitSizes; 14 | private int optimumRiceParam; 15 | private long requiredBits; 16 | private final int maxRiceParam = 20; 17 | 18 | public RiceEncoder(final RiceDecodedData data) { 19 | this.input = data.decodedData; 20 | this.unsignedInput = new long[input.length]; 21 | this.bitSizes = new ArrayList(maxRiceParam); 22 | optimumRiceParam = 0; 23 | requiredBits = 0; 24 | } 25 | 26 | private void convertSignedToUnsigned() { 27 | for (int i = 0; i < input.length; i++) { 28 | unsignedInput[i] = input[i] < 0 ? (-(input[i] << 1)) - 1 : (input[i] << 1); 29 | } 30 | } 31 | 32 | private void calculateOptimumRiceParam() { 33 | for (int i = 0; i < maxRiceParam; i++) { 34 | long temp = 0; 35 | for (int j = 0; j < unsignedInput.length; j++) { 36 | temp += (unsignedInput[j] >>> i); 37 | temp += 1; 38 | temp += i; 39 | } 40 | bitSizes.add(temp < 0 ? Long.MAX_VALUE : temp); 41 | } 42 | requiredBits = Collections.min(bitSizes); 43 | optimumRiceParam = bitSizes.indexOf(requiredBits); 44 | } 45 | 46 | private void generateEncodedBits() { 47 | bitOutput = new int[(int) (Math.ceil((double) requiredBits / 32) * 32)]; 48 | long temp = 0; 49 | int bits = 0; 50 | 51 | for (int i = 0; i < unsignedInput.length; i++) { 52 | temp = (unsignedInput[i] >>> optimumRiceParam); 53 | // Write out 'temp' number of ones 54 | for (int j = 0; j < temp; j++) { 55 | bitOutput[bits++] = 0b1; 56 | } 57 | 58 | // Write out a zero 59 | bits++; 60 | 61 | // Write out last param bits of input 62 | for (int j = (optimumRiceParam - 1); j >= 0; j--) { 63 | bitOutput[bits++] = (int) ((unsignedInput[i] >> j) & 0b1); 64 | } 65 | } 66 | } 67 | 68 | private void writeInts() { 69 | output = new int[((int) Math.ceil(((float) requiredBits) / 32))]; 70 | for (int i = 0; i < output.length; i++) { 71 | for (int j = 0; j < 32; j++) { 72 | output[i] |= ((long) bitOutput[i * 32 + j]) << j; 73 | ; 74 | } 75 | } 76 | } 77 | 78 | public RiceEncodedData process() { 79 | convertSignedToUnsigned(); 80 | calculateOptimumRiceParam(); 81 | generateEncodedBits(); 82 | writeInts(); 83 | return new RiceEncodedData(optimumRiceParam, input.length, output); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/sela/utils/ProgressPrinter.java: -------------------------------------------------------------------------------- 1 | package org.sela.utils; 2 | 3 | import java.util.Collections; 4 | 5 | import org.sela.data.Progress; 6 | 7 | // A separate thread for printing is required since audio lags when we print as 8 | // well as play audio on single thread on Windows. 9 | public class ProgressPrinter implements Runnable { 10 | private final Progress progress; 11 | 12 | public ProgressPrinter(final Progress progress) { 13 | this.progress = progress; 14 | } 15 | 16 | public void run() { 17 | while (progress.getCurrent() < progress.total) { 18 | printProgress(progress.getCurrent(), progress.total); 19 | } 20 | printProgress(progress.getCurrent(), progress.total); // Print one last time to make it 100% 21 | } 22 | 23 | private void printProgress(final long current, final long total) { 24 | final StringBuilder string = new StringBuilder(140); 25 | final int percent = (int) (current * 100 / total); 26 | string.append('\r').append(String.format("%d%% [", percent)) 27 | .append(String.join("", Collections.nCopies(percent / 2, "="))).append(">") 28 | .append(String.join("", Collections.nCopies(50 - (percent / 2), " "))).append(']').append(" (") 29 | .append(current).append('/').append(total).append(')'); 30 | System.out.print(string); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/org/sela/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import org.junit.Test; 4 | import static org.junit.Assert.*; 5 | 6 | /** 7 | * Unit test for simple App. 8 | */ 9 | public class AppTest { 10 | /** 11 | * Rigorous Test. 12 | */ 13 | @Test 14 | public void testApp() { 15 | assertTrue(true); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/sela/FrameTest.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import org.junit.Test; 4 | import org.sela.data.*; 5 | import org.sela.frame.*; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class FrameTest { 10 | @Test 11 | public void testFrameSixteenBits() { 12 | int[][] samples = new int[2][2048]; 13 | 14 | // Generate a sine wave for channel 1 15 | for (int i = 0; i < samples[0].length; i++) { 16 | samples[0][i] = (int) (32767 * Math.sin(Math.toRadians(i))); 17 | } 18 | 19 | // Generate a cosine wave for channel 2 20 | for (int i = 0; i < samples[1].length; i++) { 21 | samples[1][i] = (int) (32767 * Math.cos(Math.toRadians(i))); 22 | } 23 | 24 | WavFrame input = new WavFrame(0, samples, (byte)16); 25 | FrameEncoder encoder = new FrameEncoder(input); 26 | Frame frame = encoder.process(); 27 | 28 | FrameDecoder decoder = new FrameDecoder(frame); 29 | WavFrame output = decoder.process(); 30 | 31 | assertEquals(input.samples.length, output.samples.length); 32 | for (int i = 0; i < samples.length; i++) { 33 | assertArrayEquals(input.samples[i], output.samples[i]); 34 | } 35 | } 36 | 37 | @Test 38 | public void testFrameTwentyFourBits() { 39 | int[][] samples = new int[2][2048]; 40 | 41 | // Generate a sine wave for channel 1 42 | for (int i = 0; i < samples[0].length; i++) { 43 | samples[0][i] = (int) (8388607 * Math.sin(Math.toRadians(i))); 44 | } 45 | 46 | // Generate a cosine wave for channel 2 47 | for (int i = 0; i < samples[1].length; i++) { 48 | samples[1][i] = (int) (8388607 * Math.cos(Math.toRadians(i))); 49 | } 50 | 51 | WavFrame input = new WavFrame(0, samples, (byte)16); 52 | FrameEncoder encoder = new FrameEncoder(input); 53 | Frame frame = encoder.process(); 54 | 55 | FrameDecoder decoder = new FrameDecoder(frame); 56 | WavFrame output = decoder.process(); 57 | 58 | assertEquals(input.samples.length, output.samples.length); 59 | for (int i = 0; i < samples.length; i++) { 60 | assertArrayEquals(input.samples[i], output.samples[i]); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/test/java/org/sela/LpcTest.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import org.junit.Test; 4 | import org.sela.data.*; 5 | import org.sela.lpc.*; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class LpcTest { 10 | @Test 11 | public void testResidueSampleGenerator() { 12 | // Generate a sine wave 13 | int[] samples = new int[2048]; 14 | for (int i = 0; i < samples.length; i++) { 15 | samples[i] = (int) (32767 * Math.sin(Math.toRadians(i))); 16 | } 17 | 18 | LpcDecodedData input = new LpcDecodedData(samples, (byte)16); 19 | 20 | // Generate residues 21 | ResidueGenerator resGen = new ResidueGenerator(input); 22 | LpcEncodedData encoded = resGen.process(); 23 | 24 | // Generate samples 25 | SampleGenerator sampleGen = new SampleGenerator(encoded); 26 | LpcDecodedData decoded = sampleGen.process(); 27 | 28 | assertArrayEquals(input.samples, decoded.samples); 29 | } 30 | 31 | @Test 32 | public void testResidueSample24Bits() { 33 | // Generate a sine wave 34 | int[] samples = new int[2048]; 35 | for (int i = 0; i < samples.length; i++) { 36 | //32767 37 | //8388607 38 | //2147483647 39 | samples[i] = (int) (8388607 * Math.sin(Math.toRadians(i))); 40 | } 41 | 42 | LpcDecodedData input = new LpcDecodedData(samples, (byte)24); 43 | 44 | // Generate residues 45 | ResidueGenerator resGen = new ResidueGenerator(input); 46 | LpcEncodedData encoded = resGen.process(); 47 | 48 | // Generate samples 49 | SampleGenerator sampleGen = new SampleGenerator(encoded); 50 | LpcDecodedData decoded = sampleGen.process(); 51 | 52 | assertArrayEquals(input.samples, decoded.samples); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/org/sela/RiceTest.java: -------------------------------------------------------------------------------- 1 | package org.sela; 2 | 3 | import org.junit.Test; 4 | import org.sela.data.*; 5 | import org.sela.rice.*; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | import java.util.Random; 10 | 11 | public class RiceTest { 12 | @Test 13 | public void testRiceEncoderDecoder() { 14 | // Generate an array with random values 15 | Random rd = new Random(); 16 | int[] input = new int[2048]; 17 | for (int i = 0; i < input.length; i++) { 18 | input[i] = rd.nextInt(400) - 200; 19 | } 20 | 21 | // Encode 22 | RiceDecodedData inputData = new RiceDecodedData(input); 23 | RiceEncoder riceEncoder = new RiceEncoder(inputData); 24 | RiceEncodedData encodedData = riceEncoder.process(); 25 | 26 | // Decode 27 | RiceDecoder riceDecoder = new RiceDecoder(encodedData); 28 | RiceDecodedData decodedData = riceDecoder.process(); 29 | assertArrayEquals(inputData.decodedData, decodedData.decodedData); 30 | } 31 | } --------------------------------------------------------------------------------