├── .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 | [](https://circleci.com/gh/sahaRatul/sela-java/tree/master)
6 | [](https://sonarcloud.io/dashboard?id=sahaRatul_sela-java)
7 | [](https://sonarcloud.io/dashboard?id=sahaRatul_sela-java)
8 | [](https://opensource.org/licenses/MIT)
9 |
10 | ### Block Diagrams
11 | 
12 | 
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 | }
--------------------------------------------------------------------------------