├── noise.png ├── process.png ├── jqrtone ├── src │ ├── test │ │ ├── resources │ │ │ └── org │ │ │ │ └── noise_planet │ │ │ │ └── qrtone │ │ │ │ ├── ipfs_16khz_16bits_mono.raw │ │ │ │ ├── noisy_10sec_44100_16bitsPCMMono.raw │ │ │ │ └── sunspot.dat │ │ └── java │ │ │ ├── org │ │ │ └── noise_planet │ │ │ │ └── qrtone │ │ │ │ ├── CRC16Test.java │ │ │ │ ├── utils │ │ │ │ └── ArrayWriteProcessor.java │ │ │ │ ├── CircularArrayTest.java │ │ │ │ ├── PercentileTest.java │ │ │ │ └── PeakFinderTest.java │ │ │ └── com │ │ │ └── google │ │ │ └── zxing │ │ │ └── common │ │ │ └── reedsolomon │ │ │ └── GenericGFPolyTestCase.java │ └── main │ │ └── java │ │ ├── org │ │ └── noise_planet │ │ │ └── qrtone │ │ │ ├── IterativeTukey.java │ │ │ ├── IterativeHann.java │ │ │ ├── IterativeTone.java │ │ │ ├── Complex.java │ │ │ ├── Header.java │ │ │ ├── CRC8.java │ │ │ ├── CRC16.java │ │ │ ├── CircularArray.java │ │ │ ├── Configuration.java │ │ │ ├── IterativeGeneralizedGoertzel.java │ │ │ ├── PeakFinder.java │ │ │ ├── ApproximatePercentile.java │ │ │ └── TriggerAnalyzer.java │ │ └── com │ │ └── google │ │ └── zxing │ │ └── common │ │ └── reedsolomon │ │ ├── ReedSolomonException.java │ │ ├── ReedSolomonEncoder.java │ │ ├── GenericGF.java │ │ ├── ReedSolomonDecoder.java │ │ └── GenericGFPoly.java └── pom.xml ├── utilities ├── plotdata.py ├── plot_goertzel.py ├── dump_arduino.py ├── plot_peaks.py ├── plot_arduino.py └── MXChipAudioDump │ └── MXChipAudioDump.ino ├── library.properties ├── emscripten ├── compile.sh └── qrtone.js ├── .gitignore ├── .travis.yml ├── keywords.txt ├── pom.xml ├── README.md ├── LICENSE ├── CMakeLists.txt ├── examples ├── ArduinoBleSense │ └── ArduinoBleSense.ino └── MXChipChat │ └── MXChipChat.ino ├── icon.svg ├── src ├── reed_solomon.h ├── qrtone.h └── reed_solomon.c └── test └── c └── minunit.h /noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Universite-Gustave-Eiffel/qrtone/HEAD/noise.png -------------------------------------------------------------------------------- /process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Universite-Gustave-Eiffel/qrtone/HEAD/process.png -------------------------------------------------------------------------------- /jqrtone/src/test/resources/org/noise_planet/qrtone/ipfs_16khz_16bits_mono.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Universite-Gustave-Eiffel/qrtone/HEAD/jqrtone/src/test/resources/org/noise_planet/qrtone/ipfs_16khz_16bits_mono.raw -------------------------------------------------------------------------------- /jqrtone/src/test/resources/org/noise_planet/qrtone/noisy_10sec_44100_16bitsPCMMono.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Universite-Gustave-Eiffel/qrtone/HEAD/jqrtone/src/test/resources/org/noise_planet/qrtone/noisy_10sec_44100_16bitsPCMMono.raw -------------------------------------------------------------------------------- /utilities/plotdata.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | dat = np.fromfile("../jwarble/target/convolve.dat", dtype='>f8') 5 | 6 | 7 | plt.plot(dat, label='linear') 8 | 9 | plt.xlabel('x label') 10 | plt.ylabel('y label') 11 | 12 | plt.title("Simple Plot") 13 | 14 | plt.legend() 15 | 16 | plt.show() -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=QRTone 2 | version=1.0.0 3 | author=UMRAE 4 | maintainer=Nicolas Fortin 5 | sentence=Makes your device express itself in an electronic sound language 6 | paragraph=QRTone is an Open Source library that use speaker and microphone for communication between devices. 7 | category=Communication 8 | url=https://www.github.com/ifsttar/qrtone 9 | includes=qrtone.h -------------------------------------------------------------------------------- /utilities/plot_goertzel.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import csv 3 | import numpy as np 4 | 5 | data = None 6 | with open("../jqrtone/target/goertzel_test.csv", 'r') as f: 7 | data = list(csv.reader(f)) 8 | 9 | spectrum = np.array(data[1:], dtype=np.float) 10 | 11 | fig, ax = plt.subplots() 12 | 13 | #lines 14 | for i,f in list(enumerate(data[0]))[1:]: 15 | line, = ax.plot(spectrum[:, 0], spectrum[:, i], '-o', label=f) 16 | 17 | ax.legend() 18 | plt.show() -------------------------------------------------------------------------------- /emscripten/compile.sh: -------------------------------------------------------------------------------- 1 | emcc -Os ../src/qrtone.c ../src/reed_solomon.c -I../src -o qrtone_emscripten.js -s "EXPORTED_FUNCTIONS=['_qrtone_new', '_qrtone_init', '_qrtone_free', '_qrtone_get_maximum_length', '_qrtone_set_payload', '_qrtone_set_payload_ext', '_qrtone_get_samples', '_memset', '_qrtone_get_payload_sample_index', '_qrtone_push_samples', '_qrtone_get_payload', '_qrtone_get_payload_length']" -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap','getValue','ALLOC_NORMAL']" --pre-js qrtone.js -s ENVIRONMENT=web 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /jqrtone/src/test/java/org/noise_planet/qrtone/CRC16Test.java: -------------------------------------------------------------------------------- 1 | package org.noise_planet.qrtone; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | public class CRC16Test { 8 | 9 | @Test 10 | public void testRef() { 11 | byte[] values = new byte[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; 12 | CRC16 crc16 = new CRC16(); 13 | for(byte b : values) { 14 | crc16.add(b); 15 | } 16 | assertEquals(0x0C9E, crc16.crc()); 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build/ 3 | .build/ 4 | dist/ 5 | *.egg-info 6 | .vscode/ 7 | pom.xml.tag 8 | pom.xml.releaseBackup 9 | pom.xml.versionsBackup 10 | pom.xml.next 11 | release.properties 12 | dependency-reduced-pom.xml 13 | buildNumber.properties 14 | .mvn/timing.properties 15 | ubuntu-xenial-16.04-cloudimg-console.log 16 | .vagrant/ 17 | .idea/ 18 | *.iml 19 | *.pyc 20 | .kdev4 21 | *.kdev4 22 | *.so 23 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 24 | !/.mvn/wrapper/maven-wrapper.jar 25 | .attach_pid* 26 | venv 27 | .vs/ 28 | qrtone_emscripten.js 29 | *.wasm 30 | 31 | -------------------------------------------------------------------------------- /utilities/dump_arduino.py: -------------------------------------------------------------------------------- 1 | # pip install pyserial 2 | import serial 3 | 4 | 5 | ser = serial.Serial('COM3', 2000000) 6 | 7 | ser.close() 8 | ser.open() 9 | 10 | SAMPLE_RATE = 16000 11 | 12 | SAMPLE_SIZE = 4 13 | 14 | total_read = int(SAMPLE_RATE * SAMPLE_SIZE * 20.0) 15 | total_left = total_read 16 | last_perc = 0 17 | try: 18 | with open("arduino_audio.raw", 'wb') as f: 19 | try: 20 | while total_left > 0: 21 | f.write(ser.read(512)) 22 | total_left -= 512 23 | new_perc = int(((total_read - total_left) / float(total_read)) * 100.0) 24 | if new_perc - last_perc > 5: 25 | last_perc = new_perc 26 | print("%d %%" % new_perc) 27 | finally: 28 | f.flush() 29 | finally: 30 | ser.close() 31 | 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | matrix: 3 | include: 4 | # works on Precise and Trusty 5 | - os: linux 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.9 12 | env: 13 | - MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9" 14 | - os: osx 15 | osx_image: xcode8 16 | env: 17 | - MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9" 18 | 19 | before_install: 20 | - eval "${MATRIX_EVAL}" 21 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 22 | export CC="gcc-4.9"; 23 | export CXX="g++-4.9"; 24 | export LINK="gcc-4.9"; 25 | export LINKXX="g++-4.9"; 26 | fi 27 | 28 | script: 29 | - mkdir build 30 | - cd build 31 | - cmake .. 32 | - make 33 | - make test CTEST_OUTPUT_ON_FAILURE=TRUE 34 | - cd .. 35 | - mvn install 36 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For QRTone 3 | ####################################### 4 | 5 | ####################################### 6 | # Methods and Functions (KEYWORD2) 7 | ####################################### 8 | 9 | qrtone_new KEYWORD2 10 | qrtone_init KEYWORD2 11 | qrtone_free KEYWORD2 12 | qrtone_get_maximum_length KEYWORD2 13 | qrtone_push_samples KEYWORD2 14 | qrtone_get_payload KEYWORD2 15 | qrtone_get_payload_length KEYWORD2 16 | qrtone_get_fixed_errors KEYWORD2 17 | qrtone_set_payload KEYWORD2 18 | qrtone_set_payload_ext KEYWORD2 19 | qrtone_get_samples KEYWORD2 20 | 21 | ####################################### 22 | # Constants (LITERAL1) 23 | ####################################### 24 | 25 | QRTONE_ECC_L LITERAL1 26 | QRTONE_ECC_M LITERAL1 27 | QRTONE_ECC_Q LITERAL1 28 | QRTONE_ECC_H LITERAL1 29 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/IterativeTukey.java: -------------------------------------------------------------------------------- 1 | package org.noise_planet.qrtone; 2 | 3 | /** 4 | * tukey window - Hann window with a flat part in the middle 5 | */ 6 | public class IterativeTukey { 7 | IterativeHann hannWindow; 8 | long index = 0; 9 | int indexBeginFlat; 10 | int indexEndFlat; 11 | 12 | public IterativeTukey(int windowLength, double alpha) { 13 | indexBeginFlat = (int)(Math.floor(alpha * (windowLength - 1) / 2.0)); 14 | indexEndFlat = windowLength - indexBeginFlat; 15 | hannWindow = new IterativeHann(indexBeginFlat * 2); 16 | } 17 | 18 | void reset() { 19 | hannWindow.reset(); 20 | index = 0; 21 | } 22 | 23 | /** 24 | * Next sample value 25 | * @return Sample value [-1;1] 26 | */ 27 | double next() { 28 | if(index < indexBeginFlat || index >= indexEndFlat) { 29 | index++; 30 | return hannWindow.next(); 31 | } else { 32 | index++; 33 | return 1.0; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | /** 20 | *

Thrown when an exception occurs during Reed-Solomon decoding, such as when 21 | * there are too many errors to correct.

22 | * 23 | * @author Sean Owen 24 | */ 25 | public final class ReedSolomonException extends Exception { 26 | 27 | public ReedSolomonException(String message) { 28 | super(message); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.noise-planet 5 | qrtone-parent 6 | 0.3.1-SNAPSHOT 7 | pom 8 | 9 | qrtone-parent 10 | 11 | 12 | QRTone data over sound waves 13 | 14 | 15 | 1.7 16 | 1.7 17 | 18 | 19 | 20 | jqrtone 21 | 22 | 23 | 24 | scm:git:https://github.com/Ifsttar/qrtone.git 25 | scm:git:git@github.com:Ifsttar/qrtone.git 26 | git@github.com:Ifsttar/qrtone.git 27 | HEAD 28 | 29 | 30 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/IterativeHann.java: -------------------------------------------------------------------------------- 1 | package org.noise_planet.qrtone; 2 | 3 | /** 4 | * Generate Hann window without using cos function at each samples 5 | * https://dsp.stackexchange.com/questions/68528/compute-hann-window-without-cos-function 6 | */ 7 | public class IterativeHann { 8 | final double k1; 9 | double k2; 10 | double k3; 11 | int index = 0; 12 | 13 | public IterativeHann(int windowSize) { 14 | double wT = 2* Math.PI / (windowSize - 1); 15 | k1 = 2.0 * Math.cos(wT); 16 | reset(); 17 | } 18 | 19 | void reset() { 20 | index = 0; 21 | k2 = k1 / 2.0; 22 | k3 = 1.0; 23 | } 24 | 25 | /** 26 | * Next sample value 27 | * @return Sample value [-1;1] 28 | */ 29 | double next() { 30 | if(index >= 2) { 31 | double tmp = k2; 32 | k2 = k1 * k2 - k3; 33 | k3 = tmp; 34 | return 0.5 - 0.5 * k2; 35 | } else if(index == 1) { 36 | index++; 37 | return 0.5 - 0.5 * k2; 38 | } else { 39 | index++; 40 | return 0; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /utilities/plot_peaks.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import csv 3 | import numpy as np 4 | 5 | data = None 6 | # path = "../jqrtone/target/spectrum.csv" 7 | path = "../out/build/x64-Debug/spectrum.csv" 8 | with open(path, 'r') as f: 9 | data = list(csv.reader(f)) 10 | 11 | spectrum = np.array(data[1:], dtype=np.float) 12 | 13 | fig, ax = plt.subplots() 14 | 15 | #lines 16 | for i,f in list(enumerate(data[0]))[1:][::2]: 17 | line, = ax.plot(spectrum[:, 0], spectrum[:, i], '-o', label=f) 18 | 19 | #background lvls 20 | for i,f in list(enumerate(data[0]))[2:][::2]: 21 | line, = ax.plot(spectrum[:, 0], spectrum[:, i], dashes=[6, 2], label=f) 22 | 23 | 24 | # y(x) = a*(x-p)^2+b 25 | # p = 0.44112027275489657 26 | # y = 0.9997003184226267 27 | # a = -0.034719765186309814 28 | # #xvals = np.arange(spectrum[0, 0], spectrum[-1, 0],spectrum[1, 0] - spectrum[0, 0]) 29 | # xvals = np.arange(-5,5,0.05) 30 | # line, = ax.plot(xvals, np.array([a*(x - p)**2 + y for x in xvals]), '-o', label="interp") 31 | # 32 | # # peaks 33 | # for i,f in list(enumerate(data[0]))[1:][2::3]: 34 | # markers = np.array(np.argwhere(spectrum[:, i] > 0).flat) 35 | # line, = ax.plot(spectrum[markers, 0], spectrum[markers, i - 2], 'o', label=f) 36 | 37 | ax.legend() 38 | plt.show() -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/IterativeTone.java: -------------------------------------------------------------------------------- 1 | package org.noise_planet.qrtone; 2 | 3 | /** 4 | * Generate tone wave without using sin function at each sample 5 | * @ref http://ww1.microchip.com/downloads/en/appnotes/00543c.pdf https://ipfs.io/ipfs/QmdfpU2ziBrEg1WgzBXFqvrgRNse7btHoyVCfw8cEv5qgU 6 | */ 7 | public class IterativeTone { 8 | final double k1; 9 | final double originalK2; 10 | double k2; 11 | double k3; 12 | long index = 0; 13 | 14 | public IterativeTone(double frequency, double sampleRate) { 15 | double ffs = frequency / sampleRate; 16 | k1 = 2 * Math.cos(QRTone.M2PI * ffs); 17 | originalK2 = Math.sin(QRTone.M2PI * ffs); 18 | reset(); 19 | } 20 | 21 | public void reset() { 22 | index = 0; 23 | k2 = originalK2; 24 | k3 = 0; 25 | } 26 | 27 | /** 28 | * Next sample value 29 | * @return Sample value [-1;1] 30 | */ 31 | double next() { 32 | if(index >= 2) { 33 | double tmp = k2; 34 | k2 = k1 * k2 - k3; 35 | k3 = tmp; 36 | index++; 37 | return k2; 38 | } else if(index == 1) { 39 | index++; 40 | return k2; 41 | } else { 42 | index++; 43 | return 0; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QRTone 2 | 3 | 4 | [![Build Status](https://travis-ci.org/Ifsttar/qrtone.svg?branch=master)](https://travis-ci.org/Ifsttar/qrtone) 5 | 6 | Send and receive data using only speaker and microphone. 7 | 8 | Data are converted into dual tone melody. 9 | 10 | You can send data to another device a few meters away, depending on the power of the speaker and the sensitivity of the microphone. The transfer rate is about 50 bits per second. 11 | 12 | This library is written in java under BSD 3 license. 13 | 14 | Signal processing use method *Sysel and Rajmic:Goertzel algorithm generalized to non-integer multiples of fundamental frequency. EURASIP Journal on Advances in Signal Processing 2012 2012:56.* 15 | 16 | # How it works ? 17 | 18 | The data transmission method is similar to the first Internet modems except that it is adapted to the disturbances caused by acoustic propagation. 19 | 20 | Here a spectrogram of a sequence: 21 | 22 | ![OpenWarble spectrogram](noise.png) 23 | 24 | *Top source signal, bottom recorded audio in real situation* 25 | 26 | QRTone contain a forward correction code in order to reconstruct loss tones. 27 | 28 | The reference library **jqrtone** is full native java and can be included in android app with Api 14+ (Android 4.0.2),it does not require dependencies, the jar size is only 40 kbytes ! 29 | 30 | **cqrtone** is a rewrite using C99 language. 31 | -------------------------------------------------------------------------------- /utilities/plot_arduino.py: -------------------------------------------------------------------------------- 1 | # pip install pyserial 2 | import serial 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | ser = serial.Serial('COM3', 115200) 7 | ser.close() 8 | ser.open() 9 | max_values = 100 10 | values = [] 11 | graphs = [] 12 | 13 | # You probably won't need this if you're embedding things in a tkinter plot... 14 | plt.ion() 15 | 16 | fig, ax = plt.subplots() 17 | pushed = 0 18 | total_pushed = 0 19 | while True: 20 | data = ser.readline() 21 | column_id = 0 22 | for column in data.decode().split(","): 23 | if len(values) == column_id: 24 | # new column 25 | values.append(np.array([], dtype=np.float)) 26 | graphs.append(ax.plot(values[len(values) - 1], 'o-', label=str(column_id))[0]) 27 | else: 28 | values[column_id] = np.append(values[column_id], float(column)) 29 | column_id += 1 30 | pushed += 1 31 | total_pushed += 1 32 | if pushed == 1: 33 | pushed = 0 34 | for id_graph, graph in enumerate(graphs): 35 | if len(values[id_graph]) > max_values: 36 | values[id_graph] = values[id_graph][-max_values:] 37 | graph.set_ydata(values[id_graph]) 38 | graph.set_xdata(np.arange(total_pushed - len(values[id_graph]), total_pushed)) 39 | ax.relim() 40 | ax.autoscale_view() 41 | fig.canvas.draw() 42 | fig.canvas.flush_events() 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /jqrtone/src/test/java/com/google/zxing/common/reedsolomon/GenericGFPolyTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | 22 | /** 23 | * Tests {@link GenericGFPoly}. 24 | */ 25 | public final class GenericGFPolyTestCase extends Assert { 26 | 27 | private static final GenericGF FIELD = GenericGF.QR_CODE_FIELD_256; 28 | 29 | @Test 30 | public void testPolynomialString() { 31 | assertEquals("0", FIELD.getZero().toString()); 32 | assertEquals("-1", FIELD.buildMonomial(0, -1).toString()); 33 | GenericGFPoly p = new GenericGFPoly(FIELD, new int[] {3, 0, -2, 1, 1}); 34 | assertEquals("a^25x^4 - ax^2 + x + 1", p.toString()); 35 | p = new GenericGFPoly(FIELD, new int[] {3}); 36 | assertEquals("a^25", p.toString()); 37 | } 38 | 39 | @Test 40 | public void testZero() { 41 | assertEquals(FIELD.getZero(),FIELD.buildMonomial(1, 0)); 42 | assertEquals(FIELD.getZero(), FIELD.buildMonomial(1, 2).multiply(0)); 43 | } 44 | 45 | @Test 46 | public void testEvaluate() { 47 | assertEquals(3, FIELD.buildMonomial(0, 3).evaluateAt(0)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /jqrtone/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | qrtone 5 | 6 | qrtone-parent 7 | org.noise-planet 8 | 0.3.1-SNAPSHOT 9 | ../ 10 | 11 | 12 | 13 | 1.7 14 | 1.7 15 | 16 | 17 | 18 | junit 19 | junit 20 | 4.13.1 21 | test 22 | 23 | 24 | org.apache.commons 25 | commons-math3 26 | 3.6.1 27 | test 28 | 29 | 30 | com.github.axet 31 | TarsosDSP 32 | 2.4-1 33 | test 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-source-plugin 41 | 3.0.1 42 | 43 | 44 | attach-sources 45 | 46 | jar 47 | 48 | package 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | 3 | project (qrtone) 4 | 5 | include (CheckIncludeFile) 6 | include (CheckFunctionExists) 7 | include (CheckLibraryExists) 8 | 9 | if(MSVC) 10 | set(LIBM "") 11 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") 12 | else(MSVC) 13 | set(LIBM "m") 14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c99 -pedantic -Wall") 15 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O0 -march=native -fsanitize=address") 17 | #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie,") 18 | else() 19 | if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") 20 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") 21 | else() 22 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") 23 | endif() 24 | if(CMAKE_BUILD_TYPE STREQUAL "Profiling") 25 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -g3") 26 | endif() 27 | endif() 28 | endif(MSVC) 29 | 30 | include_directories (src) 31 | 32 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_USE_MATH_DEFINES") 33 | 34 | add_library(qrtone src/reed_solomon.c "src/qrtone.c") 35 | 36 | target_link_libraries (qrtone ${LIBM}) 37 | 38 | #------------# 39 | # TEST 1 40 | #------------# 41 | set(TEST_DATA_DIR "${PROJECT_BINARY_DIR}/TestAudio") 42 | 43 | file(MAKE_DIRECTORY ${TEST_DATA_DIR}) 44 | 45 | # Test executable and folder 46 | set(ECC_TESTS 47 | test/c/Test_reed_solomon.c) 48 | 49 | 50 | add_executable(Test_REED_SOLOMON ${ECC_TESTS}) 51 | 52 | target_link_libraries (Test_REED_SOLOMON 53 | qrtone ) 54 | 55 | set_property(TARGET Test_REED_SOLOMON PROPERTY FOLDER "tests") 56 | 57 | # Add to test suite 58 | enable_testing() 59 | add_test( NAME ecc_test1 60 | WORKING_DIRECTORY ${TEST_DATA_DIR} 61 | COMMAND Test_REED_SOLOMON ) 62 | 63 | #------------# 64 | # TEST 2 65 | #------------# 66 | 67 | file(COPY jqrtone/src/test/resources/org/noise_planet/qrtone/ipfs_16khz_16bits_mono.raw 68 | DESTINATION ${TEST_DATA_DIR}) 69 | 70 | # Test executable and folder 71 | set(QRTONE_TESTS 72 | test/c/test_qrtone.c) 73 | 74 | 75 | add_executable(Test_QRTONE ${QRTONE_TESTS}) 76 | 77 | target_link_libraries (Test_QRTONE 78 | qrtone ) 79 | 80 | set_property(TARGET Test_QRTONE PROPERTY FOLDER "tests") 81 | 82 | # Add to test suite 83 | 84 | add_test( NAME qrtone_test1 85 | WORKING_DIRECTORY ${TEST_DATA_DIR} 86 | COMMAND Test_QRTONE ) 87 | 88 | enable_testing() -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/Complex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | public class Complex { 37 | public final double r; 38 | public final double i; 39 | 40 | public Complex(double r, double i) { 41 | this.r = r; 42 | this.i = i; 43 | } 44 | 45 | Complex add(Complex c2) { 46 | return new Complex(r + c2.r, i + c2.i); 47 | } 48 | 49 | Complex sub(Complex c2) { 50 | return new Complex(r - c2.r, i - c2.i); 51 | } 52 | 53 | Complex mul(Complex c2) { 54 | return new Complex(r * c2.r - i * c2.i, r * c2.i + i * c2.r); 55 | } 56 | 57 | Complex exp() { 58 | return new Complex(Math.cos(r), -Math.sin(r)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/Header.java: -------------------------------------------------------------------------------- 1 | package org.noise_planet.qrtone; 2 | 3 | public class Header { 4 | public final int length; 5 | public final boolean crc; 6 | Configuration.ECC_LEVEL eccLevel = null; 7 | public final int payloadSymbolsSize; 8 | public final int payloadByteSize; 9 | public final int numberOfBlocks; 10 | public final int numberOfSymbols; 11 | 12 | public Header(int length, Configuration.ECC_LEVEL eccLevel, boolean crc) { 13 | this(length, Configuration.getTotalSymbolsForEcc(eccLevel), Configuration.getEccSymbolsForEcc(eccLevel), crc); 14 | this.eccLevel = eccLevel; 15 | } 16 | public Header(int length, final int blockSymbolsSize, final int blockECCSymbols, boolean crc) { 17 | this.length = length; 18 | int crcLength = 0; 19 | if(crc) { 20 | crcLength = QRTone.CRC_BYTE_LENGTH; 21 | } 22 | payloadSymbolsSize = blockSymbolsSize - blockECCSymbols; 23 | payloadByteSize = payloadSymbolsSize / 2; 24 | numberOfBlocks = (int)Math.ceil(((length + crcLength) * 2) / (double)payloadSymbolsSize); 25 | numberOfSymbols = numberOfBlocks * blockECCSymbols + ( length + crcLength) * 2; 26 | this.crc = crc; 27 | } 28 | 29 | public byte[] encodeHeader() { 30 | if(length > QRTone.MAX_PAYLOAD_LENGTH) { 31 | throw new IllegalArgumentException(String.format("Payload length cannot be superior than %d bytes", QRTone.MAX_PAYLOAD_LENGTH)); 32 | } 33 | // Generate header 34 | byte[] header = new byte[QRTone.HEADER_SIZE]; 35 | // Payload length 36 | header[0] = (byte)(length & 0xFF); 37 | // ECC level 38 | header[1] = (byte)(0x03 & eccLevel.ordinal()); 39 | // has crc 40 | if(crc) { 41 | header[1] = (byte) (header[1] | 0x01 << 3); 42 | } 43 | header[2] = QRTone.crc8(header, 0, QRTone.HEADER_SIZE - 1); 44 | return header; 45 | } 46 | 47 | public static Header decodeHeader(byte[] data) { 48 | // Check CRC 49 | byte crc = QRTone.crc8(data, 0, QRTone.HEADER_SIZE - 1); 50 | if(crc != data[QRTone.HEADER_SIZE - 1]){ 51 | // CRC error 52 | return null; 53 | } 54 | return new Header(data[0] & 0xFF, Configuration.ECC_LEVEL.values()[data[1] & 0x03], ((data[1] >> 3) & 0x01) == 1); 55 | } 56 | 57 | public Configuration.ECC_LEVEL getEccLevel() { 58 | return eccLevel; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/CRC8.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | public class CRC8 { 37 | private int crc8 = 0; 38 | 39 | void add(byte[] data, int from, int to) { 40 | for (int i=from; i < to; i++) { 41 | add(data[i]); 42 | } 43 | } 44 | 45 | void add(byte data) { 46 | int crc = 0; 47 | int accumulator = (crc8 ^ data) & 0x0FF; 48 | for (int j = 0; j < 8; j++) { 49 | if (((accumulator ^ crc) & 0x01) == 0x01) { 50 | crc = ((crc ^ 0x18) >> 1) | 0x80; 51 | } else { 52 | crc = crc >> 1; 53 | } 54 | accumulator = accumulator >> 1; 55 | } 56 | crc8 = (byte) crc; 57 | } 58 | 59 | byte crc() { 60 | return (byte) (crc8 & 0x0FF); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/CRC16.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | 37 | /** 38 | * CRC16 Code 39 | * x**16 + x**15 + x**2 + 1 40 | * @author Nicolas Fortin 41 | */ 42 | public class CRC16 { 43 | private static final int CRC_POLY_16 = 0xA001; 44 | private int crc = 0; 45 | 46 | void add(byte data) { 47 | int i = (crc ^ data) & 0xff; 48 | int crcXor = 0; 49 | int c = i; 50 | for (int j = 0; j < 8; j++) { 51 | if (((crcXor ^ c) & 0x0001) != 0) { 52 | crcXor = (crcXor >> 1) ^ CRC_POLY_16; 53 | } else { 54 | crcXor = crcXor >> 1; 55 | } 56 | c = c >> 1; 57 | } 58 | crc = (crc >>> 8) ^ crcXor; 59 | } 60 | 61 | void reset() { 62 | crc = 0; 63 | } 64 | 65 | int crc() { 66 | return crc & 0xFFFF; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jqrtone/src/test/java/org/noise_planet/qrtone/utils/ArrayWriteProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone.utils; 35 | 36 | import be.tarsos.dsp.AudioEvent; 37 | import be.tarsos.dsp.AudioProcessor; 38 | import java.util.Arrays; 39 | 40 | public class ArrayWriteProcessor implements AudioProcessor { 41 | private float[] data; 42 | private int length; 43 | private int minBufferSize; 44 | 45 | public ArrayWriteProcessor(int minBufferSize) { 46 | this.minBufferSize = minBufferSize; 47 | data = new float[minBufferSize]; 48 | } 49 | 50 | @Override 51 | public boolean process(AudioEvent audioEvent) { 52 | float[] buffer = audioEvent.getFloatBuffer(); 53 | if(buffer.length + length > data.length) { 54 | data = Arrays.copyOf(data, Math.max(data.length + minBufferSize, length + data.length)); 55 | } 56 | System.arraycopy(buffer, 0, data, length, buffer.length); 57 | length += buffer.length; 58 | return true; 59 | } 60 | 61 | public float[] getData() { 62 | return Arrays.copyOf(data, length); 63 | } 64 | 65 | @Override 66 | public void processingFinished() { 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | *

Implements Reed-Solomon encoding, as the name implies.

24 | * 25 | * @author Sean Owen 26 | * @author William Rucklidge 27 | */ 28 | public final class ReedSolomonEncoder { 29 | 30 | private final GenericGF field; 31 | private final List cachedGenerators; 32 | 33 | public ReedSolomonEncoder(GenericGF field) { 34 | this.field = field; 35 | this.cachedGenerators = new ArrayList<>(); 36 | cachedGenerators.add(new GenericGFPoly(field, new int[]{1})); 37 | } 38 | 39 | private GenericGFPoly buildGenerator(int degree) { 40 | if (degree >= cachedGenerators.size()) { 41 | GenericGFPoly lastGenerator = cachedGenerators.get(cachedGenerators.size() - 1); 42 | for (int d = cachedGenerators.size(); d <= degree; d++) { 43 | GenericGFPoly nextGenerator = lastGenerator.multiply( 44 | new GenericGFPoly(field, new int[] { 1, field.exp(d - 1 + field.getGeneratorBase()) })); 45 | cachedGenerators.add(nextGenerator); 46 | lastGenerator = nextGenerator; 47 | } 48 | } 49 | return cachedGenerators.get(degree); 50 | } 51 | 52 | public void encode(int[] toEncode, int ecBytes) { 53 | if (ecBytes == 0) { 54 | throw new IllegalArgumentException("No error correction bytes"); 55 | } 56 | int dataBytes = toEncode.length - ecBytes; 57 | if (dataBytes <= 0) { 58 | throw new IllegalArgumentException("No data bytes provided"); 59 | } 60 | GenericGFPoly generator = buildGenerator(ecBytes); 61 | int[] infoCoefficients = new int[dataBytes]; 62 | System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); 63 | GenericGFPoly info = new GenericGFPoly(field, infoCoefficients); 64 | info = info.multiplyByMonomial(ecBytes, 1); 65 | GenericGFPoly remainder = info.divide(generator)[1]; 66 | int[] coefficients = remainder.getCoefficients(); 67 | int numZeroCoefficients = ecBytes - coefficients.length; 68 | for (int i = 0; i < numZeroCoefficients; i++) { 69 | toEncode[dataBytes + i] = 0; 70 | } 71 | System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /jqrtone/src/test/java/org/noise_planet/qrtone/CircularArrayTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import org.junit.Test; 37 | 38 | import static org.junit.Assert.*; 39 | 40 | public class CircularArrayTest { 41 | 42 | @Test 43 | public void testAddGet() { 44 | CircularArray a = new CircularArray(5); 45 | assertEquals(0, a.size()); 46 | a.add(0.5f); 47 | assertEquals(1, a.size()); 48 | assertEquals(0.5f, a.get(a.size() - 1), 1e-6); 49 | a.add(0.1f); 50 | assertEquals(2, a.size()); 51 | assertEquals(0.5f, a.get(a.size() - 2), 1e-6); 52 | assertEquals(0.1f, a.get(a.size() - 1), 1e-6); 53 | a.add(0.2f); 54 | assertEquals(3, a.size()); 55 | a.add(0.3f); 56 | assertEquals(4, a.size()); 57 | a.add(0.7f); 58 | assertEquals(5, a.size()); 59 | a.add(0.9f); 60 | assertEquals(5, a.size()); 61 | assertEquals(0.9f, a.get(a.size() - 1), 1e-6); 62 | assertEquals(0.7f, a.get(a.size() - 2), 1e-6); 63 | assertEquals(0.3f, a.get(a.size() - 3), 1e-6); 64 | assertEquals(0.2f, a.get(a.size() - 4), 1e-6); 65 | assertEquals(0.1f, a.get(a.size() - 5), 1e-6); 66 | a.clear(); 67 | assertEquals(0, a.size()); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /jqrtone/src/test/java/org/noise_planet/qrtone/PercentileTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import org.apache.commons.math3.stat.descriptive.rank.PSquarePercentile; 37 | import org.junit.Test; 38 | 39 | import java.io.BufferedReader; 40 | import java.io.IOException; 41 | import java.io.InputStreamReader; 42 | import java.util.StringTokenizer; 43 | 44 | import static org.junit.Assert.assertEquals; 45 | 46 | public class PercentileTest { 47 | 48 | @Test 49 | public void testApproximatePercentile1() throws IOException { 50 | PSquarePercentile pSquarePercentile = new PSquarePercentile(50); 51 | ApproximatePercentile p = new ApproximatePercentile(0.5); 52 | String line; 53 | BufferedReader br = new BufferedReader(new InputStreamReader(PercentileTest.class.getResourceAsStream("sunspot.dat"))); 54 | long index = 1; 55 | while ((line = br.readLine()) != null) { 56 | StringTokenizer tokenizer = new StringTokenizer(line, " "); 57 | int year = Integer.parseInt(tokenizer.nextToken()); 58 | float value = Float.parseFloat(tokenizer.nextToken()); 59 | pSquarePercentile.increment(value); 60 | p.add(value); 61 | } 62 | assertEquals(pSquarePercentile.getResult(), p.result(), 1e-6); 63 | } 64 | } -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/CircularArray.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import java.util.AbstractList; 37 | 38 | /** 39 | * Keep fixed sized array of element, without moving elements at each insertion 40 | */ 41 | public class CircularArray extends AbstractList { 42 | private float[] values; 43 | private int cursor = 0; 44 | private int inserted = 0; 45 | 46 | public CircularArray(int size) { 47 | values = new float[size]; 48 | } 49 | 50 | @Override 51 | public Float get(int index) { 52 | int cicularIndex = cursor - inserted + index; 53 | if (cicularIndex < 0) { 54 | cicularIndex += values.length; 55 | } 56 | return values[cicularIndex]; 57 | } 58 | 59 | @Override 60 | public void clear() { 61 | cursor = 0; 62 | inserted = 0; 63 | } 64 | 65 | public Float last() { 66 | if(inserted == 0) { 67 | return null; 68 | } 69 | return get(size() - 1); 70 | } 71 | 72 | @Override 73 | public boolean add(Float value) { 74 | values[cursor] = value; 75 | cursor += 1; 76 | if(cursor == values.length) { 77 | cursor = 0; 78 | } 79 | inserted = Math.min(values.length, inserted + 1); 80 | return true; 81 | } 82 | 83 | @Override 84 | public int size() { 85 | return inserted; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/ArduinoBleSense/ArduinoBleSense.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * QRTone header 6 | */ 7 | #include "qrtone.h" 8 | 9 | 10 | // QRTone instance 11 | qrtone_t* qrtone = NULL; 12 | 13 | #define SAMPLE_RATE 41667 14 | 15 | // audio circular buffer size 16 | #define MAX_AUDIO_WINDOW_SIZE 512 17 | #define LEDR (22u) 18 | #define LEDG (23u) 19 | #define LEDB (24u) 20 | 21 | // buffer to read samples into, each sample is 16-bits 22 | short sampleBuffer[256]; 23 | 24 | // Audio circular buffer provided to QRTone. 25 | static int64_t scaled_input_buffer_feed_cursor = 0; 26 | static int64_t scaled_input_buffer_consume_cursor = 0; 27 | static float scaled_input_buffer[MAX_AUDIO_WINDOW_SIZE]; 28 | 29 | // number of samples read 30 | volatile int samplesRead; 31 | 32 | void setup() { 33 | Serial.begin(9600); 34 | 35 | // Uncomment to wait for serial comm to begin setup 36 | // while (!Serial); 37 | 38 | // initialize digital pins 39 | pinMode(LEDR, OUTPUT); 40 | pinMode(LEDG, OUTPUT); 41 | pinMode(LEDB, OUTPUT); 42 | 43 | // Allocate struct 44 | qrtone = qrtone_new(); 45 | 46 | // Init internal state 47 | qrtone_init(qrtone, SAMPLE_RATE); 48 | 49 | // Init callback method for displaying noise level for gate frequencies 50 | // qrtone_set_level_callback(qrtone, NULL, debug_serial); 51 | 52 | // configure the data receive callback 53 | PDM.onReceive(onPDMdata); 54 | 55 | // optionally set the gain, defaults to 20 56 | PDM.setGain(30); 57 | 58 | // initialize PDM with: 59 | // - one channel (mono mode) 60 | // - a 42 kHz sample rate 61 | if (!PDM.begin(1, SAMPLE_RATE)) { 62 | Serial.println("Failed to start PDM!"); 63 | while (1); 64 | } 65 | 66 | 67 | analogWrite(LEDR, UINT8_MAX); 68 | analogWrite(LEDG, UINT8_MAX); 69 | analogWrite(LEDB, UINT8_MAX); 70 | } 71 | 72 | void debug_serial(void *ptr, int64_t location, float first_tone_level, float second_tone_level, int32_t triggered) { 73 | char buf[100]; 74 | sprintf(buf, "%.2f,%.2f,%ld\n\r", first_tone_level, second_tone_level, triggered * -10); 75 | Serial.write(buf); 76 | } 77 | 78 | void loop() { 79 | //Once the recording buffer is full, we process it. 80 | if (scaled_input_buffer_feed_cursor > scaled_input_buffer_consume_cursor) 81 | { 82 | if(scaled_input_buffer_feed_cursor - scaled_input_buffer_consume_cursor > MAX_AUDIO_WINDOW_SIZE) { 83 | // overflow 84 | Serial.println("Buffer overflow"); 85 | } 86 | int sample_to_read = scaled_input_buffer_feed_cursor - scaled_input_buffer_consume_cursor; 87 | int max_window_length = qrtone_get_maximum_length(qrtone); 88 | int sample_index = 0; 89 | while(sample_index < sample_to_read) { 90 | int32_t position_in_buffer = ((scaled_input_buffer_consume_cursor + sample_index) % MAX_AUDIO_WINDOW_SIZE); 91 | int32_t window_length = min(max_window_length, min(sample_to_read - sample_index, MAX_AUDIO_WINDOW_SIZE - position_in_buffer % MAX_AUDIO_WINDOW_SIZE)); 92 | if(qrtone_push_samples(qrtone, scaled_input_buffer + position_in_buffer, window_length)) { 93 | // Got a message 94 | int8_t* data = qrtone_get_payload(qrtone); 95 | int32_t data_length = qrtone_get_payload_length(qrtone); 96 | if(data_length >= 3) { 97 | char buf[100]; 98 | sprintf(buf, "%d,%d,%d\n\r", (uint8_t)data[0], (uint8_t)data[1], (uint8_t)data[2]); 99 | Serial.write(buf); 100 | analogWrite(LEDR, UINT8_MAX - (uint8_t)data[0]); 101 | analogWrite(LEDG, UINT8_MAX - (uint8_t)data[1]); 102 | analogWrite(LEDB, UINT8_MAX - (uint8_t)data[2]); 103 | } 104 | } 105 | sample_index += window_length; 106 | max_window_length = qrtone_get_maximum_length(qrtone); 107 | } 108 | scaled_input_buffer_consume_cursor += sample_index; 109 | } 110 | } 111 | 112 | void onPDMdata() { 113 | // query the number of bytes available 114 | int bytesAvailable = PDM.available(); 115 | 116 | // read into the sample buffer 117 | PDM.read(sampleBuffer, bytesAvailable); 118 | 119 | // 16-bit, 2 bytes per sample 120 | samplesRead = bytesAvailable / 2; 121 | 122 | for(int sample_id = 0; sample_id < samplesRead; sample_id++) { 123 | scaled_input_buffer[(scaled_input_buffer_feed_cursor+sample_id) % MAX_AUDIO_WINDOW_SIZE] = (float)sampleBuffer[sample_id] / 32768.0f; 124 | } 125 | scaled_input_buffer_feed_cursor += samplesRead; 126 | } 127 | -------------------------------------------------------------------------------- /utilities/MXChipAudioDump/MXChipAudioDump.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MXChip and Arduino native headers. 3 | */ 4 | #include "Arduino.h" 5 | #include "AudioClassV2.h" 6 | #include "RGB_LED.h" 7 | 8 | /* 9 | * QRTone header 10 | */ 11 | #include "qrtone.h" 12 | 13 | #define RGB_LED_BRIGHTNESS 32 14 | 15 | RGB_LED rgbLed; 16 | 17 | /* 18 | * The audio sample rate used by the microphone. 19 | */ 20 | #define SAMPLE_RATE 16000 21 | 22 | #define AUDIO_SAMPLE_SIZE 16 23 | 24 | #define MAX_AUDIO_WINDOW_SIZE 512 25 | 26 | // QRTone instance 27 | qrtone_t* qrtone = NULL; 28 | 29 | // MXChip audio controler 30 | AudioClass& Audio = AudioClass::getInstance(); 31 | 32 | /* 33 | * Buffers containnig the audio to play and record data. 34 | */ 35 | // AUDIO_CHUNK_SIZE is 512 bytes 36 | // A sample is 2 bytes (16 bits) 37 | // raw_audio_buffer contains 128 short left samples and 128 short right samples 38 | // We keep only left samples 39 | static char recordBuffer[AUDIO_CHUNK_SIZE] = {0}; 40 | 41 | // Audio circular buffer provided to QRTone. 42 | static int64_t scaled_input_buffer_feed_cursor = 0; 43 | static int64_t scaled_input_buffer_consume_cursor = 0; 44 | static float scaled_input_buffer[MAX_AUDIO_WINDOW_SIZE]; 45 | 46 | static int missed_window = 0; 47 | static int last_color = -1; 48 | /* 49 | * Audio recording callback called by the Audio Class instance when a new 50 | * buffer of samples is available with new recorded samples. 51 | */ 52 | void recordCallback(void) 53 | { 54 | int32_t record_buffer_length = Audio.readFromRecordBuffer(recordBuffer, AUDIO_CHUNK_SIZE); 55 | char* cur_reader = &recordBuffer[0]; 56 | char* end_reader = &recordBuffer[record_buffer_length]; 57 | const int offset = 4; // short samples size + skip right channel 58 | int sample_index = 0; 59 | while(cur_reader < end_reader) { 60 | int16_t sample = *((int16_t *)cur_reader); 61 | scaled_input_buffer[(scaled_input_buffer_feed_cursor+sample_index) % MAX_AUDIO_WINDOW_SIZE] = (float)sample / 32768.0f; 62 | sample_index += 1; 63 | cur_reader += offset; 64 | } 65 | scaled_input_buffer_feed_cursor += sample_index; 66 | if(scaled_input_buffer_feed_cursor - scaled_input_buffer_consume_cursor > MAX_AUDIO_WINDOW_SIZE) { 67 | // it overwrites unprocessed samples 68 | missed_window += 1; 69 | } 70 | } 71 | 72 | void debug_serial(void *ptr, int64_t location, float first_tone_level, float second_tone_level, int32_t triggered) { 73 | char buf[100]; 74 | sprintf(buf, "%.2f,%.2f,%d\n\r", first_tone_level, second_tone_level, triggered); 75 | Serial.write(buf); 76 | } 77 | 78 | void setup(void) 79 | { 80 | Serial.begin(115200); 81 | // Setup the audio class 82 | Audio.format(SAMPLE_RATE, AUDIO_SAMPLE_SIZE); 83 | 84 | // Allocate struct 85 | qrtone = qrtone_new(); 86 | 87 | // Init internal state 88 | qrtone_init(qrtone, SAMPLE_RATE); 89 | 90 | // Init callback method 91 | // qrtone_set_level_callback(qrtone, NULL, debug_serial); 92 | 93 | // Start recording. 94 | int res = Audio.startRecord(recordCallback); 95 | } 96 | 97 | void loop(void) 98 | { 99 | // Once the recording buffer is full, we process it. 100 | if (scaled_input_buffer_feed_cursor > scaled_input_buffer_consume_cursor) 101 | { 102 | unsigned long start = millis(); 103 | if(missed_window > 0 && last_color != 1) { 104 | rgbLed.setColor(RGB_LED_BRIGHTNESS, 0, 0); 105 | last_color = 1; 106 | missed_window = 0; 107 | } else if(last_color != 2){ 108 | rgbLed.setColor(0, RGB_LED_BRIGHTNESS, 0); 109 | last_color = 2; 110 | } 111 | int sample_to_read = scaled_input_buffer_feed_cursor - scaled_input_buffer_consume_cursor; 112 | int max_window_length = qrtone_get_maximum_length(qrtone); 113 | int sample_index = 0; 114 | while(sample_index < sample_to_read) { 115 | int32_t position_in_buffer = ((scaled_input_buffer_consume_cursor + sample_index) % MAX_AUDIO_WINDOW_SIZE); 116 | int32_t window_length = min(max_window_length, min(sample_to_read - sample_index, MAX_AUDIO_WINDOW_SIZE - position_in_buffer % MAX_AUDIO_WINDOW_SIZE)); 117 | if(qrtone_push_samples(qrtone, scaled_input_buffer + position_in_buffer, window_length)) { 118 | // Got a message 119 | rgbLed.setColor(0, 0, RGB_LED_BRIGHTNESS); 120 | Serial.write((const char *) qrtone_get_payload(qrtone), qrtone_get_payload_length(qrtone)); 121 | } 122 | sample_index += window_length; 123 | max_window_length = qrtone_get_maximum_length(qrtone); 124 | } 125 | scaled_input_buffer_consume_cursor += sample_index; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 43 | 46 | 50 | 57 | 64 | 71 | 78 | 85 | 92 | 99 | 106 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /jqrtone/src/test/resources/org/noise_planet/qrtone/sunspot.dat: -------------------------------------------------------------------------------- 1 | 1701 11.0 2 | 1702 16.0 3 | 1703 23.0 4 | 1704 36.0 5 | 1705 58.0 6 | 1706 29.0 7 | 1707 20.0 8 | 1708 10.0 9 | 1709 8.0 10 | 1710 3.0 11 | 1711 0.0 12 | 1712 0.0 13 | 1713 2.0 14 | 1714 11.0 15 | 1715 27.0 16 | 1716 47.0 17 | 1717 63.0 18 | 1718 60.0 19 | 1719 39.0 20 | 1720 28.0 21 | 1721 26.0 22 | 1722 22.0 23 | 1723 11.0 24 | 1724 21.0 25 | 1725 40.0 26 | 1726 78.0 27 | 1727 122.0 28 | 1728 103.0 29 | 1729 73.0 30 | 1730 47.0 31 | 1731 35.0 32 | 1732 11.0 33 | 1733 5.0 34 | 1734 16.0 35 | 1735 34.0 36 | 1736 70.0 37 | 1737 81.0 38 | 1738 111.0 39 | 1739 101.0 40 | 1740 73.0 41 | 1741 40.0 42 | 1742 20.0 43 | 1743 16.0 44 | 1744 5.0 45 | 1745 11.0 46 | 1746 22.0 47 | 1747 40.0 48 | 1748 60.0 49 | 1749 80.9 50 | 1750 83.4 51 | 1751 47.7 52 | 1752 47.8 53 | 1753 30.7 54 | 1754 12.2 55 | 1755 9.6 56 | 1756 10.2 57 | 1757 32.4 58 | 1758 47.6 59 | 1759 54.0 60 | 1760 62.9 61 | 1761 85.9 62 | 1762 61.2 63 | 1763 45.1 64 | 1764 36.4 65 | 1765 20.9 66 | 1766 11.4 67 | 1767 37.8 68 | 1768 69.8 69 | 1769 106.1 70 | 1770 100.8 71 | 1771 81.6 72 | 1772 66.5 73 | 1773 34.8 74 | 1774 30.6 75 | 1775 7.0 76 | 1776 19.8 77 | 1777 92.5 78 | 1778 154.4 79 | 1779 125.9 80 | 1780 84.8 81 | 1781 68.1 82 | 1782 38.5 83 | 1783 22.8 84 | 1784 10.2 85 | 1785 24.1 86 | 1786 82.9 87 | 1787 132.0 88 | 1788 130.9 89 | 1789 118.1 90 | 1790 89.9 91 | 1791 66.6 92 | 1792 60.0 93 | 1793 46.9 94 | 1794 41.0 95 | 1795 21.3 96 | 1796 16.0 97 | 1797 6.4 98 | 1798 4.1 99 | 1799 6.8 100 | 1800 14.5 101 | 1801 34.0 102 | 1802 45.0 103 | 1803 43.1 104 | 1804 47.5 105 | 1805 42.2 106 | 1806 28.1 107 | 1807 10.1 108 | 1808 8.1 109 | 1809 2.5 110 | 1810 0.0 111 | 1811 1.4 112 | 1812 5.0 113 | 1813 12.2 114 | 1814 13.9 115 | 1815 35.4 116 | 1816 45.8 117 | 1817 41.1 118 | 1818 30.1 119 | 1819 23.9 120 | 1820 15.6 121 | 1821 6.6 122 | 1822 4.0 123 | 1823 1.8 124 | 1824 8.5 125 | 1825 16.6 126 | 1826 36.3 127 | 1827 49.6 128 | 1828 64.2 129 | 1829 67.0 130 | 1830 70.9 131 | 1831 47.8 132 | 1832 27.5 133 | 1833 8.5 134 | 1834 13.2 135 | 1835 56.9 136 | 1836 121.5 137 | 1837 138.3 138 | 1838 103.2 139 | 1839 85.7 140 | 1840 64.6 141 | 1841 36.7 142 | 1842 24.2 143 | 1843 10.7 144 | 1844 15.0 145 | 1845 40.1 146 | 1846 61.5 147 | 1847 98.5 148 | 1848 124.7 149 | 1849 96.3 150 | 1850 66.6 151 | 1851 64.5 152 | 1852 54.1 153 | 1853 39.0 154 | 1854 20.6 155 | 1855 6.7 156 | 1856 4.3 157 | 1857 22.7 158 | 1858 54.8 159 | 1859 93.8 160 | 1860 95.8 161 | 1861 77.2 162 | 1862 59.1 163 | 1863 44.0 164 | 1864 47.0 165 | 1865 30.5 166 | 1866 16.3 167 | 1867 7.3 168 | 1868 37.6 169 | 1869 74.0 170 | 1870 139.0 171 | 1871 111.2 172 | 1872 101.6 173 | 1873 66.2 174 | 1874 44.7 175 | 1875 17.0 176 | 1876 11.3 177 | 1877 12.4 178 | 1878 3.4 179 | 1879 6.0 180 | 1880 32.3 181 | 1881 54.3 182 | 1882 59.7 183 | 1883 63.7 184 | 1884 63.5 185 | 1885 52.2 186 | 1886 25.4 187 | 1887 13.1 188 | 1888 6.8 189 | 1889 6.3 190 | 1890 7.1 191 | 1891 35.6 192 | 1892 73.0 193 | 1893 85.1 194 | 1894 78.0 195 | 1895 64.0 196 | 1896 41.8 197 | 1897 26.2 198 | 1898 26.7 199 | 1899 12.1 200 | 1900 9.5 201 | 1901 2.7 202 | 1902 5.0 203 | 1903 24.4 204 | 1904 42.0 205 | 1905 63.5 206 | 1906 53.8 207 | 1907 62.0 208 | 1908 48.5 209 | 1909 43.9 210 | 1910 18.6 211 | 1911 5.7 212 | 1912 3.6 213 | 1913 1.4 214 | 1914 9.6 215 | 1915 47.4 216 | 1916 57.1 217 | 1917 103.9 218 | 1918 80.6 219 | 1919 63.6 220 | 1920 37.6 221 | 1921 26.1 222 | 1922 14.2 223 | 1923 5.8 224 | 1924 16.7 225 | 1925 44.3 226 | 1926 63.9 227 | 1927 69.0 228 | 1928 77.8 229 | 1929 64.9 230 | 1930 35.7 231 | 1931 21.2 232 | 1932 11.1 233 | 1933 5.7 234 | 1934 8.7 235 | 1935 36.1 236 | 1936 79.7 237 | 1937 114.4 238 | 1938 109.6 239 | 1939 88.8 240 | 1940 67.8 241 | 1941 47.5 242 | 1942 30.6 243 | 1943 16.3 244 | 1944 9.6 245 | 1945 33.2 246 | 1946 92.6 247 | 1947 151.6 248 | 1948 136.3 249 | 1949 134.7 250 | 1950 83.9 251 | 1951 69.4 252 | 1952 31.5 253 | 1953 13.9 254 | 1954 4.4 255 | 1955 38.0 256 | 1956 141.7 257 | 1957 190.2 258 | 1958 184.8 259 | 1959 159.0 260 | 1960 112.3 261 | 1961 53.9 262 | 1962 37.5 263 | 1963 27.9 264 | 1964 10.2 265 | 1965 15.1 266 | 1966 47.0 267 | 1967 93.8 268 | 1968 105.9 269 | 1969 105.5 270 | 1970 104.5 271 | 1971 66.6 272 | 1972 68.9 273 | 1973 38.0 274 | 1974 34.5 275 | 1975 15.5 276 | 1976 12.6 277 | 1977 27.5 278 | 1978 92.5 279 | 1979 155.4 280 | 1980 154.6 281 | 1981 140.4 282 | 1982 115.9 283 | 1983 66.6 284 | 1984 45.9 285 | 1985 17.9 286 | 1986 13.4 287 | 1987 29.3 288 | 1988 91.9 289 | 1989 149.2 290 | 1990 153.6 291 | 1991 135.9 292 | 1992 114.2 293 | 1993 70.1 294 | 1994 50.2 295 | 1995 20.5 296 | 1996 14.3 297 | 1997 31.3 298 | 1998 89.9 299 | 1999 151.5 300 | 2000 149.3 -------------------------------------------------------------------------------- /jqrtone/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | /** 20 | *

This class contains utility methods for performing mathematical operations over 21 | * the Galois Fields. Operations use a given primitive polynomial in calculations.

22 | * 23 | *

Throughout this package, elements of the GF are represented as an {@code int} 24 | * for convenience and speed (but at the cost of memory). 25 | *

26 | * 27 | * @author Sean Owen 28 | * @author David Olivier 29 | */ 30 | public final class GenericGF { 31 | 32 | public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 33 | public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1 34 | public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1 35 | public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1 36 | public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1 37 | public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1 38 | public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256; 39 | public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6; 40 | 41 | private final int[] expTable; 42 | private final int[] logTable; 43 | private final GenericGFPoly zero; 44 | private final GenericGFPoly one; 45 | private final int size; 46 | private final int primitive; 47 | private final int generatorBase; 48 | 49 | /** 50 | * Create a representation of GF(size) using the given primitive polynomial. 51 | * 52 | * @param primitive irreducible polynomial whose coefficients are represented by 53 | * the bits of an int, where the least-significant bit represents the constant 54 | * coefficient 55 | * @param size the size of the field 56 | * @param b the factor b in the generator polynomial can be 0- or 1-based 57 | * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))). 58 | * In most cases it should be 1, but for QR code it is 0. 59 | */ 60 | public GenericGF(int primitive, int size, int b) { 61 | this.primitive = primitive; 62 | this.size = size; 63 | this.generatorBase = b; 64 | 65 | expTable = new int[size]; 66 | logTable = new int[size]; 67 | int x = 1; 68 | for (int i = 0; i < size; i++) { 69 | expTable[i] = x; 70 | x *= 2; // we're assuming the generator alpha is 2 71 | if (x >= size) { 72 | x ^= primitive; 73 | x &= size - 1; 74 | } 75 | } 76 | for (int i = 0; i < size - 1; i++) { 77 | logTable[expTable[i]] = i; 78 | } 79 | // logTable[0] == 0 but this should never be used 80 | zero = new GenericGFPoly(this, new int[]{0}); 81 | one = new GenericGFPoly(this, new int[]{1}); 82 | } 83 | 84 | GenericGFPoly getZero() { 85 | return zero; 86 | } 87 | 88 | GenericGFPoly getOne() { 89 | return one; 90 | } 91 | 92 | /** 93 | * @return the monomial representing coefficient * x^degree 94 | */ 95 | GenericGFPoly buildMonomial(int degree, int coefficient) { 96 | if (degree < 0) { 97 | throw new IllegalArgumentException(); 98 | } 99 | if (coefficient == 0) { 100 | return zero; 101 | } 102 | int[] coefficients = new int[degree + 1]; 103 | coefficients[0] = coefficient; 104 | return new GenericGFPoly(this, coefficients); 105 | } 106 | 107 | /** 108 | * Implements both addition and subtraction -- they are the same in GF(size). 109 | * 110 | * @return sum/difference of a and b 111 | */ 112 | static int addOrSubtract(int a, int b) { 113 | return a ^ b; 114 | } 115 | 116 | /** 117 | * @return 2 to the power of a in GF(size) 118 | */ 119 | int exp(int a) { 120 | return expTable[a]; 121 | } 122 | 123 | /** 124 | * @return base 2 log of a in GF(size) 125 | */ 126 | int log(int a) { 127 | if (a == 0) { 128 | throw new IllegalArgumentException(); 129 | } 130 | return logTable[a]; 131 | } 132 | 133 | /** 134 | * @return multiplicative inverse of a 135 | */ 136 | int inverse(int a) { 137 | if (a == 0) { 138 | throw new ArithmeticException(); 139 | } 140 | return expTable[size - logTable[a] - 1]; 141 | } 142 | 143 | /** 144 | * @return product of a and b in GF(size) 145 | */ 146 | int multiply(int a, int b) { 147 | if (a == 0 || b == 0) { 148 | return 0; 149 | } 150 | return expTable[(logTable[a] + logTable[b]) % (size - 1)]; 151 | } 152 | 153 | public int getSize() { 154 | return size; 155 | } 156 | 157 | public int getGeneratorBase() { 158 | return generatorBase; 159 | } 160 | 161 | @Override 162 | public String toString() { 163 | return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')'; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/reed_solomon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, self 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * self list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * self software without specific prior written permission. 20 | * 21 | * self SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF self SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | 35 | /** 36 | * @file reed_solomon.h 37 | * @author Nicolas Fortin @NicolasCumu 38 | * @author Sean Owen (java version) 39 | * @author David Olivier (java version) 40 | * @date 24/03/2020 41 | * @brief Api for generic Reed-Solomon ECC 42 | * Reference algorithm is the ZXing QR-Code Apache License source code. 43 | * @link https://github.com/zxing/zxing/tree/master/core/src/main/java/com/google/zxing/common/reedsolomon 44 | */ 45 | 46 | #ifndef ECC_H 47 | #define ECC_H 48 | 49 | #ifdef __cplusplus 50 | extern "C" { 51 | #endif 52 | 53 | #include 54 | 55 | enum ECC_ERROR_CODES { ECC_NO_ERRORS = 0, ECC_ILLEGAL_ARGUMENT = 1, ECC_DIVIDE_BY_ZERO = 2, ECC_REED_SOLOMON_ERROR 56 | = 3, ECC_ILLEGAL_STATE_EXCEPTION = 4}; 57 | 58 | typedef struct _ecc_generic_gf_poly_t { 59 | int32_t* coefficients; /**< coefficients as ints representing elements of GF(size), arranged from most significant (highest-power term) coefficient to least significant */ 60 | int32_t coefficients_length; /**< Length of message attached to distance*/ 61 | } ecc_generic_gf_poly_t; 62 | 63 | typedef struct _ecc_generic_gf_t { 64 | int32_t* exp_table; 65 | int32_t* log_table; 66 | int32_t size; 67 | int32_t primitive; 68 | int32_t generator_base; 69 | ecc_generic_gf_poly_t zero; 70 | ecc_generic_gf_poly_t one; 71 | } ecc_generic_gf_t; 72 | 73 | typedef struct _ecc_reed_solomon_cached_generator_t { 74 | struct _ecc_reed_solomon_cached_generator_t* previous; 75 | int32_t index; 76 | ecc_generic_gf_poly_t* value; 77 | } ecc_reed_solomon_cached_generator_t; 78 | 79 | typedef struct _ecc_reed_solomon_encoder_t { 80 | ecc_generic_gf_t field; 81 | ecc_reed_solomon_cached_generator_t* cached_generators; 82 | } ecc_reed_solomon_encoder_t; 83 | 84 | void ecc_generic_gf_poly_init(ecc_generic_gf_poly_t* self, int32_t* coefficients, int32_t coefficients_length); 85 | 86 | void ecc_generic_gf_init(ecc_generic_gf_t* self, int32_t primitive, int32_t size, int32_t b); 87 | 88 | void ecc_generic_gf_free(ecc_generic_gf_t* self); 89 | 90 | void ecc_generic_gf_poly_free(ecc_generic_gf_poly_t* self); 91 | 92 | int32_t ecc_generic_gf_build_monomial(ecc_generic_gf_poly_t* poly, int32_t degree, int32_t coefficient); 93 | 94 | void ecc_generic_gf_poly_multiply(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, int32_t scalar, ecc_generic_gf_poly_t* result); 95 | 96 | int32_t ecc_generic_gf_poly_is_zero(ecc_generic_gf_poly_t* self); 97 | 98 | int32_t ecc_generic_gf_poly_get_coefficient(ecc_generic_gf_poly_t* self, int32_t degree); 99 | 100 | int32_t ecc_generic_gf_inverse(ecc_generic_gf_t* self, int32_t a); 101 | 102 | void ecc_generic_gf_poly_add_or_substract(ecc_generic_gf_poly_t* self, ecc_generic_gf_poly_t* other, ecc_generic_gf_poly_t* result); 103 | 104 | int32_t ecc_generic_gf_poly_get_degree(ecc_generic_gf_poly_t* self); 105 | 106 | void ecc_generic_gf_poly_multiply_other(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, ecc_generic_gf_poly_t* other, ecc_generic_gf_poly_t* result); 107 | 108 | /** 109 | * @return product of a and b in GF(size) 110 | */ 111 | int32_t ecc_generic_gf_multiply(ecc_generic_gf_t* self, int32_t a, int32_t b); 112 | 113 | /** 114 | * @return evaluation of this polynomial at a given point 115 | */ 116 | int32_t ecc_generic_gf_poly_evaluate_at(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, int32_t a); 117 | 118 | void ecc_reed_solomon_encoder_free(ecc_reed_solomon_encoder_t* self); 119 | 120 | void ecc_reed_solomon_encoder_init(ecc_reed_solomon_encoder_t* self, int32_t primitive, int32_t size, int32_t b); 121 | 122 | void ecc_reed_solomon_encoder_encode(ecc_reed_solomon_encoder_t* self, int32_t* to_encode, int32_t to_encode_length, int32_t ec_bytes); 123 | 124 | /** 125 | * @return ecc_ERROR_CODES 126 | */ 127 | int32_t ecc_reed_solomon_decoder_decode(ecc_generic_gf_t* field, int32_t* to_decode, int32_t to_decode_length, int32_t ec_bytes, int32_t* fixedErrors); 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | #ifdef __cplusplus 136 | } 137 | #endif 138 | 139 | #endif -------------------------------------------------------------------------------- /jqrtone/src/test/java/org/noise_planet/qrtone/PeakFinderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import org.junit.Test; 37 | 38 | import java.io.*; 39 | import java.util.*; 40 | 41 | 42 | import static org.junit.Assert.assertArrayEquals; 43 | 44 | public class PeakFinderTest { 45 | 46 | @Test 47 | public void findPeaks() throws IOException { 48 | PeakFinder peakFinder = new PeakFinder(); 49 | String line; 50 | BufferedReader br = new BufferedReader(new InputStreamReader(PeakFinderTest.class.getResourceAsStream("sunspot.dat"))); 51 | long index = 1; 52 | List results = new ArrayList<>(); 53 | while ((line = br.readLine()) != null) { 54 | StringTokenizer tokenizer = new StringTokenizer(line, " "); 55 | int year = Integer.parseInt(tokenizer.nextToken()); 56 | float value = Float.parseFloat(tokenizer.nextToken()); 57 | if(peakFinder.add(index++, (double)value)) { 58 | results.add(peakFinder.getLastPeak()); 59 | } 60 | } 61 | int[] expectedIndex = new int[]{5, 17, 27, 38, 50, 52, 61, 69, 78, 87, 102, 104, 116, 130, 137, 148, 160, 164, 170, 177, 183, 193, 198, 205, 207, 217, 228, 237, 247, 257, 268, 272, 279, 290, 299}; 62 | int[] got = new int[results.size()]; 63 | for(int i=0; i < results.size(); i++) { 64 | got[i] = (int)results.get(i).index; 65 | } 66 | assertArrayEquals(expectedIndex, got); 67 | } 68 | 69 | @Test 70 | public void findPeaksMinimumWidth() throws IOException { 71 | PeakFinder peakFinder = new PeakFinder(); 72 | String line; 73 | BufferedReader br = new BufferedReader(new InputStreamReader(PeakFinderTest.class.getResourceAsStream("sunspot.dat"))); 74 | long index = 1; 75 | List res = new ArrayList<>(); 76 | while ((line = br.readLine()) != null) { 77 | StringTokenizer tokenizer = new StringTokenizer(line, " "); 78 | int year = Integer.parseInt(tokenizer.nextToken()); 79 | float value = Float.parseFloat(tokenizer.nextToken()); 80 | if(peakFinder.add(index++, (double)value)) { 81 | res.add(peakFinder.getLastPeak()); 82 | } 83 | } 84 | int[] expectedIndex = new int[]{5, 17 ,27, 38, 50, 61, 69, 78, 87, 104, 116, 130, 137, 148, 160, 170, 183, 193, 205, 217, 228, 237, 247, 257, 268, 279, 290, 299}; 85 | List results = PeakFinder.filter(res,6); 86 | int[] got = new int[results.size()]; 87 | for(int i=0; i < results.size(); i++) { 88 | got[i] = (int)results.get(i).index; 89 | } 90 | assertArrayEquals(expectedIndex, got); 91 | } 92 | 93 | @Test 94 | public void findPeaksIncreaseCondition() throws IOException { 95 | PeakFinder peakFinder = new PeakFinder(); 96 | peakFinder.setMinIncreaseCount(3); 97 | double[] values = new double[] {4, 5, 7, 13, 10, 9, 9, 10, 4, 6, 7, 8, 11 , 3, 2, 2}; 98 | long index = 0; 99 | List results = new ArrayList<>(); 100 | for(double value : values) { 101 | if(peakFinder.add(index++, (double)value)) { 102 | results.add(peakFinder.getLastPeak()); 103 | } 104 | } 105 | int[] expectedIndex = new int[]{3, 12}; 106 | int[] got = new int[results.size()]; 107 | for(int i=0; i < results.size(); i++) { 108 | got[i] = (int)results.get(i).index; 109 | } 110 | assertArrayEquals(expectedIndex, got); 111 | } 112 | 113 | @Test 114 | public void findPeaksDecreaseCondition() throws IOException { 115 | PeakFinder peakFinder = new PeakFinder(); 116 | peakFinder.setMinDecreaseCount(2); 117 | double[] values = new double[] {4, 5, 7, 13, 10, 9, 9, 10, 4, 6, 7, 8, 11 , 3, 2, 2}; 118 | long index = 0; 119 | List results = new ArrayList<>(); 120 | for(double value : values) { 121 | if(peakFinder.add(index++, (double)value)) { 122 | results.add(peakFinder.getLastPeak()); 123 | } 124 | } 125 | int[] expectedIndex = new int[]{3, 12}; 126 | int[] got = new int[results.size()]; 127 | for(int i=0; i < results.size(); i++) { 128 | got[i] = (int)results.get(i).index; 129 | } 130 | assertArrayEquals(expectedIndex, got); 131 | } 132 | } -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2018, Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | /** 37 | * QRTone configuration object 38 | */ 39 | public class Configuration { 40 | public enum ECC_LEVEL {ECC_L, ECC_M, ECC_Q, ECC_H} 41 | private static final int[][] ECC_SYMBOLS = new int[][] {{14, 2}, {14, 4}, {12, 6}, {10, 6}}; 42 | public static final double MULT_SEMITONE = Math.pow(2, 1/15.0); 43 | public static final double DEFAULT_WORD_TIME = 0.06; 44 | public static final double DEFAULT_WORD_SILENCE_TIME = 0.01; 45 | public static final double DEFAULT_GATE_TIME = 0.12; 46 | public static final double DEFAULT_AUDIBLE_FIRST_FREQUENCY = 1720; 47 | public static final double DEFAULT_INAUDIBLE_FIRST_FREQUENCY = 18200; 48 | public static final int DEFAULT_INAUDIBLE_STEP = 50; 49 | public static final double DEFAULT_TRIGGER_SNR = 15; 50 | public static final ECC_LEVEL DEFAULT_ECC_LEVEL = ECC_LEVEL.ECC_Q; 51 | 52 | public final double sampleRate; 53 | public final double firstFrequency; 54 | public final int frequencyIncrement; 55 | public final double frequencyMulti; 56 | public final double wordTime; 57 | public final double triggerSnr; 58 | public final double gateTime; 59 | public final double wordSilenceTime; 60 | 61 | public Configuration(double sampleRate, double firstFrequency, int frequencyIncrement, double frequencyMulti, 62 | double wordTime, double triggerSnr, double gateTime, double wordSilenceTime) { 63 | this.sampleRate = sampleRate; 64 | this.firstFrequency = firstFrequency; 65 | this.frequencyIncrement = frequencyIncrement; 66 | this.frequencyMulti = frequencyMulti; 67 | this.wordTime = wordTime; 68 | this.triggerSnr = triggerSnr; 69 | this.gateTime = gateTime; 70 | this.wordSilenceTime = wordSilenceTime; 71 | } 72 | 73 | /** 74 | * Audible data communication 75 | * @param sampleRate Sampling rate in Hz 76 | * @return Default configuration for this profile 77 | */ 78 | public static Configuration getAudible(double sampleRate) { 79 | return new Configuration(sampleRate, DEFAULT_AUDIBLE_FIRST_FREQUENCY,0, MULT_SEMITONE, 80 | DEFAULT_WORD_TIME, DEFAULT_TRIGGER_SNR, DEFAULT_GATE_TIME, DEFAULT_WORD_SILENCE_TIME); 81 | } 82 | 83 | 84 | /** 85 | * Inaudible data communication (from 18200 Hz to 22040 hz) 86 | * @param sampleRate Sampling rate in Hz. Must be greater or equal to 44100 Hz (Nyquist frequency) 87 | * @return Default configuration for this profile 88 | */ 89 | public static Configuration getInaudible(double sampleRate) { 90 | return new Configuration(sampleRate, DEFAULT_INAUDIBLE_FIRST_FREQUENCY, DEFAULT_INAUDIBLE_STEP, 0, 91 | DEFAULT_WORD_TIME, DEFAULT_TRIGGER_SNR, DEFAULT_GATE_TIME, DEFAULT_WORD_SILENCE_TIME); 92 | } 93 | 94 | public double[] computeFrequencies(int frequencyCount) { 95 | return computeFrequencies(frequencyCount, 0); 96 | } 97 | public double[] computeFrequencies(int frequencyCount, double offset) { 98 | double[] frequencies = new double[frequencyCount]; 99 | // Precompute pitch frequencies 100 | for(int i = 0; i < frequencyCount; i++) { 101 | if(frequencyIncrement != 0) { 102 | frequencies[i] = firstFrequency + (i + offset) * frequencyIncrement; 103 | } else { 104 | frequencies[i] = firstFrequency * Math.pow(frequencyMulti, i + offset); 105 | } 106 | } 107 | return frequencies; 108 | } 109 | 110 | /** 111 | * Given sample rate and frequencies, evaluate the minimal good window size for avoiding leaks of spectral analysis. 112 | * @param sampleRate Sample rate in Hz 113 | * @param targetFrequency Spectral analysis minimal frequency 114 | * @param closestFrequency Spectral analysis closest frequency to differentiate 115 | * @return Minimum window length in samples 116 | */ 117 | public static int computeMinimumWindowSize(double sampleRate, double targetFrequency, double closestFrequency) { 118 | // Max bin size in Hz 119 | double max_bin_size = Math.abs(closestFrequency - targetFrequency) / 2.0; 120 | // Minimum window size without leaks 121 | int window_size = (int)(Math.ceil(sampleRate / max_bin_size)); 122 | return Math.max(window_size, (int)Math.ceil(sampleRate*(5*(1/targetFrequency)))); 123 | } 124 | 125 | /** 126 | * @param eccLevel Ecc level 127 | * @return Number of symbols (Payload+Ecc) corresponding to this level 128 | */ 129 | public static int getTotalSymbolsForEcc(ECC_LEVEL eccLevel) { 130 | return ECC_SYMBOLS[eccLevel.ordinal()][0]; 131 | } 132 | 133 | /** 134 | * @param eccLevel Ecc level 135 | * @return Number of Ecc symbols corresponding to this level (Correctable errors is 50 % of this number) 136 | */ 137 | public static int getEccSymbolsForEcc(ECC_LEVEL eccLevel) { 138 | return ECC_SYMBOLS[eccLevel.ordinal()][1]; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/IterativeGeneralizedGoertzel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | /** 37 | * Goertzel algorithm - Compute the RMS power of the selected frequencies for the provided audio signals. 38 | * http://asp.eurasipjournals.com/content/pdf/1687-6180-2012-56.pdf 39 | * ipfs://QmdAMfyq71Fm72Rt5u1qtWM7teReGAHmceAtDN5SG4Pt22 40 | * Sysel and Rajmic:Goertzel algorithm generalized to non-integer multiples of fundamental frequency. EURASIP Journal on Advances in Signal Processing 2012 2012:56. 41 | * Issue with integer multiples of fundamental frequency: 42 | * window_size = ( k * sample_frequency ) / target_frequency 43 | * Find k (1, 2, 3, ..) that provides an integer window_size 44 | * Bin size: 45 | * bin_size = sample_frequency / window_size 46 | */ 47 | public class IterativeGeneralizedGoertzel { 48 | public static final double M2PI = Math.PI * 2; 49 | private float[] hannWindowCache; 50 | private double s0 = 0; 51 | private double s1 = 0.; 52 | private double s2 = 0.; 53 | private double cosPikTerm2; 54 | private double pikTerm; 55 | private float lastSample = 0; 56 | private double sampleRate; 57 | private int windowSize; 58 | private int processedSamples = 0; 59 | private boolean hannWindow = false; 60 | 61 | /** 62 | * @param sampleRate Sampling rate in Hz 63 | * @param frequency Array of frequency search in Hz 64 | * @param windowSize Number of samples to analyse 65 | */ 66 | public IterativeGeneralizedGoertzel(double sampleRate, double frequency, int windowSize, boolean hannWindow) { 67 | this.sampleRate = sampleRate; 68 | this.windowSize = windowSize; 69 | this.hannWindow = hannWindow; 70 | // Fix frequency using the sampleRate of the signal 71 | double samplingRateFactor = windowSize / sampleRate; 72 | pikTerm = M2PI * (frequency * samplingRateFactor) / windowSize; 73 | cosPikTerm2 = Math.cos(pikTerm) * 2.0; 74 | if(hannWindow) { 75 | hannWindowCache = new float[windowSize / 2 + 1]; 76 | for(int i=0; i < hannWindowCache.length; i++) { 77 | hannWindowCache[i] = (float)(0.5 - 0.5 * Math.cos((M2PI * i) / (windowSize - 1))); 78 | } 79 | } 80 | } 81 | 82 | public void reset() { 83 | s0 = 0; 84 | s1 = 0; 85 | s2 = 0; 86 | processedSamples = 0; 87 | lastSample = 0; 88 | } 89 | 90 | public double getSampleRate() { 91 | return sampleRate; 92 | } 93 | 94 | public int getWindowSize() { 95 | return windowSize; 96 | } 97 | 98 | public int getProcessedSamples() { 99 | return processedSamples; 100 | } 101 | 102 | public IterativeGeneralizedGoertzel processSamples(float[] samples, int from, int to) { 103 | int length = (to - from); 104 | if(processedSamples + length > windowSize) { 105 | throw new IllegalArgumentException("Exceed window length"); 106 | } 107 | final int size; 108 | if(processedSamples + length == windowSize) { 109 | size = length - 1; 110 | if(hannWindow) { 111 | lastSample = 0; 112 | } else { 113 | lastSample = samples[from + size]; 114 | } 115 | } else { 116 | size = length; 117 | } 118 | for(int i=0; i < size; i++) { 119 | if (hannWindow) { 120 | final float hann = i + processedSamples < hannWindowCache.length ? hannWindowCache[i + processedSamples] : hannWindowCache[(windowSize - 1) - (i + processedSamples)]; 121 | s0 = samples[i + from] * hann + cosPikTerm2 * s1 - s2; 122 | } else{ 123 | s0 = samples[i + from] + cosPikTerm2 * s1 - s2; 124 | } 125 | s2 = s1; 126 | s1 = s0; 127 | } 128 | processedSamples += length; 129 | return this; 130 | } 131 | public GoertzelResult computeRMS(boolean computePhase) { 132 | if(processedSamples != windowSize) { 133 | throw new IllegalStateException("Not enough processed samples"); 134 | } 135 | final Complex cc = new Complex(pikTerm, 0).exp(); 136 | 137 | // final computations 138 | s0 = lastSample + cosPikTerm2 * s1 - s2; 139 | 140 | // complex multiplication substituting the last iteration 141 | // and correcting the phase for (potentially) non - integer valued 142 | // frequencies at the same time 143 | Complex parta = new Complex(s0, 0).sub(new Complex(s1, 0).mul(cc)); 144 | Complex partb = new Complex(pikTerm * (windowSize - 1.), 0).exp(); 145 | Complex y = parta.mul(partb); 146 | 147 | double rms = Math.sqrt((y.r * y.r + y.i * y.i) * 2) / windowSize; 148 | 149 | double phase = 0; 150 | if(computePhase) { 151 | phase = Math.atan2(y.i, y.r); 152 | } 153 | reset(); 154 | return new GoertzelResult(rms, phase); 155 | } 156 | 157 | public static class GoertzelResult { 158 | public final double rms; 159 | public final double phase; 160 | 161 | public GoertzelResult(double rms, double phase) { 162 | this.rms = rms; 163 | this.phase = phase; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/c/minunit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | #ifndef __MINUNIT_H__ 24 | #define __MINUNIT_H__ 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #if defined(_WIN32) 31 | #include 32 | #if defined(_MSC_VER) && _MSC_VER < 1900 33 | #define snprintf _snprintf 34 | #define __func__ __FUNCTION__ 35 | #endif 36 | 37 | #elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) 38 | 39 | /* Change POSIX C SOURCE version for pure c99 compilers */ 40 | #if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L 41 | #undef _POSIX_C_SOURCE 42 | #define _POSIX_C_SOURCE 200112L 43 | #endif 44 | 45 | #include /* POSIX flags */ 46 | #include 47 | #include 48 | #include 49 | 50 | #else 51 | #error "Unable to define timers for an unknown OS." 52 | #endif 53 | 54 | #include 55 | #include 56 | 57 | /* Maximum length of last message */ 58 | #define MINUNIT_MESSAGE_LEN 1024 59 | 60 | /* Misc. counters */ 61 | static int minunit_run = 0; 62 | static int minunit_assert = 0; 63 | static int minunit_fail = 0; 64 | static int minunit_status = 0; 65 | 66 | /* Last message */ 67 | static char minunit_last_message[MINUNIT_MESSAGE_LEN]; 68 | 69 | /* Test setup and teardown function pointers */ 70 | static void (*minunit_setup)(void) = NULL; 71 | static void (*minunit_teardown)(void) = NULL; 72 | 73 | /* Definitions */ 74 | #define MU_TEST(method_name) static void method_name(void) 75 | #define MU_TEST_SUITE(suite_name) static void suite_name(void) 76 | 77 | #define MU__SAFE_BLOCK(block) do {\ 78 | block\ 79 | } while(0) 80 | 81 | /* Run test suite and unset setup and teardown functions */ 82 | #define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\ 83 | suite_name();\ 84 | minunit_setup = NULL;\ 85 | minunit_teardown = NULL;\ 86 | ) 87 | 88 | /* Configure setup and teardown functions */ 89 | #define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\ 90 | minunit_setup = setup_fun;\ 91 | minunit_teardown = teardown_fun;\ 92 | ) 93 | 94 | /* Test runner */ 95 | #define MU_RUN_TEST(test) MU__SAFE_BLOCK(\ 96 | if (minunit_setup) (*minunit_setup)();\ 97 | minunit_status = 0;\ 98 | test();\ 99 | minunit_run++;\ 100 | if (minunit_status) {\ 101 | minunit_fail++;\ 102 | printf("F");\ 103 | printf("\n%s\n", minunit_last_message);\ 104 | }\ 105 | fflush(stdout);\ 106 | if (minunit_teardown) (*minunit_teardown)();\ 107 | ) 108 | 109 | /* Report */ 110 | #define MU_REPORT() MU__SAFE_BLOCK(\ 111 | printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\ 112 | ) 113 | 114 | /* Assertions */ 115 | #define mu_check(test) MU__SAFE_BLOCK(\ 116 | minunit_assert++;\ 117 | if (!(test)) {\ 118 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\ 119 | minunit_status = 1;\ 120 | return;\ 121 | } else {\ 122 | printf(".");\ 123 | }\ 124 | ) 125 | 126 | #define mu_fail(message) MU__SAFE_BLOCK(\ 127 | minunit_assert++;\ 128 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\ 129 | minunit_status = 1;\ 130 | return;\ 131 | ) 132 | 133 | #define mu_assert(test, message) MU__SAFE_BLOCK(\ 134 | minunit_assert++;\ 135 | if (!(test)) {\ 136 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\ 137 | minunit_status = 1;\ 138 | } else {\ 139 | printf(".");\ 140 | }\ 141 | ) 142 | 143 | #define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\ 144 | int minunit_tmp_e;\ 145 | int minunit_tmp_r;\ 146 | minunit_assert++;\ 147 | minunit_tmp_e = (expected);\ 148 | minunit_tmp_r = (result);\ 149 | if (minunit_tmp_e != minunit_tmp_r) {\ 150 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\ 151 | minunit_status = 1;\ 152 | } else {\ 153 | printf(".");\ 154 | }\ 155 | ) 156 | 157 | #define mu_assert_int_array_eq(expected,expected_length, result, result_length) MU__SAFE_BLOCK(\ 158 | minunit_assert++;\ 159 | mu_assert_int_eq(expected_length, result_length);\ 160 | int i;\ 161 | for(i = 0; i < expected_length && minunit_status == 0; i++) {\ 162 | int minunit_tmp_e;\ 163 | int minunit_tmp_r;\ 164 | minunit_tmp_e = (expected[i]);\ 165 | minunit_tmp_r = (result[i]);\ 166 | if (minunit_tmp_e != minunit_tmp_r) {\ 167 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\ 168 | minunit_status = 1;\ 169 | } else {\ 170 | printf(".");\ 171 | }\ 172 | }\ 173 | ) 174 | 175 | #define mu_assert_double_eq(expected, result, epsilon) MU__SAFE_BLOCK(\ 176 | double minunit_tmp_e;\ 177 | double minunit_tmp_r;\ 178 | minunit_assert++;\ 179 | minunit_tmp_e = (expected);\ 180 | minunit_tmp_r = (result);\ 181 | if (fabs(minunit_tmp_e-minunit_tmp_r) > epsilon) {\ 182 | int minunit_significant_figures = (int)(1 - log10(epsilon));\ 183 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\ 184 | minunit_status = 1;\ 185 | } else {\ 186 | printf(".");\ 187 | }\ 188 | ) 189 | 190 | #define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\ 191 | const char* minunit_tmp_e = (char*)expected;\ 192 | const char* minunit_tmp_r = (char*)result;\ 193 | minunit_assert++;\ 194 | if (!minunit_tmp_e) {\ 195 | minunit_tmp_e = "";\ 196 | }\ 197 | if (!minunit_tmp_r) {\ 198 | minunit_tmp_r = "";\ 199 | }\ 200 | if(strcmp(minunit_tmp_e, minunit_tmp_r)) {\ 201 | snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\ 202 | minunit_status = 1;\ 203 | } else {\ 204 | printf(".");\ 205 | }\ 206 | ) 207 | 208 | #ifdef __cplusplus 209 | } 210 | #endif 211 | 212 | #endif /* __MINUNIT_H__ */ 213 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | /** 20 | *

Implements Reed-Solomon decoding, as the name implies.

21 | * 22 | *

The algorithm will not be explained here, but the following references were helpful 23 | * in creating this implementation:

24 | * 25 | * 33 | * 34 | *

Much credit is due to William Rucklidge since portions of this code are an indirect 35 | * port of his C++ Reed-Solomon implementation.

36 | * 37 | * @author Sean Owen 38 | * @author William Rucklidge 39 | * @author sanfordsquires 40 | */ 41 | public final class ReedSolomonDecoder { 42 | 43 | private final GenericGF field; 44 | 45 | public ReedSolomonDecoder(GenericGF field) { 46 | this.field = field; 47 | } 48 | 49 | /** 50 | *

Decodes given set of received codewords, which include both data and error-correction 51 | * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, 52 | * in the input.

53 | * 54 | * @param received data and error-correction codewords 55 | * @param twoS number of error-correction codewords available 56 | * @throws ReedSolomonException if decoding fails for any reason 57 | */ 58 | public int decode(int[] received, int twoS) throws ReedSolomonException { 59 | GenericGFPoly poly = new GenericGFPoly(field, received); 60 | int[] syndromeCoefficients = new int[twoS]; 61 | boolean noError = true; 62 | for (int i = 0; i < twoS; i++) { 63 | int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase())); 64 | syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval; 65 | if (eval != 0) { 66 | noError = false; 67 | } 68 | } 69 | if (noError) { 70 | return 0; 71 | } 72 | GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients); 73 | GenericGFPoly[] sigmaOmega = 74 | runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS); 75 | GenericGFPoly sigma = sigmaOmega[0]; 76 | GenericGFPoly omega = sigmaOmega[1]; 77 | int[] errorLocations = findErrorLocations(sigma); 78 | int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations); 79 | for (int i = 0; i < errorLocations.length; i++) { 80 | int position = received.length - 1 - field.log(errorLocations[i]); 81 | if (position < 0) { 82 | throw new ReedSolomonException("Bad error location"); 83 | } 84 | received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]); 85 | } 86 | return errorLocations.length; 87 | } 88 | 89 | private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R) 90 | throws ReedSolomonException { 91 | // Assume a's degree is >= b's 92 | if (a.getDegree() < b.getDegree()) { 93 | GenericGFPoly temp = a; 94 | a = b; 95 | b = temp; 96 | } 97 | 98 | GenericGFPoly rLast = a; 99 | GenericGFPoly r = b; 100 | GenericGFPoly tLast = field.getZero(); 101 | GenericGFPoly t = field.getOne(); 102 | 103 | // Run Euclidean algorithm until r's degree is less than R/2 104 | while (r.getDegree() >= R / 2) { 105 | GenericGFPoly rLastLast = rLast; 106 | GenericGFPoly tLastLast = tLast; 107 | rLast = r; 108 | tLast = t; 109 | 110 | // Divide rLastLast by rLast, with quotient in q and remainder in r 111 | if (rLast.isZero()) { 112 | // Oops, Euclidean algorithm already terminated? 113 | throw new ReedSolomonException("r_{i-1} was zero"); 114 | } 115 | r = rLastLast; 116 | GenericGFPoly q = field.getZero(); 117 | int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); 118 | int dltInverse = field.inverse(denominatorLeadingTerm); 119 | while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { 120 | int degreeDiff = r.getDegree() - rLast.getDegree(); 121 | int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); 122 | q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale)); 123 | r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale)); 124 | } 125 | 126 | t = q.multiply(tLast).addOrSubtract(tLastLast); 127 | 128 | if (r.getDegree() >= rLast.getDegree()) { 129 | throw new IllegalStateException("Division algorithm failed to reduce polynomial?"); 130 | } 131 | } 132 | 133 | int sigmaTildeAtZero = t.getCoefficient(0); 134 | if (sigmaTildeAtZero == 0) { 135 | throw new ReedSolomonException("sigmaTilde(0) was zero"); 136 | } 137 | 138 | int inverse = field.inverse(sigmaTildeAtZero); 139 | GenericGFPoly sigma = t.multiply(inverse); 140 | GenericGFPoly omega = r.multiply(inverse); 141 | return new GenericGFPoly[]{sigma, omega}; 142 | } 143 | 144 | private int[] findErrorLocations(GenericGFPoly errorLocator) throws ReedSolomonException { 145 | // This is a direct application of Chien's search 146 | int numErrors = errorLocator.getDegree(); 147 | if (numErrors == 1) { // shortcut 148 | return new int[] { errorLocator.getCoefficient(1) }; 149 | } 150 | int[] result = new int[numErrors]; 151 | int e = 0; 152 | for (int i = 1; i < field.getSize() && e < numErrors; i++) { 153 | if (errorLocator.evaluateAt(i) == 0) { 154 | result[e] = field.inverse(i); 155 | e++; 156 | } 157 | } 158 | if (e != numErrors) { 159 | throw new ReedSolomonException("Error locator degree does not match number of roots"); 160 | } 161 | return result; 162 | } 163 | 164 | private int[] findErrorMagnitudes(GenericGFPoly errorEvaluator, int[] errorLocations) { 165 | // This is directly applying Forney's Formula 166 | int s = errorLocations.length; 167 | int[] result = new int[s]; 168 | for (int i = 0; i < s; i++) { 169 | int xiInverse = field.inverse(errorLocations[i]); 170 | int denominator = 1; 171 | for (int j = 0; j < s; j++) { 172 | if (i != j) { 173 | //denominator = field.multiply(denominator, 174 | // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))); 175 | // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. 176 | // Below is a funny-looking workaround from Steven Parkes 177 | int term = field.multiply(errorLocations[j], xiInverse); 178 | int termPlus1 = (term & 0x1) == 0 ? term | 1 : term & ~1; 179 | denominator = field.multiply(denominator, termPlus1); 180 | } 181 | } 182 | result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), 183 | field.inverse(denominator)); 184 | if (field.getGeneratorBase() != 0) { 185 | result[i] = field.multiply(result[i], xiInverse); 186 | } 187 | } 188 | return result; 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/PeakFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | package org.noise_planet.qrtone; 34 | 35 | import java.util.ArrayList; 36 | import java.util.Collection; 37 | import java.util.Collections; 38 | import java.util.Comparator; 39 | import java.util.List; 40 | import java.util.Objects; 41 | 42 | 43 | /** 44 | * PeakFinder will find highest values 45 | * This class results are equivalent with Octave(R) findpeaks function 46 | */ 47 | public class PeakFinder { 48 | private boolean increase = true; 49 | private double oldVal = Double.NEGATIVE_INFINITY; 50 | private long oldIndex = 0; 51 | boolean added = false; 52 | private Element lastPeak = null; 53 | private int increaseCount = 0; 54 | private int decreaseCount = 0; 55 | private int minIncreaseCount = -1; 56 | private int minDecreaseCount = -1; 57 | 58 | public Element getLastPeak() { 59 | return lastPeak; 60 | } 61 | 62 | public void reset() { 63 | increase = true; 64 | oldVal = Double.MIN_VALUE; 65 | oldIndex = 0; 66 | added = false; 67 | lastPeak = null; 68 | increaseCount = 0; 69 | decreaseCount = 0; 70 | minIncreaseCount = -1; 71 | minDecreaseCount = -1; 72 | } 73 | 74 | /** 75 | * @return Remove peaks where increase steps count are less than this number 76 | */ 77 | public int getMinIncreaseCount() { 78 | return minIncreaseCount; 79 | } 80 | 81 | /** 82 | * @param minIncreaseCount Remove peaks where increase steps count are less than this number 83 | */ 84 | public void setMinIncreaseCount(int minIncreaseCount) { 85 | this.minIncreaseCount = minIncreaseCount; 86 | } 87 | 88 | /** 89 | * @return Remove peaks where decrease steps count are less than this number 90 | */ 91 | public int getMinDecreaseCount() { 92 | return minDecreaseCount; 93 | } 94 | 95 | /** 96 | * @return Remove peaks where decrease steps count are less than this number 97 | */ 98 | public void setMinDecreaseCount(int minDecreaseCount) { 99 | this.minDecreaseCount = minDecreaseCount; 100 | } 101 | 102 | public boolean add(Long index, double value) { 103 | boolean ret = false; 104 | double diff = value - oldVal; 105 | // Detect switch from increase to decrease/stall 106 | if(diff <= 0 && increase) { 107 | if(increaseCount >= minIncreaseCount) { 108 | lastPeak = new Element(oldIndex, oldVal); 109 | added = true; 110 | if(minDecreaseCount <= 1 ) { 111 | ret = true; 112 | } 113 | } 114 | } else if(diff > 0 && !increase) { 115 | // Detect switch from decreasing to increase 116 | if(added && minDecreaseCount != -1 && decreaseCount < minDecreaseCount) { 117 | lastPeak = null; 118 | added = false; 119 | } 120 | } 121 | increase = diff > 0; 122 | if(increase) { 123 | increaseCount++; 124 | decreaseCount = 0; 125 | } else { 126 | decreaseCount++; 127 | if(decreaseCount >= minDecreaseCount && added) { 128 | // condition for decrease fulfilled 129 | added = false; 130 | ret = true; 131 | } 132 | increaseCount=0; 133 | } 134 | oldVal = value; 135 | oldIndex = index; 136 | return ret; 137 | } 138 | 139 | /** 140 | * Remove peaks where distance to other peaks are less than provided argument 141 | * @param minWidth Minium width in index 142 | */ 143 | public static List filter(List peaks, int minWidth) { 144 | // Sort peaks by value 145 | List sortedPeaks = new ArrayList<>(peaks); 146 | Collections.sort(sortedPeaks); 147 | for(int i = 0; i < sortedPeaks.size(); i++) { 148 | Element topPeak = sortedPeaks.get(i); 149 | int j = i + 1; 150 | while(j < sortedPeaks.size()) { 151 | Element otherPeak = sortedPeaks.get(j); 152 | if(Math.abs(otherPeak.index - topPeak.index) <= minWidth) { 153 | sortedPeaks.remove(j); 154 | } else { 155 | j += 1; 156 | } 157 | } 158 | } 159 | // Sort peaks by index 160 | Collections.sort(sortedPeaks, new ElementSortByIndex()); 161 | return sortedPeaks; 162 | } 163 | 164 | /** 165 | * Remove peaks where value is less than provided argument 166 | * @param minValue Minium peak value 167 | */ 168 | public static List filter(List peaks, double minValue) { 169 | // Sort peaks by value 170 | List filteredPeaks = new ArrayList<>(peaks); 171 | int j = 0; 172 | while(j < filteredPeaks.size()) { 173 | Element peak = filteredPeaks.get(j); 174 | if(peak.value < minValue) { 175 | filteredPeaks.remove(j); 176 | } else { 177 | j += 1; 178 | } 179 | } 180 | return filteredPeaks; 181 | } 182 | 183 | public static class Element implements Comparable { 184 | public final long index; 185 | public final double value; 186 | 187 | public Element(long index, double value) { 188 | this.index = index; 189 | this.value = value; 190 | } 191 | 192 | @Override 193 | public boolean equals(Object o) { 194 | if (this == o) return true; 195 | if (o == null || getClass() != o.getClass()) return false; 196 | Element element = (Element) o; 197 | return index == element.index && 198 | Double.compare(element.value, value) == 0; 199 | } 200 | 201 | // Natural order is Descendant values 202 | @Override 203 | public int compareTo(Element element) { 204 | return Double.compare(element.value, this.value); 205 | } 206 | 207 | @Override 208 | public int hashCode() { 209 | return Long.valueOf(index).hashCode() + Double.valueOf(value).hashCode(); 210 | } 211 | } 212 | 213 | public static class ElementSortByIndex implements Comparator { 214 | @Override 215 | public int compare(Element element, Element t1) { 216 | return Long.valueOf(element.index).compareTo(t1.index); 217 | } 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/ApproximatePercentile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import java.util.Arrays; 37 | 38 | /** 39 | * P^2 algorithm as documented in "The P-Square Algorithm for Dynamic Calculation of Percentiles and Histograms 40 | * without Storing Observations," Communications of the ACM, October 1985 by R. Jain and I. Chlamtac. 41 | * Converted from Aaron Small C code under MIT License 42 | * https://github.com/absmall/p2 43 | * 44 | * @author Aaron Small 45 | */ 46 | public class ApproximatePercentile { 47 | private double[] q; 48 | private double[] dn; 49 | private double[] np; 50 | private int[] n; 51 | private int count; 52 | private int marker_count; 53 | 54 | public ApproximatePercentile() { 55 | count = 0; 56 | addEndMarkers(); 57 | } 58 | 59 | public ApproximatePercentile(double quant) { 60 | if(quant < 0 || quant > 1) { 61 | throw new IllegalArgumentException("Quantile only between 0 and 1"); 62 | } 63 | count = 0; 64 | addEndMarkers(); 65 | addQuantile(quant); 66 | } 67 | 68 | public void addEqualSpacing(int count) { 69 | int index = allocateMarkers(count - 1); 70 | 71 | /* Add in appropriate dn markers */ 72 | for (int i = index + 1; i < count; i++) { 73 | dn[i - 1] = 1.0 * i / count; 74 | } 75 | 76 | updateMarkers(); 77 | } 78 | 79 | 80 | public void addQuantile(double quant) { 81 | int index = allocateMarkers(3); 82 | 83 | /* Add in appropriate dn markers */ 84 | dn[index] = quant/2.0; 85 | dn[index + 1] = quant; 86 | dn[index + 2] = (1.0 + quant) / 2.0; 87 | 88 | updateMarkers(); 89 | } 90 | 91 | 92 | private int allocateMarkers(int count) { 93 | q = Arrays.copyOf(q, marker_count + count); 94 | dn = Arrays.copyOf(dn, marker_count + count); 95 | np = Arrays.copyOf(np, marker_count + count); 96 | n = Arrays.copyOf(n, marker_count + count); 97 | 98 | marker_count += count; 99 | 100 | return marker_count - count; 101 | } 102 | 103 | private void addEndMarkers() { 104 | marker_count = 2; 105 | q = new double[marker_count]; 106 | dn = new double[marker_count]; 107 | np = new double[marker_count]; 108 | n = new int[marker_count]; 109 | dn[0] = 0.0; 110 | dn[1] = 1.0; 111 | 112 | updateMarkers(); 113 | } 114 | 115 | private void updateMarkers() { 116 | sort(dn, marker_count); 117 | 118 | /* Then entirely reset np markers, since the marker count changed */ 119 | for (int i = 0; i < marker_count; i++) { 120 | np[i] = (marker_count - 1) * dn[i] + 1; 121 | } 122 | } 123 | 124 | /** 125 | * Simple bubblesort, because bubblesort is efficient for small count, and count is likely to be small 126 | */ 127 | private void sort(double[] q, int length) { 128 | double k; 129 | int i, j; 130 | for (j = 1; j < length; j++) { 131 | k = q[j]; 132 | i = j - 1; 133 | 134 | while (i >= 0 && q[i] > k) { 135 | q[i + 1] = q[i]; 136 | i--; 137 | } 138 | q[i + 1] = k; 139 | } 140 | } 141 | 142 | private double parabolic(int i, int d) { 143 | return q[i] + d / (double) (n[i + 1] - n[i - 1]) * ((n[i] - n[i - 1] + d) * (q[i + 1] - q[i]) / 144 | (n[i + 1] - n[i]) + (n[i + 1] - n[i] - d) * (q[i] - q[i - 1]) / (n[i] - n[i - 1])); 145 | } 146 | 147 | private double linear(int i, int d) { 148 | return q[i] + d * (q[i + d] - q[i]) / (n[i + d] - n[i]); 149 | } 150 | 151 | public void add(double data) { 152 | int i; 153 | int k = 0; 154 | double d; 155 | double newq; 156 | 157 | if (count >= marker_count) { 158 | count++; 159 | 160 | // B1 161 | if (data < q[0]) { 162 | q[0] = data; 163 | k = 1; 164 | } else if (data >= q[marker_count - 1]) { 165 | q[marker_count - 1] = data; 166 | k = marker_count - 1; 167 | } else { 168 | for (i = 1; i < marker_count; i++) { 169 | if (data < q[i]) { 170 | k = i; 171 | break; 172 | } 173 | } 174 | } 175 | 176 | // B2 177 | for (i = k; i < marker_count; i++) { 178 | n[i]++; 179 | np[i] = np[i] + dn[i]; 180 | } 181 | for (i = 0; i < k; i++) { 182 | np[i] = np[i] + dn[i]; 183 | } 184 | 185 | // B3 186 | for (i = 1; i < marker_count - 1; i++) { 187 | d = np[i] - n[i]; 188 | if ((d >= 1.0 && n[i + 1] - n[i] > 1) || (d <= -1.0 && n[i - 1] - n[i] < -1.0)) { 189 | newq = parabolic(i, sign(d)); 190 | if (q[i - 1] < newq && newq < q[i + 1]) { 191 | q[i] = newq; 192 | } else { 193 | q[i] = linear(i, sign(d)); 194 | } 195 | n[i] += sign(d); 196 | } 197 | } 198 | } else { 199 | q[count] = data; 200 | count++; 201 | 202 | if (count == marker_count) { 203 | // We have enough to start the algorithm, initialize 204 | sort(q, count); 205 | 206 | for (i = 0; i < marker_count; i++) { 207 | n[i] = i + 1; 208 | } 209 | } 210 | } 211 | } 212 | 213 | public double result() { 214 | if (marker_count != 5) { 215 | throw new IllegalStateException("Multiple quantiles in use"); 216 | } 217 | return result(dn[(marker_count - 1) / 2]); 218 | } 219 | 220 | public double result(double quantile) { 221 | if (count < marker_count) { 222 | int closest = 1; 223 | sort(q, count); 224 | for (int i = 2; i < count; i++) { 225 | if (Math.abs(((double) i) / count - quantile) < Math.abs(((double) closest) / marker_count - quantile)) { 226 | closest = i; 227 | } 228 | } 229 | return q[closest]; 230 | } else { 231 | // Figure out which quantile is the one we're looking for by nearest dn 232 | int closest = 1; 233 | for (int i = 2; i < marker_count - 1; i++) { 234 | if (Math.abs(dn[i] - quantile) < Math.abs(dn[closest] - quantile)) { 235 | closest = i; 236 | } 237 | } 238 | return q[closest]; 239 | } 240 | } 241 | 242 | private static int sign(double d) { 243 | if (d >= 0.0) { 244 | return 1; 245 | } else { 246 | return -1; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/qrtone.h: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | /** 35 | * @file qrtone.h 36 | * @author Nicolas Fortin @NicolasCumu 37 | * @date 24/03/2020 38 | * @brief Api of QRTone library 39 | * Usage 40 | * Audio to Message: 41 | * 1. Declare instance of qrtone_t with qrtone_new 42 | * 2. Init with qrtone_init 43 | * 3. Get maximal expected window length with qrtone_get_maximum_length 44 | * 4. Push window samples with qrtone_push_samples 45 | * 5. When qrtone_push_samples return 1 then retrieve payload with qrtone_get_payload and qrtone_get_payload_length 46 | * Message to Audio 47 | * 1. Declare instance of qrtone_t with qrtone_new 48 | * 2. Init with qrtone_init 49 | * 3. Set message with qrtone_set_payload or qrtone_set_payload_ext 50 | * 4. Retrieve audio samples to send with qrtone_get_samples 51 | */ 52 | 53 | #ifndef QRTONE_H 54 | #define QRTONE_H 55 | 56 | #ifdef __cplusplus 57 | extern "C" { 58 | #endif 59 | 60 | #include 61 | 62 | /** 63 | * Error correction level parameter 64 | * L ecc level 7% error correction level 65 | * M ecc level 15% error correction level 66 | * Q ecc level 25% error correction level 67 | * H ecc level 30% error correction level 68 | */ 69 | enum QRTONE_ECC_LEVEL { QRTONE_ECC_L = 0, QRTONE_ECC_M = 1, QRTONE_ECC_Q = 2, QRTONE_ECC_H = 3}; 70 | 71 | /** 72 | * @brief Main QRTone structure 73 | */ 74 | typedef struct _qrtone_t qrtone_t; 75 | 76 | /////////////////////// 77 | // INITIALIZATION 78 | /////////////////////// 79 | 80 | /** 81 | * Allocation memory for a qrtone_t instance. 82 | * @return A pointer to the qrtone structure. 83 | */ 84 | qrtone_t* qrtone_new(void); 85 | 86 | /** 87 | * Initialization of the internal attributes of a qrtone_t instance. Must only be called once. 88 | * @param qrtone A pointer to the qrtone structure. 89 | * @param sample_rate Sample rate in Hz. 90 | */ 91 | void qrtone_init(qrtone_t* qrtone, float sample_rate); 92 | 93 | /** 94 | * Free allocated memory for a qrtone_t instance. 95 | * @param qrtone A pointer to the initialized qrtone structure. 96 | */ 97 | void qrtone_free(qrtone_t* qrtone); 98 | 99 | //////////////////////// 100 | // Receive payload 101 | //////////////////////// 102 | 103 | /** 104 | * Compute the maximum samples_length to feed with the method `qrtone_push_samples` 105 | * @param qrtone A pointer to the initialized qrtone structure. 106 | * @return The maximum samples_length to feed with the method `qrtone_push_samples` 107 | */ 108 | int32_t qrtone_get_maximum_length(qrtone_t* qrtone); 109 | 110 | /** 111 | * Process audio samples in order to find payload in tones. 112 | * @param qrtone A pointer to the initialized qrtone structure. 113 | * @param samples Audio samples array in float. All tests have been done with values between -1 and 1. 114 | * @param samples_length Size Audio samples array. The size should be inferior or equal to `qrtone_get_maximum_length`. 115 | * @return 1 if a payload has been received, 0 otherwise. 116 | */ 117 | int8_t qrtone_push_samples(qrtone_t* qrtone, float* samples, int32_t samples_length); 118 | 119 | /** 120 | * Fetch stored payload. Call this function only when `qrtone_push_samples` return 1. 121 | * @param qrtone A pointer to the initialized qrtone structure. 122 | * @return int8_t array of the size provided by `qrtone_get_payload_length`. QRTone is responsible for freeing this array. 123 | */ 124 | int8_t* qrtone_get_payload(qrtone_t* qrtone); 125 | 126 | /** 127 | * Get stored payload length. Call this function only when `qrtone_push_samples` return 1. 128 | * @param qrtone A pointer to the initialized qrtone structure. 129 | * @return int8_t stored payload length. 130 | */ 131 | int32_t qrtone_get_payload_length(qrtone_t* qrtone); 132 | 133 | /** 134 | * Gives the exact index of the audio sample corresponding to the beginning of the last received message. 135 | * This information can be used for synchronization purposes. 136 | * @param self A pointer to the initialized qrtone structure. 137 | * @return int64_t Audio sample index 138 | */ 139 | int64_t qrtone_get_payload_sample_index(qrtone_t* self); 140 | 141 | /** 142 | * When there is not enough signal/noise ratio, some bytes could be error corrected by Reed-Solomon code. 143 | * @param qrtone A pointer to the initialized qrtone structure. 144 | * @return Number of fixed 145 | */ 146 | 147 | int32_t qrtone_get_fixed_errors(qrtone_t* qrtone); 148 | 149 | /** 150 | * Function callback called while awaiting a message. It can be usefull in order to display if the microphone is working. 151 | * @ptr Pointer provided when calling qrtone_tone_set_level_callback. 152 | * @processed_samples Number of processed samples 153 | * @global_level Leq of signal. Expressed in dBFS (https://en.wikipedia.org/wiki/DBFS) 154 | * @first_tone_level Level of first tone frequency. Expressed in dBFS. 155 | * @second_tone_level Level of second tone frequency. Expressed in dBFS. 156 | */ 157 | typedef void (*qrtone_level_callback_t)(void *ptr, int64_t processed_samples, float first_tone_level, float second_tone_level, int32_t triggered); 158 | 159 | /** 160 | * @brief Set callback method called while awaiting a message. 161 | * 162 | * @param qrtone A pointer to the initialized qrtone structure. 163 | * @param data ptr to use when calling the callback method 164 | * @param lvl_callback Pointer to the method to call 165 | */ 166 | void qrtone_set_level_callback(qrtone_t* self, void* data, qrtone_level_callback_t lvl_callback); 167 | /////////////////////////// 168 | // Send payload 169 | /////////////////////////// 170 | 171 | /** 172 | * Set the message to send. With QRTONE_ECC_Q ECC level and with a CRC code. 173 | * @param qrtone A pointer to the initialized qrtone structure. 174 | * @param payload Byte array to send. 175 | * @param payload Byte array length. Should be less than 255 bytes. 176 | * @return The number of audio samples to send. 177 | */ 178 | int32_t qrtone_set_payload(qrtone_t* qrtone, int8_t* payload, uint8_t payload_length); 179 | 180 | /** 181 | * Set the message to send, with additional parameters. 182 | * @param qrtone A pointer to the initialized qrtone structure. 183 | * @param payload Byte array to send. 184 | * @param payload Byte array length. Should be less than 255 bytes. 185 | * @param ecc_level Error correction level `QRTONE_ECC_LEVEL`. Error correction level add robustness at the cost of tone length. 186 | * @param add_crc If 1 ,add a crc16 code in order to check if the message has not been altered on the receiver side. 187 | * @return The number of audio samples to send. 188 | */ 189 | int32_t qrtone_set_payload_ext(qrtone_t* qrtone, int8_t* payload, uint8_t payload_length, int8_t ecc_level, int8_t add_crc); 190 | 191 | /** 192 | * Populate the provided array with audio samples. You must call qrtone_set_payload function before. 193 | * @param qrtone A pointer to the initialized qrtone structure. 194 | * @param samples Pre-allocated array of samples_length length 195 | * @param samples_length array length. samples_length + offset should be equal or inferior than the total number of audio samples. 196 | * @param power Amplitude of the audio tones. 197 | */ 198 | void qrtone_get_samples(qrtone_t* qrtone, float* samples, int32_t samples_length, float power); 199 | 200 | #ifdef __cplusplus 201 | } 202 | #endif 203 | 204 | #endif -------------------------------------------------------------------------------- /examples/MXChipChat/MXChipChat.ino: -------------------------------------------------------------------------------- 1 | /**------------------------------------------------------------------------------ 2 | * 3 | * Receive and display chat messages on the Microsoft MXChip IoT DevKit. 4 | * 5 | * @file MXChipChat.ino 6 | * 7 | * @brief You can use the QRTone Android demo application to send audio message to the MXChip. 8 | * 9 | * BSD 3-Clause License 10 | * 11 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 12 | * All rights reserved. 13 | * 14 | *----------------------------------------------------------------------------*/ 15 | 16 | /* 17 | * MXChip and Arduino native headers. 18 | */ 19 | #include "Arduino.h" 20 | #include "OledDisplay.h" 21 | #include "AudioClassV2.h" 22 | 23 | /* 24 | * QRTone header 25 | */ 26 | #include "qrtone.h" 27 | 28 | /* 29 | * The audio sample rate used by the microphone. 30 | * As tones go from 1700 Hz to 8000 Hz, a sample rate of 16 KHz is the minimum. 31 | */ 32 | #define SAMPLE_RATE 16000 33 | 34 | #define SAMPLE_SIZE 16 35 | 36 | // audio circular buffer size 37 | #define MAX_AUDIO_WINDOW_SIZE 512 38 | 39 | #define AUDIO_SAMPLE_SIZE 256 // AUDIO_CHUNK_SIZE / 2 40 | 41 | #define MONO_AUDIO_SAMPLE_SIZE 128 42 | 43 | // QRTone instance 44 | qrtone_t* qrtone = NULL; 45 | 46 | // MXChip audio controler 47 | AudioClass& Audio = AudioClass::getInstance(); 48 | 49 | // Audio buffer used by MXChip audio controler 50 | static char emptyAudio[AUDIO_CHUNK_SIZE]; 51 | static char raw_audio_buffer[AUDIO_CHUNK_SIZE]; 52 | static short raw_output_audio_buffer[AUDIO_SAMPLE_SIZE]; 53 | static float scaled_input_buffer[MAX_AUDIO_WINDOW_SIZE]; 54 | static float scaled_output_buffer[MONO_AUDIO_SAMPLE_SIZE]; 55 | static int raw_output_audio_buffer_length = 0; 56 | static int audio_to_play = 0; // how many samples to play 57 | static int cursor_audio_to_play = 0; // Total played samples 58 | 59 | // Audio circular buffer provided to QRTone. 60 | static int64_t scaled_input_buffer_feed_cursor = 0; 61 | static int64_t scaled_input_buffer_consume_cursor = 0; 62 | 63 | // Buttons state vars 64 | int lastButtonAState; 65 | int buttonAState; 66 | int lastButtonBState; 67 | int buttonBState; 68 | 69 | /** 70 | * Display message on Oled Screen 71 | * Message use a special format specified by the demo android app: 72 | * [field type int8_t][field_length int8_t][field_content int8_t array] 73 | * There is currently 2 type of field username(0) and message(1) 74 | */ 75 | void process_message(void) { 76 | int32_t user_name = 0; 77 | int user_name_length = 0; 78 | int32_t message = 0; 79 | int message_length = 0; 80 | int8_t* data = qrtone_get_payload(qrtone); 81 | int32_t data_length = qrtone_get_payload_length(qrtone); 82 | int c = 0; 83 | while(c < data_length) { 84 | int8_t field_type = data[c++]; 85 | if(field_type == 0) { 86 | // username 87 | user_name_length = (int32_t)data[c++]; 88 | user_name = c; 89 | c += user_name_length; 90 | } else if(field_type == 1) { 91 | message_length = (int32_t)data[c++]; 92 | message = c; 93 | c += message_length; 94 | } 95 | } 96 | if(user_name > 0 && message > 0) { 97 | Screen.clean(); 98 | // Print username 99 | char buf[256]; 100 | memset(buf, 0, 256); 101 | memcpy(buf, data + (size_t)user_name, user_name_length); 102 | Screen.print(0, buf, false); 103 | // Print message 104 | memset(buf, 0, 256); 105 | memcpy(buf, data + (size_t)message, message_length); 106 | Screen.print(1, buf, true); 107 | } 108 | } 109 | 110 | /** 111 | * Called by AudioClass when the audio buffer is full 112 | */ 113 | void recordCallback(void) 114 | { 115 | int32_t record_buffer_length = Audio.readFromRecordBuffer(raw_audio_buffer, AUDIO_CHUNK_SIZE); 116 | char* cur_reader = &raw_audio_buffer[0]; 117 | char* end_reader = &raw_audio_buffer[record_buffer_length]; 118 | const int offset = 4; // short samples size + skip right channel 119 | int sample_index = 0; 120 | while(cur_reader < end_reader) { 121 | int16_t sample = *((int16_t *)cur_reader); 122 | scaled_input_buffer[(scaled_input_buffer_feed_cursor+sample_index) % MAX_AUDIO_WINDOW_SIZE] = (float)sample / 32768.0f; 123 | sample_index += 1; 124 | cur_reader += offset; 125 | } 126 | scaled_input_buffer_feed_cursor += sample_index; 127 | } 128 | 129 | void setup(void) 130 | { 131 | // initialize the button pin as a input 132 | pinMode(USER_BUTTON_A, INPUT); 133 | lastButtonAState = digitalRead(USER_BUTTON_A); 134 | pinMode(USER_BUTTON_B, INPUT); 135 | lastButtonBState = digitalRead(USER_BUTTON_B); 136 | 137 | 138 | memset(emptyAudio, 0x0, AUDIO_CHUNK_SIZE); 139 | 140 | Screen.init(); 141 | 142 | Audio.format(SAMPLE_RATE, SAMPLE_SIZE); 143 | // disable automatic level control 144 | // Audio.setPGAGain(0x3F); 145 | 146 | // Allocate struct 147 | qrtone = qrtone_new(); 148 | 149 | // Init internal state 150 | qrtone_init(qrtone, SAMPLE_RATE); 151 | 152 | // Init callback method 153 | // qrtone_set_level_callback(qrtone, NULL, debug_serial); 154 | 155 | delay(100); 156 | 157 | // Start to record audio data 158 | Audio.startRecord(recordCallback); 159 | 160 | printIdleMessage(); 161 | } 162 | 163 | void loop(void) 164 | { 165 | buttonAState = digitalRead(USER_BUTTON_A); 166 | buttonBState = digitalRead(USER_BUTTON_B); 167 | 168 | // Processing of audio input 169 | // Once the recording buffer is full, we process it. 170 | if (audio_to_play == 0 && scaled_input_buffer_feed_cursor > scaled_input_buffer_consume_cursor) 171 | { 172 | int sample_to_read = scaled_input_buffer_feed_cursor - scaled_input_buffer_consume_cursor; 173 | int max_window_length = qrtone_get_maximum_length(qrtone); 174 | int sample_index = 0; 175 | while(sample_index < sample_to_read) { 176 | int32_t position_in_buffer = ((scaled_input_buffer_consume_cursor + sample_index) % MAX_AUDIO_WINDOW_SIZE); 177 | int32_t window_length = min(max_window_length, min(sample_to_read - sample_index, MAX_AUDIO_WINDOW_SIZE - position_in_buffer % MAX_AUDIO_WINDOW_SIZE)); 178 | if(qrtone_push_samples(qrtone, scaled_input_buffer + position_in_buffer, window_length)) { 179 | // Got a message 180 | process_message(); 181 | } 182 | sample_index += window_length; 183 | max_window_length = qrtone_get_maximum_length(qrtone); 184 | } 185 | scaled_input_buffer_consume_cursor += sample_index; 186 | } 187 | 188 | // Check button actions 189 | boolean doPlay = false; 190 | if (buttonAState == HIGH && lastButtonAState == LOW) 191 | { 192 | Screen.clean(); 193 | Screen.print(0, "Sending message"); 194 | Screen.print(1, "On"); 195 | // Send on 196 | int8_t msg[] = {0, 6,'M', 'X', 'C', 'h', 'i', 'p', 1, 2, 'o', 'n'}; 197 | audio_to_play = qrtone_set_payload_ext(qrtone, msg, sizeof(msg), QRTONE_ECC_L, 0); 198 | cursor_audio_to_play = 0; 199 | doPlay = true; 200 | } 201 | if (buttonBState == HIGH && lastButtonBState == LOW) 202 | { 203 | Screen.clean(); 204 | Screen.print(0, "Sending message"); 205 | Screen.print(1, "Off"); 206 | // Send off 207 | int8_t msg[] = {0, 6,'M', 'X', 'C', 'h', 'i', 'p', 1, 3, 'o', 'f', 'f'}; 208 | audio_to_play = qrtone_set_payload_ext(qrtone, msg, sizeof(msg), QRTONE_ECC_L, 0); 209 | cursor_audio_to_play = 0; 210 | doPlay = true; 211 | } 212 | if(doPlay) { 213 | // Push audio data to output buffer 214 | Audio.format(SAMPLE_RATE, SAMPLE_SIZE); 215 | Audio.startPlay(playCallback); 216 | while(cursor_audio_to_play < audio_to_play) { 217 | if(raw_output_audio_buffer_length == 0) { 218 | memset(scaled_output_buffer, 0, sizeof(float) * MONO_AUDIO_SAMPLE_SIZE); 219 | qrtone_get_samples(qrtone, scaled_output_buffer, MONO_AUDIO_SAMPLE_SIZE, 16200.f); 220 | memset(raw_output_audio_buffer, 0, AUDIO_CHUNK_SIZE); 221 | for(int c=0; c < MONO_AUDIO_SAMPLE_SIZE; c++) { 222 | // store stereo audio 223 | raw_output_audio_buffer[c * 2] = (int16_t) scaled_output_buffer[c]; 224 | raw_output_audio_buffer[c * 2 + 1] = raw_output_audio_buffer[c * 2]; 225 | } 226 | raw_output_audio_buffer_length = MONO_AUDIO_SAMPLE_SIZE; 227 | cursor_audio_to_play += MONO_AUDIO_SAMPLE_SIZE; 228 | } else { 229 | delay(1); 230 | } 231 | } 232 | audio_to_play = 0; 233 | // Start to record audio data 234 | delay(125); 235 | Audio.format(SAMPLE_RATE, SAMPLE_SIZE); 236 | Audio.startRecord(recordCallback); 237 | printIdleMessage(); 238 | } 239 | lastButtonAState = buttonAState; 240 | lastButtonBState = buttonBState; 241 | } 242 | 243 | void playCallback(void) 244 | { 245 | if(raw_output_audio_buffer_length == 0) { 246 | Audio.writeToPlayBuffer(emptyAudio, AUDIO_CHUNK_SIZE); 247 | Serial.println("Play empty audio.."); 248 | } else { 249 | Audio.writeToPlayBuffer((char *)raw_output_audio_buffer, AUDIO_CHUNK_SIZE); 250 | raw_output_audio_buffer_length = 0; 251 | } 252 | } 253 | 254 | void printIdleMessage() 255 | { 256 | Screen.clean(); 257 | Screen.print(0, "Awaiting message"); 258 | } 259 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.zxing.common.reedsolomon; 18 | 19 | /** 20 | *

Represents a polynomial whose coefficients are elements of a GF. 21 | * Instances of this class are immutable.

22 | * 23 | *

Much credit is due to William Rucklidge since portions of this code are an indirect 24 | * port of his C++ Reed-Solomon implementation.

25 | * 26 | * @author Sean Owen 27 | */ 28 | final class GenericGFPoly { 29 | 30 | private final GenericGF field; 31 | private final int[] coefficients; 32 | 33 | /** 34 | * @param field the {@link GenericGF} instance representing the field to use 35 | * to perform computations 36 | * @param coefficients coefficients as ints representing elements of GF(size), arranged 37 | * from most significant (highest-power term) coefficient to least significant 38 | * @throws IllegalArgumentException if argument is null or empty, 39 | * or if leading coefficient is 0 and this is not a 40 | * constant polynomial (that is, it is not the monomial "0") 41 | */ 42 | GenericGFPoly(GenericGF field, int[] coefficients) { 43 | if (coefficients.length == 0) { 44 | throw new IllegalArgumentException(); 45 | } 46 | this.field = field; 47 | int coefficientsLength = coefficients.length; 48 | if (coefficientsLength > 1 && coefficients[0] == 0) { 49 | // Leading term must be non-zero for anything except the constant polynomial "0" 50 | int firstNonZero = 1; 51 | while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { 52 | firstNonZero++; 53 | } 54 | if (firstNonZero == coefficientsLength) { 55 | this.coefficients = new int[]{0}; 56 | } else { 57 | this.coefficients = new int[coefficientsLength - firstNonZero]; 58 | System.arraycopy(coefficients, 59 | firstNonZero, 60 | this.coefficients, 61 | 0, 62 | this.coefficients.length); 63 | } 64 | } else { 65 | this.coefficients = coefficients; 66 | } 67 | } 68 | 69 | int[] getCoefficients() { 70 | return coefficients; 71 | } 72 | 73 | /** 74 | * @return degree of this polynomial 75 | */ 76 | int getDegree() { 77 | return coefficients.length - 1; 78 | } 79 | 80 | /** 81 | * @return true iff this polynomial is the monomial "0" 82 | */ 83 | boolean isZero() { 84 | return coefficients[0] == 0; 85 | } 86 | 87 | /** 88 | * @return coefficient of x^degree term in this polynomial 89 | */ 90 | int getCoefficient(int degree) { 91 | return coefficients[coefficients.length - 1 - degree]; 92 | } 93 | 94 | /** 95 | * @return evaluation of this polynomial at a given point 96 | */ 97 | int evaluateAt(int a) { 98 | if (a == 0) { 99 | // Just return the x^0 coefficient 100 | return getCoefficient(0); 101 | } 102 | if (a == 1) { 103 | // Just the sum of the coefficients 104 | int result = 0; 105 | for (int coefficient : coefficients) { 106 | result = GenericGF.addOrSubtract(result, coefficient); 107 | } 108 | return result; 109 | } 110 | int result = coefficients[0]; 111 | int size = coefficients.length; 112 | for (int i = 1; i < size; i++) { 113 | result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]); 114 | } 115 | return result; 116 | } 117 | 118 | GenericGFPoly addOrSubtract(GenericGFPoly other) { 119 | if (!field.equals(other.field)) { 120 | throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); 121 | } 122 | if (isZero()) { 123 | return other; 124 | } 125 | if (other.isZero()) { 126 | return this; 127 | } 128 | 129 | int[] smallerCoefficients = this.coefficients; 130 | int[] largerCoefficients = other.coefficients; 131 | if (smallerCoefficients.length > largerCoefficients.length) { 132 | int[] temp = smallerCoefficients; 133 | smallerCoefficients = largerCoefficients; 134 | largerCoefficients = temp; 135 | } 136 | int[] sumDiff = new int[largerCoefficients.length]; 137 | int lengthDiff = largerCoefficients.length - smallerCoefficients.length; 138 | // Copy high-order terms only found in higher-degree polynomial's coefficients 139 | System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); 140 | 141 | for (int i = lengthDiff; i < largerCoefficients.length; i++) { 142 | sumDiff[i] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); 143 | } 144 | 145 | return new GenericGFPoly(field, sumDiff); 146 | } 147 | 148 | GenericGFPoly multiply(GenericGFPoly other) { 149 | if (!field.equals(other.field)) { 150 | throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); 151 | } 152 | if (isZero() || other.isZero()) { 153 | return field.getZero(); 154 | } 155 | int[] aCoefficients = this.coefficients; 156 | int aLength = aCoefficients.length; 157 | int[] bCoefficients = other.coefficients; 158 | int bLength = bCoefficients.length; 159 | int[] product = new int[aLength + bLength - 1]; 160 | for (int i = 0; i < aLength; i++) { 161 | int aCoeff = aCoefficients[i]; 162 | for (int j = 0; j < bLength; j++) { 163 | product[i + j] = GenericGF.addOrSubtract(product[i + j], 164 | field.multiply(aCoeff, bCoefficients[j])); 165 | } 166 | } 167 | return new GenericGFPoly(field, product); 168 | } 169 | 170 | GenericGFPoly multiply(int scalar) { 171 | if (scalar == 0) { 172 | return field.getZero(); 173 | } 174 | if (scalar == 1) { 175 | return this; 176 | } 177 | int size = coefficients.length; 178 | int[] product = new int[size]; 179 | for (int i = 0; i < size; i++) { 180 | product[i] = field.multiply(coefficients[i], scalar); 181 | } 182 | return new GenericGFPoly(field, product); 183 | } 184 | 185 | GenericGFPoly multiplyByMonomial(int degree, int coefficient) { 186 | if (degree < 0) { 187 | throw new IllegalArgumentException(); 188 | } 189 | if (coefficient == 0) { 190 | return field.getZero(); 191 | } 192 | int size = coefficients.length; 193 | int[] product = new int[size + degree]; 194 | for (int i = 0; i < size; i++) { 195 | product[i] = field.multiply(coefficients[i], coefficient); 196 | } 197 | return new GenericGFPoly(field, product); 198 | } 199 | 200 | GenericGFPoly[] divide(GenericGFPoly other) { 201 | if (!field.equals(other.field)) { 202 | throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); 203 | } 204 | if (other.isZero()) { 205 | throw new IllegalArgumentException("Divide by 0"); 206 | } 207 | 208 | GenericGFPoly quotient = field.getZero(); 209 | GenericGFPoly remainder = this; 210 | 211 | int denominatorLeadingTerm = other.getCoefficient(other.getDegree()); 212 | int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); 213 | 214 | while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { 215 | int degreeDifference = remainder.getDegree() - other.getDegree(); 216 | int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); 217 | GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale); 218 | GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale); 219 | quotient = quotient.addOrSubtract(iterationQuotient); 220 | remainder = remainder.addOrSubtract(term); 221 | } 222 | 223 | return new GenericGFPoly[] { quotient, remainder }; 224 | } 225 | 226 | @Override 227 | public String toString() { 228 | if (isZero()) { 229 | return "0"; 230 | } 231 | StringBuilder result = new StringBuilder(8 * getDegree()); 232 | for (int degree = getDegree(); degree >= 0; degree--) { 233 | int coefficient = getCoefficient(degree); 234 | if (coefficient != 0) { 235 | if (coefficient < 0) { 236 | if (degree == getDegree()) { 237 | result.append("-"); 238 | } else { 239 | result.append(" - "); 240 | } 241 | coefficient = -coefficient; 242 | } else { 243 | if (result.length() > 0) { 244 | result.append(" + "); 245 | } 246 | } 247 | if (degree == 0 || coefficient != 1) { 248 | int alphaPower = field.log(coefficient); 249 | if (alphaPower == 0) { 250 | result.append('1'); 251 | } else if (alphaPower == 1) { 252 | result.append('a'); 253 | } else { 254 | result.append("a^"); 255 | result.append(alphaPower); 256 | } 257 | } 258 | if (degree != 0) { 259 | if (degree == 1) { 260 | result.append('x'); 261 | } else { 262 | result.append("x^"); 263 | result.append(degree); 264 | } 265 | } 266 | } 267 | } 268 | return result.toString(); 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /jqrtone/src/main/java/org/noise_planet/qrtone/TriggerAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | package org.noise_planet.qrtone; 35 | 36 | import java.util.Arrays; 37 | import java.util.concurrent.atomic.AtomicInteger; 38 | 39 | /** 40 | * Analyse audio samples in order to detect trigger signal 41 | * Evaluate the exact position of the first tone 42 | */ 43 | public class TriggerAnalyzer { 44 | public static final double M2PI = Math.PI * 2; 45 | public static final double PERCENTILE_BACKGROUND = 0.5; 46 | private AtomicInteger processedWindowAlpha = new AtomicInteger(0); 47 | private AtomicInteger processedWindowBeta = new AtomicInteger(0); 48 | private final int windowOffset; 49 | private final int gateLength; 50 | private IterativeGeneralizedGoertzel[] frequencyAnalyzersAlpha; 51 | private IterativeGeneralizedGoertzel[] frequencyAnalyzersBeta; 52 | final ApproximatePercentile backgroundNoiseEvaluator; 53 | final CircularArray[] splHistory; 54 | private float[] hannWindowCache; 55 | final PeakFinder peakFinder; 56 | private final int windowAnalyze; 57 | private TriggerCallback triggerCallback = null; 58 | final double[] frequencies; 59 | final double sampleRate; 60 | public final double triggerSnr; 61 | private long firstToneLocation = -1; 62 | 63 | 64 | 65 | public TriggerAnalyzer(double sampleRate, int gateLength, double[] frequencies, int windowLength, double triggerSnr) { 66 | this.windowAnalyze = windowLength; 67 | this.frequencies = frequencies; 68 | this.sampleRate = sampleRate; 69 | this.triggerSnr = triggerSnr; 70 | this.gateLength = gateLength; 71 | if(windowAnalyze < Configuration.computeMinimumWindowSize(sampleRate, frequencies[0], frequencies[1])) { 72 | throw new IllegalArgumentException("Tone length are not compatible with sample rate and selected frequencies"); 73 | } 74 | // 50% overlap 75 | windowOffset = windowAnalyze / 2; 76 | frequencyAnalyzersAlpha = new IterativeGeneralizedGoertzel[frequencies.length]; 77 | frequencyAnalyzersBeta = new IterativeGeneralizedGoertzel[frequencies.length]; 78 | backgroundNoiseEvaluator = new ApproximatePercentile(PERCENTILE_BACKGROUND); 79 | splHistory = new CircularArray[frequencies.length]; 80 | peakFinder = new PeakFinder(); 81 | peakFinder.setMinDecreaseCount((gateLength / 2) / windowOffset); 82 | hannWindowCache = new float[windowLength / 2 + 1]; 83 | for(int i=0; i < hannWindowCache.length; i++) { 84 | hannWindowCache[i] = (float)(0.5 - 0.5 * Math.cos((M2PI * i) / (windowLength - 1))); 85 | } 86 | for(int i=0; i backgroundNoiseSecondPeak + triggerSnr) { 147 | // Check if the level on other triggering frequencies is below triggering level (at the same time) 148 | int peakIndex = splHistory[frequencies.length - 1].size() - 1 - 149 | (int)(location / windowOffset - element.index / windowOffset); 150 | if(peakIndex >= 0 && peakIndex < splHistory[0].size() && 151 | splHistory[0].get(peakIndex) < element.value - triggerSnr) { 152 | int firstPeakIndex = peakIndex - (gateLength / windowOffset); 153 | // Check if for the first peak the level was inferior than trigger level 154 | if(firstPeakIndex >= 0 && firstPeakIndex < splHistory[0].size() 155 | && splHistory[0].get(firstPeakIndex) > element.value - triggerSnr && 156 | splHistory[frequencies.length - 1].get(firstPeakIndex) < element.value - triggerSnr) { 157 | // All trigger conditions are met 158 | // Evaluate the exact position of the first tone 159 | long peakLocation = findPeakLocation(splHistory[frequencies.length - 1].get(peakIndex-1) 160 | ,element.value,splHistory[frequencies.length - 1].get(peakIndex+1),element.index,windowOffset); 161 | firstToneLocation = peakLocation + gateLength / 2 + windowOffset; 162 | if(triggerCallback != null) { 163 | triggerCallback.onTrigger(this, firstToneLocation); 164 | } 165 | } 166 | } 167 | } 168 | } 169 | if(triggerCallback != null) { 170 | triggerCallback.onNewLevels(this, location, splLevels); 171 | } 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * @return Maximum window length in order to have not more than 1 processed window 178 | */ 179 | public int getMaximumWindowLength() { 180 | return Math.min(windowAnalyze - processedWindowAlpha.get(), windowAnalyze - processedWindowBeta.get()); 181 | } 182 | 183 | public void processSamples(float[] samples, long totalProcessed) { 184 | doProcess(Arrays.copyOf(samples, samples.length), totalProcessed, processedWindowAlpha, frequencyAnalyzersAlpha); 185 | if(totalProcessed > windowOffset) { 186 | doProcess(Arrays.copyOf(samples, samples.length), totalProcessed, processedWindowBeta, frequencyAnalyzersBeta); 187 | } else if(windowOffset - totalProcessed < samples.length){ 188 | // Start to process on the part used by the offset window 189 | doProcess(Arrays.copyOfRange(samples, (int)(windowOffset - totalProcessed), 190 | samples.length), totalProcessed + (int)(windowOffset - totalProcessed), processedWindowBeta, 191 | frequencyAnalyzersBeta); 192 | } 193 | } 194 | 195 | /** 196 | * Quadratic interpolation of three adjacent samples 197 | * @param p0 y value of left point 198 | * @param p1 y value of center point (maximum height) 199 | * @param p2 y value of right point 200 | * @return location [-1; 1] relative to center point, height and half-curvature of a parabolic fit through 201 | * @link https://www.dsprelated.com/freebooks/sasp/Sinusoidal_Peak_Interpolation.html 202 | * three points 203 | */ 204 | static double[] quadraticInterpolation(double p0, double p1, double p2) { 205 | double location; 206 | double height; 207 | double halfCurvature; 208 | location = (p2 - p0) / (2.0 * (2 * p1 - p2 - p0)); 209 | height = p1 - 0.25 * (p0 - p2) * location; 210 | halfCurvature = 0.5 * (p0 - 2 * p1 + p2); 211 | return new double[]{location, height, halfCurvature}; 212 | } 213 | 214 | /** 215 | * Evaluate peak location of a gaussian 216 | * @param p0 y value of left point 217 | * @param p1 y value of center point (maximum height) 218 | * @param p2 y value of right point 219 | * @param p1Location x value of p1 220 | * @param windowLength x delta between points 221 | * @return Peak x value 222 | */ 223 | public static long findPeakLocation(double p0, double p1, double p2, long p1Location, int windowLength) { 224 | double location = quadraticInterpolation(p0, p1, p2)[0]; 225 | return p1Location + (int)(location*windowLength); 226 | } 227 | 228 | public interface TriggerCallback { 229 | void onNewLevels(TriggerAnalyzer triggerAnalyzer, long location, double[] spl); 230 | void onTrigger(TriggerAnalyzer triggerAnalyzer, long messageStartLocation); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /emscripten/qrtone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Nicolas Fortin, Univ. Gustave Eiffel, UMRAE 3 | * JS Code derived from QuietJS 3-clause BSD code Copyright 2016, Brian Armstrong 4 | */ 5 | 6 | var QRTone = (function() { 7 | 8 | // sampleBufferSize is the number of audio samples we'll write per onaudioprocess call 9 | // must be a power of two. we choose the absolute largest permissible value 10 | // we implicitly assume that the browser will play back a written buffer without any gaps 11 | var sampleBufferSize = 16384; 12 | 13 | // initialization flags 14 | var emscriptenInitialized = false; 15 | 16 | // our local instance of window.AudioContext 17 | var audioCtx; 18 | 19 | // consumer callbacks. these fire once QRTone is ready to create transmitter/receiver 20 | var readyCallbacks = []; 21 | var readyErrbacks = []; 22 | var failReason = ""; 23 | 24 | // these are used for receiver only 25 | var gUM; 26 | var audioInput; 27 | var audioInputFailedReason = ""; 28 | var audioInputReadyCallbacks = []; 29 | var audioInputFailedCallbacks = []; 30 | 31 | // anti-gc 32 | var receivers = {}; 33 | var receivers_idx = 0; 34 | 35 | // isReady tells us if we can start creating transmitters and receivers 36 | // we need the emscripten portion to be running and we need our 37 | // async fetch of the profiles to be completed 38 | function isReady() { 39 | return emscriptenInitialized; 40 | }; 41 | 42 | function isFailed() { 43 | return failReason !== ""; 44 | }; 45 | 46 | // start gets our AudioContext and notifies consumers that QRTone can be used 47 | function start() { 48 | var len = readyCallbacks.length; 49 | for (var i = 0; i < len; i++) { 50 | readyCallbacks[i](); 51 | } 52 | }; 53 | 54 | function initAudioContext() { 55 | if (audioCtx === undefined) { 56 | audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 57 | console.log(audioCtx.sampleRate); 58 | } 59 | }; 60 | 61 | function fail(reason) { 62 | failReason = reason; 63 | var len = readyErrbacks.length; 64 | for (var i = 0; i < len; i++) { 65 | readyErrbacks[i](reason); 66 | } 67 | }; 68 | 69 | function checkInitState() { 70 | if (isReady()) { 71 | start(); 72 | } 73 | }; 74 | 75 | // this is intended to be called only by emscripten 76 | function onEmscriptenInitialized() { 77 | emscriptenInitialized = true; 78 | checkInitState(); 79 | }; 80 | 81 | /** 82 | * Add a callback to be called when QRTone is ready for use, e.g. when transmitters and receivers can be created. 83 | * @function addReadyCallback 84 | * @memberof QRTone 85 | * @param {function} c - The user function which will be called 86 | * @param {onError} [onError] - User errback function 87 | * @example 88 | * addReadyCallback(function() { console.log("ready!"); }); 89 | */ 90 | function addReadyCallback(c, errback) { 91 | if (isReady()) { 92 | c(); 93 | return; 94 | } 95 | readyCallbacks.push(c); 96 | if (errback !== undefined) { 97 | if (isFailed()) { 98 | errback(failReason); 99 | return; 100 | } 101 | readyErrbacks.push(errback); 102 | } 103 | }; 104 | 105 | // receiver functions 106 | 107 | function audioInputReady() { 108 | var len = audioInputReadyCallbacks.length; 109 | for (var i = 0; i < len; i++) { 110 | audioInputReadyCallbacks[i](); 111 | } 112 | }; 113 | 114 | function audioInputFailed(reason) { 115 | audioInputFailedReason = reason; 116 | var len = audioInputFailedCallbacks.length; 117 | for (var i = 0; i < len; i++) { 118 | audioInputFailedCallbacks[i](audioInputFailedReason); 119 | } 120 | }; 121 | 122 | function addAudioInputReadyCallback(c, errback) { 123 | if (errback !== undefined) { 124 | if (audioInputFailedReason !== "") { 125 | errback(audioInputFailedReason); 126 | return 127 | } 128 | audioInputFailedCallbacks.push(errback); 129 | } 130 | if (audioInput instanceof MediaStreamAudioSourceNode) { 131 | c(); 132 | return 133 | } 134 | audioInputReadyCallbacks.push(c); 135 | } 136 | 137 | function gUMConstraints() { 138 | if (navigator.webkitGetUserMedia !== undefined) { 139 | return { 140 | audio: { 141 | optional: [ 142 | {googAutoGainControl: false}, 143 | {googAutoGainControl2: false}, 144 | {echoCancellation: false}, 145 | {googEchoCancellation: false}, 146 | {googEchoCancellation2: false}, 147 | {googDAEchoCancellation: false}, 148 | {googNoiseSuppression: false}, 149 | {googNoiseSuppression2: false}, 150 | {googHighpassFilter: false}, 151 | {googTypingNoiseDetection: false}, 152 | {googAudioMirroring: false}, 153 | {autoGainControl: false}, 154 | {noiseSuppression: false} 155 | ] 156 | } 157 | }; 158 | } 159 | if (navigator.mozGetUserMedia !== undefined) { 160 | return { 161 | audio: { 162 | echoCancellation: false, 163 | mozAutoGainControl: false, 164 | mozNoiseSuppression: false, 165 | autoGainControl: false, 166 | noiseSuppression: false 167 | } 168 | }; 169 | 170 | } 171 | return { 172 | audio: { 173 | echoCancellation: false, 174 | optional: [ 175 | {autoGainControl: false}, 176 | {noiseSuppression: false} 177 | ] 178 | } 179 | }; 180 | }; 181 | 182 | 183 | function createAudioInput() { 184 | audioInput = 0; // prevent others from trying to create 185 | window.setTimeout(function() { 186 | gUM.call(navigator, gUMConstraints(), 187 | function(e) { 188 | audioInput = audioCtx.createMediaStreamSource(e); 189 | 190 | // stash a very permanent reference so this isn't collected 191 | window.qrtone_receiver_anti_gc = audioInput; 192 | 193 | audioInputReady(); 194 | }, function(reason) { 195 | audioInputFailed(reason.name); 196 | }); 197 | }, 0); 198 | }; 199 | 200 | /** 201 | * Disconnect QRTone.js from its microphone source 202 | * This will disconnect QRTone.js's microphone fully from all receivers 203 | * This is useful to cause the browser to stop displaying the microphone icon 204 | * Browser support is limited for disconnecting a single destination, so this 205 | * call will disconnect all receivers. 206 | * It is highly recommended to call this only after destroying any receivers. 207 | * @function disconnect 208 | */ 209 | function disconnect() { 210 | if (audioInput !== undefined) { 211 | audioInput.disconnect(); 212 | audioInput = undefined; 213 | delete window.qrtone_receiver_anti_gc; 214 | } 215 | }; 216 | function transmitter(opts) { 217 | initAudioContext(); 218 | var done = opts.onFinish; 219 | 220 | var encoder = _qrtone_new(); 221 | _qrtone_init(encoder, audioCtx.sampleRate); 222 | 223 | var frame_len = 0; 224 | 225 | var samples = _malloc(4 * sampleBufferSize); 226 | 227 | // yes, this is pointer arithmetic, in javascript :) 228 | var sample_view = Module.HEAPF32.subarray((samples/4), (samples/4) + sampleBufferSize); 229 | 230 | var dummy_osc; 231 | 232 | // we'll start and stop transmitter as needed 233 | // if we have something to send, start it 234 | // if we are done talking, stop it 235 | var running = false; 236 | var transmitter; 237 | 238 | // prevent races with callbacks on destroyed in-flight objects 239 | var destroyed = false; 240 | 241 | var onaudioprocess = function(e) { 242 | var output_l = e.outputBuffer.getChannelData(0); 243 | 244 | if (played === true) { 245 | // we've already played what's in sample_view, and it hasn't been 246 | // rewritten for whatever reason, so just play out silence 247 | for (var i = 0; i < sampleBufferSize; i++) { 248 | output_l[i] = 0; 249 | } 250 | return; 251 | } 252 | 253 | played = true; 254 | 255 | output_l.set(sample_view); 256 | window.setTimeout(writebuf, 0); 257 | }; 258 | 259 | var startTransmitter = function () { 260 | if (destroyed) { 261 | return; 262 | } 263 | if (transmitter === undefined) { 264 | // we have to start transmitter here because mobile safari wants it to be in response to a 265 | // user action 266 | var script_processor = (audioCtx.createScriptProcessor || audioCtx.createJavaScriptNode); 267 | // we want a single input because some implementations will not run a node without some kind of source 268 | // we want two outputs so that we can explicitly silence the right channel and no mixing will occur 269 | transmitter = script_processor.call(audioCtx, sampleBufferSize, 1, 2); 270 | transmitter.onaudioprocess = onaudioprocess; 271 | // put an input node on the graph. some browsers require this to run our script processor 272 | // this oscillator will not actually be used in any way 273 | dummy_osc = audioCtx.createOscillator(); 274 | dummy_osc.type = 'square'; 275 | dummy_osc.frequency.value = 420; 276 | 277 | } 278 | dummy_osc.connect(transmitter); 279 | transmitter.connect(audioCtx.destination); 280 | running = true; 281 | }; 282 | 283 | var stopTransmitter = function () { 284 | if (destroyed) { 285 | return; 286 | } 287 | dummy_osc.disconnect(); 288 | transmitter.disconnect(); 289 | running = false; 290 | }; 291 | 292 | // we are only going to keep one chunk of samples around 293 | // ideally there will be a 1:1 sequence between writebuf and onaudioprocess 294 | // but just in case one gets ahead of the other, this flag will prevent us 295 | // from throwing away a buffer or playing a buffer twice 296 | var played = true; 297 | 298 | // unfortunately, we need to flush out the browser's sound sample buffer ourselves 299 | // the way we do this is by writing empty blocks once we're done and *then* we can disconnect 300 | var empties_written = 0; 301 | 302 | var written = 0; 303 | 304 | // measure some stats about encoding time for user 305 | var last_emit_times = []; 306 | var num_emit_times = 3; 307 | 308 | // writebuf calls _send and _emit on the encoder 309 | // first we push as much payload as will fit into encoder's tx queue 310 | // then we create the next sample block (if played = true) 311 | var writebuf = function() { 312 | if (destroyed) { 313 | return; 314 | } 315 | 316 | if (running === false) { 317 | startTransmitter(); 318 | } 319 | 320 | // now set the sample block 321 | if (played === false) { 322 | // the existing sample block has yet to be played 323 | // we are done 324 | return; 325 | } 326 | 327 | // reset signal buffer 328 | _memset(samples, 0, sampleBufferSize * 4); 329 | _qrtone_get_samples(encoder, samples, sampleBufferSize, 1.0); 330 | written += sampleBufferSize; 331 | 332 | // frame_len is the total number of audio samples of the payload 333 | // So if written >= number of samples. The output is done 334 | if (written >= frame_len + sampleBufferSize) { 335 | if (empties_written < 3) { 336 | // flush out browser's sound sample buffer before quitting 337 | for (var i = 0; i < sampleBufferSize; i++) { 338 | sample_view[i] = 0; 339 | } 340 | empties_written++; 341 | played = false; 342 | return; 343 | } 344 | // looks like we are done 345 | // user callback 346 | if (done !== undefined) { 347 | done(); 348 | } 349 | if (running === true) { 350 | stopTransmitter(); 351 | } 352 | return; 353 | } 354 | 355 | played = false; 356 | empties_written = 0; 357 | 358 | 359 | }; 360 | 361 | var transmit = function(buf, qLevel, addCRC) { 362 | if (destroyed) { 363 | return; 364 | } 365 | if(!Array.isArray(buf)) { 366 | console.error("transmit expect Array of integer as first parameter") 367 | return; 368 | } 369 | if(qLevel === undefined) { 370 | qLevel = 2; 371 | } else { 372 | qLevel = Math.min(3, Math.max(0, qLevel)); 373 | } 374 | 375 | if(addCRC === undefined) { 376 | addCRC = 1; 377 | } 378 | 379 | var contenti8 = _malloc(buf.length); 380 | writeArrayToMemory(new Uint8Array(buf), contenti8); 381 | frame_len = _qrtone_set_payload_ext(encoder, contenti8, buf.length, qLevel, addCRC); 382 | 383 | _free(contenti8); 384 | 385 | // now do an update. this may or may not write samples 386 | writebuf(); 387 | }; 388 | 389 | var destroy = function() { 390 | if (destroyed) { 391 | return; 392 | } 393 | 394 | if (running === true) { 395 | stopTransmitter(); 396 | } 397 | 398 | _free(samples); 399 | _qrtone_free(encoder); 400 | _free(encoder); 401 | 402 | destroyed = true; 403 | }; 404 | 405 | return { 406 | transmit: transmit, 407 | destroy: destroy, 408 | frameLength: frame_len 409 | }; 410 | }; 411 | 412 | function resumeAudioContext() { 413 | if (audioCtx.state === 'suspended') { 414 | audioCtx.resume(); 415 | } 416 | }; 417 | 418 | /** 419 | * Create a new receiver 420 | * @function receiver 421 | * @memberof QRTone 422 | * @param {object} opts - receiver params 423 | * @param {onReceive} opts.onReceive - callback which receiver will call to send user received data 424 | * @param {function} [opts.onCreate] - callback to notify user that receiver has been created and is ready to receive. if the user needs to grant permission to use the microphone, this callback fires after that permission is granted. 425 | * @param {onReceiverCreateFail} [opts.onCreateFail] - callback to notify user that receiver could not be created 426 | * @param {onReceiveFail} [opts.onReceiveFail] - callback to notify user that receiver received corrupted data 427 | * @param {onReceiverStatsUpdate} [opts.onReceiverStatsUpdate] - callback to notify user with new decode stats 428 | * @returns {Receiver} - Receiver object 429 | * @example 430 | * receiver({onReceive: function(payload, sampleIndex) { console.log("received chunk of data: " + payload); }}); 431 | */ 432 | function receiver(opts) { 433 | initAudioContext(); 434 | resumeAudioContext(); 435 | // quiet does not create an audio input when it starts 436 | // getting microphone access requires a permission dialog so only ask for it if we need it 437 | if (gUM === undefined) { 438 | gUM = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia); 439 | } 440 | 441 | if (gUM === undefined) { 442 | // we couldn't find a suitable getUserMedia, so fail fast 443 | if (opts.onCreateFail !== undefined) { 444 | opts.onCreateFail("getUserMedia undefined (mic not supported by browser)"); 445 | } 446 | return; 447 | } 448 | 449 | if (audioInput === undefined) { 450 | createAudioInput() 451 | } 452 | 453 | var epochRef = 0; 454 | 455 | // TODO investigate if this still needs to be placed on window. 456 | // seems this was done to keep it from being collected 457 | var scriptProcessor = audioCtx.createScriptProcessor(sampleBufferSize, 2, 1); 458 | var idx = receivers_idx; 459 | receivers[idx] = scriptProcessor; 460 | receivers_idx++; 461 | 462 | // Init qrtone 463 | var decoder; 464 | 465 | var samples = _malloc(4 * sampleBufferSize); 466 | 467 | _memset(samples, 0, sampleBufferSize * 4); 468 | 469 | // Message stacks 470 | var messages = []; 471 | 472 | var destroyed = false; 473 | 474 | var readbuf = function() { 475 | if (destroyed) { 476 | return; 477 | } 478 | while (messages.length > 0) { 479 | let evt = messages.pop(); 480 | opts.onReceive(evt[1], evt[0]); // payload, samplesIndex 481 | } 482 | }; 483 | 484 | var lastChecksumFailCount = 0; 485 | var last_consume_times = []; 486 | var num_consume_times = 3; 487 | var consume = function() { 488 | if (destroyed) { 489 | return; 490 | } 491 | 492 | if(epochRef == 0) { 493 | epochRef = Date.now(); 494 | } 495 | 496 | var cursor = 0; 497 | while(cursor < sampleBufferSize) { 498 | // Get maximum samples that QRTone can process 499 | var windowLen = Math.min(sampleBufferSize - cursor, _qrtone_get_maximum_length(decoder)); 500 | let res = _qrtone_push_samples(decoder, samples + (cursor * 4), windowLen); 501 | if(res) { 502 | // Got message 503 | var payload = _qrtone_get_payload(decoder); 504 | var payload_length = _qrtone_get_payload_length(decoder); 505 | var payloadContent = Module.HEAPU8.slice(payload, payload + payload_length); 506 | var payloadSampleIndex = _qrtone_get_payload_sample_index(decoder); 507 | messages.push([epochRef / 1000.0 + payloadSampleIndex / audioCtx.sampleRate, payloadContent]); 508 | } 509 | cursor += windowLen; 510 | } 511 | window.setTimeout(readbuf, 0); 512 | } 513 | 514 | scriptProcessor.onaudioprocess = function(e) { 515 | if (destroyed) { 516 | return; 517 | } 518 | var input = e.inputBuffer.getChannelData(0); 519 | var sample_view = Module.HEAPF32.subarray(samples/4, samples/4 + sampleBufferSize); 520 | sample_view.set(input); 521 | 522 | if(decoder !== undefined) { 523 | window.setTimeout(consume, 0); 524 | } 525 | } 526 | 527 | var initDecoder = function() { 528 | 529 | decoder = _qrtone_new(); 530 | 531 | _qrtone_init(decoder, audioCtx.sampleRate); 532 | } 533 | 534 | // if this is the first receiver object created, wait for our input node to be created 535 | addAudioInputReadyCallback(function() { 536 | audioInput.connect(scriptProcessor); 537 | fakeGain = audioCtx.createGain(); 538 | fakeGain.value = 0; 539 | scriptProcessor.connect(fakeGain); 540 | fakeGain.connect(audioCtx.destination); 541 | if (opts.onCreate !== undefined) { 542 | window.setTimeout(opts.onCreate, 0); 543 | } 544 | window.setTimeout(initDecoder, 2000); 545 | }, opts.onCreateFail); 546 | 547 | // more unused nodes in the graph that some browsers insist on having 548 | var fakeGain; 549 | 550 | var destroy = function() { 551 | if (destroyed) { 552 | return; 553 | } 554 | fakeGain.disconnect(); 555 | scriptProcessor.disconnect(); 556 | _free(samples); 557 | _qrtone_free(decoder); 558 | _free(decoder); 559 | delete receivers[idx]; 560 | destroyed = true; 561 | }; 562 | 563 | return { 564 | destroy: destroy 565 | } 566 | }; 567 | 568 | 569 | return { 570 | emscriptenInitialized: onEmscriptenInitialized, 571 | addReadyCallback: addReadyCallback, 572 | transmitter: transmitter, 573 | receiver: receiver, 574 | disconnect: disconnect 575 | }; 576 | })(); 577 | 578 | 579 | Module['onRuntimeInitialized'] = QRTone.emscriptenInitialized; 580 | 581 | -------------------------------------------------------------------------------- /src/reed_solomon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) Unité Mixte de Recherche en Acoustique Environnementale (univ-gustave-eiffel) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | */ 33 | 34 | #ifdef _DEBUG 35 | #define _CRTDBG_MAP_ALLOC 36 | #ifdef _WIN32 37 | #include 38 | #endif 39 | #endif 40 | 41 | #include "reed_solomon.h" 42 | #include 43 | #include 44 | 45 | // @link https://github.com/zxing/zxing/tree/master/core/src/main/java/com/google/zxing/common/reedsolomon 46 | 47 | 48 | /** 49 | * @file reed_solomon.c 50 | * @author Nicolas Fortin @NicolasCumu 51 | * @author Sean Owen (java version) 52 | * @author David Olivier (java version) 53 | * @date 24/03/2020 54 | * @brief Implementation of Reed-Solomon ECC 55 | * Reference algorithm is the ZXing QR-Code Apache License source code. 56 | */ 57 | 58 | void ecc_generic_gf_poly_copy(ecc_generic_gf_poly_t* self, ecc_generic_gf_poly_t* other) { 59 | self->coefficients = malloc(sizeof(int32_t) * other->coefficients_length); 60 | memcpy(self->coefficients, other->coefficients, other->coefficients_length * sizeof(int32_t)); 61 | self->coefficients_length = other->coefficients_length; 62 | } 63 | 64 | /** 65 | * GenericGFPoly constructor 66 | * @ref com.google.zxing.common.reedsolomon 67 | * @author Sean Owen (java version) 68 | * @author David Olivier (java version) 69 | */ 70 | void ecc_generic_gf_poly_init(ecc_generic_gf_poly_t* self, int32_t* coefficients, int32_t coefficients_length) { 71 | if (coefficients_length == 0) { 72 | return; 73 | } 74 | if (coefficients_length > 1 && coefficients[0] == 0) { 75 | // Leading term must be non-zero for anything except the constant polynomial "0" 76 | int32_t firstNonZero = 1; 77 | while (firstNonZero < coefficients_length && coefficients[firstNonZero] == 0) { 78 | firstNonZero++; 79 | } 80 | if (firstNonZero == coefficients_length) { 81 | self->coefficients = malloc(sizeof(int32_t) * 1); 82 | self->coefficients_length = 1; 83 | self->coefficients[0] = 0; 84 | } else { 85 | self->coefficients_length = coefficients_length - firstNonZero; 86 | self->coefficients = malloc(sizeof(int32_t) * self->coefficients_length); 87 | memcpy(self->coefficients, coefficients + firstNonZero, sizeof(int32_t) * self->coefficients_length); 88 | } 89 | } else { 90 | self->coefficients = malloc(sizeof(int32_t) * coefficients_length); 91 | self->coefficients_length = coefficients_length; 92 | memcpy(self->coefficients, coefficients, sizeof(int32_t) * coefficients_length); 93 | } 94 | } 95 | 96 | int32_t ecc_generic_gf_poly_multiply_by_monomial(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, int32_t degree, int32_t coefficient, ecc_generic_gf_poly_t* result) { 97 | if (degree < 0) { 98 | return ECC_ILLEGAL_ARGUMENT; 99 | } 100 | if (coefficient == 0) { 101 | int32_t zero[1] = { 0 }; 102 | ecc_generic_gf_poly_init(result, zero, 1); 103 | return ECC_NO_ERRORS; 104 | } 105 | int32_t product_length = self->coefficients_length + degree; 106 | int32_t* product = malloc(sizeof(int32_t) * product_length); 107 | memset(product, 0, sizeof(int32_t) * product_length); 108 | int32_t i; 109 | for (i = 0; i < self->coefficients_length; i++) { 110 | product[i] = ecc_generic_gf_multiply(field, self->coefficients[i], coefficient); 111 | } 112 | ecc_generic_gf_poly_init(result, product, product_length); 113 | free(product); 114 | return ECC_NO_ERRORS; 115 | } 116 | 117 | int32_t ecc_generic_gf_poly_divide(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, ecc_generic_gf_poly_t* other, ecc_generic_gf_poly_t* result) { 118 | if (ecc_generic_gf_poly_is_zero(other)) { 119 | return ECC_DIVIDE_BY_ZERO; 120 | } 121 | ecc_generic_gf_poly_t remainder; 122 | ecc_generic_gf_poly_copy(&remainder, self); 123 | 124 | int32_t denominatorLeadingTerm = ecc_generic_gf_poly_get_coefficient(other, ecc_generic_gf_poly_get_degree(other)); 125 | int32_t inverseDenominatorLeadingTerm = ecc_generic_gf_inverse(field, denominatorLeadingTerm); 126 | 127 | ecc_generic_gf_poly_t new_remainder; 128 | 129 | while (ecc_generic_gf_poly_get_degree(&remainder) >= ecc_generic_gf_poly_get_degree(other) && 130 | !ecc_generic_gf_poly_is_zero(&remainder)) { 131 | int32_t degree_difference = ecc_generic_gf_poly_get_degree(&remainder) - ecc_generic_gf_poly_get_degree(other); 132 | int32_t scale = ecc_generic_gf_multiply(field, ecc_generic_gf_poly_get_coefficient(&remainder, 133 | ecc_generic_gf_poly_get_degree(&remainder)), inverseDenominatorLeadingTerm); 134 | ecc_generic_gf_poly_t term; 135 | ecc_generic_gf_poly_multiply_by_monomial(other, field, degree_difference, scale, &term); 136 | ecc_generic_gf_poly_add_or_substract(&remainder, &term, &new_remainder); 137 | ecc_generic_gf_poly_free(&term); 138 | ecc_generic_gf_poly_free(&remainder); 139 | ecc_generic_gf_poly_copy(&remainder, &new_remainder); 140 | ecc_generic_gf_poly_free(&new_remainder); 141 | } 142 | ecc_generic_gf_poly_copy(result, &remainder); 143 | ecc_generic_gf_poly_free(&remainder); 144 | return ECC_NO_ERRORS; 145 | } 146 | 147 | void ecc_generic_gf_poly_free(ecc_generic_gf_poly_t* self) { 148 | free(self->coefficients); 149 | } 150 | 151 | void ecc_generic_gf_init(ecc_generic_gf_t* self, int32_t primitive, int32_t size, int32_t b) { 152 | self->primitive = primitive; 153 | self->size = size; 154 | self->generator_base = b; 155 | self->exp_table = malloc(sizeof(int32_t) * size); 156 | self->log_table = malloc(sizeof(int32_t) * size); 157 | memset(self->log_table, 0, sizeof(int32_t) * size); 158 | int32_t x = 1; 159 | int32_t i; 160 | for (i = 0; i < size; i++) { 161 | self->exp_table[i] = x; 162 | x *= 2; // we're assuming the generator alpha is 2 163 | if (x >= size) { 164 | x ^= primitive; 165 | x &= size - 1; 166 | } 167 | } 168 | for (i = 0; i < size - 1; i++) { 169 | self->log_table[self->exp_table[i]] = i; 170 | } 171 | // logTable[0] == 0 but self should never be used 172 | int32_t zero[1] = { 0 }; 173 | ecc_generic_gf_poly_init(&(self->zero), zero, 1); 174 | int32_t one[1] = { 1 }; 175 | ecc_generic_gf_poly_init(&(self->one), one, 1); 176 | } 177 | 178 | void ecc_generic_gf_free(ecc_generic_gf_t* self) { 179 | free(self->exp_table); 180 | free(self->log_table); 181 | ecc_generic_gf_poly_free(&(self->zero)); 182 | ecc_generic_gf_poly_free(&(self->one)); 183 | } 184 | 185 | int32_t ecc_generic_gf_build_monomial(ecc_generic_gf_poly_t* poly, int32_t degree, int32_t coefficient) { 186 | if (degree < 0) { 187 | return ECC_ILLEGAL_ARGUMENT; 188 | } 189 | if (coefficient == 0) { 190 | int32_t zero[1] = { 0 }; 191 | ecc_generic_gf_poly_init(poly, zero, 1); 192 | return ECC_NO_ERRORS; 193 | } 194 | int32_t* coefficients = malloc(sizeof(int32_t) * ((size_t)degree + 1)); 195 | memset(coefficients, 0, sizeof(int32_t) * ((size_t)degree + 1)); 196 | coefficients[0] = coefficient; 197 | ecc_generic_gf_poly_init(poly, coefficients, degree + 1); 198 | free(coefficients); 199 | return ECC_NO_ERRORS; 200 | } 201 | 202 | void ecc_generic_gf_poly_multiply(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, int32_t scalar, ecc_generic_gf_poly_t* result) { 203 | if (scalar == 0) { 204 | int32_t zero[1] = { 0 }; 205 | ecc_generic_gf_poly_init(result, zero, 1); 206 | return; 207 | } 208 | if (scalar == 1) { 209 | ecc_generic_gf_poly_copy(result, self); 210 | return; 211 | } 212 | int32_t i; 213 | int32_t* product = malloc(sizeof(int32_t) * self->coefficients_length); 214 | for (i = 0; i < self->coefficients_length; i++) { 215 | product[i] = ecc_generic_gf_multiply(field, self->coefficients[i], scalar); 216 | } 217 | ecc_generic_gf_poly_init(result, product, self->coefficients_length); 218 | free(product); 219 | } 220 | 221 | int32_t ecc_generic_gf_multiply(ecc_generic_gf_t* self, int32_t a, int32_t b) { 222 | if (a == 0 || b == 0) { 223 | return 0; 224 | } 225 | return self->exp_table[(self->log_table[a] + self->log_table[b]) % (self->size - 1)]; 226 | } 227 | 228 | int32_t ecc_generic_gf_poly_get_coefficient(ecc_generic_gf_poly_t* self, int32_t degree) { 229 | return self->coefficients[self->coefficients_length - 1 - degree]; 230 | } 231 | 232 | int32_t ecc_generic_gf_poly_get_degree(ecc_generic_gf_poly_t* self) { 233 | return self->coefficients_length - 1; 234 | } 235 | 236 | int32_t ecc_generic_gf_add_or_substract(int32_t a, int32_t b) { 237 | return a ^ b; 238 | } 239 | 240 | void ecc_generic_gf_poly_add_or_substract(ecc_generic_gf_poly_t* self, ecc_generic_gf_poly_t* other, ecc_generic_gf_poly_t* result) { 241 | if (ecc_generic_gf_poly_is_zero(self)) { 242 | ecc_generic_gf_poly_copy(result, other); 243 | return; 244 | } 245 | if (ecc_generic_gf_poly_is_zero(other)) { 246 | ecc_generic_gf_poly_copy(result, self); 247 | return; 248 | } 249 | 250 | int32_t* smaller_coefficients = self->coefficients; 251 | int32_t smaller_coefficients_length = self->coefficients_length; 252 | int32_t* larger_coefficients = other->coefficients; 253 | int32_t larger_coefficients_length = other->coefficients_length; 254 | if (self->coefficients_length > other->coefficients_length) { 255 | smaller_coefficients = other->coefficients; 256 | larger_coefficients = self->coefficients; 257 | smaller_coefficients_length = other->coefficients_length; 258 | larger_coefficients_length = self->coefficients_length; 259 | } 260 | 261 | int32_t* sum_diff = malloc(sizeof(int32_t) * larger_coefficients_length); 262 | memset(sum_diff, 0, sizeof(int32_t) * larger_coefficients_length); 263 | int32_t length_diff = larger_coefficients_length - smaller_coefficients_length; 264 | 265 | // Copy high-order terms only found in higher-degree polynomial's coefficients 266 | memcpy(sum_diff, larger_coefficients, length_diff * sizeof(int32_t)); 267 | 268 | int32_t i; 269 | for (i = length_diff; i < larger_coefficients_length; i++) { 270 | sum_diff[i] = ecc_generic_gf_add_or_substract(smaller_coefficients[i - length_diff], larger_coefficients[i]); 271 | } 272 | 273 | ecc_generic_gf_poly_init(result, sum_diff, larger_coefficients_length); 274 | free(sum_diff); 275 | } 276 | 277 | int32_t ecc_generic_gf_poly_is_zero(ecc_generic_gf_poly_t* self) { 278 | return self->coefficients[0] == 0; 279 | } 280 | 281 | void ecc_generic_gf_poly_multiply_other(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, ecc_generic_gf_poly_t* other, ecc_generic_gf_poly_t* result) { 282 | if (ecc_generic_gf_poly_is_zero(self) || ecc_generic_gf_poly_is_zero(other)) { 283 | int32_t zero[1] = { 0 }; 284 | ecc_generic_gf_poly_init(result, zero, 1); 285 | return; 286 | } 287 | int32_t product_length = self->coefficients_length + other->coefficients_length - 1; 288 | int32_t* product = malloc(sizeof(int32_t) * product_length); 289 | memset(product, 0, sizeof(int32_t) * product_length); 290 | int32_t i; 291 | int32_t j; 292 | for (i = 0; i < self->coefficients_length; i++) { 293 | for (j = 0; j < other->coefficients_length; j++) { 294 | product[i + j] = ecc_generic_gf_add_or_substract(product[i + j], ecc_generic_gf_multiply(field, self->coefficients[i], other->coefficients[j])); 295 | } 296 | } 297 | ecc_generic_gf_poly_init(result, product, product_length); 298 | free(product); 299 | } 300 | 301 | int32_t ecc_generic_gf_inverse(ecc_generic_gf_t* self, int32_t a) { 302 | return self->exp_table[self->size - self->log_table[a] - 1]; 303 | } 304 | 305 | int32_t ecc_generic_gf_poly_evaluate_at(ecc_generic_gf_poly_t* self, ecc_generic_gf_t* field, int32_t a) { 306 | if (a == 0) { 307 | // Just return the x^0 coefficient 308 | return ecc_generic_gf_poly_get_coefficient(self, 0); 309 | } 310 | if (a == 1) { 311 | // Just the sum of the coefficients 312 | int32_t result = 0; 313 | int32_t i; 314 | for (i = 0; i < self->coefficients_length; i++) { 315 | result = ecc_generic_gf_add_or_substract(result, self->coefficients[i]); 316 | } 317 | return result; 318 | } 319 | int32_t result = self->coefficients[0]; 320 | int32_t i; 321 | for (i = 1; i < self->coefficients_length; i++) { 322 | result = ecc_generic_gf_add_or_substract(ecc_generic_gf_multiply(field, a, result), self->coefficients[i]); 323 | } 324 | return result; 325 | } 326 | 327 | void ecc_reed_solomon_encoder_add(ecc_reed_solomon_encoder_t* self, ecc_generic_gf_poly_t* el) { 328 | ecc_reed_solomon_cached_generator_t* last = self->cached_generators; 329 | self->cached_generators = malloc(sizeof(ecc_reed_solomon_cached_generator_t)); 330 | self->cached_generators->index = last->index + 1; 331 | self->cached_generators->value = el; 332 | self->cached_generators->previous = last; 333 | } 334 | 335 | void ecc_reed_solomon_encoder_free(ecc_reed_solomon_encoder_t* self) { 336 | ecc_generic_gf_free(&(self->field)); 337 | ecc_reed_solomon_cached_generator_t* previous = self->cached_generators; 338 | while (previous != NULL) { 339 | ecc_generic_gf_poly_free(previous->value); 340 | free(previous->value); 341 | ecc_reed_solomon_cached_generator_t* to_free = previous; 342 | previous = previous->previous; 343 | free(to_free); 344 | } 345 | } 346 | 347 | ecc_generic_gf_poly_t* ecc_reed_solomon_encoder_get(ecc_reed_solomon_encoder_t* self, int32_t index) { 348 | ecc_reed_solomon_cached_generator_t* previous = self->cached_generators; 349 | while (previous != NULL) { 350 | if (previous->index == index) { 351 | return previous->value; 352 | } else { 353 | previous = previous->previous; 354 | } 355 | } 356 | return NULL; 357 | } 358 | 359 | void ecc_reed_solomon_encoder_init(ecc_reed_solomon_encoder_t* self, int32_t primitive, int32_t size, int32_t b) { 360 | ecc_generic_gf_init(&(self->field), primitive, size, b); 361 | self->cached_generators = malloc(sizeof(ecc_reed_solomon_cached_generator_t)); 362 | self->cached_generators->index = 0; 363 | self->cached_generators->previous = NULL; 364 | int32_t one[1] = { 1 }; 365 | self->cached_generators->value = malloc(sizeof(ecc_generic_gf_poly_t)); 366 | ecc_generic_gf_poly_init(self->cached_generators->value, one, 1); 367 | } 368 | 369 | ecc_generic_gf_poly_t* ecc_reed_solomon_encoder_build_generator(ecc_reed_solomon_encoder_t* self, int32_t degree) { 370 | if (degree >= self->cached_generators->index + 1) { 371 | ecc_generic_gf_poly_t* last_generator = self->cached_generators->value; 372 | int32_t d; 373 | for (d = self->cached_generators->index + 1; d <= degree; d++) { 374 | ecc_generic_gf_poly_t* next_generator = malloc(sizeof(ecc_generic_gf_poly_t)); 375 | ecc_generic_gf_poly_t gen; 376 | int32_t* data = malloc(sizeof(int32_t) * 2); 377 | data[0] = 1; 378 | data[1] = self->field.exp_table[d - 1 + self->field.generator_base]; 379 | ecc_generic_gf_poly_init(&gen, data, 2); 380 | free(data); 381 | ecc_generic_gf_poly_multiply_other(last_generator, &(self->field), &gen, next_generator); 382 | ecc_generic_gf_poly_free(&gen); 383 | ecc_reed_solomon_encoder_add(self, next_generator); 384 | last_generator = next_generator; 385 | } 386 | } 387 | return ecc_reed_solomon_encoder_get(self, degree); 388 | } 389 | 390 | void ecc_reed_solomon_encoder_encode(ecc_reed_solomon_encoder_t* self, int32_t* to_encode, int32_t to_encode_length, int32_t ec_bytes) { 391 | int32_t data_bytes = to_encode_length - ec_bytes; 392 | ecc_generic_gf_poly_t* generator = ecc_reed_solomon_encoder_build_generator(self, ec_bytes); 393 | int32_t* info_coefficients = malloc(sizeof(int32_t) * data_bytes); 394 | memcpy(info_coefficients, to_encode, data_bytes * sizeof(int32_t)); 395 | ecc_generic_gf_poly_t info; 396 | ecc_generic_gf_poly_init(&info, info_coefficients, data_bytes); 397 | free(info_coefficients); 398 | ecc_generic_gf_poly_t monomial_result; 399 | ecc_generic_gf_poly_multiply_by_monomial(&info,&(self->field), ec_bytes, 1, &monomial_result); 400 | ecc_generic_gf_poly_free(&info); 401 | ecc_generic_gf_poly_t remainder; 402 | ecc_generic_gf_poly_divide(&monomial_result, &(self->field), generator, &remainder); 403 | ecc_generic_gf_poly_free(&monomial_result); 404 | int32_t num_zero_coefficients = ec_bytes - remainder.coefficients_length; 405 | int32_t i; 406 | for (i = 0; i < num_zero_coefficients; i++) { 407 | to_encode[data_bytes + i] = 0; 408 | } 409 | memcpy(to_encode + data_bytes + num_zero_coefficients, remainder.coefficients, remainder.coefficients_length * sizeof(int32_t)); 410 | ecc_generic_gf_poly_free(&remainder); 411 | } 412 | 413 | 414 | 415 | int32_t ecc_reed_solomon_decoder_run_euclidean_algorithm(ecc_generic_gf_t* field, ecc_generic_gf_poly_t* a, ecc_generic_gf_poly_t* b, int32_t r_degree, 416 | ecc_generic_gf_poly_t* sigma, ecc_generic_gf_poly_t* omega) { 417 | int32_t ret = ECC_NO_ERRORS; 418 | // Assume a's degree is >= b's 419 | if (ecc_generic_gf_poly_get_degree(a) < ecc_generic_gf_poly_get_degree(b)) { 420 | ecc_generic_gf_poly_t* temp = a; 421 | a = b; 422 | b = temp; 423 | } 424 | 425 | ecc_generic_gf_poly_t r_last; 426 | ecc_generic_gf_poly_copy(&r_last, a); 427 | ecc_generic_gf_poly_t r; 428 | ecc_generic_gf_poly_copy(&r, b); 429 | ecc_generic_gf_poly_t t_last; 430 | ecc_generic_gf_poly_copy(&t_last, &(field->zero)); 431 | ecc_generic_gf_poly_t t; 432 | ecc_generic_gf_poly_copy(&t, &(field->one)); 433 | 434 | // Run Euclidean algorithm until r's degree is less than R/2 435 | while (ret == ECC_NO_ERRORS && ecc_generic_gf_poly_get_degree(&r) >= r_degree / 2) { 436 | ecc_generic_gf_poly_t r_last_last; 437 | ecc_generic_gf_poly_copy(&r_last_last, &r_last); 438 | ecc_generic_gf_poly_t t_last_last; 439 | ecc_generic_gf_poly_copy(&t_last_last, &t_last); 440 | 441 | ecc_generic_gf_poly_free(&r_last); 442 | ecc_generic_gf_poly_copy(&r_last, &r); 443 | ecc_generic_gf_poly_free(&t_last); 444 | ecc_generic_gf_poly_copy(&t_last, &t); 445 | 446 | if (ecc_generic_gf_poly_is_zero(&r_last)) { 447 | // Oops, Euclidean algorithm already terminated? 448 | ret = ECC_REED_SOLOMON_ERROR; 449 | ecc_generic_gf_poly_free(&t); 450 | ecc_generic_gf_poly_free(&r); 451 | } 452 | else { 453 | ecc_generic_gf_poly_free(&r); 454 | ecc_generic_gf_poly_copy(&r, &r_last_last); 455 | ecc_generic_gf_poly_t q; 456 | ecc_generic_gf_poly_copy(&q, &(field->zero)); 457 | 458 | int32_t denominator_leading_term = ecc_generic_gf_poly_get_coefficient(&r_last, ecc_generic_gf_poly_get_degree(&r_last)); 459 | int32_t dlt_inverse = ecc_generic_gf_inverse(field, denominator_leading_term); 460 | while (ecc_generic_gf_poly_get_degree(&r) >= ecc_generic_gf_poly_get_degree(&r_last) && !ecc_generic_gf_poly_is_zero(&r)) { 461 | int32_t degree_diff = ecc_generic_gf_poly_get_degree(&r) - ecc_generic_gf_poly_get_degree(&r_last); 462 | int32_t scale = ecc_generic_gf_multiply(field, ecc_generic_gf_poly_get_coefficient(&r, ecc_generic_gf_poly_get_degree(&r)), dlt_inverse); 463 | ecc_generic_gf_poly_t other; 464 | ecc_generic_gf_build_monomial(&other, degree_diff, scale); 465 | ecc_generic_gf_poly_t new_value; 466 | ecc_generic_gf_poly_add_or_substract(&q, &other, &new_value); 467 | ecc_generic_gf_poly_free(&other); 468 | ecc_generic_gf_poly_free(&q); 469 | ecc_generic_gf_poly_copy(&q, &new_value); 470 | ecc_generic_gf_poly_free(&new_value); 471 | ecc_generic_gf_poly_multiply_by_monomial(&r_last, field, degree_diff, scale, &other); 472 | ecc_generic_gf_poly_add_or_substract(&r, &other, &new_value); 473 | ecc_generic_gf_poly_free(&other); 474 | ecc_generic_gf_poly_free(&r); 475 | ecc_generic_gf_poly_copy(&r, &new_value); 476 | ecc_generic_gf_poly_free(&new_value); 477 | } 478 | ecc_generic_gf_poly_t result; 479 | ecc_generic_gf_poly_multiply_other(&q, field, &t_last, &result); 480 | ecc_generic_gf_poly_free(&t); 481 | ecc_generic_gf_poly_add_or_substract(&result, &t_last_last, &t); 482 | ecc_generic_gf_poly_free(&result); 483 | 484 | if (ecc_generic_gf_poly_get_degree(&r) >= ecc_generic_gf_poly_get_degree(&r_last)) { 485 | ret = ECC_ILLEGAL_STATE_EXCEPTION; 486 | // Division algorithm failed to reduce polynomial? 487 | } 488 | ecc_generic_gf_poly_free(&q); 489 | } 490 | ecc_generic_gf_poly_free(&t_last_last); 491 | ecc_generic_gf_poly_free(&r_last_last); 492 | } 493 | 494 | if (ret == ECC_NO_ERRORS) { 495 | int32_t sigma_tilde_at_zero = ecc_generic_gf_poly_get_coefficient(&t, 0); 496 | if (sigma_tilde_at_zero == 0) { 497 | ret = ECC_REED_SOLOMON_ERROR; 498 | } 499 | int32_t inverse = ecc_generic_gf_inverse(field, sigma_tilde_at_zero); 500 | ecc_generic_gf_poly_multiply(&t, field, inverse, sigma); 501 | ecc_generic_gf_poly_multiply(&r, field, inverse, omega); 502 | } 503 | 504 | ecc_generic_gf_poly_free(&t); 505 | ecc_generic_gf_poly_free(&t_last); 506 | ecc_generic_gf_poly_free(&r); 507 | ecc_generic_gf_poly_free(&r_last); 508 | return ret; 509 | } 510 | 511 | int32_t ecc_reed_solomon_decoder_find_error_locations(ecc_generic_gf_poly_t* error_locator, ecc_generic_gf_t* field, int32_t* result) { 512 | int32_t ret = ECC_NO_ERRORS; 513 | int32_t num_errors = ecc_generic_gf_poly_get_degree(error_locator); 514 | if (num_errors == 1) { //Shortcut 515 | result[0] = ecc_generic_gf_poly_get_coefficient(error_locator, 1); 516 | ret = ECC_NO_ERRORS; 517 | } 518 | else { 519 | int32_t e = 0; 520 | int32_t i; 521 | for (i = 0; i < field->size && e < num_errors; i++) { 522 | if (ecc_generic_gf_poly_evaluate_at(error_locator,field, i) == 0) { 523 | result[e] = ecc_generic_gf_inverse(field, i); 524 | e++; 525 | } 526 | } 527 | if (e != num_errors) { 528 | ret = ECC_REED_SOLOMON_ERROR; 529 | } 530 | } 531 | return ret; 532 | } 533 | 534 | void ecc_reed_solomon_decoder_find_error_magnitudes(ecc_generic_gf_poly_t* error_locator, ecc_generic_gf_t* field, int32_t* error_locations, int32_t error_locations_length, int32_t* result) { 535 | int32_t s = error_locations_length; 536 | int32_t i; 537 | for (i = 0; i < s; i++) { 538 | int32_t xi_inverse = ecc_generic_gf_inverse(field, error_locations[i]); 539 | int32_t denominator = 1; 540 | int32_t j; 541 | for (j = 0; j < s; j++) { 542 | if (i != j) { 543 | denominator = ecc_generic_gf_multiply(field, denominator, 544 | ecc_generic_gf_add_or_substract(1, ecc_generic_gf_multiply(field, error_locations[j], xi_inverse))); 545 | } 546 | } 547 | result[i] = ecc_generic_gf_multiply(field, ecc_generic_gf_poly_evaluate_at(error_locator, field, xi_inverse), ecc_generic_gf_inverse(field, denominator)); 548 | if (field->generator_base != 0) { 549 | result[i] = ecc_generic_gf_multiply(field, result[i], xi_inverse); 550 | } 551 | } 552 | } 553 | 554 | int32_t ecc_reed_solomon_decoder_decode(ecc_generic_gf_t* field, int32_t* to_decode, int32_t to_decode_length, int32_t ec_bytes, int32_t* fixedErrors) { 555 | int32_t ret = ECC_NO_ERRORS; 556 | ecc_generic_gf_poly_t poly; 557 | ecc_generic_gf_poly_init(&poly, to_decode, to_decode_length); 558 | int32_t syndrome_coefficients_length = ec_bytes; 559 | int32_t* syndrome_coefficients = malloc(sizeof(int32_t) * syndrome_coefficients_length); 560 | memset(syndrome_coefficients, 0, sizeof(int32_t) * syndrome_coefficients_length); 561 | int32_t no_error = 1; 562 | int32_t i; 563 | int32_t number_of_errors = 0; 564 | for (i = 0; i < ec_bytes; i++) { 565 | int32_t eval = ecc_generic_gf_poly_evaluate_at(&poly, field, field->exp_table[i + field->generator_base]); 566 | syndrome_coefficients[syndrome_coefficients_length - 1 - i] = eval; 567 | if (eval != 0) { 568 | no_error = 0; 569 | } 570 | } 571 | if (no_error == 0) { 572 | ecc_generic_gf_poly_t syndrome; 573 | ecc_generic_gf_poly_init(&syndrome, syndrome_coefficients, syndrome_coefficients_length); 574 | ecc_generic_gf_poly_t sigma; 575 | ecc_generic_gf_poly_t omega; 576 | ecc_generic_gf_poly_t mono; 577 | ecc_generic_gf_build_monomial(&mono, ec_bytes, 1); 578 | ret = ecc_reed_solomon_decoder_run_euclidean_algorithm(field, &mono, &syndrome, ec_bytes, &sigma, &omega); 579 | ecc_generic_gf_poly_free(&mono); 580 | if (ret == ECC_NO_ERRORS) { 581 | number_of_errors = ecc_generic_gf_poly_get_degree(&sigma); 582 | int32_t* error_locations = malloc(sizeof(int32_t) * number_of_errors); 583 | int32_t* error_magnitude = malloc(sizeof(int32_t) * number_of_errors); 584 | ret = ecc_reed_solomon_decoder_find_error_locations(&sigma, field, error_locations); 585 | ecc_generic_gf_poly_free(&sigma); 586 | if (ret == ECC_NO_ERRORS) { 587 | ecc_reed_solomon_decoder_find_error_magnitudes(&omega, field, error_locations, number_of_errors, error_magnitude); 588 | ecc_generic_gf_poly_free(&omega); 589 | for (i = 0; i < number_of_errors && ret == ECC_NO_ERRORS; i++) { 590 | int32_t position = to_decode_length - 1 - field->log_table[error_locations[i]]; 591 | if (position < 0) { 592 | ret = ECC_REED_SOLOMON_ERROR; // Bad error location 593 | } 594 | to_decode[position] = ecc_generic_gf_add_or_substract(to_decode[position], error_magnitude[i]); 595 | } 596 | } 597 | free(error_locations); 598 | free(error_magnitude); 599 | } 600 | ecc_generic_gf_poly_free(&syndrome); 601 | } 602 | free(syndrome_coefficients); 603 | ecc_generic_gf_poly_free(&poly); 604 | if(ret == ECC_NO_ERRORS && fixedErrors != NULL) { 605 | *fixedErrors += number_of_errors; 606 | } 607 | return ret; 608 | } 609 | 610 | 611 | 612 | 613 | --------------------------------------------------------------------------------