Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch origin/sfml Excluding Merge-Ins
This is equivalent to a diff from 616e4e5238 to 2fdfa46cc7
2016-02-17
| ||
20:54 | added eraser toggle; change key bindings - Mod+Space "locks" a mode Leaf check-in: 2fdfa46cc7 user: jesse.mcclure@umassmed.edu tags: trunk, origin/sfml | |
19:07 | log transform added; cursor colog configurable check-in: b6d5daddeb user: jesse.mcclure@umassmed.edu tags: trunk, origin/sfml | |
2016-02-10
| ||
19:14 | added logFreq option to log transform frequencies - I advise *against* using this option check-in: 4fd5b5e5b0 user: jesse.mcclure@umassmed.edu tags: trunk, master | |
2015-06-16
| ||
15:46 | first commit for freeglut branch check-in: da496e6e10 user: jesse@mccluresk9.com tags: trunk, origin/sfml | |
2015-01-11
| ||
14:35 | remove broken link (wiki is moving) check-in: 616e4e5238 user: jesse@mccluresk9.com tags: trunk, master | |
14:34 | allow multiple frequency exports per session check-in: 1c506d359b user: jesse@mccluresk9.com tags: trunk, master | |
Deleted COPYING.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to Makefile.
|
| | | > | < > | > > > | | < | < | | < < < < | < < < < < < < < | < < < < > | | < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | PROG = fex VER = 3.0pre PREFIX ?= /usr CC = g++ CONFFILE ?= ${PREFIX}/share/${PROG}/${PROG}rc DEFS = -DPROGRAM_NAME=${PROG} -DPROGRAM_VER=${VER} -DDEFAULT_CONFIG=${CONFFILE} CPPFLAGS ?= -D_FORTIFY_SOURCE=2 CXXFLAGS ?= -march=native -O2 -pipe -fstack-protector-strong -Wstack-protector LDFLAGS ?= -Wl,-O1,--sort-common,--as-needed,-z,relro CXXFLAGS += $(shell pkg-config --cflags sfml-all fftw3) -std=c++11 ${DEFS} LDLIBS += $(shell pkg-config --libs sfml-all fftw3) MODULES = config fex fft spectrogram MANPAGES = ${PROG}.1 ${PROG}rc.5 VPATH = src ${PROG}: ${MODULES:%=%.o} %.o: %.cpp %.hpp install: ${PROG} @echo ${PROG}-${VER} is not a release candidate and is not suitable for installation clean: @rm -rf ${PROG}-*.tar.gz @rm -rf ${MODULES:%=%.o} distclean: clean @rm -rf ${PROG} tarball: distclean @tar -czf ${PROG}-${VER}.tar.gz * .PHONY: clean distclean install tarball |
Added Makefile.win.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | PROG = fex VER = 3.0pre PREFIX ?= /usr CC = i686-w64-mingw32-c++ CXX = i686-w64-mingw32-c++ CONFFILE ?= ${PREFIX}/share/${PROG}/${PROG}rc DEFS = -DPROGRAM_NAME=${PROG} -DPROGRAM_VER=${VER} -DDEFAULT_CONFIG=${CONFFILE} CPPFLAGS ?= -D_FORTIFY_SOURCE=2 CXXFLAGS ?= -O2 -pipe --param=ssp-buffer-size=4 LDFLAGS ?= -Wl,-O1,--sort-common,--as-needed CXXFLAGS += $(shell i686-w64-mingw32-pkg-config --cflags fftw3) -std=c++11 ${DEFS} LDLIBS += $(shell i686-w64-mingw32-pkg-config --libs fftw3) $(shell pkg-config --libs sfml-all) MODULES = config fex fft spectrogram MANPAGES = ${PROG}.1 ${PROG}rc.5 VPATH = src ${PROG}: ${MODULES:%=%.o} %.o: %.cpp %.hpp clean: @rm -rf ${PROG}-*.tar.gz @rm -rf ${MODULES:%=%.o} distclean: clean @rm -rf ${PROG} *.tar.gz tarball: distclean @tar -czf ${PROG}-${VER}.tar.gz * bundle: ${PROG} @mv ${PROG} ${PROG}.exe @cp /usr/i686-w64-mingw32/bin/libfftw3-3.dll ./ @cp /usr/i686-w64-mingw32/bin/libfftw3f-3.dll ./ @cp /usr/i686-w64-mingw32/bin/libfftw3l-3.dll ./ @cp /usr/i686-w64-mingw32/bin/sfml-audio-2.dll ./ @cp /usr/i686-w64-mingw32/bin/sfml-graphics-2.dll ./ @cp /usr/i686-w64-mingw32/bin/sfml-network-2.dll ./ @cp /usr/i686-w64-mingw32/bin/sfml-system-2.dll ./ @cp /usr/i686-w64-mingw32/bin/sfml-window-2.dll ./ @tar -czf ${PROG}-${VER}-win32.tar.gz ${PROG}.exe *.dll @rm -f *.dll .PHONY: clean distclean tarball bundle |
Changes to README.md.
|
| < < | < | < | < | | | | 1 2 3 4 5 6 7 8 | 2016.02.16 SFML branch is now minimally functional but not feature complete, well tested, or at all documented. The brave are free to tinker with it. Anyone with experience building for Windows is invited to fix my massively broken windows Makefile(s). 2016.01.10 Branch created to test SFML. This branch is not yet ready for use. Please use Master branch. |
Added TODO.
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | x Add eraser function to spectrogram module - needs testing - Too many "magic numbers" for eraser sizes... x Implement config file reading * offloaded to pyfex * fex only uses command line parameters * this makes porting across platforms easier * at least for me: I have no idea where a Win user would keep config files. - Complete the python front end x Parse config (ini) file to pass CLI parameters to fex binary - Show file selection dialogs for chosing songs, and saving data - Make either UI agnostic, or seperate versions for GTK, QT, etc - Are there Win / Mac native python dialog APIs? - Or do these need to use GTK/QT or Tk/Wx/... - Need input from python coders (pull requests welcomed!) |
Deleted doc/fex-1.tex.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted doc/fex-help-1.tex.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted doc/fex-help.1.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted doc/fex.1.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/README.md.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/archlinux/PKGBUILD.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/archlinux/fex.install.
|
| < < < < < < < < < < < |
Deleted pkg/deb/README.
|
| < < < < < < < < |
Deleted pkg/deb/fex-calc.spec.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/deb/makedeb.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/rpm/README.
|
| < < < < < < < |
Deleted pkg/rpm/fex.spec.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted pkg/rpm/makerpm.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added pyfex/default.ini.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | [defaults] window length = 256 bin size = 64 low pass = 01.25 high pass = 10.00 threshold = 18.00 floor = 24.00 log transform = false [colors] window background = #606468FF spectrogram background = #FFFFFFFF spectrogram foreground = #000000FF threshold = #58749848 point border = #3399FFFF point fill = #3399FF48 lines = #E86850FF cursor = #FF0000AA [warbler] #match file extension = wav #match mime type = audio/x-wav match path = warber threshold = 14.00 [sparrow] match path = sparrow threshold = 24.00 |
Added pyfex/fexUI.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/usr/bin/env python from sys import argv from subprocess import DEVNULL, check_output from configparser import ConfigParser ini = ConfigParser() ini.read('default.ini') conf = ini['defaults'] col = ini['colors'] args = [ './fex', '--winSize=' + conf['window length'], '--binSize=' + conf['bin size'], '--loPass=' + conf['low pass'], '--hiPass=' + conf['high pass'], '--threshold=' + conf['threshold'], '--floor=' + conf['floor'], '--log10=' + conf['log transform'], '--winBG=' + col['window background'], '--specBG=' + col['spectrogram background'], '--specFG=' + col['spectrogram foreground'], '--threshFG=' + col['threshold'], '--pointBG=' + col['point fill'], '--pointFG=' + col['point border'], '--linesFG=' + col['lines'] '--cursorFG=' + col['cursor'] ] def run_fex(files): if len(files) == 0: print("zero") # prompt with dialog returning list of files # run_fex(list of files) elif len(files) == 1: print(check_output(args + [files[0]],stderr=DEVNULL).decode("utf-8")) else: print("many") # loop through files running fex on each argv.remove(argv[0]) run_fex(argv) |
Deleted share/config.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted share/fex.desktop.
|
| < < < < < < < < < < |
Deleted share/icon.png.
cannot compute difference between binary files
Deleted src/config.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/config.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | #include "config.hpp" #include <getopt.h> sf::Color toColor(char *str) { if (str[0] == '#') ++str; unsigned long hex = strtoul(str,NULL,16); unsigned short int r,g,b,a; r = (hex & 0xFF000000) >> 24; g = (hex & 0x00FF0000) >> 16; b = (hex & 0x0000FF00) >> 8; a = (hex & 0x000000FF); return sf::Color(r,g,b,a); } bool toBool(char *str) { switch (str[0]) { case 'T': case 't': case '1': case 'Y': case 'y': return true; break; case 'F': case 'f': case '0': case 'N': case 'n': return false; break; default: return false; } } Config::Config(int argc, char *const *argv) { struct option opts[] = { { "winSize", optional_argument, 0, 0 }, { "binSize", optional_argument, 0, 0 }, { "loPass", optional_argument, 0, 0 }, { "hiPass", optional_argument, 0, 0 }, { "threshold", optional_argument, 0, 0 }, { "floor", optional_argument, 0, 0 }, { "log10", optional_argument, 0, 0 }, { "winBG", optional_argument, 0, 0 }, { "specBG", optional_argument, 0, 0 }, { "specFG", optional_argument, 0, 0 }, { "threshFG", optional_argument, 0, 0 }, { "pointBG", optional_argument, 0, 0 }, { "pointFG", optional_argument, 0, 0 }, { "linesFG", optional_argument, 0, 0 }, { "cursorFG", optional_argument, 0, 0 }, {0, 0, 0, 0 } }; int i, c, index; char noarg[] = ""; for (i = 1; (c=getopt_long_only(argc, argv, "", opts, &index)) != -1; ++i) { if (c != 0 || !optarg) continue; else if (index == 0) conf.winlen = atoi(optarg); else if (index == 1) conf.hop = atoi(optarg); else if (index == 2) conf.lopass = atof(optarg); else if (index == 3) conf.hipass = atof(optarg); else if (index == 4) conf.threshold = atof(optarg); else if (index == 5) conf.floor = atof(optarg); else if (index == 6) conf.log10 = toBool(optarg); else if (index == 7) conf.winBG = toColor(optarg); else if (index == 8) conf.specBG = toColor(optarg); else if (index == 9) conf.specFG = toColor(optarg); else if (index == 10) conf.threshFG = toColor(optarg); else if (index == 11) conf.pointBG = toColor(optarg); else if (index == 12) conf.pointFG = toColor(optarg); else if (index == 13) conf.linesFG = toColor(optarg); else if (index == 14) conf.cursorFG = toColor(optarg); } if (i > argc) exit(1); fname = strdup(argv[i]); char *dup = strdup(argv[i]); name = strdup(basename(dup)); free(dup); } Config::~Config() { if (name) free(name); if (fname) free(fname); } |
Deleted src/config.h.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/config.hpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <SFML/Audio.hpp> #include <SFML/Graphics.hpp> #include <string.h> #include <libgen.h> class Config { protected: struct { int winlen = 256; int hop = 64; double lopass = 01.25; double hipass = 10.00; double threshold = 18.00; double floor = 24.00; bool log10 = false; sf::Color winBG = sf::Color(0x60,0x64,0x68); sf::Color specBG = sf::Color(0xFF,0xFF,0xFF); sf::Color specFG = sf::Color(0x00,0x00,0x00); sf::Color threshFG = sf::Color(0x58,0x74,0x98,0x48); sf::Color pointFG = sf::Color(0x33,0x99,0xFF); sf::Color pointBG = sf::Color(0x33,0x99,0xFF,0x48); sf::Color linesFG = sf::Color(0xE8,0x68,0x50); sf::Color cursorFG = sf::Color(0xFF,0x00,0x00,0xAA); } conf; struct { bool overlay = true; bool cursor = false; bool eraser = false; } toggle; char *fname = NULL, *name = NULL; public: Config(int, char *const *); ~Config(); }; |
Deleted src/fex-gtk.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/fex.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/fex.cpp.
> > > > > > > > | 1 2 3 4 5 6 7 8 | #include "spectrogram.hpp" int main(int argc, char *const *argv) { Spectrogram spec(argc, argv); spec.mainLoop(); return EXIT_SUCCESS; } |
Deleted src/fex.h.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/fft.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/fft.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | #include "fft.hpp" // TODO configurable window_function // * Maybe these should just be defined from the command line. static double window_function[4] = { 0.5, 0.5, 0, 0 }; // hanning /* Hamming 0.54 0.46 0.00 0.00 Hanning 0.50 0.50 0.00 0.00 Blackman 0.42659 0.49656 0.076849 0.00 Nuttall 0.355768 0.487396 0.144232 0.012604 BlackNutt 0.3635819 0.4891775 0.1365995 0.0106411 BlackHarris 0.35875 0.48829 0.14128 0.01168 */ Fft::Fft(int argc, char *const *argv) : Config(argc, argv) { song.loadFromFile(fname); ntime = song.getSampleCount() / conf.hop; nfreq = conf.winlen / 2 + 0.5; time = (double *) malloc(ntime * sizeof(double)); freq = (double *) malloc(nfreq * sizeof(double)); amp = (double *) malloc(nfreq * ntime * sizeof(double)); erase = (unsigned short int *) calloc(nfreq * ntime, sizeof(unsigned short int *)); /* fill time and freq arrays */ double nyquist = (double) song.getSampleRate() / 2000.0; double dt = song.getDuration().asSeconds() / (double) ntime; double df = nyquist / (double) nfreq; int i; time[0] = freq[0] = 0.0; for (i = 1; i < ntime; ++i) time[i] = time[i-1] + dt; for (i = 1; i < nfreq; ++i) freq[i] = freq[i-1] + df; /* preare fftw */ fftw_complex *in, *out; fftw_plan plan; in = (fftw_complex *) fftw_malloc(conf.winlen * sizeof(fftw_complex)); out = (fftw_complex *) fftw_malloc(conf.winlen * sizeof(fftw_complex)); plan = fftw_plan_dft_1d(conf.winlen, in, out, FFTW_FORWARD, FFTW_ESTIMATE); /* windowing function */ double *window = (double *) malloc(conf.winlen * sizeof(double)); int t, f, pos; for (t = 0; t < conf.winlen; ++t) window[t] = window_function[0] - window_function[1] * cos(2 * M_PI * t / (conf.winlen - 1.0)) + window_function[2] * cos(4 * M_PI * t / (conf.winlen - 1.0)) - window_function[3] * cos(6 * M_PI * t / (conf.winlen - 1.0)); /* loop over signal */ const sf::Int16 *data = song.getSamples(); for (pos = 0, t = 0; pos < song.getSampleCount() && t < ntime; pos += conf.hop, ++t) { /* copy windowed chunk to in */ for (i = 0; i < conf.winlen; ++i) { if (pos + i < song.getSampleCount()) in[i][0] = data[pos + i] * window[i]; else in[i][0] = 0.0; in[i][1] = 0.0; } /* calculate fft and fill amp */ fftw_execute(plan); for (f = 0; f < nfreq; ++f) amp[nfreq * t + f] = sqrt(out[f][0] * out[f][0] + out[f][1] * out[f][1]); } /* zero unused bins */ for (; t < ntime; ++t) for (f = 0; f < nfreq; ++f) amp[nfreq * t + f] = 0; /* drestroy and free data */ fftw_destroy_plan(plan); fftw_free(out); fftw_free(in); free(window); /* psuedo bandpass filter */ int f_zero, f_count = 0; for (f = 0; freq[f] < conf.lopass; ++f); f_zero = f; for (; freq[f] <= conf.hipass; ++f) f_count++; for (f = 0; f < f_count; ++f) freq[f] = freq[f+f_zero]; freq = (double *) realloc(freq, f_count * sizeof(double)); double *new_amp = (double *) malloc(ntime * f_count * sizeof(double)); for (t = 0; t < ntime; ++t) for (f = 0; f < f_count; ++f) new_amp[f_count * t + f] = amp[nfreq * t + f + f_zero]; nfreq = f_count; free(amp); amp = new_amp; /* normalize, log transform and scale to dB */ double max = 0.0; for (t = 0; t < ntime; ++t) for (f = 0; f < nfreq; ++f) if (amp[nfreq * t + f] > max) max = amp[nfreq * t + f]; for (t = 0; t < ntime; ++t) for (f = 0; f < nfreq; ++f) amp[nfreq * t + f] = 10.0 * log10(amp[nfreq * t + f] / max); /* prepare point and line overlays */ lines.setPrimitiveType(sf::LinesStrip); lines.resize(ntime); points.setPrimitiveType(sf::Quads); points.resize(ntime * 4); for (t = 0; t < ntime; ++t) { lines[t].color = conf.linesFG; points[4*t].texCoords = sf::Vector2f(0,0); points[4*t+1].texCoords = sf::Vector2f(64,0); points[4*t+2].texCoords = sf::Vector2f(64,64); points[4*t+3].texCoords = sf::Vector2f(0,64); } t1 = f1 = 0; t2 = ntime; f2 = nfreq; /* prepare sprites */ makeSpectrogram(); makeThreshold(); makeOverlay(); } void Fft::setCrop(sf::Vector2f a, sf::Vector2f b) { if (a.x == -1) { /* reset */ t1 = f1 = 0; t2 = ntime; f2 = nfreq; } else { a.y *= -1; b.y *= -1; t1 = (a.x < b.x ? a.x : b.x); t2 = (a.x < b.x ? b.x : a.x); f1 = (a.y < b.y ? a.y : b.y); f2 = (a.y < b.y ? b.y : a.y); } makeThreshold(); makeOverlay(); } void Fft::makeSpectrogram() { int t, f; sf::Image img; img.create(ntime, nfreq); double dd; unsigned short int di; for (f = 0; f < nfreq; ++f) for (t = 0; t < ntime; ++t) { dd = 255 * (1 + amp[nfreq * t + f] / conf.floor); di = (dd > 255 ? 255 : (dd < 0 ? 0 : dd)); img.setPixel(t, f, sf::Color(255, 255, 255, di)); } texSpec.loadFromImage(img); texSpec.setSmooth(true); spec = sf::Sprite(texSpec); spec.setScale(1.0,-1.0); } void Fft::makeOverlay() { // * Change function name to "recalculate"? int t, f, fmax, n;; double max, pt, pf; pathLength = timeLength = 0.0; for (t = t1, n = 0; t < t2; ++t) { fmax = -1; max = std::numeric_limits<double>::lowest(); // TODO: is this negative? Should it be? for (f = f1; f < f2; ++f) { if (erase[nfreq * t + f]) continue; if (amp[nfreq * t + f] < max) continue; max = amp[nfreq * t + (fmax=f)]; } if (max < -1.0 * conf.threshold) continue; if (n) { /* increment lengths for fex calculation for all but first point */ if (conf.log10) pathLength += hypot(log10(freq[fmax])-log10(pf),time[t]-pt); else pathLength += hypot(freq[fmax]-pf,time[t]-pt); timeLength += time[t]-pt; } pf = freq[fmax]; pt = time[t]; lines[n].position = sf::Vector2f(t + 0.5, - fmax - 0.5); points[4*n].position = sf::Vector2f(t, - fmax); points[4*n+1].position = sf::Vector2f(t + 1, - fmax); points[4*n+2].position = sf::Vector2f(t + 1, - fmax - 1); points[4*n+3].position = sf::Vector2f(t, - fmax - 1); ++n; } for (; n < ntime; ++n) { /* set remaining lines to last point and points to off screen */ lines[n].position = lines[n-1].position; points[4*n].position = sf::Vector2f(-2,0); points[4*n+1].position = sf::Vector2f(-2,0); points[4*n+2].position = sf::Vector2f(-2,0); points[4*n+3].position = sf::Vector2f(-2,0); } } void Fft::makeThreshold() { int t, f; sf::Image img; img.create(ntime, nfreq); for (f = f1; f < f2; ++f) for (t = t1; t < t2; ++t) img.setPixel(t, f, sf::Color(255, 255, 255, (amp[nfreq * t + f] > - conf.threshold ? 255 : 0))); texThresh.loadFromImage(img); texThresh.setSmooth(true); thresh = sf::Sprite(texThresh); thresh.setScale(1.0,-1.0); } void Fft::eraseShift() { int t, f; unsigned short int *a; for (f = 0; f < nfreq; ++f) for (t = 0; t < ntime; ++t) { a = &erase[nfreq * t + f]; if (*a) *a |= (*a<<1); } } void Fft::eraseUndo() { int t, f; unsigned short int *a; for (f = 0; f < nfreq; ++f) for (t = 0; t < ntime; ++t) { a = &erase[nfreq * t + f]; if (!(*a & 0xFF)) *a = (*a>>1); } } void Fft::erasePoint(int x, int y) { if (x < ntime && y < nfreq) erase[nfreq * x + y] |= 0x01; } Fft::~Fft() { if (amp) free(amp); amp = NULL; if (time) free(time); time = NULL; if (freq) free(freq); freq = NULL; if (erase) free(erase); erase = NULL; ntime = nfreq = 0; } |
Added src/fft.hpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include "config.hpp" #include <math.h> #include <fftw3.h> #include <SFML/Audio.hpp> class Fft : public Config { private: sf::Texture texSpec, texThresh; sf::VertexArray points, lines; double *freq = NULL, *time = NULL, *amp = NULL; unsigned short int *erase = NULL; int t1, t2, f1, f2; protected: sf::Sprite spec, thresh; sf::SoundBuffer song; int ntime, nfreq; double pathLength, timeLength; void makeSpectrogram(); void makeThreshold(); void makeOverlay(); void setCrop(sf::Vector2f, sf::Vector2f); sf::FloatRect getCrop() { return sf::FloatRect(t1, f1, t2 - t1, f2 - f1); } sf::VertexArray const &getPoints() const { return points; }; sf::VertexArray const &getLines() const { return lines; }; void eraseShift(); void erasePoint(int, int); void eraseUndo(); public: Fft(int, char *const *); ~Fft(); }; |
Deleted src/icon.xpm.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/spectro.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/spectrogram.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | #include "spectrogram.hpp" #include <unistd.h> Spectrogram::Spectrogram(int argc, char *const *argv) : Fft(argc, argv) { // if (getenv("FEX_FULLSCREEN")) win.create(sf::VideoMode::getDesktopMode(), "Fex", sf::Style::Fullscreen); // else // win.create(sf::VideoMode(640,480), "Fex"); aspect = (ntime * win.getSize().y * conf.hop) / (float) (nfreq * win.getSize().x * conf.winlen); view.reset(sf::FloatRect(0, -nfreq * (1 + aspect)/2.0, ntime, nfreq * aspect)); win.setView(view); spec.setColor(conf.specFG); sf::RenderTexture render_ball; render_ball.setSmooth(true); render_ball.create(64,64); sf::CircleShape dot(32); dot.setFillColor(conf.pointBG); dot.setOutlineColor(conf.pointFG); dot.setOutlineThickness(-8); render_ball.draw(dot); ball = render_ball.getTexture(); back.setSize(sf::Vector2f(ntime,-nfreq)); back.setFillColor(conf.specBG); mouse.x = ntime / 2; mouse.y = - nfreq / 2; crop1 = sf::Vector2f(-1, -1); eraser.setSize(sf::Vector2f(4, 18)); eraser.setFillColor(sf::Color(255,200,0,200)); eraser.setPosition(0, 0); eraser.setOrigin(2, 10); } Spectrogram::~Spectrogram() { } int Spectrogram::mainLoop() { sf::Event ev; while (win.isOpen() && win.waitEvent(ev)) { evHandler(ev); while (win.pollEvent(ev)) evHandler(ev); drawMain(); win.display(); char out[256]; snprintf(out,255,"%s - (%0.3fs, %0.3fKHz) FE: %lf ", name, song.getDuration().asSeconds() * mouse.x / ntime, conf.lopass - (conf.hipass - conf.lopass) * mouse.y / nfreq, 1234.5 ); win.setTitle(sf::String(out)); } printf("%lf\t%lf\t%lf\n", pathLength, timeLength, pathLength / timeLength); return 0; } void Spectrogram::evHandler(sf::Event ev) { switch (ev.type) { case sf::Event::Closed: evClose(ev); break; case sf::Event::KeyPressed: evKeyPress(ev); break; case sf::Event::KeyReleased: evKeyRelease(ev); break; case sf::Event::MouseMoved: evMouseMove(ev); break; case sf::Event::MouseButtonPressed: evMouseButton(ev); break; case sf::Event::MouseWheelScrolled: evMouseWheel(ev); break; case sf::Event::Resized: evResize(ev); break; } } void Spectrogram::drawMain() { win.clear(conf.winBG); win.draw(back); win.draw(spec); thresh.setColor(conf.threshFG); if (toggle.overlay) { win.draw(thresh); win.draw(getPoints(), &ball); win.draw(getLines()); if (toggle.cursor) { if (crop1.x > 0) drawCursor(crop1.x, crop1.y); drawCursor(mouse.x, mouse.y); } if (toggle.eraser) win.draw(eraser); } } void Spectrogram::drawCursor(float x, float y) { sf::RectangleShape lineV(sf::Vector2f(1, nfreq)); lineV.setFillColor(conf.cursorFG); lineV.setPosition(view.getViewport().left + x, -nfreq); win.draw(lineV); sf::RectangleShape lineH(sf::Vector2f(ntime, 1)); lineH.setFillColor(conf.cursorFG); lineH.setPosition(0,view.getViewport().top + y); win.draw(lineH); } void Spectrogram::listen(float speed) { sf::Sound snd(song); snd.play(); snd.setPitch(speed); while(snd.getStatus() == 2) { sf::RectangleShape line(sf::Vector2f(10, nfreq)); line.setFillColor(sf::Color(0,255,0,120)); line.setPosition(ntime * (snd.getPlayingOffset() / song.getDuration()), -nfreq); drawMain(); win.draw(line); win.display(); } } void Spectrogram::evClose(sf::Event ev) { } void Spectrogram::evKeyPress(sf::Event ev) { if (ev.key.code == sf::Keyboard::LShift) toggle.cursor = !toggle.cursor; if (ev.key.code == sf::Keyboard::RShift) toggle.cursor = !toggle.cursor; if (ev.key.code == sf::Keyboard::LAlt) toggle.eraser = !toggle.eraser; if (ev.key.code == sf::Keyboard::RAlt) toggle.eraser = !toggle.eraser; if (ev.key.control) switch (ev.key.code) { case sf::Keyboard::Space: toggle.overlay = !toggle.overlay; break; case sf::Keyboard::Right: conf.floor -= 0.25; makeSpectrogram(); break; case sf::Keyboard::Left: conf.floor += 0.25; makeSpectrogram(); break; case sf::Keyboard::Up: conf.threshold -= 0.25; makeOverlay(); break; case sf::Keyboard::Down: conf.threshold += 0.25; makeOverlay(); break; case sf::Keyboard::Q: win.close(); break; case sf::Keyboard::Num1: listen(1.0); break; case sf::Keyboard::Num2: listen(0.5); break; case sf::Keyboard::Num3: listen(0.333); break; case sf::Keyboard::Num4: listen(0.25); break; case sf::Keyboard::Num5: listen(0.2); break; } else if (ev.key.alt) switch (ev.key.code) { case sf::Keyboard::Space: toggle.eraser = !toggle.eraser; break; } if (ev.key.shift) switch (ev.key.code) { case sf::Keyboard::Space: toggle.cursor = !toggle.cursor; break; } } void Spectrogram::evKeyRelease(sf::Event ev) { if (ev.key.code == sf::Keyboard::LShift) toggle.cursor = !toggle.cursor; if (ev.key.code == sf::Keyboard::RShift) toggle.cursor = !toggle.cursor; if (ev.key.code == sf::Keyboard::LAlt) toggle.eraser = !toggle.eraser; if (ev.key.code == sf::Keyboard::RAlt) toggle.eraser = !toggle.eraser; } void Spectrogram::evResize(sf::Event ev) { aspect = (ntime * win.getSize().y * conf.hop) / (float) (nfreq * win.getSize().x * conf.winlen); } void Spectrogram::evMouseMove(sf::Event ev) { sf::Vector2f prev = mouse; mouse = win.mapPixelToCoords(sf::Vector2i(ev.mouseMove.x,ev.mouseMove.y)); if (toggle.overlay && toggle.cursor) { // } else if (toggle.overlay && toggle.eraser) { if (sf::Mouse::isButtonPressed(sf::Mouse::Left) && toggle.eraser) erase(); } else { if (sf::Mouse::isButtonPressed(sf::Mouse::Right)) { view.setSize(view.getSize().x + prev.x - mouse.x, view.getSize().y + prev.y - mouse.y); view.move((prev.x - mouse.x)/2.0, (prev.y - mouse.y)/2.0); } if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) { view.move(prev.x - mouse.x, prev.y - mouse.y); } win.setView(view); } mouse = win.mapPixelToCoords(sf::Vector2i(ev.mouseMove.x,ev.mouseMove.y)); float in_bounds = 1.0; if (mouse.x < 0) in_bounds = mouse.x = 0.0; else if (mouse.x > ntime) in_bounds = mouse.x = ntime; if (mouse.y > 0) in_bounds = mouse.y = 0.0; else if (mouse.y < - nfreq) in_bounds = mouse.y = - nfreq; if (in_bounds == 1.0) eraser.setPosition(mouse); else eraser.setPosition(sf::Vector2f(-ntime,nfreq)); } void Spectrogram::erase() { sf::FloatRect rect = eraser.getGlobalBounds(); sf::Transform t = eraser.getInverseTransform(); sf::Vector2f point; int i, j; for (i = rect.left - 0.5; i < rect.left + rect.width + 1; ++i) { for (j = rect.top - 0.5; j < rect.top + rect.height + 1; ++j) { point = t.transformPoint(i + 0.5, j - 0.5); if (point.x > -0.25 && point.x < 4.25 && point.y > -0.25 && point.y < 18.25) erasePoint(i, -j); } } makeOverlay(); } void Spectrogram::evMouseButton(sf::Event ev) { sf::FloatRect rect; if (toggle.overlay && toggle.cursor) switch (ev.mouseButton.button) { /* set a crop window: requires 2 presses */ case sf::Mouse::Button::Left: if (crop1.x < 0) crop1 = mouse; else { setCrop(crop1, mouse); crop1.x = -1; } break; /* zoom to fit crop area to window */ case sf::Mouse::Button::Middle: rect = getCrop(); rect.top -= rect.height * (1 + aspect) / 2.0; rect.height *= aspect; view.reset(rect); win.setView(view); break; /* reset crop to full song */ case sf::Mouse::Button::Right: crop1.x = -1; setCrop(crop1, mouse); break; } else if (toggle.overlay && toggle.eraser) switch (ev.mouseButton.button) { case sf::Mouse::Button::Left: if (toggle.eraser) erase(); break; } else switch (ev.mouseButton.button) { /* NOTE: left and right are handled in evMouseMove */ /* zoom to fit song to window */ case sf::Mouse::Button::Middle: view.reset(sf::FloatRect(0, -nfreq * (1 + aspect)/2.0, ntime, nfreq * aspect)); win.setView(view); break; } } void Spectrogram::evMouseWheel(sf::Event ev) { bool vert = (ev.mouseWheelScroll.wheel == 0); float dx = ev.mouseWheelScroll.delta; if (toggle.overlay && toggle.cursor) { // TODO: anything here? Probably not. } if (toggle.overlay && toggle.eraser) { /* rotate */ if (!vert) { eraser.rotate(-dx); return; } /* or scale */ sf::Vector2f scale = eraser.getScale(); float r = eraser.getRotation(); if (r < 30 || r > 330 || (r > 150 && r < 210) ) scale.y += 0.008 * dx; else if ( (r > 60 && r < 120) || (r > 240 && r < 300) ) scale.x += 0.008 * dx; else { scale.x += 0.004 * dx; scale.y += 0.004 * dx; } if (scale.x < 0.5) scale.x = 0.5; else if (scale.x > 5.0) scale.x = 5.0; if (scale.y < 0.5) scale.y = 0.5; else if (scale.y > 5.0) scale.y = 5.0; eraser.setScale(scale); } /* No modifier wheel movements are for zooming: */ else if (vert) { // TODO: make 0.0075 step size customizable? float vx = view.getSize().x / ntime, vy = view.getSize().y / nfreq; if (dx < 0 && vx > 1.20 && vy > 1.20) return; if (dx > 0 && vx < 0.01 && vy > 0.01) return; view.zoom(1.0 - 0.0075 * dx); win.setView(view); } } |
Added src/spectrogram.hpp.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include "fft.hpp" #include <SFML/Audio.hpp> #include <SFML/Graphics.hpp> #include <SFML/Window.hpp> class Spectrogram : public Fft { private: sf::RenderWindow win; sf::View view; sf::Vector2f mouse, crop1; sf::RectangleShape back; sf::Texture ball; sf::RectangleShape eraser; bool mod_ctrl, mod_shift, mod_alt; float aspect; void drawMain(); void drawCursor(float, float); void drawHud(); void erase(); void checkModKeys(); void evHandler(sf::Event); void evClose(sf::Event); void evKeyPress(sf::Event); void evKeyRelease(sf::Event); void evMouseButton(sf::Event); void evMouseMove(sf::Event); void evMouseWheel(sf::Event); void evResize(sf::Event); protected: void listen(float=1.0); public: int mainLoop(); Spectrogram(int, char *const *); ~Spectrogram(); }; |
Deleted src/wave.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/xlib.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/xlib_actions.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/xlib_events.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/xlib_toolwin.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |