├── cricket ├── data.txt ├── inf.mat ├── aeiou.wav ├── cricket.wav ├── triplet.wav ├── cricket1.wav ├── Huffmann English.xls ├── detect.m ├── encode_cricket.m ├── createpacket.m ├── huffencode.m ├── decode_cricket.m └── huffdecode.m ├── ask-simple-java ├── jon.txt ├── test.txt ├── foo.wav ├── jon.wav ├── test.wav ├── Constants.java ├── AudioBuffer.java ├── ArrayUtils.java ├── MicrophoneListener.java ├── Main.java ├── StreamDecoder.java ├── Encoder.java ├── AudioUtils.java └── Decoder.java ├── cricket-android ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── values │ │ └── strings.xml │ └── layout │ │ └── main.xml ├── bin │ └── DigitalVoices-CricketAndroid.apk ├── src │ └── com │ │ └── jonas │ │ └── CricketAndroid │ │ ├── test.wav │ │ ├── AudioUtils.java │ │ ├── PlayThread.java │ │ ├── AudioBuffer.java │ │ ├── Loopback.java │ │ ├── Constants.java │ │ ├── ArrayUtils.java │ │ ├── MicrophoneListener.java │ │ ├── StreamDecoder.java │ │ ├── Huffman.java │ │ ├── Decoder.java │ │ ├── Encoder.java │ │ └── Main.java ├── .settings │ └── org.eclipse.jdt.core.prefs ├── .classpath ├── default.properties ├── .project ├── gen │ └── com │ │ └── jonas │ │ └── CricketAndroid │ │ └── R.java └── AndroidManifest.xml ├── ask-simple-android ├── bin │ └── ask-simple-android.apk ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── values │ │ └── strings.xml │ └── layout │ │ └── main.xml ├── src │ └── com │ │ └── jarkman │ │ └── ASKSimpleAndroid │ │ ├── test.wav │ │ ├── PlayThread.java │ │ ├── Constants.java │ │ ├── AudioBuffer.java │ │ ├── Loopback.java │ │ ├── ArrayUtils.java │ │ ├── MicrophoneListener.java │ │ ├── AudioUtils.java │ │ ├── Encoder.java │ │ ├── StreamDecoder.java │ │ ├── Main.java │ │ └── Decoder.java ├── .settings │ └── org.eclipse.jdt.core.prefs ├── .classpath ├── default.properties ├── .project └── AndroidManifest.xml └── README.txt /cricket/data.txt: -------------------------------------------------------------------------------- 1 | AEIOU. -------------------------------------------------------------------------------- /ask-simple-java/jon.txt: -------------------------------------------------------------------------------- 1 | J O N -------------------------------------------------------------------------------- /ask-simple-java/test.txt: -------------------------------------------------------------------------------- 1 | :) Hello Bill! 2 | 3 | -------------------------------------------------------------------------------- /cricket/inf.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/inf.mat -------------------------------------------------------------------------------- /cricket/aeiou.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/aeiou.wav -------------------------------------------------------------------------------- /cricket/cricket.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/cricket.wav -------------------------------------------------------------------------------- /cricket/triplet.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/triplet.wav -------------------------------------------------------------------------------- /cricket/cricket1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/cricket1.wav -------------------------------------------------------------------------------- /ask-simple-java/foo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-java/foo.wav -------------------------------------------------------------------------------- /ask-simple-java/jon.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-java/jon.wav -------------------------------------------------------------------------------- /ask-simple-java/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-java/test.wav -------------------------------------------------------------------------------- /cricket/Huffmann English.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket/Huffmann English.xls -------------------------------------------------------------------------------- /cricket-android/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket-android/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /cricket-android/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket-android/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /cricket-android/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket-android/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /ask-simple-android/bin/ask-simple-android.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-android/bin/ask-simple-android.apk -------------------------------------------------------------------------------- /ask-simple-android/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-android/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /ask-simple-android/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-android/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /ask-simple-android/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-android/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /cricket-android/bin/DigitalVoices-CricketAndroid.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket-android/bin/DigitalVoices-CricketAndroid.apk -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/cricket-android/src/com/jonas/CricketAndroid/test.wav -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva/digital-voices/HEAD/ask-simple-android/src/com/jarkman/ASKSimpleAndroid/test.wav -------------------------------------------------------------------------------- /ask-simple-android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, Main! 4 | ASKSimpleAndroid 5 | 6 | -------------------------------------------------------------------------------- /ask-simple-android/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Wed Apr 07 09:06:44 BST 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 4 | org.eclipse.jdt.core.compiler.compliance=1.5 5 | org.eclipse.jdt.core.compiler.source=1.5 6 | -------------------------------------------------------------------------------- /cricket-android/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Wed Apr 07 09:06:44 BST 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 4 | org.eclipse.jdt.core.compiler.compliance=1.5 5 | org.eclipse.jdt.core.compiler.source=1.5 6 | -------------------------------------------------------------------------------- /cricket-android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, Main! 4 | CricketAndroid 5 | text to chirp 6 | 7 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Welcome to the Digital Voices project! 2 | 3 | Here you find a collection of codecs for sending and 4 | receiving audio signals over regular mics and speakers. 5 | The code is licensed under the MIT license. 6 | 7 | If you want to improve and/or add to this collection, 8 | please drop me a line. 9 | -------------------------------------------------------------------------------- /cricket-android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ask-simple-android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ask-simple-android/default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-6 12 | # Indicates whether an apk should be generated for each density. 13 | split.density=false 14 | -------------------------------------------------------------------------------- /cricket-android/default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | # Indicates whether an apk should be generated for each density. 13 | split.density=false 14 | -------------------------------------------------------------------------------- /cricket/detect.m: -------------------------------------------------------------------------------- 1 | function correlation = detect(signal, Freq, fs) 2 | 3 | % constants 4 | floatToByteShift = 128; 5 | 6 | % y = e^(ju) = cos(u) + j*sin(u) 7 | u = 2*pi*Freq/fs; 8 | 9 | % realSum = real(signal * u) .* signal; 10 | % imaginarySum = imag(signal * u) .* signal; 11 | % 12 | % realAvg = mean(realSum); 13 | % imaginaryAvg = mean(imaginarySum); 14 | 15 | realSum = 0; 16 | imaginarySum = 0; 17 | for i = 1:length(signal) 18 | realSum = realSum + (cos(i*u) * signal(i)); 19 | imaginarySum = imaginarySum + (sin(i*u) * signal(i)); 20 | end 21 | 22 | realAvg = realSum/length(signal); 23 | imaginaryAvg = imaginarySum/length(signal); 24 | 25 | correlation = sqrt(power(realAvg, 2) + power(imaginaryAvg, 2)); -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/AudioUtils.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | * (Modified by Jonas Michel, 2012) 8 | */ 9 | 10 | 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | 16 | 17 | /** 18 | * 19 | * @author jrm 20 | */ 21 | 22 | public class AudioUtils { 23 | 24 | public static void performData(byte[] data) 25 | throws IOException { 26 | 27 | PlayThread p = new PlayThread( data ); 28 | 29 | } 30 | 31 | public static void performString(String string) throws IOException { 32 | ArrayList huffencoded = Huffman.huffencode(string); 33 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 34 | Encoder.encodeCricket(huffencoded, baos); 35 | performData(baos.toByteArray()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ask-simple-android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ASKSimpleAndroid 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /cricket-android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | DigitalVoices-CricketAndroid 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /ask-simple-android/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 14 | 15 | 17 | 18 | 20 | 21 | 23 | 24 | -------------------------------------------------------------------------------- /cricket-android/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 14 | 15 | 17 | 18 | 20 | 21 | 23 | 24 | -------------------------------------------------------------------------------- /cricket-android/gen/com/jonas/CricketAndroid/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.jonas.CricketAndroid; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class drawable { 14 | public static final int icon=0x7f020000; 15 | } 16 | public static final class id { 17 | public static final int ButtonListen=0x7f050002; 18 | public static final int ButtonPlay=0x7f050001; 19 | public static final int EditTextToPlay=0x7f050000; 20 | public static final int TextListen=0x7f050004; 21 | public static final int TextStatus=0x7f050003; 22 | } 23 | public static final class layout { 24 | public static final int main=0x7f030000; 25 | } 26 | public static final class string { 27 | public static final int app_name=0x7f040001; 28 | public static final int default_text=0x7f040002; 29 | public static final int hello=0x7f040000; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ask-simple-android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /cricket-android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/PlayThread.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | 4 | import android.media.AudioFormat; 5 | import android.media.AudioManager; 6 | import android.media.AudioTrack; 7 | 8 | public class PlayThread extends Thread{ 9 | 10 | public boolean isPlaying = true; 11 | private byte[] buffer; 12 | 13 | public PlayThread( byte[] b ) 14 | { 15 | buffer = new byte[b.length * 2]; 16 | 17 | // convert from 8 bit per sample to little-endian 16 bit per sample, IOW 16-bit PCM 18 | int i, j; 19 | for(i=0, j =0; i < b.length; i++, j += 2) 20 | { 21 | buffer[j] = 0; 22 | buffer[j+1] = b[i]; 23 | } 24 | 25 | 26 | start(); 27 | } 28 | 29 | public void run() 30 | { 31 | 32 | isPlaying = true; 33 | 34 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 35 | 36 | AudioTrack atrack = new AudioTrack(AudioManager.STREAM_MUSIC, 37 | (int) Encoder.kSamplingFrequency, 38 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 39 | AudioFormat.ENCODING_PCM_16BIT, // ENCODING_PCM_8BIT sounds very scratchy, so we use 16 bit and double up the data 40 | buffer.length, 41 | AudioTrack.MODE_STREAM); 42 | 43 | atrack.setPlaybackRate((int) Encoder.kSamplingFrequency); 44 | 45 | 46 | 47 | atrack.play(); 48 | 49 | try { 50 | atrack.write(buffer, 0, buffer.length); 51 | } catch (Exception e) { 52 | 53 | e.printStackTrace(); 54 | } 55 | 56 | 57 | atrack.stop(); 58 | } 59 | 60 | public void stopLoop() 61 | { 62 | isPlaying = false; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/PlayThread.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | import android.media.AudioFormat; 10 | import android.media.AudioManager; 11 | import android.media.AudioTrack; 12 | 13 | public class PlayThread extends Thread{ 14 | 15 | public boolean isPlaying = true; 16 | private byte[] buffer; 17 | 18 | public PlayThread( byte[] b ) 19 | { 20 | buffer = new byte[b.length * 2]; 21 | 22 | // convert from 8 bit per sample to little-endian 16 bit per sample, IOW 16-bit PCM 23 | int i, j; 24 | for(i=0, j =0; i < b.length; i++, j += 2) 25 | { 26 | buffer[j] = 0; 27 | buffer[j+1] = b[i]; 28 | } 29 | 30 | 31 | start(); 32 | } 33 | 34 | public void run() 35 | { 36 | 37 | isPlaying = true; 38 | 39 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 40 | 41 | AudioTrack atrack = new AudioTrack(AudioManager.STREAM_MUSIC, 42 | (int) Encoder.kSamplingFrequency, 43 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 44 | AudioFormat.ENCODING_PCM_16BIT, // ENCODING_PCM_8BIT sounds very scratchy, so we use 16 bit and double up the data 45 | buffer.length, 46 | AudioTrack.MODE_STREAM); 47 | 48 | atrack.setPlaybackRate((int) Encoder.kSamplingFrequency); 49 | 50 | 51 | 52 | atrack.play(); 53 | 54 | try { 55 | atrack.write(buffer, 0, buffer.length); 56 | } catch (Exception e) { 57 | 58 | e.printStackTrace(); 59 | } 60 | 61 | 62 | atrack.stop(); 63 | } 64 | 65 | public void stopLoop() 66 | { 67 | isPlaying = false; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /cricket/encode_cricket.m: -------------------------------------------------------------------------------- 1 | % Copyright 2001 by the authors. All rights reserved. 2 | % Authors: Cristina V Lopes (crista at tagide dot com) 3 | % Patricio de la Guardia 4 | % 5 | % Permission is hereby granted, free of charge, to any person obtaining a copy 6 | % of this software and associated documentation files (the "Software"), to deal 7 | % in the Software without restriction, including without limitation the rights 8 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | % copies of the Software, and to permit persons to whom the Software is 10 | % furnished to do so, subject to the following conditions: 11 | % 12 | % The above copyright notice and this permission notice shall be included in 13 | % all copies or substantial portions of the Software. 14 | % 15 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | % THE SOFTWARE. 22 | 23 | 24 | clear all 25 | close all 26 | 27 | fs= 22050; 28 | Freq = 4184; 29 | framelength = 420; %miliseconds 30 | % packetlength = 17; %miliseconds 31 | % silencelength = 22; %miliseconds 32 | FILE = 'data.txt'; 33 | [datain, count] = fread(fopen(FILE, 'r'), 'uint8'); 34 | % texto = []; 35 | % for k = 1: count 36 | % texto = [texto,char(datain(k))]; 37 | % end 38 | info = huffencode(datain); 39 | signal = []; 40 | encoded = []; 41 | for i = 1:4:length(info) - 4 42 | packet = createpacket(info(i:i+4), fs, Freq, framelength); 43 | signal = [signal, packet]; 44 | end 45 | 46 | % include a hail and EOT packet 47 | % signal = [createpacket([1 3 3 3], fs, Freq, framelength) signal]; 48 | window = hanning(0.1*fs); 49 | time = 0:1/fs:(fs*0.1-1)/fs; 50 | hail = sin(2*pi*time*Freq); 51 | hail = hail.*window'; 52 | signal = [hail signal hail]; 53 | 54 | %signal = signal + .08*rand(1,length(signal)); 55 | plot(signal); 56 | wavwrite(signal/max(abs(signal)),fs,'signal.wav'); 57 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/Constants.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | 4 | /** 5 | * Copyright 2002 by the authors. All rights reserved. 6 | * 7 | * Authir: Cristina V Lopes 8 | */ 9 | 10 | public interface Constants { 11 | 12 | public static final double kLowFrequency = 600; //the lowest frequency used 13 | public static final double kFrequencyStep = 50; //the distance between frequencies 14 | 15 | public static final int kBytesPerDuration = 1; //how wide is the data stream 16 | // (rps - that is, the pre-encoding data stream, not the audio) 17 | // (rps - kFrequencies must be this long) 18 | 19 | public static final int kBitsPerByte = 8; //unlikely to change, I know 20 | 21 | // Amplitude of each frequency in a frame. 22 | public static final double kAmplitude = 0.125d; /* (1/8) */ 23 | 24 | // Sampling frequency (number of sample values per second) 25 | public static final double kSamplingFrequency = 11025; //rps - reduced to 11025 from 22050 26 | // to enable the decoder to keep up with the audio on Motorola CLIQ 27 | 28 | // Sound duration of encoded byte (in seconds) 29 | public static final double kDuration = 0.2; // rps - increased from 0.1 to improve reliability on Android 30 | 31 | // Number of samples per duration 32 | public static final int kSamplesPerDuration = (int)(kSamplingFrequency * kDuration); 33 | 34 | //This is used to convert the floats of the encoding to the bytes of the audio 35 | public static final int kFloatToByteShift = 128; 36 | 37 | // The length, in durations, of the key sequence 38 | public static final int kDurationsPerKey = 3; 39 | 40 | //The frequency used in the initial hail of the key 41 | public static final int kHailFrequency = 3000; 42 | 43 | //The frequencies we use for each of the 8 bits 44 | public static final int[] kFrequencies = {1000, //1000 45 | (int)(1000 * (float)27/24), //1125 46 | (int)(1000 * (float)30/24), //1250 47 | (int)(1000 * (float)36/24), //1500 48 | (int)(1000 * (float)40/24), //1666 49 | (int)(1000 * (float)48/24), //2000 50 | (int)(1000 * (float)54/24), //2250 51 | (int)(1000 * (float)60/24)};//2500 52 | } 53 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/AudioBuffer.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | /** 4 | * Copyright 2002 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | import java.util.*; 10 | import java.io.*; 11 | 12 | /** 13 | * A thread safe buffer for audio samples 14 | * NOTE: This has no hard limits for memory usage 15 | * 16 | * @author CVL 17 | */ 18 | public class AudioBuffer { 19 | private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 20 | private Object lock = new Object(); 21 | 22 | public AudioBuffer(){}; 23 | 24 | /** 25 | * @param input an array to write to the end of the buffer 26 | */ 27 | public synchronized void write(byte[] input) 28 | throws IOException { 29 | baos.write(input); 30 | } 31 | 32 | /** 33 | * @param input the source array 34 | * @param offset the offset into the array from which to start copying 35 | * @param length the length to copy 36 | */ 37 | public synchronized void write(byte[] input, int offset, int length) 38 | throws IOException { 39 | baos.write(input, offset, length); 40 | } 41 | 42 | /** 43 | * @param n the number of bytes to try to read (nondestructively) 44 | * @return if the buffer.size >= n, return the requested byte array, otherwise null 45 | * 46 | * NOTE: THIS DOES NOT REMOVE BYTES FROM THE BUFFER 47 | */ 48 | public synchronized byte[] read(int n){ 49 | if(baos.size() < n){ 50 | return null; 51 | } 52 | byte[] result = ArrayUtils.subarray(baos.toByteArray(), 0, n); 53 | return result; 54 | } 55 | 56 | /** 57 | * @param n the number of bytes to remove from the buffer. 58 | * If n > buffer.size, it has the same effect as n = buffer.size. 59 | */ 60 | public synchronized void delete(int n) 61 | throws IOException { 62 | if(n <= 0){ 63 | return; 64 | } 65 | if(baos.size() < n){ 66 | baos.reset(); 67 | return; 68 | } 69 | byte[] buff = ArrayUtils.subarray(baos.toByteArray(), n - 1, baos.size() - n); 70 | baos.reset(); 71 | baos.write(buff); 72 | } 73 | 74 | /** 75 | * @return the current size of the buffer 76 | */ 77 | public synchronized int size(){ 78 | int size = 0; 79 | size = baos.size(); 80 | return size; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/AudioBuffer.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | 12 | /** 13 | * A thread safe buffer for audio samples 14 | * NOTE: This has no hard limits for memory usage 15 | * 16 | * @author CVL 17 | */ 18 | public class AudioBuffer { 19 | private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 20 | private Object lock = new Object(); 21 | 22 | public AudioBuffer(){}; 23 | 24 | /** 25 | * @param input an array to write to the end of the buffer 26 | */ 27 | public synchronized void write(byte[] input) 28 | throws IOException { 29 | baos.write(input); 30 | } 31 | 32 | /** 33 | * @param input the source array 34 | * @param offset the offset into the array from which to start copying 35 | * @param length the length to copy 36 | */ 37 | public synchronized void write(byte[] input, int offset, int length) 38 | throws IOException { 39 | baos.write(input, offset, length); 40 | } 41 | 42 | /** 43 | * @param n the number of bytes to try to read (nondestructively) 44 | * @return if the buffer.size >= n, return the requested byte array, otherwise null 45 | * 46 | * NOTE: THIS DOES NOT REMOVE BYTES FROM THE BUFFER 47 | */ 48 | public synchronized byte[] read(int n){ 49 | if(baos.size() < n){ 50 | return null; 51 | } 52 | byte[] result = ArrayUtils.subarray(baos.toByteArray(), 0, n); 53 | return result; 54 | } 55 | 56 | /** 57 | * @param n the number of bytes to remove from the buffer. 58 | * If n > buffer.size, it has the same effect as n = buffer.size. 59 | */ 60 | public synchronized void delete(int n) 61 | throws IOException { 62 | if(n <= 0){ 63 | return; 64 | } 65 | if(baos.size() < n){ 66 | baos.reset(); 67 | return; 68 | } 69 | byte[] buff = ArrayUtils.subarray(baos.toByteArray(), n - 1, baos.size() - n); 70 | baos.reset(); 71 | baos.write(buff); 72 | } 73 | 74 | /** 75 | * @return the current size of the buffer 76 | */ 77 | public synchronized int size(){ 78 | int size = 0; 79 | size = baos.size(); 80 | return size; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Loopback.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioManager; 5 | import android.media.AudioRecord; 6 | import android.media.AudioTrack; 7 | import android.media.MediaRecorder; 8 | 9 | // Simple microphone/speaker loopback test to check Andoid audio APIs are working 10 | 11 | public class Loopback extends Thread{ 12 | 13 | public boolean isRecording = true; 14 | 15 | public void run() 16 | { 17 | // from http://www.mail-archive.com/android-developers@googlegroups.com/msg76498.html 18 | 19 | isRecording = true; 20 | 21 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 22 | 23 | int buffersize = AudioRecord.getMinBufferSize(11025, 24 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 25 | AudioFormat.ENCODING_PCM_16BIT); 26 | 27 | if( buffersize < 1 ) 28 | { 29 | // parameters not supported by hardware, probably 30 | isRecording = false; 31 | return; 32 | } 33 | 34 | AudioRecord arec = new AudioRecord(MediaRecorder.AudioSource.MIC, 35 | 11025, 36 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 37 | AudioFormat.ENCODING_PCM_16BIT, 38 | buffersize); 39 | 40 | AudioTrack atrack = new AudioTrack(AudioManager.STREAM_MUSIC, 41 | 11025, 42 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 43 | AudioFormat.ENCODING_PCM_16BIT, 44 | buffersize, //ba.size(), 45 | AudioTrack.MODE_STREAM); 46 | 47 | atrack.setPlaybackRate(11025); 48 | 49 | byte[] buffer = new byte[buffersize]; 50 | 51 | arec.startRecording(); 52 | atrack.play(); 53 | 54 | while(isRecording) { 55 | arec.read(buffer, 0, buffersize); 56 | try { 57 | atrack.write(buffer, 0, buffer.length); 58 | } catch (Exception e) { 59 | 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | arec.stop(); 65 | atrack.stop(); 66 | } 67 | 68 | public void stopLoop() 69 | { 70 | isRecording = false; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/Loopback.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | import java.io.IOException; 4 | 5 | import android.media.AudioFormat; 6 | import android.media.AudioManager; 7 | import android.media.AudioRecord; 8 | import android.media.AudioTrack; 9 | import android.media.MediaRecorder; 10 | 11 | // Simple microphone/speaker loopback test to check Andoid audio APIs are working 12 | 13 | public class Loopback extends Thread{ 14 | 15 | public boolean isRecording = true; 16 | 17 | public void run() 18 | { 19 | // from http://www.mail-archive.com/android-developers@googlegroups.com/msg76498.html 20 | 21 | isRecording = true; 22 | 23 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 24 | 25 | int buffersize = AudioRecord.getMinBufferSize(11025, 26 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 27 | AudioFormat.ENCODING_PCM_16BIT); 28 | 29 | if( buffersize < 1 ) 30 | { 31 | // parameters not supported by hardware, probably 32 | isRecording = false; 33 | return; 34 | } 35 | 36 | AudioRecord arec = new AudioRecord(MediaRecorder.AudioSource.MIC, 37 | 11025, 38 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 39 | AudioFormat.ENCODING_PCM_16BIT, 40 | buffersize); 41 | 42 | AudioTrack atrack = new AudioTrack(AudioManager.STREAM_MUSIC, 43 | 11025, 44 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 45 | AudioFormat.ENCODING_PCM_16BIT, 46 | buffersize, //ba.size(), 47 | AudioTrack.MODE_STREAM); 48 | 49 | atrack.setPlaybackRate(11025); 50 | 51 | byte[] buffer = new byte[buffersize]; 52 | 53 | arec.startRecording(); 54 | atrack.play(); 55 | 56 | while(isRecording) { 57 | arec.read(buffer, 0, buffersize); 58 | try { 59 | atrack.write(buffer, 0, buffer.length); 60 | } catch (Exception e) { 61 | 62 | e.printStackTrace(); 63 | } 64 | } 65 | 66 | arec.stop(); 67 | atrack.stop(); 68 | } 69 | 70 | public void stopLoop() 71 | { 72 | isRecording = false; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | /** 4 | * Copyright 2002 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | 10 | /** 11 | * A set of handy array manipulation utilities 12 | * @author CVL 13 | */ 14 | public class ArrayUtils { 15 | 16 | /** 17 | * Create a new array of length 'length' and fill it from array 'array' 18 | * @param array the array from which to take the subsection 19 | * @param start the index from which to start the subsection copy 20 | * @param length the length of the returned array. 21 | * @return byte[] of length 'length', padded with zeros if array.length is shorter than 'start' + 'length' 22 | * 23 | * NOTE! if start + length goes beyond the end of array.length, the returned value will be padded with 0s. 24 | */ 25 | public static byte[] subarray(byte[] array, int start, int length){ 26 | byte[] result = new byte[length]; 27 | for(int i=0; (i < length) && (i + start < array.length); i++){ 28 | result[i] = array[i + start]; 29 | } 30 | return result; 31 | } 32 | 33 | /** 34 | * Converts the input matrix into a single dimensional array by transposing and concatenating the columns 35 | * @param input a 2D array whose columns will be concatenated 36 | * @return the concatenated array 37 | */ 38 | public static byte[] concatenate(byte[][] input){ 39 | //sum the lengths of the columns 40 | int totalLength = 0; 41 | for(int i = 0; i < input.length; i++){ 42 | totalLength += input[i].length; 43 | } 44 | //create the result array 45 | byte[] result = new byte[totalLength]; 46 | 47 | //populate the result array 48 | int currentIndex = 0; 49 | for(int i=0; i < input.length; i++){ 50 | for(int j = 0; j < input[i].length; j++){ 51 | result[currentIndex++] = input[i][j]; 52 | } 53 | } 54 | return result; 55 | } 56 | 57 | /** 58 | * @param sequence the array of floats to return as a shifted and clipped array of bytes 59 | * @return byte[i] = sequence[i] * Constants.kFloatToByteShift cast to a byte 60 | * Note!: This doesn't handle cast/conversion issues, so don't use this unless you understand the code 61 | */ 62 | public static byte[] getByteArrayFromDoubleArray(double[] sequence){ 63 | byte[] result = new byte[sequence.length]; 64 | for(int i=0; i < result.length; i++){ 65 | result[i] = (byte)((sequence[i] * Constants.kFloatToByteShift) - 1); 66 | } 67 | return result; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cricket/createpacket.m: -------------------------------------------------------------------------------- 1 | % Copyright 2001 by the authors. All rights reserved. 2 | % Authors: Cristina V Lopes (crista at tagide dot com) 3 | % Patricio de la Guardia 4 | % 5 | % Permission is hereby granted, free of charge, to any person obtaining a copy 6 | % of this software and associated documentation files (the "Software"), to deal 7 | % in the Software without restriction, including without limitation the rights 8 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | % copies of the Software, and to permit persons to whom the Software is 10 | % furnished to do so, subject to the following conditions: 11 | % 12 | % The above copyright notice and this permission notice shall be included in 13 | % all copies or substantial portions of the Software. 14 | % 15 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | % THE SOFTWARE. 22 | 23 | function signal = createpacket(info, fs, Freq, framelength) 24 | 25 | shortsize = 15; 26 | longsize = 24; 27 | bigamplitude = 1; 28 | smallamplitude = 0.5; 29 | 30 | packet = []; 31 | for k = 2:4 32 | switch info(k) 33 | case 1, 34 | packetlength = shortsize; 35 | silencelength = longsize; 36 | amplitude = bigamplitude; 37 | case 2, 38 | packetlength = shortsize; 39 | silencelength = longsize; 40 | amplitude = smallamplitude; 41 | case 3, 42 | packetlength = longsize; 43 | silencelength = shortsize; 44 | amplitude = bigamplitude; 45 | case 4, 46 | packetlength = longsize; 47 | silencelength = shortsize; 48 | amplitude = smallamplitude; 49 | otherwise 50 | displ('Wrong number, only valid 1,2,3,4');break; 51 | end 52 | framesamples = round(fs*framelength/1000); 53 | packetsamples = round(fs*packetlength/1000); 54 | silencesamples = round(fs*silencelength/1000); 55 | 56 | window = hanning(packetsamples); 57 | time = (1:packetsamples)*packetlength/(packetsamples*1000); 58 | smallpacket = amplitude*sin(2*pi*time(1:length(window))*Freq); 59 | smallpacket = smallpacket.*window'; 60 | packet = [packet, zeros(1,round(silencesamples)), smallpacket]; 61 | end 62 | 63 | signal = [zeros(1,round(fs*(info(1)-1)*(2*shortsize + 1.5*longsize)/1000)), packet]; 64 | signal = [signal, zeros(1,framesamples - length(signal))]; 65 | 66 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Constants.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Jonas Michel 7 | */ 8 | 9 | public interface Constants { 10 | 11 | public static final int kBitsPerByte = 8; //unlikely to change, I know 12 | 13 | // Sampling frequency (number of sample values per second) 14 | public static final double kSamplingFrequency = 22050; 15 | 16 | // Sound duration of encoded byte (in seconds) 17 | public static final double kDuration = 0.2; // increased from 0.1 to improve reliability on Android 18 | 19 | // Number of samples per duration 20 | public static final int kSamplesPerDuration = (int)(kSamplingFrequency * kDuration); 21 | 22 | //This is used to convert the floats of the encoding to the bytes of the audio 23 | public static final int kFloatToByteShift = 128; 24 | 25 | // The length, in durations, of the key sequence 26 | public static final int kDurationsPerKey = 3; 27 | 28 | //The frequency used in the initial hail of the key 29 | public static final int kHailFrequency = 3000; 30 | 31 | // The detection threshold for a hail key 32 | public static final double kKeyDetectionThreshold = 0.20; 33 | 34 | // Hail detection granularities 35 | public static final int kKeyDetectionGranularityCoarse = 400; 36 | public static final int kKeyDetectionGranularityFine = 20; 37 | 38 | // The chirping frequency 39 | public static final int kFrequency = 4184; 40 | 41 | // The length in milliseconds of a frame 42 | public static final int kFrameLength = 420; // ms 43 | 44 | // The number of samples in a frame 45 | public static final int kSamplesPerFrame = (int) Math.round((kSamplingFrequency * kFrameLength) / 1000); 46 | 47 | // The length (width) in milliseconds of "short" and "long" chirps 48 | public static final int kShortSize = (int) Math.round(15); // ms 49 | public static final int kLongSize = (int) Math.round(24*1.5); // ms 50 | 51 | // Aliases for chirp lengths 52 | public static final int kPacketLength = kShortSize; // ms 53 | public static final int kSilenceLength = kLongSize; // ms 54 | 55 | // The amplitudes of "big" and "small" chirps 56 | public static final double kBigAmplitude = 1.0; 57 | public static final double kSmallAmplitude = 0.25; 58 | 59 | // Some heavily used convenience constants 60 | public static final int kSilencePacketLength = (kShortSize + kLongSize); 61 | public static final int kSilencePacketSamples = (int) Math.round((kSamplingFrequency * kSilencePacketLength) / 1000); 62 | public static final int kThreePacketSamples = (int) Math.round((3 * kSamplingFrequency * kSilencePacketLength) / 1000); 63 | 64 | // Chirp decoding thresholds (these may need to be tuned to fit your smartphone) 65 | public static final double kThreshWidth = 16.0; 66 | public static final double kThreshAmplitude = 1.50; 67 | } 68 | -------------------------------------------------------------------------------- /ask-simple-java/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | public interface Constants { 27 | 28 | public static final double kLowFrequency = 600; //the lowest frequency used 29 | public static final double kFrequencyStep = 50; //the distance between frequencies 30 | 31 | public static final int kBytesPerDuration = 1; //how wide is the data stream 32 | public static final int kBitsPerByte = 8; //unlikely to change, I know 33 | 34 | // Amplitude of each frequency in a frame. 35 | public static final double kAmplitude = 0.125d; /* (1/8) */ 36 | 37 | // Sampling frequency (number of sample values per second) 38 | public static final double kSamplingFrequency = 22050; 39 | 40 | // Sound duration of encoded byte (in seconds) 41 | public static final double kDuration = 0.1; 42 | 43 | // Number of samples per duration 44 | public static final int kSamplesPerDuration = (int)(kSamplingFrequency * kDuration); 45 | 46 | //This is used to convert the floats of the encoding to the bytes of the audio 47 | public static final int kFloatToByteShift = 128; 48 | 49 | // The length, in durations, of the key sequence 50 | public static final int kDurationsPerKey = 3; 51 | 52 | //The frequency used in the initial hail of the key 53 | public static final int kHailFrequency = 3000; 54 | 55 | //The frequencies we use for each of the 8 bits 56 | public static final int[] kFrequencies = {1000, //1000 57 | (int)(1000 * (float)27/24), //1125 58 | (int)(1000 * (float)30/24), //1250 59 | (int)(1000 * (float)36/24), //1500 60 | (int)(1000 * (float)40/24), //1666 61 | (int)(1000 * (float)48/24), //2000 62 | (int)(1000 * (float)54/24), //2250 63 | (int)(1000 * (float)60/24)};//2500 64 | } 65 | -------------------------------------------------------------------------------- /cricket/huffencode.m: -------------------------------------------------------------------------------- 1 | % Copyright 2001 by the authors. All rights reserved. 2 | % Authors: Cristina V Lopes (crista at tagide dot com) 3 | % Patricio de la Guardia 4 | % 5 | % Permission is hereby granted, free of charge, to any person obtaining a copy 6 | % of this software and associated documentation files (the "Software"), to deal 7 | % in the Software without restriction, including without limitation the rights 8 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | % copies of the Software, and to permit persons to whom the Software is 10 | % furnished to do so, subject to the following conditions: 11 | % 12 | % The above copyright notice and this permission notice shall be included in 13 | % all copies or substantial portions of the Software. 14 | % 15 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | % THE SOFTWARE. 22 | 23 | function output = huffencode(input) 24 | output = []; 25 | for i = 1:length(input) 26 | switch input(i) 27 | case {' ',','} 28 | output = [output, 4]; 29 | case {'a','A'} 30 | output = [output,1, 4]; 31 | case {'b','B'} 32 | output = [output,2, 4,4,2 ]; 33 | case {'c','C'} 34 | output = [output,1,1,3 ]; 35 | case {'d','D'} 36 | output = [output,1,1,2 ]; 37 | case {'e','E'} 38 | output = [output,1,2 ]; 39 | case {'f','F'} 40 | output = [output, 2,4,3]; 41 | case {'g','G'} 42 | output = [output, 2,3,2]; 43 | case {'h','H'} 44 | output = [output, 3,4]; 45 | case {'i','I'} 46 | output = [output,2,2 ]; 47 | case {'j','J'} 48 | output = [output, 2,4,2,2]; 49 | case {'k','K'} 50 | output = [output, 2,4,4,4]; 51 | case {'l','L'} 52 | output = [output, 1,1,1]; 53 | case {'m','M'} 54 | output = [output, 2,4,1]; 55 | case {'n','N'} 56 | output = [output, 3,1]; 57 | case {'o','O'} 58 | output = [output, 2,1]; 59 | case {'p','P'} 60 | output = [output, 2,3,1]; 61 | case {'q','Q'} 62 | output = [output,2,4,2,3 ]; 63 | case {'r','R'} 64 | output = [output, 3,3]; 65 | case {'s','S'} 66 | output = [output, 3,2]; 67 | case {'t','T'} 68 | output = [output, 1,3]; 69 | case {'u','U'} 70 | output = [output, 1,1,4]; 71 | case {'v','V'} 72 | output = [output, 2,4,4,3]; 73 | case {'w','W'} 74 | output = [output, 2,3,3]; 75 | case {'x','X'} 76 | output = [output, 2,4,2,1]; 77 | case {'y','Y'} 78 | output = [output, 2,4,4,1]; 79 | case {'z','Z'} 80 | output = [output, 2,4,2,4]; 81 | case {'.',';'} 82 | output = [output, 2,3,4]; 83 | 84 | otherwise 85 | disp('ERROR: Character not known!!'); 86 | disp(input(i)) 87 | end 88 | end 89 | 90 | output 91 | -------------------------------------------------------------------------------- /ask-simple-java/AudioBuffer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | import java.util.*; 27 | import java.io.*; 28 | 29 | /** 30 | * A thread safe buffer for audio samples 31 | * NOTE: This has no hard limits for memory usage 32 | * 33 | * @author CVL 34 | */ 35 | public class AudioBuffer { 36 | private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 37 | private Object lock = new Object(); 38 | 39 | public AudioBuffer(){}; 40 | 41 | /** 42 | * @param input an array to write to the end of the buffer 43 | */ 44 | public synchronized void write(byte[] input) 45 | throws IOException { 46 | baos.write(input); 47 | } 48 | 49 | /** 50 | * @param input the source array 51 | * @param offset the offset into the array from which to start copying 52 | * @param length the length to copy 53 | */ 54 | public synchronized void write(byte[] input, int offset, int length) 55 | throws IOException { 56 | baos.write(input, offset, length); 57 | } 58 | 59 | /** 60 | * @param n the number of bytes to try to read (nondestructively) 61 | * @return if the buffer.size >= n, return the requested byte array, otherwise null 62 | * 63 | * NOTE: THIS DOES NOT REMOVE BYTES FROM THE BUFFER 64 | */ 65 | public synchronized byte[] read(int n){ 66 | if(baos.size() < n){ 67 | return null; 68 | } 69 | byte[] result = ArrayUtils.subarray(baos.toByteArray(), 0, n); 70 | return result; 71 | } 72 | 73 | /** 74 | * @param n the number of bytes to remove from the buffer. 75 | * If n > buffer.size, it has the same effect as n = buffer.size. 76 | */ 77 | public synchronized void delete(int n) 78 | throws IOException { 79 | if(n <= 0){ 80 | return; 81 | } 82 | if(baos.size() < n){ 83 | baos.reset(); 84 | return; 85 | } 86 | byte[] buff = ArrayUtils.subarray(baos.toByteArray(), n - 1, baos.size() - n); 87 | baos.reset(); 88 | baos.write(buff); 89 | } 90 | 91 | /** 92 | * @return the current size of the buffer 93 | */ 94 | public synchronized int size(){ 95 | int size = 0; 96 | size = baos.size(); 97 | return size; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ask-simple-java/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | /** 28 | * A set of handy array manipulation utilities 29 | * @author CVL 30 | */ 31 | public class ArrayUtils { 32 | 33 | /** 34 | * Create a new array of length 'length' and fill it from array 'array' 35 | * @param array the array from which to take the subsection 36 | * @param start the index from which to start the subsection copy 37 | * @param length the length of the returned array. 38 | * @return byte[] of length 'length', padded with zeros if array.length is shorter than 'start' + 'length' 39 | * 40 | * NOTE! if start + length goes beyond the end of array.length, the returned value will be padded with 0s. 41 | */ 42 | public static byte[] subarray(byte[] array, int start, int length){ 43 | byte[] result = new byte[length]; 44 | for(int i=0; (i < length) && (i + start < array.length); i++){ 45 | result[i] = array[i + start]; 46 | } 47 | return result; 48 | } 49 | 50 | /** 51 | * Converts the input matrix into a single dimensional array by transposing and concatenating the columns 52 | * @param input a 2D array whose columns will be concatenated 53 | * @return the concatenated array 54 | */ 55 | public static byte[] concatenate(byte[][] input){ 56 | //sum the lengths of the columns 57 | int totalLength = 0; 58 | for(int i = 0; i < input.length; i++){ 59 | totalLength += input[i].length; 60 | } 61 | //create the result array 62 | byte[] result = new byte[totalLength]; 63 | 64 | //populate the result array 65 | int currentIndex = 0; 66 | for(int i=0; i < input.length; i++){ 67 | for(int j = 0; j < input[i].length; j++){ 68 | result[currentIndex++] = input[i][j]; 69 | } 70 | } 71 | return result; 72 | } 73 | 74 | /** 75 | * @param sequence the array of floats to return as a shifted and clipped array of bytes 76 | * @return byte[i] = sequence[i] * Constants.kFloatToByteShift cast to a byte 77 | * Note!: This doesn't handle cast/conversion issues, so don't use this unless you understand the code 78 | */ 79 | public static byte[] getByteArrayFromDoubleArray(double[] sequence){ 80 | byte[] result = new byte[sequence.length]; 81 | for(int i=0; i < result.length; i++){ 82 | result[i] = (byte)((sequence[i] * Constants.kFloatToByteShift) - 1); 83 | } 84 | return result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | * (Modified by Jonas Michel, 2012) 8 | */ 9 | 10 | 11 | /** 12 | * A set of handy array manipulation utilities 13 | * @author CVL 14 | */ 15 | public class ArrayUtils { 16 | 17 | /** 18 | * Create a new array of length 'length' and fill it from array 'array' 19 | * @param array the array from which to take the subsection 20 | * @param start the index from which to start the subsection copy 21 | * @param length the length of the returned array. 22 | * @return byte[] of length 'length', padded with zeros if array.length is shorter than 'start' + 'length' 23 | * 24 | * NOTE! if start + length goes beyond the end of array.length, the returned value will be padded with 0s. 25 | */ 26 | public static byte[] subarray(byte[] array, int start, int length){ 27 | byte[] result = new byte[length]; 28 | for(int i=0; (i < length) && (i + start < array.length); i++){ 29 | result[i] = array[i + start]; 30 | } 31 | return result; 32 | } 33 | 34 | /** 35 | * Converts the input matrix into a single dimensional array by transposing and concatenating the columns 36 | * @param input a 2D array whose columns will be concatenated 37 | * @return the concatenated array 38 | */ 39 | public static byte[] concatenate(byte[][] input){ 40 | //sum the lengths of the columns 41 | int totalLength = 0; 42 | for(int i = 0; i < input.length; i++){ 43 | totalLength += input[i].length; 44 | } 45 | //create the result array 46 | byte[] result = new byte[totalLength]; 47 | 48 | //populate the result array 49 | int currentIndex = 0; 50 | for(int i=0; i < input.length; i++){ 51 | for(int j = 0; j < input[i].length; j++){ 52 | result[currentIndex++] = input[i][j]; 53 | } 54 | } 55 | return result; 56 | } 57 | 58 | /** 59 | * @param sequence the array of floats to return as a shifted and clipped array of bytes 60 | * @return byte[i] = sequence[i] * Constants.kFloatToByteShift cast to a byte 61 | * Note!: This doesn't handle cast/conversion issues, so don't use this unless you understand the code 62 | */ 63 | public static byte[] getByteArrayFromDoubleArray(double[] sequence){ 64 | byte[] result = new byte[sequence.length]; 65 | for(int i=0; i < result.length; i++){ 66 | result[i] = (byte)((sequence[i] * Constants.kFloatToByteShift) - 1); 67 | } 68 | return result; 69 | } 70 | 71 | /** 72 | * @param sequence the array of bytes to return as an array of doubles 73 | * @return double[i] = sequence[i] / Constants.kFloatToByteShift cast to a byte 74 | * Note!: This doesn't handle cast/conversion issues, so don't use this unless you understand the code 75 | */ 76 | public static double[] getDoubleArrayfromByteArray(byte[] sequence) { 77 | double[] result = new double[sequence.length]; 78 | for (int i = 0; i < result.length; i++) { 79 | result[i] = (double)(sequence[i]/(double)Constants.kFloatToByteShift); 80 | } 81 | return result; 82 | } 83 | 84 | /** 85 | * @param src the array of doubles to get the slice of 86 | * @param begin the starting index of the slice 87 | * @param end the ending index of the slice 88 | * @return new sliced array 89 | */ 90 | public static double[] getSlice(double[] src, int begin, int end) { 91 | double slice[] = new double[end - begin]; 92 | for (int i = 0; i < (end - begin); i++) { 93 | slice[i] = src[i + begin]; 94 | } 95 | return slice; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ask-simple-java/MicrophoneListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | * 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | */ 26 | 27 | import javax.sound.sampled.*; 28 | import java.io.*; 29 | 30 | /** 31 | * This thread puts bytes from the microphone into the StreamDecoder's buffer. 32 | * 33 | * @author CVL 34 | */ 35 | public class MicrophoneListener implements Runnable { 36 | 37 | public static final String kThreadName = "MicrophoneListener"; 38 | 39 | private AudioBuffer buffer = null; 40 | private Thread myThread = null; 41 | private Object runLock = new Object(); 42 | private boolean running = false; 43 | 44 | /** 45 | * NOTE: This spawns a thread to do the listening and then returns 46 | * @param _buffer the AudioBuffer into which to write the microphone input 47 | */ 48 | public MicrophoneListener(AudioBuffer _buffer) { 49 | buffer = _buffer; 50 | myThread = new Thread(this, kThreadName); 51 | myThread.start(); 52 | } 53 | 54 | public void run() { 55 | synchronized(runLock){ 56 | running = true; 57 | } 58 | 59 | try { 60 | /** 61 | * NOTE: we want buffSize large so that we don't loose samples when the 62 | * StreamDecoder thread kicks in. But we want to read a small number of 63 | * samples at a time, so that StreamDecoder can process them and they get 64 | * freed from the buffer as soon as possible. 65 | * So there's a fine balance going on here between the two threads, and 66 | * if it's not tuned, samples will be lost. 67 | */ 68 | int buffSize = 32000; 69 | int buffSizeFraction = 8; 70 | TargetDataLine line = AudioUtils.getTargetDataLine(AudioUtils.kDefaultFormat); 71 | line.open(AudioUtils.kDefaultFormat, buffSize); 72 | System.out.println(Thread.currentThread().getName() + "> bufferSize = " + line.getBufferSize()); 73 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 74 | byte[] data = new byte[line.getBufferSize() / buffSizeFraction]; 75 | int numBytesRead; 76 | line.start(); 77 | while(running){ 78 | numBytesRead = line.read(data, 0, data.length); 79 | // System.out.println(Thread.currentThread().getName() + "> bytesRead = " + numBytesRead); 80 | buffer.write(data, 0, numBytesRead); 81 | } 82 | line.drain(); 83 | line.stop(); 84 | line.close(); 85 | } catch (Exception e){ 86 | System.out.println(e.toString()); 87 | } 88 | } 89 | 90 | public void quit(){ 91 | synchronized(runLock){ 92 | running = false; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/MicrophoneListener.java: -------------------------------------------------------------------------------- 1 | 2 | package com.jonas.CricketAndroid; 3 | 4 | /** 5 | * Copyright 2012 by the authors. All rights reserved. 6 | * 7 | * Author: Cristina V Lopes 8 | */ 9 | 10 | 11 | import android.media.AudioFormat; 12 | import android.media.AudioRecord; 13 | import android.media.MediaRecorder; 14 | 15 | /** 16 | * This thread puts bytes from the microphone into the StreamDecoder's buffer. 17 | * 18 | * @author CVL 19 | */ 20 | public class MicrophoneListener implements Runnable { 21 | 22 | public static final String kThreadName = "MicrophoneListener"; 23 | 24 | private AudioBuffer buffer = null; 25 | private Thread myThread = null; 26 | private Object runLock = new Object(); 27 | private boolean running = false; 28 | 29 | /** 30 | * NOTE: This spawns a thread to do the listening and then returns 31 | * @param _buffer the AudioBuffer into which to write the microphone input 32 | */ 33 | public MicrophoneListener(AudioBuffer _buffer) { 34 | buffer = _buffer; 35 | myThread = new Thread(this, kThreadName); 36 | myThread.start(); 37 | } 38 | 39 | 40 | 41 | public void run() { 42 | synchronized(runLock){ 43 | running = true; 44 | } 45 | 46 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 47 | 48 | 49 | try { 50 | /** 51 | * NOTE: we want buffSize large so that we don't loose samples when the 52 | * StreamDecoder thread kicks in. But we want to read a small number of 53 | * samples at a time, so that StreamDecoder can process them and they get 54 | * freed from the buffer as soon as possible. 55 | * So there's a fine balance going on here between the two threads, and 56 | * if it's not tuned, samples will be lost. 57 | */ 58 | 59 | 60 | int buffSize = (int) Encoder.kSamplingFrequency 61 | * 2 // 2 seconds in the buffer 62 | * 2; // recording in 16 bit 63 | 64 | int biteSize = ((int) Encoder.kSamplingFrequency ) / 8; // move 1/8 sec of data at a time 65 | 66 | AudioRecord arec = new AudioRecord(MediaRecorder.AudioSource.MIC, 67 | (int) Encoder.kSamplingFrequency, //11025, 68 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 69 | AudioFormat.ENCODING_PCM_16BIT, // must match PlayThread. Tried (and failed) to make ENCODING_PCM_8BIT work on Android 70 | buffSize); 71 | System.out.println(Thread.currentThread().getName() + " is recording"); 72 | //ByteArrayOutputStream out = new ByteArrayOutputStream(); 73 | byte[] data_16bit = new byte[ biteSize * 2]; 74 | byte[] data_8bit = new byte[ biteSize ]; 75 | 76 | int numBytesRead; 77 | arec.startRecording(); 78 | while(running) 79 | { 80 | numBytesRead = arec.read(data_16bit, 0, data_16bit.length); 81 | 82 | // convert the 16bit we have to read to to 8bit by discarding alternate bytes - PCM is little-endian, so we discard the first byte 83 | int i; 84 | int j; 85 | for( i = 1, j = 0; i < numBytesRead; i += 2, j ++) 86 | data_8bit[j] = data_16bit[i]; 87 | 88 | //System.out.println(Thread.currentThread().getName() + "> bytesRead = " + numBytesRead); 89 | 90 | buffer.write(data_8bit, 0, j); //writes into the buffer, which StreamDecoder is eagerly reading from 91 | //buffer.write(data, 0, numBytesRead); 92 | 93 | Thread.yield(); 94 | } 95 | 96 | arec.stop(); 97 | arec.release(); 98 | } catch (Exception e){ 99 | System.out.println(e.toString()); 100 | } 101 | } 102 | 103 | public void quit(){ 104 | synchronized(runLock){ 105 | running = false; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/MicrophoneListener.java: -------------------------------------------------------------------------------- 1 | 2 | package com.jarkman.ASKSimpleAndroid; 3 | 4 | /** 5 | * Copyright 2002 by the authors. All rights reserved. 6 | * 7 | * Author: Cristina V Lopes 8 | */ 9 | 10 | 11 | import java.io.*; 12 | 13 | import android.media.AudioFormat; 14 | import android.media.AudioRecord; 15 | import android.media.MediaRecorder; 16 | 17 | /** 18 | * This thread puts bytes from the microphone into the StreamDecoder's buffer. 19 | * 20 | * @author CVL 21 | */ 22 | public class MicrophoneListener implements Runnable { 23 | 24 | public static final String kThreadName = "MicrophoneListener"; 25 | 26 | private AudioBuffer buffer = null; 27 | private Thread myThread = null; 28 | private Object runLock = new Object(); 29 | private boolean running = false; 30 | 31 | /** 32 | * NOTE: This spawns a thread to do the listening and then returns 33 | * @param _buffer the AudioBuffer into which to write the microphone input 34 | */ 35 | public MicrophoneListener(AudioBuffer _buffer) { 36 | buffer = _buffer; 37 | myThread = new Thread(this, kThreadName); 38 | myThread.start(); 39 | } 40 | 41 | 42 | 43 | public void run() { 44 | synchronized(runLock){ 45 | running = true; 46 | } 47 | 48 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 49 | 50 | 51 | try { 52 | /** 53 | * NOTE: we want buffSize large so that we don't loose samples when the 54 | * StreamDecoder thread kicks in. But we want to read a small number of 55 | * samples at a time, so that StreamDecoder can process them and they get 56 | * freed from the buffer as soon as possible. 57 | * So there's a fine balance going on here between the two threads, and 58 | * if it's not tuned, samples will be lost. 59 | */ 60 | 61 | 62 | int buffSize = (int) Encoder.kSamplingFrequency 63 | * 2 // 2 seconds in the buffer 64 | * 2; // recording in 16 bit 65 | 66 | int biteSize = ((int) Encoder.kSamplingFrequency ) / 8; // move 1/8 sec of data at a time 67 | 68 | AudioRecord arec = new AudioRecord(MediaRecorder.AudioSource.MIC, 69 | (int) Encoder.kSamplingFrequency, //11025, 70 | AudioFormat.CHANNEL_CONFIGURATION_MONO, 71 | AudioFormat.ENCODING_PCM_16BIT, // must match PlayThread. Tried (and failed) to make ENCODING_PCM_8BIT work on Android 72 | buffSize); 73 | System.out.println(Thread.currentThread().getName() + " is recording"); 74 | //ByteArrayOutputStream out = new ByteArrayOutputStream(); 75 | byte[] data_16bit = new byte[ biteSize * 2]; 76 | byte[] data_8bit = new byte[ biteSize ]; 77 | 78 | int numBytesRead; 79 | arec.startRecording(); 80 | while(running) 81 | { 82 | numBytesRead = arec.read(data_16bit, 0, data_16bit.length); 83 | 84 | // convert the 16bit we have to read to to 8bit by discarding alternate bytes - PCM is little-endian, so we discard the first byte 85 | int i; 86 | int j; 87 | for( i = 1, j = 0; i < numBytesRead; i += 2, j ++) 88 | data_8bit[j] = data_16bit[i]; 89 | 90 | //System.out.println(Thread.currentThread().getName() + "> bytesRead = " + numBytesRead); 91 | 92 | buffer.write(data_8bit, 0, j); //writes into the buffer, which StreamDecoder is eagerly reading from 93 | //buffer.write(data, 0, numBytesRead); 94 | 95 | Thread.yield(); 96 | } 97 | 98 | arec.stop(); 99 | arec.release(); 100 | } catch (Exception e){ 101 | System.out.println(e.toString()); 102 | } 103 | } 104 | 105 | public void quit(){ 106 | synchronized(runLock){ 107 | running = false; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cricket/decode_cricket.m: -------------------------------------------------------------------------------- 1 | % Copyright 2001 by the authors. All rights reserved. 2 | % Authors: Cristina V Lopes (crista at tagide dot com) 3 | % Patricio de la Guardia 4 | % 5 | % Permission is hereby granted, free of charge, to any person obtaining a copy 6 | % of this software and associated documentation files (the "Software"), to deal 7 | % in the Software without restriction, including without limitation the rights 8 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | % copies of the Software, and to permit persons to whom the Software is 10 | % furnished to do so, subject to the following conditions: 11 | % 12 | % The above copyright notice and this permission notice shall be included in 13 | % all copies or substantial portions of the Software. 14 | % 15 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | % THE SOFTWARE. 22 | 23 | clear all 24 | close all 25 | [signal, fs, nbits] = wavread('signal.wav'); 26 | 27 | shortsize = 15; 28 | longsize = 24; 29 | Freq = 4184; 30 | framelength = 420; 31 | packetlength = shortsize; 32 | silencelength = longsize; 33 | silencepacketlength = shortsize + longsize; 34 | silencepacketsamples = round(fs*(shortsize + longsize)/1000); 35 | threepacketsamples = round(fs*3*silencepacketlength/1000); 36 | framesamples = round(fs*framelength/1000); 37 | 38 | output=[]; 39 | threshwidth = 9; 40 | threshamplitude = 0.75; 41 | 42 | %here we need to synchronize 43 | window = hanning(0.1*fs); 44 | time = 0:1/fs:(fs*0.1-1)/fs; 45 | hail = exp(sqrt(-1)*2*pi*time*Freq); 46 | hail = hail.*window'; 47 | %startindex = synchronize(signal(1:fs*5), hail'); 48 | startindex = finddelay(hail', signal); 49 | if startindex == 0 50 | startindex = 1; 51 | end 52 | maxvalue=max(abs(signal(startindex:startindex+length(hail)))); 53 | startindex=startindex+length(hail)-1; 54 | % normalize 55 | signal=signal/maxvalue; 56 | 57 | winner = 0; 58 | for i = startindex:framesamples:length(signal) 59 | eot=detect(signal(i:i+length(hail)),Freq,fs); 60 | if (eot > 0.15) 61 | break; 62 | end 63 | maxpower = 0; 64 | %First symbol 65 | for k = 1:4 66 | 67 | beggining = i + round(fs*(k-1)*(2*packetlength + 1.5*silencelength)/1000); 68 | if (beggining + threepacketsamples > length(signal)) 69 | signal = [signal; zeros(length(signal)-beggining + threepacketsamples+1, 1)]; 70 | end 71 | power = norm(signal(beggining:beggining + threepacketsamples)); 72 | %figure(2), plot(signal(beggining:beggining + threepacketsamples)),pause 73 | if power > maxpower 74 | maxpower = power; 75 | winner = k; 76 | end 77 | end 78 | %Second Symbol, we need the possition of the three packets 79 | begg = i + round(fs*(winner-1)*(2*packetlength + 1.5*silencelength)/1000); 80 | for jj = 1:3 81 | currframe = signal(begg + silencepacketsamples*(jj-1):begg + silencepacketsamples*jj); 82 | %figure(2), plot(currframe),pause 83 | amplitude(jj) = abs(max(currframe)); 84 | width(jj) = norm(currframe/amplitude(jj)); 85 | if width(jj) < threshwidth 86 | if amplitude(jj) < threshamplitude 87 | value(jj) = 2; 88 | else 89 | value(jj) = 1; 90 | end 91 | else %width(jj) >= threshwidth 92 | if amplitude(jj) < threshamplitude 93 | value(jj) = 4; 94 | else 95 | value(jj) = 3; 96 | end 97 | end 98 | end 99 | % plot(signal(begg :begg + silencepacketsamples*3)) 100 | % winner 101 | % value 102 | % pause; 103 | output = [output, winner, value]; 104 | end 105 | output 106 | text = huffdecode(output) 107 | -------------------------------------------------------------------------------- /ask-simple-java/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | * 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | */ 26 | 27 | import java.io.*; 28 | 29 | /** 30 | *

This class gives example uses of the digital voices code.

31 | * 38 | * 39 | * @author CVL 40 | */ 41 | public class Main { 42 | 43 | public static void main(String[] args){ 44 | //if there are no arguments or -h or -?, print usage and exit 45 | if(args.length < 1 46 | || "-h".equals(args[0]) 47 | || "-?".equals(args[0])){ 48 | printUsage(); 49 | System.exit(0); 50 | } 51 | 52 | //reads the microphone input in real time and decodes it to System.out 53 | if("-listen".equals(args[0])){ 54 | //the StreamDecoder uses the codec.Decoder to decode samples put in its AudioBuffer 55 | // StreamDecoder starts a thread 56 | StreamDecoder sDecoder = new StreamDecoder(System.out); 57 | 58 | //the MicrophoneListener feeds the microphone samples into the AudioBuffer 59 | // MicrophoneListener starts a thread 60 | MicrophoneListener listener = new MicrophoneListener(sDecoder.getAudioBuffer()); 61 | System.out.println("Listening"); 62 | 63 | // The main thread does nothing more, just waits for the others to die 64 | } 65 | else if("-decode".equals(args[0])) { 66 | 67 | //decodes the file named in args[1] and prints it to System.out 68 | 69 | //we must have a file name to decode 70 | if(args.length < 2){ 71 | printUsage(); 72 | System.exit(0); 73 | } 74 | 75 | try { 76 | File inputFile = new File(args[1]); 77 | if(inputFile.exists()){ 78 | //now decode the inputFile to System.out 79 | AudioUtils.decodeWavFile(inputFile, System.out); 80 | } else { 81 | System.out.println("Cannot find file " + args[1]); 82 | } 83 | } catch (javax.sound.sampled.UnsupportedAudioFileException e){ 84 | System.out.println("Error reading " + args[1] + ":" + e); 85 | } catch (IOException e){ 86 | System.out.println("IO Error reading " + args[1] + ":" + e); 87 | } 88 | } 89 | else if("-hardware".equals(args[0])) { 90 | //this is a little utility to show what hardware the JVM finds 91 | 92 | AudioUtils.displayMixerInfo(); 93 | System.exit(0); 94 | } 95 | 96 | else if("-record".equals(args[0])) { 97 | AudioUtils.recordToFile(new File(args[1]), 100); 98 | System.exit(0); 99 | } 100 | 101 | else { 102 | //Try to perform or encode the file named in args[0] 103 | String inputFile = args[0]; 104 | String outputFile = null; 105 | if(args.length > 1 && args[1].length() > 0){ 106 | //the existence of a second argument indicates that we should encode to args[1] 107 | outputFile = args[1]; 108 | } 109 | try { 110 | if(outputFile == null){ 111 | //try to play the file 112 | System.out.println("Performing " + args[0]); 113 | AudioUtils.performFile(new File(inputFile)); 114 | } else { 115 | //There was an output file specified, so we should write the wav 116 | System.out.println("Encoding " + args[0]); 117 | AudioUtils.encodeFileToWav(new File(inputFile), new File(outputFile)); 118 | } 119 | } catch (Exception e){ 120 | System.out.println("Could not encode " + inputFile + " because of " + e); 121 | } 122 | System.exit(0); 123 | } 124 | } 125 | 126 | /** 127 | * Prints the help text to System.out 128 | */ 129 | public static void printUsage(){ 130 | System.out.println("usage: dv.Main -hardware : displays audio hardware info"); 131 | System.out.println("usage: dv.Main -decode : decodes the wav file"); 132 | System.out.println("usage: dv.Main -listen : listens on the microphone for audio bits"); 133 | System.out.println("usage: dv.Main : plays the encoded file"); 134 | System.out.println("usage: dv.Main : encodes input.txt into output.wav"); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/StreamDecoder.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | * (Modified by Jonas Michel, 2012) 8 | */ 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.util.ArrayList; 13 | 14 | 15 | /** 16 | * This starts a Thread which decodes data in an AudioBuffer and writes it to an 17 | * OutputStream. StreamDecoder holds the buffer where the MicrophoneListener 18 | * puts bytes. 19 | * 20 | * @author CVL 21 | */ 22 | public class StreamDecoder implements Runnable { 23 | 24 | public static String kThreadName = "StreamDecoder"; 25 | 26 | private Thread myThread = null; 27 | private Object runLock = new Object(); 28 | private boolean running = false; 29 | 30 | private AudioBuffer buffer = new AudioBuffer(); // THE buffer where bytes 31 | // are being put 32 | private OutputStream out = null; 33 | boolean hasKey = false; 34 | 35 | private ArrayList huffsequence; 36 | 37 | /** 38 | * This creates and starts the decoding Thread 39 | * 40 | * @param _out the OutputStream which will receive the decoded data 41 | */ 42 | public StreamDecoder(OutputStream _out) { 43 | out = _out; 44 | huffsequence = new ArrayList(); 45 | myThread = new Thread(this, kThreadName); 46 | myThread.start(); 47 | } 48 | 49 | public String getStatusString() { 50 | String s = ""; 51 | 52 | int backlog = (int) ((1000 * buffer.size()) / Constants.kSamplingFrequency); 53 | 54 | if (backlog > 0) 55 | s += "Backlog: " + backlog + " mS "; 56 | 57 | if (hasKey) 58 | s += "Found key sequence "; 59 | 60 | return s; 61 | } 62 | 63 | public AudioBuffer getAudioBuffer() { 64 | return buffer; 65 | } 66 | 67 | public void run() { 68 | synchronized (runLock) { 69 | running = true; 70 | } 71 | 72 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 73 | 74 | //int durationsToRead = Constants.kDurationsPerKey; 75 | int deletedSamples = 0; 76 | 77 | double signalStrength_begin[] = new double[1]; 78 | double signalStrength_end[] = new double[1]; 79 | boolean notEnoughSamples = true; 80 | //byte samples[] = null; 81 | 82 | byte samples[] = null; 83 | 84 | hasKey = false; 85 | 86 | while (running) { 87 | notEnoughSamples = true; 88 | while (notEnoughSamples) { 89 | // grab enough samples to make a key 90 | samples = buffer.read(Constants.kSamplesPerFrame); 91 | 92 | if (samples != null) 93 | notEnoughSamples = false; 94 | else 95 | Thread.yield(); 96 | } 97 | 98 | /* START DECODING */ 99 | if (hasKey) { 100 | int eot = Decoder.findKeySequence(ArrayUtils.subarray(samples, 0, Constants.kSamplesPerDuration), signalStrength_end, Constants.kKeyDetectionGranularityFine); 101 | if (eot > -1) { 102 | try{ 103 | // delete these samples 104 | buffer.delete(eot + Constants.kSamplesPerDuration); 105 | deletedSamples += eot + Constants.kSamplesPerDuration; 106 | 107 | // decode the huffman sequence 108 | String decoded = Huffman.huffdecode(huffsequence) + "\n"; 109 | huffsequence.clear(); 110 | 111 | // display the text 112 | out.write(decoded.getBytes()); 113 | } catch (IOException e) { 114 | } 115 | 116 | // reset 117 | hasKey = false; 118 | continue; 119 | } 120 | 121 | // grab enough samples to make a frame 122 | samples = buffer.read(Constants.kSamplesPerFrame); 123 | huffsequence.addAll(Decoder.decodeFrame(signalStrength_begin[0], samples)); 124 | 125 | // delete these samples 126 | try { 127 | buffer.delete(Constants.kSamplesPerFrame); 128 | deletedSamples += Constants.kSamplesPerFrame; 129 | } catch (IOException e) { 130 | } 131 | 132 | try { 133 | // this provides the audio sampling mechanism a chance to maintain continuity 134 | Thread.sleep(10); 135 | } catch (InterruptedException e) { 136 | System.out.println("Stream Decoding thread interrupted:" + e); 137 | break; 138 | } 139 | continue; 140 | } 141 | /* END DECODING */ 142 | 143 | // we don't have the key, so we are in key detection mode from this point on 144 | 145 | // System.out.println("Search Start: " + deletedSamples + " End: " + 146 | // (deletedSamples + samples.length)); 147 | // System.out.println("Search Time: " + ((float)deletedSamples / 148 | // Constants.kSamplingFrequency) + " End: " 149 | // + ((float)(deletedSamples + samples.length) / 150 | // Constants.kSamplingFrequency)); 151 | int startIndex = Decoder.findKeySequence(samples, signalStrength_begin, Constants.kKeyDetectionGranularityCoarse); 152 | if (startIndex > -1) { 153 | // found key using coarse detection 154 | try { 155 | buffer.delete(startIndex); 156 | } catch (IOException e) { 157 | } 158 | deletedSamples += startIndex; 159 | 160 | // now find key using fine detection 161 | notEnoughSamples = true; 162 | while (notEnoughSamples) { 163 | samples= buffer.read(Constants.kSamplesPerFrame); 164 | if (samples != null) 165 | notEnoughSamples = false; 166 | else 167 | Thread.yield(); 168 | } 169 | startIndex = Decoder.findKeySequence(samples, signalStrength_begin, Constants.kKeyDetectionGranularityFine); 170 | 171 | // found key using fine detection 172 | try { 173 | buffer.delete(startIndex + Constants.kSamplesPerDuration); 174 | } catch (IOException e) { 175 | } 176 | deletedSamples += startIndex; 177 | 178 | hasKey = true; 179 | } else { 180 | try { 181 | buffer.delete(Constants.kSamplesPerDuration); 182 | deletedSamples += (Constants.kSamplesPerDuration); 183 | } catch (IOException e) { 184 | } 185 | } 186 | } 187 | } 188 | 189 | public void quit() { 190 | synchronized (runLock) { 191 | running = false; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /cricket/huffdecode.m: -------------------------------------------------------------------------------- 1 | % Copyright 2001 by the authors. All rights reserved. 2 | % Authors: Cristina V Lopes (crista at tagide dot com) 3 | % Patricio de la Guardia 4 | % 5 | % Permission is hereby granted, free of charge, to any person obtaining a copy 6 | % of this software and associated documentation files (the "Software"), to deal 7 | % in the Software without restriction, including without limitation the rights 8 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | % copies of the Software, and to permit persons to whom the Software is 10 | % furnished to do so, subject to the following conditions: 11 | % 12 | % The above copyright notice and this permission notice shall be included in 13 | % all copies or substantial portions of the Software. 14 | % 15 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | % THE SOFTWARE. 22 | 23 | function output = huffdecode(input) 24 | output = []; 25 | %for i = 1 : length(input) 26 | i = 1; 27 | while i <= length(input) - 3, 28 | switch input(i) 29 | case 4 30 | output = [output, ' ']; 31 | %disp(' '); 32 | i = i + 1; 33 | 34 | case 3 35 | switch input(i+1) 36 | case 1 37 | output = [output, 'N']; 38 | %disp('N'); 39 | case 2 40 | output = [output, 'S']; 41 | %disp('S'); 42 | case 3 43 | output = [output, 'R']; 44 | %disp('R'); 45 | case 4 46 | output = [output, 'H']; 47 | %disp('H'); 48 | otherwise 49 | disp('error inside case 3') 50 | break 51 | end 52 | i = i + 2; 53 | 54 | case 2 55 | 56 | switch input(i+1) 57 | case 1 58 | output = [output, 'O']; 59 | %disp('O'); 60 | i = i + 2; 61 | case 2 62 | output = [output, 'I']; 63 | %disp('I'); 64 | i = i + 2; 65 | case 3 66 | 67 | switch input(i+2) 68 | case 1 69 | output = [output, 'P']; 70 | %disp('P'); 71 | case 2 72 | output = [output, 'G']; 73 | %disp('G'); 74 | case 3 75 | output = [output, 'W']; 76 | %disp('W'); 77 | case 4 78 | output = [output, '.']; 79 | %disp('.'); 80 | otherwise 81 | disp('error inside case 23') 82 | break 83 | end 84 | i = i + 3; 85 | case 4 86 | switch input(i+2) 87 | case 1 88 | output = [output, 'M']; 89 | %disp('M'); 90 | i = i + 3; 91 | 92 | case 2 93 | switch input(i+3) 94 | case 1 95 | output = [output, 'X']; 96 | %disp('X'); 97 | case 2 98 | output = [output, 'J']; 99 | %disp('J'); 100 | case 3 101 | output = [output, 'Q']; 102 | %disp('Q'); 103 | case 4 104 | output = [output, 'Z']; 105 | %disp('Z'); 106 | otherwise 107 | disp('error inside case 242') 108 | break 109 | end 110 | i = i + 4; 111 | case 3 112 | output = [output, 'F']; 113 | %disp('F'); 114 | i = i + 3; 115 | 116 | case 4 117 | switch input(i+3) 118 | case 1 119 | output = [output, 'Y']; 120 | %disp('Y'); 121 | case 2 122 | output = [output, 'B']; 123 | %disp('B'); 124 | case 3 125 | output = [output, 'V']; 126 | %disp('V'); 127 | case 4 128 | output = [output, 'K']; 129 | %disp('K'); 130 | otherwise 131 | disp('error inside case 244') 132 | break 133 | end 134 | i = i + 4; 135 | otherwise 136 | disp('error inside case 24') 137 | break 138 | end 139 | otherwise 140 | disp('error inside case 2') 141 | break 142 | end 143 | 144 | 145 | case 1 146 | switch input(i+1) 147 | case 1 148 | switch input(i+2) 149 | case 1 150 | output = [output, 'L']; 151 | %disp('L'); 152 | case 2 153 | output = [output, 'D']; 154 | %disp('D'); 155 | 156 | case 3 157 | output = [output, 'C']; 158 | %disp('C'); 159 | case 4 160 | output = [output, 'U']; 161 | %disp('U'); 162 | otherwise 163 | disp('error inside case 11') 164 | break 165 | end 166 | i = i + 3; 167 | 168 | case 2 169 | output = [output, 'E']; 170 | %disp('E'); 171 | i = i + 2; 172 | 173 | case 3 174 | output = [output, 'T']; 175 | %disp('T'); 176 | i = i + 2; 177 | 178 | case 4 179 | output = [output, 'A'];; 180 | %disp('A'); 181 | i = i + 2; 182 | 183 | otherwise 184 | disp('error inside case 1') 185 | break 186 | end 187 | 188 | 189 | otherwise 190 | 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/AudioUtils.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | /** 4 | * Copyright 2002 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | 10 | 11 | import java.io.*; 12 | import java.util.Date; 13 | 14 | 15 | /** 16 | * 17 | * @author CVL 18 | */ 19 | 20 | public class AudioUtils { 21 | 22 | /* 23 | //the default format for reading and writing audio information 24 | public static AudioFormat kDefaultFormat = new AudioFormat((float) Encoder.kSamplingFrequency, 25 | (int) 8, (int) 1, true, false); 26 | */ 27 | 28 | /* 29 | public static void decodeWavFile(File inputFile, OutputStream out) 30 | throws UnsupportedAudioFileException, 31 | IOException { 32 | StreamDecoder sDecoder = new StreamDecoder(out); 33 | AudioBuffer aBuffer = sDecoder.getAudioBuffer(); 34 | 35 | AudioInputStream audioInputStream = 36 | AudioSystem.getAudioInputStream(kDefaultFormat, 37 | AudioSystem.getAudioInputStream(inputFile)); 38 | int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); 39 | // Set an arbitrary buffer size of 1024 frames. 40 | int numBytes = 1024 * bytesPerFrame; 41 | byte[] audioBytes = new byte[numBytes]; 42 | int numBytesRead = 0; 43 | // Try to read numBytes bytes from the file and write it to the buffer 44 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 45 | while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { 46 | 47 | aBuffer.write(audioBytes, 0, numBytesRead); 48 | } 49 | } 50 | */ 51 | 52 | /* 53 | public static void writeWav(File file, byte[] data, AudioFormat format) 54 | throws IllegalArgumentException, 55 | IOException { 56 | ByteArrayInputStream bais = new ByteArrayInputStream(data); 57 | AudioInputStream ais = new AudioInputStream(bais, format, data.length); 58 | AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file); 59 | } 60 | */ 61 | 62 | /* 63 | public static void displayMixerInfo(){ 64 | Mixer.Info[] mInfos = AudioSystem.getMixerInfo(); 65 | if(mInfos == null){ 66 | System.out.println("No Mixers found"); 67 | return; 68 | } 69 | 70 | for(int i=0; i < mInfos.length; i++){ 71 | System.out.println("Mixer Info: " + mInfos[i]); 72 | Mixer mixer = AudioSystem.getMixer(mInfos[i]); 73 | Line.Info[] lines = mixer.getSourceLineInfo(); 74 | for(int j = 0; j < lines.length; j++){ 75 | System.out.println("\tSource: " + lines[j]); 76 | } 77 | lines = mixer.getTargetLineInfo(); 78 | for(int j = 0; j < lines.length; j++){ 79 | System.out.println("\tTarget: " + lines[j]); 80 | } 81 | } 82 | } 83 | */ 84 | 85 | /* 86 | public static void displayAudioFileTypes(){ 87 | AudioFileFormat.Type[] types = AudioSystem.getAudioFileTypes(); 88 | for(int i=0; i < types.length; i++){ 89 | System.out.println("Audio File Type:" + types[i].toString()); 90 | } 91 | } 92 | */ 93 | 94 | /* 95 | //This never returns, which is kind of lame. 96 | // NOT USED!! - replaced by MicrophoneListener.run() 97 | public static void listenToMicrophone(AudioBuffer buff){ 98 | try { 99 | int buffSize = 4096; 100 | TargetDataLine line = getTargetDataLine(kDefaultFormat); 101 | line.open(kDefaultFormat, buffSize); 102 | 103 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 104 | int numBytesRead; 105 | byte[] data = new byte[line.getBufferSize() / 5]; 106 | line.start(); 107 | while(true){ 108 | numBytesRead = line.read(data, 0, data.length); 109 | buff.write(data, 0, numBytesRead); 110 | } 111 | 112 | } catch (Exception e){ 113 | System.out.println(e.toString()); 114 | } 115 | } 116 | */ 117 | 118 | /* 119 | public static void recordToFile(File file, int length){ 120 | try { 121 | int buffSize = 4096; 122 | TargetDataLine line = getTargetDataLine(kDefaultFormat); 123 | line.open(kDefaultFormat, buffSize); 124 | 125 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 126 | int numBytesRead; 127 | byte[] data = new byte[line.getBufferSize() / 5]; 128 | line.start(); 129 | for(int i=0; i < length; i++) { 130 | numBytesRead = line.read(data, 0, data.length); 131 | out.write(data, 0, numBytesRead); 132 | } 133 | line.drain(); 134 | line.stop(); 135 | line.close(); 136 | 137 | writeWav(file, out.toByteArray(), kDefaultFormat); 138 | 139 | } catch (Exception e){ 140 | System.out.println(e.toString()); 141 | } 142 | } 143 | */ 144 | 145 | /* 146 | public static TargetDataLine getTargetDataLine(AudioFormat format) 147 | throws LineUnavailableException { 148 | DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); 149 | if (!AudioSystem.isLineSupported(info)) { 150 | throw new LineUnavailableException(); 151 | } 152 | return (TargetDataLine) AudioSystem.getLine(info); 153 | } 154 | 155 | public static SourceDataLine getSourceDataLine(AudioFormat format) 156 | throws LineUnavailableException { 157 | DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 158 | if (!AudioSystem.isLineSupported(info)) { 159 | throw new LineUnavailableException(); 160 | } 161 | return (SourceDataLine) AudioSystem.getLine(info); 162 | } 163 | */ 164 | 165 | 166 | public static void encodeFileToWav(File inputFile, File outputFile) 167 | throws IOException { 168 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 169 | Encoder.encodeStream(new FileInputStream(inputFile), baos); 170 | // patched out for Android 171 | // writeWav(outputFile, baos.toByteArray()); 172 | } 173 | 174 | 175 | 176 | 177 | public static void performData(byte[] data) 178 | throws IOException { 179 | 180 | PlayThread p = new PlayThread( data ); 181 | 182 | 183 | } 184 | 185 | public static void performFile(File file) 186 | throws IOException { 187 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 188 | Encoder.encodeStream(new FileInputStream(file), baos); 189 | performData(baos.toByteArray()); 190 | } 191 | 192 | public static void performArray(byte[] array) 193 | throws IOException { 194 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 195 | Encoder.encodeStream(new ByteArrayInputStream(array), baos); 196 | performData(baos.toByteArray()); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Huffman.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /** 4 | * Copyright 2012 by the authors. All rights reserved. 5 | * 6 | * Author: Jonas Michel 7 | */ 8 | 9 | import java.util.ArrayList; 10 | 11 | public class Huffman implements Constants { 12 | 13 | /** 14 | * @param input string consisting of {'a'-'z',' ','.'} 15 | * @return list of huffman-encoded integers 16 | */ 17 | public static ArrayList huffencode(String input) { 18 | ArrayList output = new ArrayList(); 19 | for (int i = 0; i < input.length(); i++) { 20 | switch (input.charAt(i)) { 21 | case ' ': output.add(4); 22 | break; 23 | case 'a': output.add(1); output.add(4); 24 | break; 25 | case 'b': output.add(2); output.add(4); output.add(4); output.add(2); 26 | break; 27 | case 'c': output.add(1); output.add(1); output.add(3); 28 | break; 29 | case 'd': output.add(1); output.add(1); output.add(2); 30 | break; 31 | case 'e': output.add(1); output.add(2); 32 | break; 33 | case 'f': output.add(2); output.add(4); output.add(3); 34 | break; 35 | case 'g': output.add(2); output.add(3); output.add(2); 36 | break; 37 | case 'h': output.add(3); output.add(4); 38 | break; 39 | case 'i': output.add(2); output.add(2); 40 | break; 41 | case 'j': output.add(2); output.add(4); output.add(2); output.add(2); 42 | break; 43 | case 'k': output.add(2); output.add(4); output.add(4); output.add(4); 44 | break; 45 | case 'l': output.add(1); output.add(1); output.add(1); 46 | break; 47 | case 'm': output.add(2); output.add(4); output.add(1); 48 | break; 49 | case 'n': output.add(3); output.add(1); 50 | break; 51 | case 'o': output.add(2); output.add(1); 52 | break; 53 | case 'p': output.add(2); output.add(3); output.add(1); 54 | break; 55 | case 'q': output.add(2); output.add(4); output.add(2); output.add(3); 56 | break; 57 | case 'r': output.add(3); output.add(3); 58 | break; 59 | case 's': output.add(3); output.add(2); 60 | break; 61 | case 't': output.add(1); output.add(3); 62 | break; 63 | case 'u': output.add(1); output.add(1); output.add(4); 64 | break; 65 | case 'v': output.add(2); output.add(4); output.add(4); output.add(3); 66 | break; 67 | case 'w': output.add(2); output.add(3); output.add(3); 68 | break; 69 | case 'x': output.add(2); output.add(4); output.add(2); output.add(1); 70 | break; 71 | case 'y': output.add(2); output.add(4); output.add(4); output.add(1); 72 | break; 73 | case 'z': output.add(2); output.add(4); output.add(2); output.add(4); 74 | break; 75 | case '.': output.add(2); output.add(3); output.add(4); 76 | break; 77 | default: System.out.println("ERROR: Character not recognized!!"); 78 | break; 79 | 80 | } 81 | } 82 | return output; 83 | } 84 | 85 | /** 86 | * @param input list of huffman-encoded integers 87 | * @return decoded string 88 | */ 89 | public static String huffdecode(ArrayList input) { 90 | StringBuilder output = new StringBuilder(); 91 | int i = 0; 92 | while (i < input.size() - 3) { 93 | switch (input.get(i)) { 94 | case 4: output.append(' '); 95 | i += 1; 96 | break; 97 | case 3: 98 | switch (input.get(i+1)) { 99 | case 1: output.append('n'); 100 | break; 101 | case 2: output.append('s'); 102 | break; 103 | case 3: output.append('r'); 104 | break; 105 | case 4: output.append('h'); 106 | break; 107 | default: System.out.println("ERROR inside case 3"); 108 | break; 109 | } 110 | i += 2; 111 | break; 112 | case 2: 113 | switch (input.get(i+1)) { 114 | case 1: output.append('o'); 115 | i += 2; 116 | break; 117 | case 2: output.append('i'); 118 | i += 2; 119 | break; 120 | case 3: 121 | switch (input.get(i+2)) { 122 | case 1: output.append('p'); 123 | break; 124 | case 2: output.append('g'); 125 | break; 126 | case 3: output.append('w'); 127 | break; 128 | case 4: output.append('.'); 129 | break; 130 | default: System.out.println("ERROR inside case 2,3"); 131 | break; 132 | } 133 | i += 3; 134 | break; 135 | case 4: 136 | switch (input.get(i+2)) { 137 | case 1: output.append('m'); 138 | i += 3; 139 | break; 140 | case 2: 141 | switch (input.get(i+3)) { 142 | case 1: output.append('x'); 143 | break; 144 | case 2: output.append('j'); 145 | break; 146 | case 3: output.append('q'); 147 | break; 148 | case 4: output.append('z'); 149 | break; 150 | default: System.out.println("ERROR inside case 2,4,2"); 151 | break; 152 | } 153 | i += 4; 154 | break; 155 | case 3: output.append('f'); 156 | i += 3; 157 | break; 158 | case 4: 159 | switch (input.get(i+3)) { 160 | case 1: output.append('y'); 161 | break; 162 | case 2: output.append('b'); 163 | break; 164 | case 3: output.append('v'); 165 | break; 166 | case 4: output.append('k'); 167 | break; 168 | default: System.out.println("ERROR inside case 2,4,4"); 169 | break; 170 | } 171 | i += 4; 172 | break; 173 | default: System.out.println("ERROR inside case 2,4"); 174 | break; 175 | } 176 | break; 177 | default: System.out.println("ERROR inside case 2"); 178 | break; 179 | } 180 | break; 181 | 182 | case 1: 183 | switch (input.get(i+1)) { 184 | case 1: 185 | switch (input.get(i+2)) { 186 | case 1: output.append('l'); 187 | break; 188 | case 2: output.append('d'); 189 | break; 190 | case 3: output.append('c'); 191 | break; 192 | case 4: output.append('u'); 193 | break; 194 | default: System.out.println("ERROR inside case 1,1"); 195 | break; 196 | } 197 | i += 3; 198 | break; 199 | case 2: output.append('e'); 200 | i += 2; 201 | break; 202 | case 3: output.append('t'); 203 | i += 2; 204 | break; 205 | case 4: output.append('a'); 206 | i += 2; 207 | break; 208 | default: System.out.println("ERROR inside case 1"); 209 | break; 210 | } 211 | break; 212 | default: System.out.println("ERROR: Number not recognized!!"); 213 | break; 214 | } 215 | } 216 | return output.toString(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/Encoder.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | 4 | /** 5 | * Copyright 2002 by the authors. All rights reserved. 6 | * 7 | * Author: Cristina V Lopes 8 | */ 9 | 10 | 11 | import java.io.*; 12 | 13 | /** 14 | * Encoder. 15 | * @author CVL 16 | */ 17 | public class Encoder implements Constants { 18 | 19 | /** 20 | * encodeStream is the public function of class Encoder. 21 | * @param input the stream of bytes to encode 22 | * @param output the stream of audio samples representing the input, 23 | * prefixed with an hail signal and a calibration signal 24 | */ 25 | public static void encodeStream(InputStream input, OutputStream output) 26 | throws IOException { 27 | 28 | System.out.println("encodeStream starts"); 29 | 30 | byte[] zeros = new byte[kSamplesPerDuration]; 31 | 32 | //write out the hail and calibration sequences 33 | output.write(zeros); 34 | output.write(getHailSequence()); 35 | output.write(getCalibrationSequence()); 36 | 37 | //now write the data 38 | int read = 0; 39 | byte[] buff = new byte[kBytesPerDuration]; 40 | while( (read = input.read(buff)) == kBytesPerDuration){ 41 | output.write(Encoder.encodeDuration(buff)); 42 | } 43 | if(read > 0){ 44 | for(int i=read; i < kBytesPerDuration; i++){ 45 | buff[i] = 0; 46 | } 47 | output.write(Encoder.encodeDuration(buff)); 48 | } 49 | 50 | System.out.println("encodeStream ends"); 51 | 52 | } 53 | 54 | /** 55 | * @param bitPosition the position in the kBytesPerDuration wide byte array for which you want a frequency 56 | * @return the frequency in which to sound to indicate a 1 for this bitPosition 57 | * NOTE!: This blindly assumes that bitPosition is in the range [0 - 7] 58 | */ 59 | 60 | public static int getFrequency(int bitPosition){ 61 | return Constants.kFrequencies[bitPosition]; 62 | } 63 | /* 64 | public static double getFrequency(int bitPosition){ 65 | return Constants.kFrequencies[bitPosition]; 66 | } 67 | */ 68 | 69 | /** 70 | * @param input a kBytesPerDuration long array of bytes to encode 71 | * @return an array of audio samples of type AudioUtil.kDefaultFormat 72 | */ 73 | private static byte[] encodeDuration(byte[] input){ 74 | double[] signal = new double[kSamplesPerDuration]; 75 | for(int j = 0; j < kBytesPerDuration; j++){ 76 | for(int k = 0; k < kBitsPerByte; k++){ 77 | if( ((input[j] >> k) & 0x1) == 0 ){ 78 | //no need to go through encoding a zero 79 | continue; 80 | } 81 | 82 | //add a sinusoid of getFrequency(j), amplitude kAmplitude and duration kDuration 83 | double innerMultiplier = getFrequency((j * kBitsPerByte) + k) 84 | * (1 / kSamplingFrequency) * 2 * Math.PI; 85 | for(int l = 0; l < signal.length; l++){ 86 | signal[l] = signal[l] + (kAmplitude * Math.cos(innerMultiplier * l)); 87 | } 88 | } 89 | } 90 | 91 | return ArrayUtils.getByteArrayFromDoubleArray(smoothWindow(signal)); 92 | } 93 | 94 | /** 95 | * @return audio samples for a duration of the hail frequency, Constants.kHailFrequency 96 | */ 97 | private static byte[] getHailSequence(){ 98 | double[] signal = new double[kSamplesPerDuration]; 99 | //add a sinusoid of the hail frequency, amplitude kAmplitude and duration kDuration 100 | double innerMultiplier = Constants.kHailFrequency * (1 / kSamplingFrequency) * 2 * Math.PI; 101 | for(int l = 0; l < signal.length; l++){ 102 | signal[l] = /*kAmplitude **/ Math.cos(innerMultiplier * l); 103 | } 104 | return ArrayUtils.getByteArrayFromDoubleArray(smoothWindow(signal, 0.3)); 105 | } 106 | 107 | /** 108 | * @return audio samples (of length 2 * kSamplesPerDuration), used to calibrate the decoding 109 | */ 110 | private static byte[] getCalibrationSequence(){ 111 | byte[] results = new byte[2 * kSamplesPerDuration]; 112 | byte[] inputBytes1 = new byte[kBytesPerDuration]; 113 | byte[] inputBytes2 = new byte[kBytesPerDuration]; 114 | for(int i=0; i < kBytesPerDuration; i++){ 115 | inputBytes1[i] = (byte)0xAA; 116 | inputBytes2[i] = (byte)0x55; 117 | } 118 | 119 | //encode inputBytes1 and 2 in sequence 120 | byte[] partialResult = encodeDuration(inputBytes1 ); 121 | for(int k = 0; k < kSamplesPerDuration; k++){ 122 | results[k] = partialResult[k]; 123 | } 124 | partialResult = encodeDuration(inputBytes2); 125 | for(int k = 0; k < kSamplesPerDuration; k++){ 126 | results[k + kSamplesPerDuration] = partialResult[k]; 127 | } 128 | 129 | return results; 130 | } 131 | 132 | /** About smoothwindow. 133 | * This is a data set in with the following form: 134 | * 135 | * | 136 | * 1 | +-------------------+ 137 | * | / \ 138 | * |/ \ 139 | * +--|-------------------|--+--- 140 | * 0.01 0.09 0.1 time 141 | * 142 | * It is used to smooth the edges of the signal in each duration 143 | * 144 | */ 145 | private static double[] smoothWindow(double[] input, double magicScalingNumber){ 146 | double[] smoothWindow = new double[input.length]; 147 | double minVal = 0; 148 | double maxVal = 0; 149 | int peaks = (int)(input.length * 0.1); 150 | double steppingValue = 1 / (double)peaks; 151 | for (int i=0; i input.length - peaks){ 155 | smoothWindow[i] = input[i] * (steppingValue * (input.length - i - 1)) /* / magicScalingNumber */; 156 | } else { 157 | //don't touch the middle values 158 | smoothWindow[i] = input[i] /* / magicScalingNumber */; 159 | } 160 | if (smoothWindow[i] < minVal){ 161 | minVal = smoothWindow[i]; 162 | } 163 | if(smoothWindow[i] > maxVal){ 164 | maxVal = smoothWindow[i]; 165 | } 166 | } 167 | return smoothWindow; 168 | } 169 | 170 | private static double[] smoothWindow(double[] input){ 171 | double magicScalingNumber = 0.8; 172 | return smoothWindow(input, magicScalingNumber); 173 | } 174 | 175 | /** 176 | * This isn't used at the moment, but it does sound nice 177 | */ 178 | private static double[] blackmanSmoothWindow(double[] input){ 179 | double magicScalingNumber = 3.5; 180 | double[] smoothWindow = new double[input.length]; 181 | double steppingValue = 2*Math.PI/(input.length -1); 182 | double maxVal = 0; 183 | double minVal = 0; 184 | for(int i=0; i maxVal){ 191 | maxVal = smoothWindow[i]; 192 | } 193 | } 194 | return smoothWindow; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/StreamDecoder.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | /** 4 | * Copyright 2002 by the authors. All rights reserved. 5 | * 6 | * Author: Cristina V Lopes 7 | */ 8 | 9 | 10 | import java.io.*; 11 | 12 | /** 13 | * This starts a Thread which decodes data in an AudioBuffer and writes it to an OutputStream. 14 | * StreamDecoder holds the buffer where the MicrophoneListener puts bytes. 15 | * 16 | * @author CVL 17 | */ 18 | public class StreamDecoder implements Runnable { 19 | 20 | public static String kThreadName = "StreamDecoder"; 21 | 22 | private Thread myThread = null; 23 | private Object runLock = new Object(); 24 | private boolean running = false; 25 | 26 | private AudioBuffer buffer = new AudioBuffer(); // THE buffer where bytes are being put 27 | private OutputStream out = null; 28 | boolean hasKey = false; 29 | 30 | 31 | /** 32 | * This creates and starts the decoding Thread 33 | * @param _out the OutputStream which will receive the decoded data 34 | */ 35 | public StreamDecoder(OutputStream _out) { 36 | out = _out; 37 | myThread = new Thread(this, kThreadName); 38 | myThread.start(); 39 | } 40 | 41 | public String getStatusString() 42 | { 43 | String s = ""; 44 | 45 | int backlog = (int) ((1000 * buffer.size()) / Constants.kSamplingFrequency); 46 | 47 | if( backlog > 0 ) 48 | s += "Backlog: " + backlog + " mS "; 49 | 50 | if( hasKey ) 51 | s += "Found key sequence "; 52 | 53 | return s; 54 | } 55 | 56 | public AudioBuffer getAudioBuffer(){ 57 | return buffer; 58 | } 59 | 60 | public void run() { 61 | synchronized(runLock){ 62 | running = true; 63 | } 64 | 65 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 66 | 67 | 68 | int durationsToRead = Constants.kDurationsPerKey; 69 | int deletedSamples = 0; 70 | 71 | boolean hasEOF = false; 72 | double[] startSignals = new double[Constants.kBitsPerByte * Constants.kBytesPerDuration]; 73 | boolean notEnoughSamples = true; 74 | byte samples[] = null; 75 | 76 | hasKey = false; 77 | 78 | while(running) 79 | { 80 | notEnoughSamples = true; 81 | while (notEnoughSamples) 82 | { 83 | samples = buffer.read(Constants.kSamplesPerDuration * durationsToRead); 84 | if (samples != null) 85 | notEnoughSamples = false; 86 | else 87 | Thread.yield(); 88 | } 89 | 90 | if(hasKey) 91 | { //we found the key, so decode this duration 92 | byte[] decoded = Decoder.decode(startSignals, samples); 93 | try { 94 | buffer.delete(samples.length); 95 | deletedSamples += samples.length; 96 | out.write(decoded); 97 | 98 | System.out.println("decoded " + decoded.length + " bytes"); 99 | 100 | if(decoded[0] == 0){ //we are recieving no signal, so go back to key detection mode 101 | //out.write("EOF\r\n".getBytes()); //this is for debugging 102 | hasKey = false; 103 | durationsToRead = Constants.kDurationsPerKey; 104 | } 105 | } catch (IOException e){ 106 | System.out.println("Exception while decoding:" + e); 107 | break; 108 | } 109 | 110 | try{ 111 | //this provides the audio sampling mechanism a chance to maintain continuity 112 | Thread.sleep(10); 113 | } catch(InterruptedException e){ 114 | System.out.println("Stream Decoding thread interrupted:" + e); 115 | break; 116 | } 117 | continue; 118 | } 119 | 120 | //we don't have the key, so we are in key detection mode from this point on 121 | int initialGranularity = 400; 122 | int finalGranularity = 20; 123 | //System.out.println("Search Start: " + deletedSamples + " End: " + (deletedSamples + samples.length)); 124 | //System.out.println("Search Time: " + ((float)deletedSamples / Constants.kSamplingFrequency) + " End: " 125 | // + ((float)(deletedSamples + samples.length) / Constants.kSamplingFrequency)); 126 | int startIndex = Decoder.findKeySequence(samples, startSignals, initialGranularity); 127 | if(startIndex > -1) 128 | { 129 | System.out.println("\nRough Start Index: " + (deletedSamples + startIndex)); 130 | //System.out.println("Rough Start Time: " 131 | // + (deletedSamples + startIndex) / (float)Constants.kSamplingFrequency); 132 | 133 | int shiftAmount = startIndex /* - (Constants.kSamplesPerDuration)*/; 134 | if(shiftAmount < 0){ 135 | shiftAmount = 0; 136 | } 137 | System.out.println("Shift amount: " + shiftAmount); 138 | try { buffer.delete(shiftAmount);} catch (IOException e){} 139 | deletedSamples += shiftAmount; 140 | 141 | durationsToRead = Constants.kDurationsPerKey ; 142 | notEnoughSamples = true; 143 | while (notEnoughSamples) { 144 | samples = buffer.read(Constants.kSamplesPerDuration * durationsToRead); 145 | if (samples != null) 146 | notEnoughSamples = false; 147 | else Thread.yield(); 148 | } 149 | 150 | //System.out.println("Search Start: " + deletedSamples + " End: " + (deletedSamples + samples.length)); 151 | //System.out.println("Search Time: " + ((float)deletedSamples / Constants.kSamplesPerDuration) + " End: " 152 | // + ((float)(deletedSamples + samples.length) / Constants.kSamplingFrequency)); 153 | 154 | startIndex = Decoder.findKeySequence(samples, startSignals, finalGranularity); 155 | System.out.println("Refined Start Index: " + (deletedSamples + startIndex)); 156 | //System.out.println("Start Time: " + 157 | // (deletedSamples + startIndex) / (float)Constants.kSamplingFrequency); 158 | try { 159 | notEnoughSamples = true; 160 | while (notEnoughSamples) { 161 | samples = buffer.read(startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey)); 162 | if (samples != null) 163 | notEnoughSamples = false; 164 | else Thread.yield(); 165 | } 166 | 167 | samples = ArrayUtils.subarray(samples, startIndex + Constants.kSamplesPerDuration, 168 | 2 * Constants.kSamplesPerDuration); 169 | Decoder.getKeySignalStrengths(samples, startSignals); 170 | /* 171 | System.out.println(" f(0): " + startSignals[0] + " f(1): " + startSignals[1] + 172 | " f(2): " + startSignals[2] + " f(3): " + startSignals[3] + 173 | " f(4): " + startSignals[4] + " f(5): " + startSignals[5] + 174 | " f(6): " + startSignals[6] + " f(7): " + startSignals[7]); 175 | */ 176 | 177 | buffer.delete(startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey)); 178 | deletedSamples += startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey); 179 | } catch (IOException e){} 180 | hasKey = true; 181 | 182 | System.out.println(">>>>>>>>>>>>>>>>>>>>> found key <<<<<<<<<<<<<<<<<<<<<"); 183 | 184 | 185 | durationsToRead = 1; 186 | } else { 187 | try { 188 | buffer.delete(Constants.kSamplesPerDuration); 189 | deletedSamples += Constants.kSamplesPerDuration; 190 | } catch (IOException e){} 191 | } 192 | } 193 | } 194 | 195 | public void quit(){ 196 | synchronized(runLock){ 197 | running = false; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Decoder.java: -------------------------------------------------------------------------------- 1 | 2 | package com.jonas.CricketAndroid; 3 | 4 | /** 5 | * Copyright 2012 by the authors. All rights reserved. 6 | * 7 | * Author: Jonas Michel 8 | */ 9 | 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * This class contains the signal processing functions. 15 | * 16 | * @author jrm 17 | */ 18 | public class Decoder implements Constants { 19 | 20 | /** 21 | * @param signal the audio samples to search 22 | * @param signalStrengths this will be filled in with the strengths for each frequency (NOTE THIS SIDE EFFECT) 23 | * @param granularity a correlation will be determined every granularity samples (lower is slower) 24 | * @return the index in signal of the key sequence, or -1 if it wasn't found (in which case signalStrengths is trashed) 25 | */ 26 | public static int findKeySequence(byte[] signal, double[] signalStrength, int granularity){ 27 | int maxCorrelationIndex = -1; 28 | double maxCorrelation = -1; 29 | double minSignal = 0.003; 30 | //double acceptedSignal = 0.01; 31 | int i=0; 32 | for(i = 0; i <= signal.length - kSamplesPerDuration; i += granularity){ 33 | //test the correlation 34 | byte[] partialSignal = ArrayUtils.subarray(signal, i, kSamplesPerDuration); 35 | double corr = complexDetect(partialSignal, Constants.kHailFrequency) /* * 4 */; 36 | // System.out.println("Correlation at " + i + ":" + corr); 37 | if (corr > maxCorrelation){ 38 | maxCorrelation = corr; 39 | maxCorrelationIndex = i; 40 | } 41 | if(granularity <= 0){ 42 | break; 43 | } 44 | } 45 | 46 | //System.out.println("Searched to index:" + i); 47 | if (maxCorrelation < Constants.kKeyDetectionThreshold && maxCorrelation > -1){ 48 | //System.out.println("Best Correlation:" + maxCorrelation); 49 | maxCorrelationIndex = -1; 50 | } 51 | 52 | if (maxCorrelation >= Constants.kKeyDetectionThreshold) 53 | System.out.println("gotkey"); 54 | //if(maxCorrelationIndex >= 0){ 55 | //System.out.println("\r\nMax Correlation:" + maxCorrelation + " index:" + maxCorrelationIndex); 56 | //System.out.println("signal.length:" + signal.length); 57 | //getKeySignalStrengths(ArrayUtils.subarray(signal, maxCorrelationIndex + kSamplesPerDuration, 58 | // kSamplesPerDuration * 2), 59 | // signalStrengths); 60 | //} 61 | 62 | signalStrength[0] = maxCorrelation; 63 | 64 | return maxCorrelationIndex; 65 | } 66 | 67 | /** 68 | * 69 | * @param signalStrength the signal strength of the chirp frequency 70 | * @param samples the audio samples to be decoded 71 | * @return array list of decoded integers 72 | */ 73 | public static ArrayList decodeFrame(double signalStrength, byte[] samples) { 74 | ArrayList output = new ArrayList(); 75 | int winner = 0; 76 | double maxpower = 0; 77 | double signal[] = ArrayUtils.getDoubleArrayfromByteArray(samples); 78 | 79 | // normalize samples signalStrength 80 | Decoder.normalize(signal, signalStrength); 81 | 82 | // first symbol 83 | for (int k = 1; k <=4; k++) { 84 | int beginning = (int) Math.round(Constants.kSamplingFrequency * (k-1) * (2*Constants.kPacketLength + 1.5*Constants.kSilenceLength) / 1000); 85 | if (beginning + Constants.kThreePacketSamples > signal.length) { 86 | // pad with enough zeros to make a frame 87 | double[] signal_padded = new double[beginning + Constants.kThreePacketSamples]; 88 | // copy samples into padded array 89 | System.arraycopy(signal, 0, signal_padded, 0, signal.length); 90 | signal = signal_padded; 91 | } 92 | double power = Decoder.norm(ArrayUtils.getSlice(signal, beginning, (beginning + Constants.kThreePacketSamples))); 93 | if (power > maxpower) { 94 | maxpower = power; 95 | winner = k; 96 | } 97 | } 98 | output.add(winner); 99 | 100 | // second symbol, we need the position of the three packets 101 | double amplitude, width; 102 | int beginning = (int) Math.round(Constants.kSamplingFrequency * (winner-1) * (2*Constants.kPacketLength + 1.5*Constants.kSilenceLength) / 1000); 103 | for (int jj = 1; jj < 4; jj++) { 104 | double[] currframe = ArrayUtils.getSlice(signal, (beginning + Constants.kSilencePacketSamples*(jj-1)), (beginning + Constants.kSilencePacketSamples*jj)); 105 | amplitude = Decoder.absMax(currframe); 106 | Decoder.normalize(currframe, amplitude); 107 | width = Decoder.norm(currframe); 108 | if (width < Constants.kThreshWidth) { 109 | if (amplitude < Constants.kThreshAmplitude) 110 | output.add(2); 111 | else 112 | output.add(1); 113 | } else { 114 | if (amplitude < Constants.kThreshAmplitude) 115 | output.add(4); 116 | else 117 | output.add(3); 118 | } 119 | } 120 | return output; 121 | } 122 | 123 | // original implementation from ask-simple-java : 124 | private static double complexDetect(byte[] signal, double frequency){ 125 | double realSum = 0; 126 | double imaginarySum = 0; 127 | double u = 2 * Math.PI * frequency / kSamplingFrequency; 128 | // y = e^(ju) = cos(u) + j * sin(u) 129 | 130 | for(int i = 0; i < signal.length; i++){ 131 | //System.out.println("signal[" +i +"]: " +signal[i] + "; convert: " + (signal[i])/(float)Constants.kFloatToByteShift); 132 | realSum = realSum + (Math.cos(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 133 | imaginarySum = imaginarySum + (Math.sin(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 134 | } 135 | //System.out.println("realSum=" + realSum + "; imSum=" + imaginarySum); 136 | double realAve = realSum/signal.length; 137 | double imaginaryAve = imaginarySum/signal.length; 138 | // System.out.println("u:" + u + " realAve:" + realAve + " imaginaryAve:" + imaginaryAve 139 | // + " \r\nfrequency:" + frequency + " signal.length:" + signal.length 140 | // + " realSum:" + realSum + " imaginarySum:" + imaginarySum 141 | // + "signal[100]:" + (signal[100]/(float)Constants.kFloatToByteShift)); 142 | // return the abs ( realAve + imaginaryAve * i ) which equals sqrt( realAve^2 + imaginaryAve^2) 143 | return Math.sqrt( (realAve * realAve) + (imaginaryAve * imaginaryAve) ); 144 | } 145 | 146 | private static void normalize(double[] signal, double value) { 147 | // normalize samples value 148 | for (int i = 0; i < signal.length; i++) { 149 | signal[i] = signal[i] / value; 150 | } 151 | } 152 | 153 | private static double norm(double[] signal) { 154 | double sum = 0; 155 | // calculate 2-norm 156 | for (int i = 0; i < signal.length; i++) { 157 | //sum += Math.pow(signal[i], 2); 158 | sum += Math.pow(signal[i], 2); 159 | } 160 | return Math.sqrt(sum); 161 | } 162 | 163 | private static double absMax(double[] signal) { 164 | double abs, maxvalue = 0; 165 | for (int i = 0; i < signal.length; i++) { 166 | abs = Math.abs(signal[i]); 167 | if (abs > maxvalue) 168 | maxvalue = abs; 169 | } 170 | return maxvalue; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /ask-simple-java/StreamDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | import java.io.*; 28 | 29 | /** 30 | * This starts a Thread which decodes data in an AudioBuffer and writes it to an OutputStream. 31 | * StreamDecoder holds the buffer where the MicrophoneListener puts bytes. 32 | * 33 | * @author CVL 34 | */ 35 | public class StreamDecoder implements Runnable { 36 | 37 | public static String kThreadName = "StreamDecoder"; 38 | 39 | private Thread myThread = null; 40 | private Object runLock = new Object(); 41 | private boolean running = false; 42 | 43 | private AudioBuffer buffer = new AudioBuffer(); // THE buffer where bytes are being put 44 | private OutputStream out = null; 45 | 46 | 47 | /** 48 | * This creates and starts the decoding Thread 49 | * @param _out the OutputStream which will receive the decoded data 50 | */ 51 | public StreamDecoder(OutputStream _out) { 52 | out = _out; 53 | myThread = new Thread(this, kThreadName); 54 | myThread.start(); 55 | } 56 | 57 | public AudioBuffer getAudioBuffer(){ 58 | return buffer; 59 | } 60 | 61 | public void run() { 62 | synchronized(runLock){ 63 | running = true; 64 | } 65 | 66 | int durationsToRead = Constants.kDurationsPerKey; 67 | int deletedSamples = 0; 68 | boolean hasKey = false; 69 | boolean hasEOF = false; 70 | double[] startSignals = new double[Constants.kBitsPerByte * Constants.kBytesPerDuration]; 71 | boolean notEnoughSamples = true; 72 | byte samples[] = null; 73 | 74 | while(running){ 75 | notEnoughSamples = true; 76 | while (notEnoughSamples) { 77 | samples = buffer.read(Constants.kSamplesPerDuration * durationsToRead); 78 | if (samples != null) 79 | notEnoughSamples = false; 80 | else Thread.currentThread().yield(); 81 | } 82 | if(hasKey){ //we found the key, so decode this duration 83 | byte[] decoded = Decoder.decode(startSignals, samples); 84 | try { 85 | buffer.delete(samples.length); 86 | deletedSamples += samples.length; 87 | out.write(decoded); 88 | if(decoded[0] == 0){ //we are recieving no signal, so go back to key detection mode 89 | //out.write("EOF\r\n".getBytes()); //this is for debugging 90 | hasKey = false; 91 | durationsToRead = Constants.kDurationsPerKey; 92 | } 93 | } catch (IOException e){ 94 | System.out.println("Exception while decoding:" + e); 95 | break; 96 | } 97 | 98 | try{ 99 | //this provides the audio sampling mechanism a chance to maintain continuity 100 | myThread.sleep(10); 101 | } catch(InterruptedException e){ 102 | System.out.println("Stream Decoding thread interrupted:" + e); 103 | break; 104 | } 105 | continue; 106 | } 107 | 108 | //we don't have the key, so we are in key detection mode from this point on 109 | int initialGranularity = 400; 110 | int finalGranularity = 20; 111 | //System.out.println("Search Start: " + deletedSamples + " End: " + (deletedSamples + samples.length)); 112 | //System.out.println("Search Time: " + ((float)deletedSamples / Constants.kSamplingFrequency) + " End: " 113 | // + ((float)(deletedSamples + samples.length) / Constants.kSamplingFrequency)); 114 | int startIndex = Decoder.findKeySequence(samples, startSignals, initialGranularity); 115 | if(startIndex > -1){ 116 | System.out.println("\nRough Start Index: " + (deletedSamples + startIndex)); 117 | //System.out.println("Rough Start Time: " 118 | // + (deletedSamples + startIndex) / (float)Constants.kSamplingFrequency); 119 | 120 | int shiftAmount = startIndex /* - (Constants.kSamplesPerDuration)*/; 121 | if(shiftAmount < 0){ 122 | shiftAmount = 0; 123 | } 124 | System.out.println("Shift amount: " + shiftAmount); 125 | try { buffer.delete(shiftAmount);} catch (IOException e){} 126 | deletedSamples += shiftAmount; 127 | 128 | durationsToRead = Constants.kDurationsPerKey ; 129 | notEnoughSamples = true; 130 | while (notEnoughSamples) { 131 | samples = buffer.read(Constants.kSamplesPerDuration * durationsToRead); 132 | if (samples != null) 133 | notEnoughSamples = false; 134 | else Thread.currentThread().yield(); 135 | } 136 | 137 | //System.out.println("Search Start: " + deletedSamples + " End: " + (deletedSamples + samples.length)); 138 | //System.out.println("Search Time: " + ((float)deletedSamples / Constants.kSamplesPerDuration) + " End: " 139 | // + ((float)(deletedSamples + samples.length) / Constants.kSamplingFrequency)); 140 | 141 | startIndex = Decoder.findKeySequence(samples, startSignals, finalGranularity); 142 | System.out.println("Refined Start Index: " + (deletedSamples + startIndex)); 143 | //System.out.println("Start Time: " + 144 | // (deletedSamples + startIndex) / (float)Constants.kSamplingFrequency); 145 | try { 146 | notEnoughSamples = true; 147 | while (notEnoughSamples) { 148 | samples = buffer.read(startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey)); 149 | if (samples != null) 150 | notEnoughSamples = false; 151 | else Thread.currentThread().yield(); 152 | } 153 | 154 | samples = ArrayUtils.subarray(samples, startIndex + Constants.kSamplesPerDuration, 155 | 2 * Constants.kSamplesPerDuration); 156 | Decoder.getKeySignalStrengths(samples, startSignals); 157 | /* 158 | System.out.println(" f(0): " + startSignals[0] + " f(1): " + startSignals[1] + 159 | " f(2): " + startSignals[2] + " f(3): " + startSignals[3] + 160 | " f(4): " + startSignals[4] + " f(5): " + startSignals[5] + 161 | " f(6): " + startSignals[6] + " f(7): " + startSignals[7]); 162 | */ 163 | 164 | buffer.delete(startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey)); 165 | deletedSamples += startIndex + (Constants.kSamplesPerDuration * Constants.kDurationsPerKey); 166 | } catch (IOException e){} 167 | hasKey = true; 168 | durationsToRead = 1; 169 | } else { 170 | try { 171 | buffer.delete(Constants.kSamplesPerDuration); 172 | deletedSamples += Constants.kSamplesPerDuration; 173 | } catch (IOException e){} 174 | } 175 | } 176 | } 177 | 178 | public void quit(){ 179 | synchronized(runLock){ 180 | running = false; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Encoder.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | 4 | /** 5 | * Copyright 2012 by the authors. All rights reserved. 6 | * 7 | * Author: Jonas Michel 8 | */ 9 | 10 | 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * Encoder. 17 | * @author jrm 18 | */ 19 | public class Encoder implements Constants { 20 | 21 | /** 22 | * encodeCricket is the public function of class Encoder. 23 | * @param input the ArrayList of integers to encode 24 | * @param output the stream of audio samples representing the input, 25 | * pre- and post-fixed with an hail signal 26 | */ 27 | public static void encodeCricket(ArrayList input, OutputStream output) throws IOException { 28 | System.out.println("encodeCricket starts"); 29 | 30 | byte[] zeros = new byte[kSamplesPerDuration]; 31 | 32 | // write out the initial hail sequence 33 | output.write(zeros); 34 | output.write(Encoder.getHailSequence()); 35 | 36 | // now write the data 37 | int info[] = new int[4]; 38 | for (int i = 0; i < input.size() - 3; i+=4) { 39 | for (int j = 0; j < 4; j++) 40 | info[j] = input.get(i+j); 41 | output.write(Encoder.createPacket(info)); 42 | } 43 | 44 | // write out the closing hail sequence 45 | output.write(Encoder.getHailSequence()); 46 | 47 | System.out.println("encodeCricket ends"); 48 | } 49 | 50 | /** 51 | * @return audio samples for a duration of the hail frequency, Constants.kHailFrequency 52 | */ 53 | private static byte[] getHailSequence(){ 54 | double[] signal = new double[kSamplesPerDuration]; 55 | //add a sinusoid of the hail frequency, amplitude kAmplitude and duration kDuration 56 | double innerMultiplier = Constants.kHailFrequency * (1 / kSamplingFrequency) * 2 * Math.PI; 57 | for(int l = 0; l < signal.length; l++){ 58 | signal[l] = /*kAmplitude **/ Math.cos(innerMultiplier * l); 59 | } 60 | return ArrayUtils.getByteArrayFromDoubleArray(smoothWindow(signal, 0.3)); 61 | } 62 | 63 | /** About smoothwindow. 64 | * This is a data set in with the following form: 65 | * 66 | * | 67 | * 1 | +-------------------+ 68 | * | / \ 69 | * |/ \ 70 | * +--|-------------------|--+--- 71 | * 0.01 0.09 0.1 time 72 | * 73 | * It is used to smooth the edges of the signal in each duration 74 | * 75 | */ 76 | private static double[] smoothWindow(double[] input, double magicScalingNumber){ 77 | double[] smoothWindow = new double[input.length]; 78 | double minVal = 0; 79 | double maxVal = 0; 80 | int peaks = (int)(input.length * 0.1); 81 | double steppingValue = 1 / (double)peaks; 82 | for (int i=0; i input.length - peaks){ 86 | smoothWindow[i] = input[i] * (steppingValue * (input.length - i - 1)) /* / magicScalingNumber */; 87 | } else { 88 | //don't touch the middle values 89 | smoothWindow[i] = input[i] /* / magicScalingNumber */; 90 | } 91 | if (smoothWindow[i] < minVal){ 92 | minVal = smoothWindow[i]; 93 | } 94 | if(smoothWindow[i] > maxVal){ 95 | maxVal = smoothWindow[i]; 96 | } 97 | } 98 | return smoothWindow; 99 | } 100 | 101 | private static double[] smoothWindow(double[] input){ 102 | double magicScalingNumber = 0.8; 103 | return smoothWindow(input, magicScalingNumber); 104 | } 105 | 106 | /** 107 | * This isn't used at the moment, but it does sound nice 108 | */ 109 | private static double[] blackmanSmoothWindow(double[] input){ 110 | double magicScalingNumber = 3.5; 111 | double[] smoothWindow = new double[input.length]; 112 | double steppingValue = 2*Math.PI/(input.length -1); 113 | double maxVal = 0; 114 | double minVal = 0; 115 | for(int i=0; i maxVal){ 122 | maxVal = smoothWindow[i]; 123 | } 124 | } 125 | return smoothWindow; 126 | } 127 | 128 | /** 129 | * Creates a three-chirp byte array of audio samples. 130 | * @param info array of four integers to encode to a chirp frame 131 | * @return byte array of audio samples representing the info integers 132 | */ 133 | private static byte[] createPacket(int[] info) { 134 | ArrayList packet = new ArrayList(); 135 | int packetlength, silencelength, packetsamples, silencesamples; 136 | double amplitude; 137 | 138 | // info[0] determines the phase of this packet 139 | int zeros_prefix = (int) Math.round(Constants.kSamplingFrequency * (info[0]-1) * (2 * Constants.kShortSize + 1.5 * Constants.kLongSize)/1000); 140 | for (int i = 0; i < zeros_prefix; i++) { 141 | packet.add(0.0); 142 | } 143 | 144 | // info[1,2,3] are encoded to chirp (amplitude,length) pairs 145 | for (int k = 1; k < 4; k++) { 146 | switch (info[k]) { 147 | case 1: 148 | packetlength = Constants.kShortSize; 149 | silencelength = Constants.kLongSize; 150 | amplitude = Constants.kBigAmplitude; 151 | break; 152 | case 2: 153 | packetlength = Constants.kShortSize; 154 | silencelength = Constants.kLongSize; 155 | amplitude = Constants.kSmallAmplitude; 156 | break; 157 | case 3: 158 | packetlength = Constants.kLongSize; 159 | silencelength = Constants.kShortSize; 160 | amplitude = Constants.kBigAmplitude; 161 | break; 162 | case 4: 163 | packetlength = Constants.kLongSize; 164 | silencelength = Constants.kShortSize; 165 | amplitude = Constants.kSmallAmplitude; 166 | break; 167 | default: 168 | packetlength = 0; 169 | silencelength = 0; 170 | amplitude = 0; 171 | break; 172 | } 173 | packetsamples = (int) Math.round((Constants.kSamplingFrequency * packetlength) / 1000); 174 | silencesamples = (int) Math.round((Constants.kSamplingFrequency * silencelength) / 1000); 175 | 176 | double time[] = new double[packetsamples]; 177 | for (int t = 0; t < packetsamples; t++) { 178 | time[t] = t * (((double) packetlength) / (packetsamples*1000)); 179 | } 180 | 181 | double smallpacket[] = new double[packetsamples]; 182 | for (int t = 0; t < packetsamples; t++) { 183 | smallpacket[t] = amplitude * Math.sin(2 * Math.PI * time[t] * Constants.kFrequency); 184 | } 185 | 186 | for (int i = 0; i < silencesamples; i++) { 187 | packet.add(0.0); 188 | } 189 | 190 | double smoothsmallpacket[] = smoothWindow(smallpacket); 191 | for (int i = 0; i < smoothsmallpacket.length; i++) { 192 | packet.add(smoothsmallpacket[i]); 193 | } 194 | } 195 | 196 | int zeros_suffix = (Constants.kSamplesPerFrame - packet.size()); 197 | for (int i = 0; i < zeros_suffix; i++) { 198 | packet.add(0.0); 199 | } 200 | 201 | double signal[] = new double[packet.size()]; 202 | for (int i = 0; i < signal.length; i++) { 203 | signal[i] = packet.get(i); 204 | } 205 | 206 | // convert to bytes 207 | return ArrayUtils.getByteArrayFromDoubleArray(signal); 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /ask-simple-java/Encoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | import java.io.*; 28 | 29 | /** 30 | * Encoder. 31 | * @author CVL 32 | */ 33 | public class Encoder implements Constants { 34 | 35 | /** 36 | * encodeStream is the public function of class Encoder. 37 | * @param input the stream of bytes to encode 38 | * @param output the stream of audio samples representing the input, 39 | * prefixed with an hail signal and a calibration signal 40 | */ 41 | public static void encodeStream(InputStream input, OutputStream output) 42 | throws IOException { 43 | byte[] zeros = new byte[kSamplesPerDuration]; 44 | 45 | //write out the hail and calibration sequences 46 | output.write(zeros); 47 | output.write(getHailSequence()); 48 | output.write(getCalibrationSequence()); 49 | 50 | //now write the data 51 | int read = 0; 52 | byte[] buff = new byte[kBytesPerDuration]; 53 | while( (read = input.read(buff)) == kBytesPerDuration){ 54 | output.write(Encoder.encodeDuration(buff)); 55 | } 56 | if(read > 0){ 57 | for(int i=read; i < kBytesPerDuration; i++){ 58 | buff[i] = 0; 59 | } 60 | output.write(Encoder.encodeDuration(buff)); 61 | } 62 | } 63 | 64 | /** 65 | * @param bitPosition the position in the kBytesPerDuration wide byte array for which you want a frequency 66 | * @return the frequency in which to sound to indicate a 1 for this bitPosition 67 | * NOTE!: This blindly assumes that bitPosition is in the range [0 - 7] 68 | */ 69 | public static double getFrequency(int bitPosition){ 70 | return Constants.kFrequencies[bitPosition]; 71 | } 72 | 73 | /** 74 | * @param input a kBytesPerDuration long array of bytes to encode 75 | * @return an array of audio samples of type AudioUtil.kDefaultFormat 76 | */ 77 | private static byte[] encodeDuration(byte[] input){ 78 | double[] signal = new double[kSamplesPerDuration]; 79 | for(int j = 0; j < kBytesPerDuration; j++){ 80 | for(int k = 0; k < kBitsPerByte; k++){ 81 | if( ((input[j] >> k) & 0x1) == 0 ){ 82 | //no need to go through encoding a zero 83 | continue; 84 | } 85 | 86 | //add a sinusoid of getFrequency(j), amplitude kAmplitude and duration kDuration 87 | double innerMultiplier = getFrequency((j * kBitsPerByte) + k) 88 | * (1 / kSamplingFrequency) * 2 * Math.PI; 89 | for(int l = 0; l < signal.length; l++){ 90 | signal[l] = signal[l] + (kAmplitude * Math.cos(innerMultiplier * l)); 91 | } 92 | } 93 | } 94 | 95 | return ArrayUtils.getByteArrayFromDoubleArray(smoothWindow(signal)); 96 | } 97 | 98 | /** 99 | * @return audio samples for a duration of the hail frequency, Constants.kHailFrequency 100 | */ 101 | private static byte[] getHailSequence(){ 102 | double[] signal = new double[kSamplesPerDuration]; 103 | //add a sinusoid of the hail frequency, amplitude kAmplitude and duration kDuration 104 | double innerMultiplier = Constants.kHailFrequency * (1 / kSamplingFrequency) * 2 * Math.PI; 105 | for(int l = 0; l < signal.length; l++){ 106 | signal[l] = /*kAmplitude **/ Math.cos(innerMultiplier * l); 107 | } 108 | return ArrayUtils.getByteArrayFromDoubleArray(smoothWindow(signal, 0.3)); 109 | } 110 | 111 | /** 112 | * @return audio samples (of length 2 * kSamplesPerDuration), used to calibrate the decoding 113 | */ 114 | private static byte[] getCalibrationSequence(){ 115 | byte[] results = new byte[2 * kSamplesPerDuration]; 116 | byte[] inputBytes1 = new byte[kBytesPerDuration]; 117 | byte[] inputBytes2 = new byte[kBytesPerDuration]; 118 | for(int i=0; i < kBytesPerDuration; i++){ 119 | inputBytes1[i] = (byte)0xAA; 120 | inputBytes2[i] = (byte)0x55; 121 | } 122 | 123 | //encode inputBytes1 and 2 in sequence 124 | byte[] partialResult = encodeDuration(inputBytes1 ); 125 | for(int k = 0; k < kSamplesPerDuration; k++){ 126 | results[k] = partialResult[k]; 127 | } 128 | partialResult = encodeDuration(inputBytes2); 129 | for(int k = 0; k < kSamplesPerDuration; k++){ 130 | results[k + kSamplesPerDuration] = partialResult[k]; 131 | } 132 | 133 | return results; 134 | } 135 | 136 | /** About smoothwindow. 137 | * This is a data set in with the following form: 138 | * 139 | * | 140 | * 1 | +-------------------+ 141 | * | / \ 142 | * |/ \ 143 | * +--|-------------------|--+--- 144 | * 0.01 0.09 0.1 time 145 | * 146 | * It is used to smooth the edges of the signal in each duration 147 | * 148 | */ 149 | private static double[] smoothWindow(double[] input, double magicScalingNumber){ 150 | double[] smoothWindow = new double[input.length]; 151 | double minVal = 0; 152 | double maxVal = 0; 153 | int peaks = (int)(input.length * 0.1); 154 | double steppingValue = 1 / (double)peaks; 155 | for (int i=0; i input.length - peaks){ 159 | smoothWindow[i] = input[i] * (steppingValue * (input.length - i - 1)) /* / magicScalingNumber */; 160 | } else { 161 | //don't touch the middle values 162 | smoothWindow[i] = input[i] /* / magicScalingNumber */; 163 | } 164 | if (smoothWindow[i] < minVal){ 165 | minVal = smoothWindow[i]; 166 | } 167 | if(smoothWindow[i] > maxVal){ 168 | maxVal = smoothWindow[i]; 169 | } 170 | } 171 | return smoothWindow; 172 | } 173 | 174 | private static double[] smoothWindow(double[] input){ 175 | double magicScalingNumber = 0.8; 176 | return smoothWindow(input, magicScalingNumber); 177 | } 178 | 179 | /** 180 | * This isn't used at the moment, but it does sound nice 181 | */ 182 | private static double[] blackmanSmoothWindow(double[] input){ 183 | double magicScalingNumber = 3.5; 184 | double[] smoothWindow = new double[input.length]; 185 | double steppingValue = 2*Math.PI/(input.length -1); 186 | double maxVal = 0; 187 | double minVal = 0; 188 | for(int i=0; i maxVal){ 195 | maxVal = smoothWindow[i]; 196 | } 197 | } 198 | return smoothWindow; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /ask-simple-java/AudioUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | import javax.sound.sampled.*; 28 | import java.io.*; 29 | import java.util.Date; 30 | 31 | /** 32 | * 33 | * @author CVL 34 | */ 35 | 36 | public class AudioUtils { 37 | 38 | //the default format for reading and writing audio information 39 | public static AudioFormat kDefaultFormat = new AudioFormat((float) Encoder.kSamplingFrequency, 40 | (int) 8, (int) 1, true, false); 41 | 42 | public static void decodeWavFile(File inputFile, OutputStream out) 43 | throws UnsupportedAudioFileException, 44 | IOException { 45 | StreamDecoder sDecoder = new StreamDecoder(out); 46 | AudioBuffer aBuffer = sDecoder.getAudioBuffer(); 47 | 48 | AudioInputStream audioInputStream = 49 | AudioSystem.getAudioInputStream(kDefaultFormat, 50 | AudioSystem.getAudioInputStream(inputFile)); 51 | int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); 52 | // Set an arbitrary buffer size of 1024 frames. 53 | int numBytes = 1024 * bytesPerFrame; 54 | byte[] audioBytes = new byte[numBytes]; 55 | int numBytesRead = 0; 56 | // Try to read numBytes bytes from the file and write it to the buffer 57 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 58 | while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { 59 | /* 60 | for(int i=0; i < numBytesRead; i++){ 61 | float val = audioBytes[i] / (float)Constants.kFloatToByteShift; 62 | //System.out.println("" + val); 63 | } 64 | */ 65 | aBuffer.write(audioBytes, 0, numBytesRead); 66 | } 67 | } 68 | 69 | public static void writeWav(File file, byte[] data, AudioFormat format) 70 | throws IllegalArgumentException, 71 | IOException { 72 | ByteArrayInputStream bais = new ByteArrayInputStream(data); 73 | AudioInputStream ais = new AudioInputStream(bais, format, data.length); 74 | AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file); 75 | } 76 | 77 | public static void displayMixerInfo(){ 78 | Mixer.Info[] mInfos = AudioSystem.getMixerInfo(); 79 | if(mInfos == null){ 80 | System.out.println("No Mixers found"); 81 | return; 82 | } 83 | 84 | for(int i=0; i < mInfos.length; i++){ 85 | System.out.println("Mixer Info: " + mInfos[i]); 86 | Mixer mixer = AudioSystem.getMixer(mInfos[i]); 87 | Line.Info[] lines = mixer.getSourceLineInfo(); 88 | for(int j = 0; j < lines.length; j++){ 89 | System.out.println("\tSource: " + lines[j]); 90 | } 91 | lines = mixer.getTargetLineInfo(); 92 | for(int j = 0; j < lines.length; j++){ 93 | System.out.println("\tTarget: " + lines[j]); 94 | } 95 | } 96 | } 97 | 98 | public static void displayAudioFileTypes(){ 99 | AudioFileFormat.Type[] types = AudioSystem.getAudioFileTypes(); 100 | for(int i=0; i < types.length; i++){ 101 | System.out.println("Audio File Type:" + types[i].toString()); 102 | } 103 | } 104 | 105 | //This never returns, which is kind of lame. 106 | // NOT USED!! - replaced by MicrophoneListener.run() 107 | public static void listenToMicrophone(AudioBuffer buff){ 108 | try { 109 | int buffSize = 4096; 110 | TargetDataLine line = getTargetDataLine(kDefaultFormat); 111 | line.open(kDefaultFormat, buffSize); 112 | 113 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 114 | int numBytesRead; 115 | byte[] data = new byte[line.getBufferSize() / 5]; 116 | line.start(); 117 | while(true){ 118 | numBytesRead = line.read(data, 0, data.length); 119 | buff.write(data, 0, numBytesRead); 120 | } 121 | /* 122 | line.drain(); 123 | line.stop(); 124 | line.close(); 125 | */ 126 | } catch (Exception e){ 127 | System.out.println(e.toString()); 128 | } 129 | } 130 | 131 | public static void recordToFile(File file, int length){ 132 | try { 133 | int buffSize = 4096; 134 | TargetDataLine line = getTargetDataLine(kDefaultFormat); 135 | line.open(kDefaultFormat, buffSize); 136 | 137 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 138 | int numBytesRead; 139 | byte[] data = new byte[line.getBufferSize() / 5]; 140 | line.start(); 141 | for(int i=0; i < length; i++) { 142 | numBytesRead = line.read(data, 0, data.length); 143 | out.write(data, 0, numBytesRead); 144 | } 145 | line.drain(); 146 | line.stop(); 147 | line.close(); 148 | 149 | writeWav(file, out.toByteArray(), kDefaultFormat); 150 | 151 | } catch (Exception e){ 152 | System.out.println(e.toString()); 153 | } 154 | } 155 | 156 | public static TargetDataLine getTargetDataLine(AudioFormat format) 157 | throws LineUnavailableException { 158 | DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); 159 | if (!AudioSystem.isLineSupported(info)) { 160 | throw new LineUnavailableException(); 161 | } 162 | return (TargetDataLine) AudioSystem.getLine(info); 163 | } 164 | 165 | public static SourceDataLine getSourceDataLine(AudioFormat format) 166 | throws LineUnavailableException { 167 | DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 168 | if (!AudioSystem.isLineSupported(info)) { 169 | throw new LineUnavailableException(); 170 | } 171 | return (SourceDataLine) AudioSystem.getLine(info); 172 | } 173 | 174 | public static void encodeFileToWav(File inputFile, File outputFile) 175 | throws IOException { 176 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 177 | Encoder.encodeStream(new FileInputStream(inputFile), baos); 178 | writeWav(outputFile, baos.toByteArray(), kDefaultFormat); 179 | } 180 | 181 | public static void performData(byte[] data) 182 | throws IOException { 183 | //For some reason line.write seems to affect the data 184 | //to avoid the side effect, we copy it 185 | byte[] dataCopy = new byte[data.length]; 186 | for(int i=0; i < data.length; i++){ 187 | dataCopy[i] = data[i]; 188 | } 189 | 190 | SourceDataLine line = null; 191 | try { 192 | line = getSourceDataLine(kDefaultFormat); 193 | line.open(kDefaultFormat); 194 | } catch (LineUnavailableException ex) { 195 | System.out.println("Line Unavailable: " + ex); 196 | return; 197 | } 198 | line.start(); 199 | line.write(dataCopy, 0, dataCopy.length); 200 | line.drain(); 201 | line.stop(); 202 | line.close(); 203 | } 204 | 205 | public static void performFile(File file) 206 | throws IOException { 207 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 208 | Encoder.encodeStream(new FileInputStream(file), baos); 209 | performData(baos.toByteArray()); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /ask-simple-java/Decoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2002 by the authors. All rights reserved. 3 | * 4 | * Author: Cristina V Lopes (crista at tagide dot com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | 27 | import java.io.ByteArrayOutputStream; 28 | import java.util.Date; 29 | 30 | /** 31 | * This class contains the signal processing functions. 32 | * 33 | * @author CVL 34 | */ 35 | public class Decoder implements Constants { 36 | 37 | /** 38 | * @param signal the audio samples to search 39 | * @param signalStrengths this will be filled in with the strengths for each frequency (NOTE THIS SIDE EFFECT) 40 | * @param granularity a correlation will be determined every granularity samples (lower is slower) 41 | * @return the index in signal of the key sequence, or -1 if it wasn't found (in which case signalStrengths is trashed) 42 | */ 43 | public static int findKeySequence(byte[] signal, double[] signalStrengths, int granularity){ 44 | int maxCorrelationIndex = -1; 45 | double maxCorrelation = -1; 46 | double minSignal = 0.003; 47 | double acceptedSignal = 0.01; 48 | int i=0; 49 | for(i = 0; i <= signal.length - kSamplesPerDuration; i += granularity){ 50 | //test the correlation 51 | byte[] partialSignal = ArrayUtils.subarray(signal, i, kSamplesPerDuration); 52 | double corr = complexDetect(partialSignal, Constants.kHailFrequency) /* * 4 */; 53 | // System.out.println("Correlation at " + i + ":" + corr); 54 | if (corr > maxCorrelation){ 55 | maxCorrelation = corr; 56 | maxCorrelationIndex = i; 57 | } 58 | if(granularity <= 0){ 59 | break; 60 | } 61 | } 62 | 63 | //System.out.println("Searched to index:" + i); 64 | if (maxCorrelation < acceptedSignal && maxCorrelation > -1){ 65 | //System.out.println("Best Correlation:" + maxCorrelation); 66 | maxCorrelationIndex = -1; 67 | } 68 | //if(maxCorrelationIndex >= 0){ 69 | //System.out.println("\r\nMax Correlation:" + maxCorrelation + " index:" + maxCorrelationIndex); 70 | //System.out.println("signal.length:" + signal.length); 71 | //getKeySignalStrengths(ArrayUtils.subarray(signal, maxCorrelationIndex + kSamplesPerDuration, 72 | // kSamplesPerDuration * 2), 73 | // signalStrengths); 74 | //} 75 | 76 | return maxCorrelationIndex; 77 | } 78 | 79 | /** 80 | * @param startSignals the signal strengths of each of the frequencies 81 | * @param samples the samples 82 | * @return the decoded bytes 83 | */ 84 | public static byte[] decode(double[] startSignals, byte[] samples){ 85 | return decode(startSignals, getSignalStrengths(samples)); 86 | } 87 | 88 | /** 89 | * @param startSignals the signal strengths of each of the frequencies 90 | * @param signal the signal strengths for each frequency for each duration [strength][duration index] 91 | * SIDE EFFECT: THE signal PARAMETER WILL BE SCALED BY THE STARTSIGNALS 92 | * @return the decoded bytes 93 | */ 94 | private static byte[] decode(double[] startSignals, double[][] signal){ 95 | //normalize to the start signals 96 | for(int i = 0; i < (kBitsPerByte * kBytesPerDuration); i++){ 97 | for(int j = 0; j < signal[i].length; j++){ 98 | signal[i][j] = signal[i][j] / startSignals[i]; 99 | } 100 | } 101 | 102 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 103 | for(int i = 0; i < signal[0].length; i++){ 104 | for(int k = 0; k < kBytesPerDuration; k++){ 105 | byte value = 0; 106 | for(int j = 0; j < kBitsPerByte; j++){ 107 | if(signal[(k * kBitsPerByte) + j][i] > 0.4){ 108 | value = (byte)(value | ( 1 << j)); 109 | } else { 110 | } 111 | } 112 | baos.write(value); 113 | } 114 | } 115 | 116 | return baos.toByteArray(); 117 | 118 | } 119 | 120 | /** 121 | * @param input audio sample array 122 | * @return the signal strengths of each frequency in each duration: [signal strength][duration index] 123 | */ 124 | private static double[][] getSignalStrengths(byte[] input){ 125 | //detect the signal strength of each frequency in each duration 126 | int durations = input.length / kSamplesPerDuration; 127 | 128 | // rows are durations, cols are bit strengths 129 | double[][] signal = new double[kBitsPerByte * kBytesPerDuration][durations]; 130 | 131 | //for each duration, check each bit for representation in the input 132 | for(int i=0; i < durations; i++){ 133 | //separate this duration's input into its own array 134 | byte[] durationInput = ArrayUtils.subarray(input, i * kSamplesPerDuration, kSamplesPerDuration); 135 | 136 | //for each bit represented, detect 137 | for(int j = 0; j < kBitsPerByte * kBytesPerDuration; j++){ 138 | signal[j][i] = 139 | complexDetect(durationInput, Encoder.getFrequency(j)); 140 | /* 141 | if (j == 0) 142 | System.out.println("\nsignal[" + j + "][" + i + "]=" + signal [j][i]); 143 | else 144 | System.out.println("signal[" + j + "][" + i + "]=" + signal [j][i]); 145 | */ 146 | } 147 | } 148 | return signal; 149 | } 150 | 151 | public static void getKeySignalStrengths(byte[] signal, double[] signalStrengths){ 152 | byte[] partialSignal = ArrayUtils.subarray(signal, 0, kSamplesPerDuration); 153 | for(int j = 1; j < kBitsPerByte * kBytesPerDuration; j += 2){ 154 | signalStrengths[j] = complexDetect(partialSignal, Encoder.getFrequency(j)); 155 | } 156 | 157 | byte[] partialSignal2 = ArrayUtils.subarray(signal, kSamplesPerDuration, kSamplesPerDuration); 158 | for(int j = 0; j < kBitsPerByte * kBytesPerDuration; j += 2){ 159 | signalStrengths[j] = complexDetect(partialSignal2, Encoder.getFrequency(j)); 160 | //System.out.println(signalStrengths[j]); 161 | } 162 | } 163 | 164 | /** 165 | * @param signal audio samples 166 | * @param frequence the frequency to search for in signal 167 | * @return the strength of the correlation of the frequency in the signal 168 | */ 169 | private static double complexDetect(byte[] signal, double frequency){ 170 | double realSum = 0; 171 | double imaginarySum = 0; 172 | double u = 2 * Math.PI * frequency / kSamplingFrequency; 173 | // y = e^(ju) = cos(u) + j * sin(u) 174 | 175 | for(int i = 0; i < signal.length; i++){ 176 | //System.out.println("signal[" +i +"]: " +signal[i] + "; convert: " + (signal[i])/(float)Constants.kFloatToByteShift); 177 | realSum = realSum + (Math.cos(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 178 | imaginarySum = imaginarySum + (Math.sin(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 179 | } 180 | //System.out.println("realSum=" + realSum + "; imSum=" + imaginarySum); 181 | double realAve = realSum/signal.length; 182 | double imaginaryAve = imaginarySum/signal.length; 183 | // System.out.println("u:" + u + " realAve:" + realAve + " imaginaryAve:" + imaginaryAve 184 | // + " \r\nfrequency:" + frequency + " signal.length:" + signal.length 185 | // + " realSum:" + realSum + " imaginarySum:" + imaginarySum 186 | // + "signal[100]:" + (signal[100]/(float)Constants.kFloatToByteShift)); 187 | // return the abs ( realAve + imaginaryAve * i ) which equals sqrt( realAve^2 + imaginaryAve^2) 188 | return Math.sqrt( (realAve * realAve) + (imaginaryAve * imaginaryAve) ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /cricket-android/src/com/jonas/CricketAndroid/Main.java: -------------------------------------------------------------------------------- 1 | package com.jonas.CricketAndroid; 2 | 3 | /* 4 | * This is an Android port of the Digital Voices cricket codec, 5 | * https://github.com/diva/digital-voices/tree/master/cricket 6 | * 7 | * See also http://github.com/diva/digital-voices 8 | * 9 | * Ported April 2012 by jonasrmichel@mail.utexas.edu 10 | * borrowing heavily from Richard Jarkman's port of the 11 | * Digital Voices B-ASK pentatonic codec. 12 | * 13 | * Usage notes: 14 | * 15 | * - Type something in and hit Chirp to have it encoded and chirped 16 | * 17 | * - Hit Listen to start the listen process 18 | * A status message will appear below the Listen button 19 | * When input is decoded, it'll show up below that 20 | * 21 | */ 22 | 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.InputStream; 26 | import java.util.Timer; 27 | import java.util.TimerTask; 28 | 29 | import android.app.Activity; 30 | import android.content.Intent; 31 | import android.net.Uri; 32 | import android.os.Bundle; 33 | import android.os.Handler; 34 | import android.view.View; 35 | import android.widget.Button; 36 | import android.widget.EditText; 37 | import android.widget.TextView; 38 | 39 | public class Main extends Activity { 40 | /** Called when the activity is first created. */ 41 | //Loopback l = null; 42 | 43 | MicrophoneListener microphoneListener = null; 44 | StreamDecoder sDecoder = null; 45 | ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(); 46 | Timer refreshTimer = null; 47 | 48 | Handler mHandler = new Handler(); 49 | 50 | TextView textListen; 51 | TextView textStatus; 52 | 53 | Uri mCreateDataUri = null; 54 | String mCreateDataType = null; 55 | String mCreateDataExtraText = null; 56 | 57 | 58 | @Override 59 | public void onCreate(Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | setContentView(R.layout.main); 62 | 63 | textStatus = (TextView) findViewById(R.id.TextStatus); 64 | textListen = (TextView) findViewById(R.id.TextListen); 65 | 66 | Button t = (Button) findViewById(R.id.ButtonPlay); 67 | t.setOnClickListener(mPlayListener); 68 | 69 | t = (Button) findViewById(R.id.ButtonListen); 70 | t.setOnClickListener(mListenListener); 71 | 72 | final Intent intent = getIntent(); 73 | final String action = intent.getAction(); 74 | if (Intent.ACTION_SEND.equals(action)) 75 | { 76 | 77 | mCreateDataUri = intent.getData(); 78 | mCreateDataType = intent.getType(); 79 | 80 | if( mCreateDataUri == null ) 81 | { 82 | mCreateDataUri = intent.getParcelableExtra( Intent.EXTRA_STREAM ); 83 | 84 | 85 | } 86 | 87 | mCreateDataExtraText = intent.getStringExtra( Intent.EXTRA_TEXT ); 88 | 89 | if( mCreateDataUri == null ) 90 | mCreateDataType = null; 91 | 92 | // The new entry was created, so assume all will end well and 93 | // set the result to be returned. 94 | setResult(RESULT_OK, (new Intent()).setAction(null)); 95 | } 96 | 97 | } 98 | 99 | 100 | View.OnClickListener mPlayListener = new View.OnClickListener() 101 | { 102 | public void onClick(View v) 103 | { 104 | EditText e = (EditText) findViewById(R.id.EditTextToPlay); 105 | String s = e.getText().toString().toLowerCase(); // just use lower case 106 | 107 | 108 | 109 | 110 | perform( s ); 111 | 112 | } 113 | }; 114 | 115 | 116 | View.OnClickListener mListenListener = new View.OnClickListener() 117 | { 118 | public void onClick(View v) 119 | { 120 | 121 | 122 | if( microphoneListener == null ) 123 | { 124 | 125 | listen(); 126 | ((Button)v).setText("Stop listening"); 127 | } 128 | else 129 | { 130 | 131 | 132 | stopListening(); 133 | ((Button)v).setText("Listen"); 134 | } 135 | 136 | } 137 | }; 138 | 139 | 140 | 141 | 142 | /* (non-Javadoc) 143 | * @see android.app.Activity#onPause() 144 | */ 145 | @Override 146 | protected void onPause() { 147 | 148 | //if( l != null ) 149 | // l.stopLoop(); 150 | 151 | super.onPause(); 152 | 153 | if( refreshTimer != null ) 154 | { 155 | refreshTimer.cancel(); 156 | refreshTimer = null; 157 | } 158 | 159 | stopListening(); 160 | } 161 | 162 | /* (non-Javadoc) 163 | * @see android.app.Activity#onResume() 164 | */ 165 | @Override 166 | protected void onResume() 167 | { 168 | super.onResume(); 169 | 170 | String sent = null; 171 | 172 | if( mCreateDataExtraText != null ) 173 | { 174 | sent = mCreateDataExtraText; 175 | } 176 | else if( mCreateDataType != null && mCreateDataType.startsWith("text/") ) 177 | { 178 | //read the URI into a string 179 | 180 | byte[] b = readDataFromUri( this.mCreateDataUri ); 181 | if( b != null ) 182 | sent = new String( b ); 183 | 184 | 185 | } 186 | 187 | if( sent != null ) 188 | { 189 | EditText e = (EditText) findViewById(R.id.EditTextToPlay); 190 | e.setText(sent); 191 | } 192 | 193 | 194 | refreshTimer = new Timer(); 195 | 196 | 197 | 198 | refreshTimer.schedule( 199 | new TimerTask() 200 | { 201 | @Override 202 | public void run() 203 | { 204 | 205 | mHandler.post(new Runnable() // have to do this on the UI thread 206 | { 207 | public void run() 208 | { 209 | updateResults(); 210 | 211 | 212 | } 213 | }); 214 | 215 | } 216 | }, 500, 500); 217 | 218 | } 219 | 220 | private void updateResults() 221 | { 222 | if( microphoneListener != null ) 223 | { 224 | textListen.setText(decodedStream.toString()); 225 | textStatus.setText(sDecoder.getStatusString()); 226 | } 227 | else 228 | { 229 | textStatus.setText(""); 230 | } 231 | 232 | } 233 | private void listen() 234 | { 235 | stopListening(); 236 | 237 | decodedStream.reset(); 238 | 239 | //the StreamDecoder uses the Decoder to decode samples put in its AudioBuffer 240 | // StreamDecoder starts a thread 241 | sDecoder = new StreamDecoder( decodedStream ); 242 | 243 | //the MicrophoneListener feeds the microphone samples into the AudioBuffer 244 | // MicrophoneListener starts a thread 245 | 246 | microphoneListener = new MicrophoneListener(sDecoder.getAudioBuffer()); 247 | System.out.println("Listening"); 248 | } 249 | 250 | private void stopListening() 251 | { 252 | if( microphoneListener != null ) 253 | microphoneListener.quit(); 254 | 255 | microphoneListener = null; 256 | 257 | if( sDecoder != null ) 258 | sDecoder.quit(); 259 | 260 | sDecoder = null; 261 | } 262 | 263 | private void perform( String input ) 264 | { 265 | 266 | try 267 | { 268 | 269 | //try to play the file 270 | System.out.println("Performing " + input); 271 | // AudioUtils.performArray(input.getBytes()); 272 | AudioUtils.performString(input); 273 | } 274 | 275 | catch (Exception e){ 276 | System.out.println("Could not encode " + input + " because of " + e); 277 | } 278 | 279 | } 280 | 281 | /* 282 | private void encode( String inputFile, String outputFile ) 283 | { 284 | 285 | try 286 | { 287 | 288 | //There was an output file specified, so we should write the wav 289 | System.out.println("Encoding " + inputFile); 290 | AudioUtils.encodeFileToWav(new File(inputFile), new File(outputFile)); 291 | 292 | } 293 | catch (Exception e) 294 | { 295 | System.out.println("Could not encode " + inputFile + " because of " + e); 296 | } 297 | 298 | } 299 | */ 300 | 301 | private byte[] readDataFromUri( Uri uri ) 302 | { 303 | byte[] buffer = null; 304 | 305 | try 306 | { 307 | InputStream stream = getContentResolver().openInputStream(uri) ; 308 | 309 | int bytesAvailable = stream.available(); 310 | //int maxBufferSize = 1024; 311 | int bufferSize = bytesAvailable; //Math.min(bytesAvailable, maxBufferSize); 312 | int totalRead = 0; 313 | buffer = new byte[bufferSize]; 314 | 315 | // read file and write it into form... 316 | int bytesRead = stream.read(buffer, 0, bufferSize); 317 | while (bytesRead > 0) 318 | { 319 | bytesRead = stream.read(buffer, totalRead, bufferSize); 320 | totalRead += bytesRead; 321 | } 322 | } catch (Exception e) { 323 | // TODO Auto-generated catch block 324 | e.printStackTrace(); 325 | 326 | } 327 | 328 | return buffer; 329 | } 330 | 331 | } -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/Main.java: -------------------------------------------------------------------------------- 1 | package com.jarkman.ASKSimpleAndroid; 2 | 3 | /* 4 | * This is an Android port of the Digital Voices Java sample, 5 | * from http://www.ics.uci.edu/~lopes/dv/dv.html 6 | * 7 | * See also http://github.com/diva/digital-voices 8 | * 9 | * Ported April 2010 by richard@jarkman.co.uk 10 | * 11 | * Usage notes: 12 | * 13 | * - Type something in and hit Play to have it encoded and played 14 | * 15 | * - Hit Listen to start the listen process 16 | * A status message will appear below the Listen button 17 | * When input is decoded, it'll show up below that 18 | * 19 | * I've halved kSamplingFrequency to 11025 Hz, but even so this code is teetering 20 | * on the edge of a performance problem when decoding audio. 21 | * 22 | * On a Nexus One, it keeps up, on a CLIQ it doesn't, and so a decoding 23 | * backlog accumulates quite quickly. You can see that from the status field. 24 | * 25 | * I've also doubled kDuration to 0.2 seconds per byte, which improved the error rate to something like 2%. 26 | * 27 | * Possible next steps: 28 | * - Rewrite decoding to work in ints rather than doubles, which should help with speed 29 | * - Rewrite audio handling to work in 16 bit not 8 bit, which should improve effective microphone sensitivity 30 | * - Rewrite decoding in C with the Android NDK 31 | * - Look for more efficient decoding algorithms to replace complexDetect 32 | */ 33 | 34 | 35 | import android.app.Activity; 36 | import android.net.Uri; 37 | import android.os.Bundle; 38 | 39 | 40 | import java.io.ByteArrayOutputStream; 41 | import java.io.File; 42 | import java.io.InputStream; 43 | import java.util.Timer; 44 | import java.util.TimerTask; 45 | 46 | 47 | import android.app.Activity; 48 | import android.content.Intent; 49 | import android.os.Bundle; 50 | import android.os.Handler; 51 | import android.util.Log; 52 | import android.view.View; 53 | import android.widget.Button; 54 | import android.widget.EditText; 55 | import android.widget.TextView; 56 | 57 | public class Main extends Activity { 58 | /** Called when the activity is first created. */ 59 | //Loopback l = null; 60 | 61 | MicrophoneListener microphoneListener = null; 62 | StreamDecoder sDecoder = null; 63 | ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(); 64 | Timer refreshTimer = null; 65 | 66 | Handler mHandler = new Handler(); 67 | 68 | TextView textListen; 69 | TextView textStatus; 70 | 71 | Uri mCreateDataUri = null; 72 | String mCreateDataType = null; 73 | String mCreateDataExtraText = null; 74 | 75 | 76 | @Override 77 | public void onCreate(Bundle savedInstanceState) { 78 | super.onCreate(savedInstanceState); 79 | setContentView(R.layout.main); 80 | 81 | textStatus = (TextView) findViewById(R.id.TextStatus); 82 | textListen = (TextView) findViewById(R.id.TextListen); 83 | 84 | Button t = (Button) findViewById(R.id.ButtonPlay); 85 | t.setOnClickListener(mPlayListener); 86 | 87 | t = (Button) findViewById(R.id.ButtonListen); 88 | t.setOnClickListener(mListenListener); 89 | 90 | final Intent intent = getIntent(); 91 | final String action = intent.getAction(); 92 | if (Intent.ACTION_SEND.equals(action)) 93 | { 94 | 95 | mCreateDataUri = intent.getData(); 96 | mCreateDataType = intent.getType(); 97 | 98 | if( mCreateDataUri == null ) 99 | { 100 | mCreateDataUri = intent.getParcelableExtra( Intent.EXTRA_STREAM ); 101 | 102 | 103 | } 104 | 105 | mCreateDataExtraText = intent.getStringExtra( Intent.EXTRA_TEXT ); 106 | 107 | if( mCreateDataUri == null ) 108 | mCreateDataType = null; 109 | 110 | // The new entry was created, so assume all will end well and 111 | // set the result to be returned. 112 | setResult(RESULT_OK, (new Intent()).setAction(null)); 113 | } 114 | 115 | } 116 | 117 | 118 | View.OnClickListener mPlayListener = new View.OnClickListener() 119 | { 120 | public void onClick(View v) 121 | { 122 | EditText e = (EditText) findViewById(R.id.EditTextToPlay); 123 | String s = e.getText().toString(); 124 | 125 | 126 | 127 | 128 | perform( s ); 129 | 130 | } 131 | }; 132 | 133 | 134 | View.OnClickListener mListenListener = new View.OnClickListener() 135 | { 136 | public void onClick(View v) 137 | { 138 | 139 | 140 | if( microphoneListener == null ) 141 | { 142 | 143 | listen(); 144 | ((Button)v).setText("Stop listening"); 145 | } 146 | else 147 | { 148 | 149 | 150 | stopListening(); 151 | ((Button)v).setText("Listen"); 152 | } 153 | 154 | } 155 | }; 156 | 157 | 158 | 159 | 160 | /* (non-Javadoc) 161 | * @see android.app.Activity#onPause() 162 | */ 163 | @Override 164 | protected void onPause() { 165 | 166 | //if( l != null ) 167 | // l.stopLoop(); 168 | 169 | super.onPause(); 170 | 171 | if( refreshTimer != null ) 172 | { 173 | refreshTimer.cancel(); 174 | refreshTimer = null; 175 | } 176 | 177 | stopListening(); 178 | } 179 | 180 | /* (non-Javadoc) 181 | * @see android.app.Activity#onResume() 182 | */ 183 | @Override 184 | protected void onResume() 185 | { 186 | super.onResume(); 187 | 188 | String sent = null; 189 | 190 | if( mCreateDataExtraText != null ) 191 | { 192 | sent = mCreateDataExtraText; 193 | } 194 | else if( mCreateDataType != null && mCreateDataType.startsWith("text/") ) 195 | { 196 | //read the URI into a string 197 | 198 | byte[] b = readDataFromUri( this.mCreateDataUri ); 199 | if( b != null ) 200 | sent = new String( b ); 201 | 202 | 203 | } 204 | 205 | if( sent != null ) 206 | { 207 | EditText e = (EditText) findViewById(R.id.EditTextToPlay); 208 | e.setText(sent); 209 | } 210 | 211 | 212 | refreshTimer = new Timer(); 213 | 214 | 215 | 216 | refreshTimer.schedule( 217 | new TimerTask() 218 | { 219 | @Override 220 | public void run() 221 | { 222 | 223 | mHandler.post(new Runnable() // have to do this on the UI thread 224 | { 225 | public void run() 226 | { 227 | updateResults(); 228 | 229 | 230 | } 231 | }); 232 | 233 | } 234 | }, 500, 500); 235 | 236 | } 237 | 238 | private void updateResults() 239 | { 240 | if( microphoneListener != null ) 241 | { 242 | textListen.setText(decodedStream.toString()); 243 | textStatus.setText(sDecoder.getStatusString()); 244 | } 245 | else 246 | { 247 | textStatus.setText(""); 248 | } 249 | 250 | } 251 | private void listen() 252 | { 253 | stopListening(); 254 | 255 | decodedStream.reset(); 256 | 257 | //the StreamDecoder uses the Decoder to decode samples put in its AudioBuffer 258 | // StreamDecoder starts a thread 259 | sDecoder = new StreamDecoder( decodedStream ); 260 | 261 | //the MicrophoneListener feeds the microphone samples into the AudioBuffer 262 | // MicrophoneListener starts a thread 263 | 264 | microphoneListener = new MicrophoneListener(sDecoder.getAudioBuffer()); 265 | System.out.println("Listening"); 266 | } 267 | 268 | private void stopListening() 269 | { 270 | if( microphoneListener != null ) 271 | microphoneListener.quit(); 272 | 273 | microphoneListener = null; 274 | 275 | if( sDecoder != null ) 276 | sDecoder.quit(); 277 | 278 | sDecoder = null; 279 | } 280 | 281 | private void perform( String input ) 282 | { 283 | 284 | try 285 | { 286 | 287 | //try to play the file 288 | System.out.println("Performing " + input); 289 | AudioUtils.performArray(input.getBytes()); 290 | } 291 | 292 | catch (Exception e){ 293 | System.out.println("Could not encode " + input + " because of " + e); 294 | } 295 | 296 | } 297 | 298 | /* 299 | private void encode( String inputFile, String outputFile ) 300 | { 301 | 302 | try 303 | { 304 | 305 | //There was an output file specified, so we should write the wav 306 | System.out.println("Encoding " + inputFile); 307 | AudioUtils.encodeFileToWav(new File(inputFile), new File(outputFile)); 308 | 309 | } 310 | catch (Exception e) 311 | { 312 | System.out.println("Could not encode " + inputFile + " because of " + e); 313 | } 314 | 315 | } 316 | */ 317 | 318 | private byte[] readDataFromUri( Uri uri ) 319 | { 320 | byte[] buffer = null; 321 | 322 | try 323 | { 324 | InputStream stream = getContentResolver().openInputStream(uri) ; 325 | 326 | int bytesAvailable = stream.available(); 327 | //int maxBufferSize = 1024; 328 | int bufferSize = bytesAvailable; //Math.min(bytesAvailable, maxBufferSize); 329 | int totalRead = 0; 330 | buffer = new byte[bufferSize]; 331 | 332 | // read file and write it into form... 333 | int bytesRead = stream.read(buffer, 0, bufferSize); 334 | while (bytesRead > 0) 335 | { 336 | bytesRead = stream.read(buffer, totalRead, bufferSize); 337 | totalRead += bytesRead; 338 | } 339 | } catch (Exception e) { 340 | // TODO Auto-generated catch block 341 | e.printStackTrace(); 342 | 343 | } 344 | 345 | return buffer; 346 | } 347 | 348 | } -------------------------------------------------------------------------------- /ask-simple-android/src/com/jarkman/ASKSimpleAndroid/Decoder.java: -------------------------------------------------------------------------------- 1 | 2 | package com.jarkman.ASKSimpleAndroid; 3 | 4 | /** 5 | * Copyright 2002 by the authors. All rights reserved. 6 | * 7 | * Author: Cristina V Lopes 8 | */ 9 | 10 | 11 | import java.io.ByteArrayOutputStream; 12 | import java.util.Date; 13 | import java.util.Vector; 14 | 15 | /** 16 | * This class contains the signal processing functions. 17 | * 18 | * @author CVL 19 | */ 20 | public class Decoder implements Constants { 21 | 22 | /** 23 | * @param signal the audio samples to search 24 | * @param signalStrengths this will be filled in with the strengths for each frequency (NOTE THIS SIDE EFFECT) 25 | * @param granularity a correlation will be determined every granularity samples (lower is slower) 26 | * @return the index in signal of the key sequence, or -1 if it wasn't found (in which case signalStrengths is trashed) 27 | */ 28 | public static int findKeySequence(byte[] signal, double[] signalStrengths, int granularity){ 29 | int maxCorrelationIndex = -1; 30 | double maxCorrelation = -1; 31 | double minSignal = 0.003; 32 | double acceptedSignal = 0.01; 33 | int i=0; 34 | for(i = 0; i <= signal.length - kSamplesPerDuration; i += granularity){ 35 | //test the correlation 36 | byte[] partialSignal = ArrayUtils.subarray(signal, i, kSamplesPerDuration); 37 | double corr = complexDetect(partialSignal, Constants.kHailFrequency) /* * 4 */; 38 | // System.out.println("Correlation at " + i + ":" + corr); 39 | if (corr > maxCorrelation){ 40 | maxCorrelation = corr; 41 | maxCorrelationIndex = i; 42 | } 43 | if(granularity <= 0){ 44 | break; 45 | } 46 | } 47 | 48 | //System.out.println("Searched to index:" + i); 49 | if (maxCorrelation < acceptedSignal && maxCorrelation > -1){ 50 | //System.out.println("Best Correlation:" + maxCorrelation); 51 | maxCorrelationIndex = -1; 52 | } 53 | //if(maxCorrelationIndex >= 0){ 54 | //System.out.println("\r\nMax Correlation:" + maxCorrelation + " index:" + maxCorrelationIndex); 55 | //System.out.println("signal.length:" + signal.length); 56 | //getKeySignalStrengths(ArrayUtils.subarray(signal, maxCorrelationIndex + kSamplesPerDuration, 57 | // kSamplesPerDuration * 2), 58 | // signalStrengths); 59 | //} 60 | 61 | return maxCorrelationIndex; 62 | } 63 | 64 | /** 65 | * @param startSignals the signal strengths of each of the frequencies 66 | * @param samples the samples 67 | * @return the decoded bytes 68 | */ 69 | public static byte[] decode(double[] startSignals, byte[] samples){ 70 | return decode(startSignals, getSignalStrengths(samples)); 71 | } 72 | 73 | /** 74 | * @param startSignals the signal strengths of each of the frequencies 75 | * @param signal the signal strengths for each frequency for each duration [strength][duration index] 76 | * SIDE EFFECT: THE signal PARAMETER WILL BE SCALED BY THE STARTSIGNALS 77 | * @return the decoded bytes 78 | */ 79 | private static byte[] decode(double[] startSignals, double[][] signal){ 80 | //normalize to the start signals 81 | for(int i = 0; i < (kBitsPerByte * kBytesPerDuration); i++){ 82 | for(int j = 0; j < signal[i].length; j++){ 83 | signal[i][j] = signal[i][j] / startSignals[i]; 84 | } 85 | } 86 | 87 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 88 | for(int i = 0; i < signal[0].length; i++){ 89 | for(int k = 0; k < kBytesPerDuration; k++){ 90 | byte value = 0; 91 | for(int j = 0; j < kBitsPerByte; j++){ 92 | if(signal[(k * kBitsPerByte) + j][i] > 0.4){ 93 | value = (byte)(value | ( 1 << j)); 94 | } else { 95 | } 96 | } 97 | baos.write(value); 98 | } 99 | } 100 | 101 | return baos.toByteArray(); 102 | 103 | } 104 | 105 | /** 106 | * @param input audio sample array 107 | * @return the signal strengths of each frequency in each duration: [signal strength][duration index] 108 | */ 109 | private static double[][] getSignalStrengths(byte[] input){ 110 | //detect the signal strength of each frequency in each duration 111 | int durations = input.length / kSamplesPerDuration; 112 | 113 | // rows are durations, cols are bit strengths 114 | double[][] signal = new double[kBitsPerByte * kBytesPerDuration][durations]; 115 | 116 | //for each duration, check each bit for representation in the input 117 | for(int i=0; i < durations; i++){ 118 | //separate this duration's input into its own array 119 | byte[] durationInput = ArrayUtils.subarray(input, i * kSamplesPerDuration, kSamplesPerDuration); 120 | 121 | //for each bit represented, detect 122 | for(int j = 0; j < kBitsPerByte * kBytesPerDuration; j++){ 123 | signal[j][i] = 124 | complexDetect(durationInput, Encoder.getFrequency(j)); 125 | /* 126 | if (j == 0) 127 | System.out.println("\nsignal[" + j + "][" + i + "]=" + signal [j][i]); 128 | else 129 | System.out.println("signal[" + j + "][" + i + "]=" + signal [j][i]); 130 | */ 131 | } 132 | } 133 | return signal; 134 | } 135 | 136 | public static void getKeySignalStrengths(byte[] signal, double[] signalStrengths){ 137 | byte[] partialSignal = ArrayUtils.subarray(signal, 0, kSamplesPerDuration); 138 | for(int j = 1; j < kBitsPerByte * kBytesPerDuration; j += 2){ 139 | signalStrengths[j] = complexDetect(partialSignal, Encoder.getFrequency(j)); 140 | } 141 | 142 | byte[] partialSignal2 = ArrayUtils.subarray(signal, kSamplesPerDuration, kSamplesPerDuration); 143 | for(int j = 0; j < kBitsPerByte * kBytesPerDuration; j += 2){ 144 | signalStrengths[j] = complexDetect(partialSignal2, Encoder.getFrequency(j)); 145 | //System.out.println(signalStrengths[j]); 146 | } 147 | } 148 | 149 | /** 150 | * @param signal audio samples 151 | * @param frequence the frequency to search for in signal 152 | * @return the strength of the correlation of the frequency in the signal 153 | */ 154 | 155 | /* 156 | // fast variant with cached trig values, rps, doesn't actually seem to run any faster ! 157 | 158 | private static double complexDetect(byte[] signal, int frequency){ 159 | double realSum = 0; 160 | double imaginarySum = 0; 161 | // y = e^(ju) = cos(u) + j * sin(u) 162 | 163 | int table = trigTableforFrequency( frequency ); 164 | double[] rowCos = (double[]) trigTablesCos.elementAt(table); 165 | double[] rowSin = (double[]) trigTablesSin.elementAt(table); 166 | 167 | for(int i = 0; i < signal.length; i++){ 168 | //System.out.println("signal[" +i +"]: " +signal[i] + "; convert: " + (signal[i])/(float)Constants.kFloatToByteShift); 169 | //realSum = realSum + (Math.cos(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 170 | //imaginarySum = imaginarySum + (Math.sin(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 171 | 172 | realSum = realSum + (fastCos( i, rowCos) * signal[i]); 173 | imaginarySum = imaginarySum + (fastSin(i, rowSin) * signal[i]); 174 | } 175 | 176 | realSum = realSum / Constants.kFloatToByteShift; 177 | imaginarySum = imaginarySum /Constants.kFloatToByteShift; 178 | 179 | //System.out.println("realSum=" + realSum + "; imSum=" + imaginarySum); 180 | double realAve = realSum/signal.length; 181 | double imaginaryAve = imaginarySum/signal.length; 182 | // System.out.println("u:" + u + " realAve:" + realAve + " imaginaryAve:" + imaginaryAve 183 | // + " \r\nfrequency:" + frequency + " signal.length:" + signal.length 184 | // + " realSum:" + realSum + " imaginarySum:" + imaginarySum 185 | // + "signal[100]:" + (signal[100]/(float)Constants.kFloatToByteShift)); 186 | // return the abs ( realAve + imaginaryAve * i ) which equals sqrt( realAve^2 + imaginaryAve^2) 187 | 188 | Thread.yield(); 189 | 190 | return Math.sqrt( (realAve * realAve) + (imaginaryAve * imaginaryAve) ); 191 | } 192 | 193 | static Vector trigTablesFreq = new Vector(); 194 | static Vector trigTablesCos = new Vector(); 195 | static Vector trigTablesSin = new Vector(); 196 | 197 | static int trigTableforFrequency( int frequency ) 198 | { 199 | int t; 200 | 201 | for( t = 0; t < trigTablesFreq.size(); t ++ ) 202 | { 203 | int f = ((Integer)trigTablesFreq.elementAt(t)).intValue(); 204 | 205 | if( f == frequency ) 206 | return t; // already precalculated this frequency 207 | } 208 | 209 | System.out.println("trigTableforFrequency calculating for " + frequency); 210 | 211 | trigTablesFreq.add(Integer.valueOf(frequency)); 212 | 213 | int samplesPerCycle = (int) (kSamplingFrequency / frequency); 214 | 215 | double[] rowCos = new double[samplesPerCycle]; 216 | double[] rowSin = new double[samplesPerCycle]; 217 | 218 | 219 | double u = 2 * Math.PI * ((double)frequency) / kSamplingFrequency; 220 | 221 | for( int i = 0; i < samplesPerCycle; i ++ ) 222 | { 223 | rowCos[i] = Math.cos(i * u); 224 | rowSin[i] = Math.sin(i * u); 225 | } 226 | 227 | trigTablesCos.add(rowCos); 228 | trigTablesSin.add(rowSin); 229 | 230 | return t; 231 | } 232 | 233 | static double fastCos( int sample, double[] row) 234 | { 235 | 236 | return row[ sample % row.length ]; 237 | 238 | } 239 | 240 | static double fastSin( int sample, double[] row) 241 | { 242 | return row[ sample % row.length ]; 243 | 244 | } 245 | */ 246 | 247 | // original implementaiton from ask-simple-java : 248 | private static double complexDetect(byte[] signal, double frequency){ 249 | double realSum = 0; 250 | double imaginarySum = 0; 251 | double u = 2 * Math.PI * frequency / kSamplingFrequency; 252 | // y = e^(ju) = cos(u) + j * sin(u) 253 | 254 | for(int i = 0; i < signal.length; i++){ 255 | //System.out.println("signal[" +i +"]: " +signal[i] + "; convert: " + (signal[i])/(float)Constants.kFloatToByteShift); 256 | realSum = realSum + (Math.cos(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 257 | imaginarySum = imaginarySum + (Math.sin(i * u) * (signal[i]/(float)Constants.kFloatToByteShift)); 258 | } 259 | //System.out.println("realSum=" + realSum + "; imSum=" + imaginarySum); 260 | double realAve = realSum/signal.length; 261 | double imaginaryAve = imaginarySum/signal.length; 262 | // System.out.println("u:" + u + " realAve:" + realAve + " imaginaryAve:" + imaginaryAve 263 | // + " \r\nfrequency:" + frequency + " signal.length:" + signal.length 264 | // + " realSum:" + realSum + " imaginarySum:" + imaginarySum 265 | // + "signal[100]:" + (signal[100]/(float)Constants.kFloatToByteShift)); 266 | // return the abs ( realAve + imaginaryAve * i ) which equals sqrt( realAve^2 + imaginaryAve^2) 267 | return Math.sqrt( (realAve * realAve) + (imaginaryAve * imaginaryAve) ); 268 | } 269 | 270 | 271 | } 272 | --------------------------------------------------------------------------------