├── .gitignore ├── LICENSE ├── README.MD ├── package-lock.json ├── package.json ├── src ├── algorithm.ts ├── api.ts ├── index.ts ├── signature-format.ts ├── types.ts └── utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## Shazam Api for node-js 2 | 3 | This library is heavily based on [Numenorean's work](https://github.com/Numenorean/ShazamAPI). 4 | 5 | Usage: 6 | 7 | ```js 8 | import { Shazam, s16LEToSamplesArray } from 'shazam-api'; 9 | import fs from 'fs'; 10 | 11 | const shazam = new Shazam(); 12 | const fileContents = fs.readFileSync("test.pcm"); 13 | const samples = s16LEToSamplesArray(fileContents); 14 | 15 | const songData = await shazam.recognizeSong(samples); 16 | console.log(songData); 17 | ``` 18 | 19 | The data passed to s16LEToSamplesArray needs to be raw PCM with: 20 | - Sample rate set to 16000Hz 21 | - Bit depth set to 16 bits 22 | - 1 channel of audio 23 | 24 | Such a file can be generated with ffmpeg using the command: 25 | ``` 26 | ffmpeg -i -ar 16000 -ac 1 -f s16le test.pcm 27 | ``` 28 | 29 | Or within node or the browser thanks to [ffmpeg.wasm](https://www.npmjs.com/package/@ffmpeg/ffmpeg) 30 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shazam-api", 3 | "version": "0.2.1-0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "shazam-api", 9 | "version": "0.2.1-0", 10 | "license": "GPL-2.0", 11 | "dependencies": { 12 | "buffer": "^6.0.3", 13 | "fft.js": "^4.0.4", 14 | "node-fetch": "^2.6.7" 15 | }, 16 | "devDependencies": { 17 | "@types/node-fetch": "^2.6.2", 18 | "@types/numjs": "^0.16.2", 19 | "ts-node": "^10.9.1", 20 | "typescript": "^4.8.3" 21 | } 22 | }, 23 | "node_modules/@cspotcode/source-map-support": { 24 | "version": "0.8.1", 25 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 26 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 27 | "dev": true, 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@jridgewell/resolve-uri": { 36 | "version": "3.1.0", 37 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 38 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 39 | "dev": true, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/sourcemap-codec": { 45 | "version": "1.4.14", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 47 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 48 | "dev": true 49 | }, 50 | "node_modules/@jridgewell/trace-mapping": { 51 | "version": "0.3.9", 52 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 53 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 54 | "dev": true, 55 | "dependencies": { 56 | "@jridgewell/resolve-uri": "^3.0.3", 57 | "@jridgewell/sourcemap-codec": "^1.4.10" 58 | } 59 | }, 60 | "node_modules/@tsconfig/node10": { 61 | "version": "1.0.9", 62 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 63 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 64 | "dev": true 65 | }, 66 | "node_modules/@tsconfig/node12": { 67 | "version": "1.0.11", 68 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 69 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 70 | "dev": true 71 | }, 72 | "node_modules/@tsconfig/node14": { 73 | "version": "1.0.3", 74 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 75 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 76 | "dev": true 77 | }, 78 | "node_modules/@tsconfig/node16": { 79 | "version": "1.0.3", 80 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 81 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 82 | "dev": true 83 | }, 84 | "node_modules/@types/ndarray": { 85 | "version": "1.0.11", 86 | "resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.0.11.tgz", 87 | "integrity": "sha512-hOZVTN24zDHwCHaW7mF9n1vHJt83fZhNZ0YYRBwQGhA96yBWWDPTDDlqJatagHIOJB0a4xoNkNc+t/Cxd+6qUA==", 88 | "dev": true 89 | }, 90 | "node_modules/@types/node": { 91 | "version": "18.7.19", 92 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", 93 | "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==", 94 | "dev": true 95 | }, 96 | "node_modules/@types/node-fetch": { 97 | "version": "2.6.2", 98 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", 99 | "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", 100 | "dev": true, 101 | "dependencies": { 102 | "@types/node": "*", 103 | "form-data": "^3.0.0" 104 | } 105 | }, 106 | "node_modules/@types/numjs": { 107 | "version": "0.16.2", 108 | "resolved": "https://registry.npmjs.org/@types/numjs/-/numjs-0.16.2.tgz", 109 | "integrity": "sha512-iS3YMJo9Xh//2WTPFlfGPwkqi9HVH/EA5V+hmAmLgwmBImP5mAspVUxLVYhwvohO6Hmjjigs7YRHGyB/KMLuWQ==", 110 | "dev": true, 111 | "dependencies": { 112 | "@types/ndarray": "*" 113 | } 114 | }, 115 | "node_modules/acorn": { 116 | "version": "8.8.0", 117 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", 118 | "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", 119 | "dev": true, 120 | "bin": { 121 | "acorn": "bin/acorn" 122 | }, 123 | "engines": { 124 | "node": ">=0.4.0" 125 | } 126 | }, 127 | "node_modules/acorn-walk": { 128 | "version": "8.2.0", 129 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 130 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 131 | "dev": true, 132 | "engines": { 133 | "node": ">=0.4.0" 134 | } 135 | }, 136 | "node_modules/arg": { 137 | "version": "4.1.3", 138 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 139 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 140 | "dev": true 141 | }, 142 | "node_modules/asynckit": { 143 | "version": "0.4.0", 144 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 145 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 146 | "dev": true 147 | }, 148 | "node_modules/base64-js": { 149 | "version": "1.5.1", 150 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 151 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 152 | "funding": [ 153 | { 154 | "type": "github", 155 | "url": "https://github.com/sponsors/feross" 156 | }, 157 | { 158 | "type": "patreon", 159 | "url": "https://www.patreon.com/feross" 160 | }, 161 | { 162 | "type": "consulting", 163 | "url": "https://feross.org/support" 164 | } 165 | ] 166 | }, 167 | "node_modules/buffer": { 168 | "version": "6.0.3", 169 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 170 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 171 | "funding": [ 172 | { 173 | "type": "github", 174 | "url": "https://github.com/sponsors/feross" 175 | }, 176 | { 177 | "type": "patreon", 178 | "url": "https://www.patreon.com/feross" 179 | }, 180 | { 181 | "type": "consulting", 182 | "url": "https://feross.org/support" 183 | } 184 | ], 185 | "dependencies": { 186 | "base64-js": "^1.3.1", 187 | "ieee754": "^1.2.1" 188 | } 189 | }, 190 | "node_modules/combined-stream": { 191 | "version": "1.0.8", 192 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 193 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 194 | "dev": true, 195 | "dependencies": { 196 | "delayed-stream": "~1.0.0" 197 | }, 198 | "engines": { 199 | "node": ">= 0.8" 200 | } 201 | }, 202 | "node_modules/create-require": { 203 | "version": "1.1.1", 204 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 205 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 206 | "dev": true 207 | }, 208 | "node_modules/delayed-stream": { 209 | "version": "1.0.0", 210 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 211 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 212 | "dev": true, 213 | "engines": { 214 | "node": ">=0.4.0" 215 | } 216 | }, 217 | "node_modules/diff": { 218 | "version": "4.0.2", 219 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 220 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 221 | "dev": true, 222 | "engines": { 223 | "node": ">=0.3.1" 224 | } 225 | }, 226 | "node_modules/fft.js": { 227 | "version": "4.0.4", 228 | "resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz", 229 | "integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==", 230 | "license": "MIT" 231 | }, 232 | "node_modules/form-data": { 233 | "version": "3.0.1", 234 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 235 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 236 | "dev": true, 237 | "dependencies": { 238 | "asynckit": "^0.4.0", 239 | "combined-stream": "^1.0.8", 240 | "mime-types": "^2.1.12" 241 | }, 242 | "engines": { 243 | "node": ">= 6" 244 | } 245 | }, 246 | "node_modules/ieee754": { 247 | "version": "1.2.1", 248 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 249 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 250 | "funding": [ 251 | { 252 | "type": "github", 253 | "url": "https://github.com/sponsors/feross" 254 | }, 255 | { 256 | "type": "patreon", 257 | "url": "https://www.patreon.com/feross" 258 | }, 259 | { 260 | "type": "consulting", 261 | "url": "https://feross.org/support" 262 | } 263 | ] 264 | }, 265 | "node_modules/make-error": { 266 | "version": "1.3.6", 267 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 268 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 269 | "dev": true 270 | }, 271 | "node_modules/mime-db": { 272 | "version": "1.52.0", 273 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 274 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 275 | "dev": true, 276 | "engines": { 277 | "node": ">= 0.6" 278 | } 279 | }, 280 | "node_modules/mime-types": { 281 | "version": "2.1.35", 282 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 283 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 284 | "dev": true, 285 | "dependencies": { 286 | "mime-db": "1.52.0" 287 | }, 288 | "engines": { 289 | "node": ">= 0.6" 290 | } 291 | }, 292 | "node_modules/node-fetch": { 293 | "version": "2.6.7", 294 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 295 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 296 | "dependencies": { 297 | "whatwg-url": "^5.0.0" 298 | }, 299 | "engines": { 300 | "node": "4.x || >=6.0.0" 301 | }, 302 | "peerDependencies": { 303 | "encoding": "^0.1.0" 304 | }, 305 | "peerDependenciesMeta": { 306 | "encoding": { 307 | "optional": true 308 | } 309 | } 310 | }, 311 | "node_modules/tr46": { 312 | "version": "0.0.3", 313 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 314 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 315 | }, 316 | "node_modules/ts-node": { 317 | "version": "10.9.1", 318 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 319 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 320 | "dev": true, 321 | "dependencies": { 322 | "@cspotcode/source-map-support": "^0.8.0", 323 | "@tsconfig/node10": "^1.0.7", 324 | "@tsconfig/node12": "^1.0.7", 325 | "@tsconfig/node14": "^1.0.0", 326 | "@tsconfig/node16": "^1.0.2", 327 | "acorn": "^8.4.1", 328 | "acorn-walk": "^8.1.1", 329 | "arg": "^4.1.0", 330 | "create-require": "^1.1.0", 331 | "diff": "^4.0.1", 332 | "make-error": "^1.1.1", 333 | "v8-compile-cache-lib": "^3.0.1", 334 | "yn": "3.1.1" 335 | }, 336 | "bin": { 337 | "ts-node": "dist/bin.js", 338 | "ts-node-cwd": "dist/bin-cwd.js", 339 | "ts-node-esm": "dist/bin-esm.js", 340 | "ts-node-script": "dist/bin-script.js", 341 | "ts-node-transpile-only": "dist/bin-transpile.js", 342 | "ts-script": "dist/bin-script-deprecated.js" 343 | }, 344 | "peerDependencies": { 345 | "@swc/core": ">=1.2.50", 346 | "@swc/wasm": ">=1.2.50", 347 | "@types/node": "*", 348 | "typescript": ">=2.7" 349 | }, 350 | "peerDependenciesMeta": { 351 | "@swc/core": { 352 | "optional": true 353 | }, 354 | "@swc/wasm": { 355 | "optional": true 356 | } 357 | } 358 | }, 359 | "node_modules/typescript": { 360 | "version": "4.8.3", 361 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", 362 | "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", 363 | "dev": true, 364 | "bin": { 365 | "tsc": "bin/tsc", 366 | "tsserver": "bin/tsserver" 367 | }, 368 | "engines": { 369 | "node": ">=4.2.0" 370 | } 371 | }, 372 | "node_modules/v8-compile-cache-lib": { 373 | "version": "3.0.1", 374 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 375 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 376 | "dev": true 377 | }, 378 | "node_modules/webidl-conversions": { 379 | "version": "3.0.1", 380 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 381 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 382 | }, 383 | "node_modules/whatwg-url": { 384 | "version": "5.0.0", 385 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 386 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 387 | "dependencies": { 388 | "tr46": "~0.0.3", 389 | "webidl-conversions": "^3.0.0" 390 | } 391 | }, 392 | "node_modules/yn": { 393 | "version": "3.1.1", 394 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 395 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 396 | "dev": true, 397 | "engines": { 398 | "node": ">=6" 399 | } 400 | } 401 | }, 402 | "dependencies": { 403 | "@cspotcode/source-map-support": { 404 | "version": "0.8.1", 405 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 406 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 407 | "dev": true, 408 | "requires": { 409 | "@jridgewell/trace-mapping": "0.3.9" 410 | } 411 | }, 412 | "@jridgewell/resolve-uri": { 413 | "version": "3.1.0", 414 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 415 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 416 | "dev": true 417 | }, 418 | "@jridgewell/sourcemap-codec": { 419 | "version": "1.4.14", 420 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 421 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 422 | "dev": true 423 | }, 424 | "@jridgewell/trace-mapping": { 425 | "version": "0.3.9", 426 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 427 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 428 | "dev": true, 429 | "requires": { 430 | "@jridgewell/resolve-uri": "^3.0.3", 431 | "@jridgewell/sourcemap-codec": "^1.4.10" 432 | } 433 | }, 434 | "@tsconfig/node10": { 435 | "version": "1.0.9", 436 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 437 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 438 | "dev": true 439 | }, 440 | "@tsconfig/node12": { 441 | "version": "1.0.11", 442 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 443 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 444 | "dev": true 445 | }, 446 | "@tsconfig/node14": { 447 | "version": "1.0.3", 448 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 449 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 450 | "dev": true 451 | }, 452 | "@tsconfig/node16": { 453 | "version": "1.0.3", 454 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 455 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 456 | "dev": true 457 | }, 458 | "@types/ndarray": { 459 | "version": "1.0.11", 460 | "resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.0.11.tgz", 461 | "integrity": "sha512-hOZVTN24zDHwCHaW7mF9n1vHJt83fZhNZ0YYRBwQGhA96yBWWDPTDDlqJatagHIOJB0a4xoNkNc+t/Cxd+6qUA==", 462 | "dev": true 463 | }, 464 | "@types/node": { 465 | "version": "18.7.19", 466 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", 467 | "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==", 468 | "dev": true 469 | }, 470 | "@types/node-fetch": { 471 | "version": "2.6.2", 472 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", 473 | "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", 474 | "dev": true, 475 | "requires": { 476 | "@types/node": "*", 477 | "form-data": "^3.0.0" 478 | } 479 | }, 480 | "@types/numjs": { 481 | "version": "0.16.2", 482 | "resolved": "https://registry.npmjs.org/@types/numjs/-/numjs-0.16.2.tgz", 483 | "integrity": "sha512-iS3YMJo9Xh//2WTPFlfGPwkqi9HVH/EA5V+hmAmLgwmBImP5mAspVUxLVYhwvohO6Hmjjigs7YRHGyB/KMLuWQ==", 484 | "dev": true, 485 | "requires": { 486 | "@types/ndarray": "*" 487 | } 488 | }, 489 | "acorn": { 490 | "version": "8.8.0", 491 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", 492 | "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", 493 | "dev": true 494 | }, 495 | "acorn-walk": { 496 | "version": "8.2.0", 497 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 498 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 499 | "dev": true 500 | }, 501 | "arg": { 502 | "version": "4.1.3", 503 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 504 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 505 | "dev": true 506 | }, 507 | "asynckit": { 508 | "version": "0.4.0", 509 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 510 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 511 | "dev": true 512 | }, 513 | "base64-js": { 514 | "version": "1.5.1", 515 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 516 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 517 | }, 518 | "buffer": { 519 | "version": "6.0.3", 520 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 521 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 522 | "requires": { 523 | "base64-js": "^1.3.1", 524 | "ieee754": "^1.2.1" 525 | } 526 | }, 527 | "combined-stream": { 528 | "version": "1.0.8", 529 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 530 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 531 | "dev": true, 532 | "requires": { 533 | "delayed-stream": "~1.0.0" 534 | } 535 | }, 536 | "create-require": { 537 | "version": "1.1.1", 538 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 539 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 540 | "dev": true 541 | }, 542 | "delayed-stream": { 543 | "version": "1.0.0", 544 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 545 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 546 | "dev": true 547 | }, 548 | "diff": { 549 | "version": "4.0.2", 550 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 551 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 552 | "dev": true 553 | }, 554 | "fft.js": { 555 | "version": "4.0.4", 556 | "resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz", 557 | "integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==" 558 | }, 559 | "form-data": { 560 | "version": "3.0.1", 561 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 562 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 563 | "dev": true, 564 | "requires": { 565 | "asynckit": "^0.4.0", 566 | "combined-stream": "^1.0.8", 567 | "mime-types": "^2.1.12" 568 | } 569 | }, 570 | "ieee754": { 571 | "version": "1.2.1", 572 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 573 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 574 | }, 575 | "make-error": { 576 | "version": "1.3.6", 577 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 578 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 579 | "dev": true 580 | }, 581 | "mime-db": { 582 | "version": "1.52.0", 583 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 584 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 585 | "dev": true 586 | }, 587 | "mime-types": { 588 | "version": "2.1.35", 589 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 590 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 591 | "dev": true, 592 | "requires": { 593 | "mime-db": "1.52.0" 594 | } 595 | }, 596 | "node-fetch": { 597 | "version": "2.6.7", 598 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 599 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 600 | "requires": { 601 | "whatwg-url": "^5.0.0" 602 | } 603 | }, 604 | "tr46": { 605 | "version": "0.0.3", 606 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 607 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 608 | }, 609 | "ts-node": { 610 | "version": "10.9.1", 611 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 612 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 613 | "dev": true, 614 | "requires": { 615 | "@cspotcode/source-map-support": "^0.8.0", 616 | "@tsconfig/node10": "^1.0.7", 617 | "@tsconfig/node12": "^1.0.7", 618 | "@tsconfig/node14": "^1.0.0", 619 | "@tsconfig/node16": "^1.0.2", 620 | "acorn": "^8.4.1", 621 | "acorn-walk": "^8.1.1", 622 | "arg": "^4.1.0", 623 | "create-require": "^1.1.0", 624 | "diff": "^4.0.1", 625 | "make-error": "^1.1.1", 626 | "v8-compile-cache-lib": "^3.0.1", 627 | "yn": "3.1.1" 628 | } 629 | }, 630 | "typescript": { 631 | "version": "4.8.3", 632 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", 633 | "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", 634 | "dev": true 635 | }, 636 | "v8-compile-cache-lib": { 637 | "version": "3.0.1", 638 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 639 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 640 | "dev": true 641 | }, 642 | "webidl-conversions": { 643 | "version": "3.0.1", 644 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 645 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 646 | }, 647 | "whatwg-url": { 648 | "version": "5.0.0", 649 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 650 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 651 | "requires": { 652 | "tr46": "~0.0.3", 653 | "webidl-conversions": "^3.0.0" 654 | } 655 | }, 656 | "yn": { 657 | "version": "3.1.1", 658 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 659 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 660 | "dev": true 661 | } 662 | } 663 | } 664 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shazam-api", 3 | "version": "0.2.1-0", 4 | "description": "A shazam API", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc" 8 | }, 9 | "author": "asivery", 10 | "files": [ 11 | "dist" 12 | ], 13 | "license": "GPL-2.0", 14 | "devDependencies": { 15 | "@types/node-fetch": "^2.6.2", 16 | "@types/numjs": "^0.16.2", 17 | "ts-node": "^10.9.1", 18 | "typescript": "^4.8.3" 19 | }, 20 | "dependencies": { 21 | "buffer": "^6.0.3", 22 | "fft.js": "^4.0.4", 23 | "node-fetch": "^2.6.7" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithm.ts: -------------------------------------------------------------------------------- 1 | import { DecodedMessage, FrequencyBand, FrequencyPeak } from "./signature-format"; 2 | import FFT from 'fft.js'; 3 | 4 | const hanning = (m: number) => Array(m) 5 | .fill(0) 6 | .map((_, n) => 0.5 - 0.5 * Math.cos((2 * Math.PI * n) / (m - 1))); 7 | 8 | const pyMod = (a: number, b: number) => (a % b) >= 0 ? (a % b) : b + (a % b); 9 | 10 | const MAX_TIME_SECONDS = 8; 11 | const MAX_PEAKS = 255; 12 | 13 | const HANNING_MATRIX = hanning(2050).slice(1, 2049); 14 | 15 | export class RingBuffer { 16 | public list: (T|null)[]; 17 | public position: number = 0; 18 | public written: number = 0; 19 | 20 | constructor(public bufferSize: number, defaultValue?: T | (() => T)){ 21 | if(typeof defaultValue === 'function'){ 22 | this.list = Array(bufferSize).fill(null).map(defaultValue as (() => T)); 23 | }else{ 24 | this.list = Array(bufferSize).fill(defaultValue ?? null); 25 | } 26 | } 27 | 28 | append(value: T){ 29 | this.list[this.position] = value; 30 | this.position++; 31 | this.written++; 32 | this.position %= this.bufferSize; 33 | } 34 | } 35 | 36 | export class SignatureGenerator{ 37 | // @ts-ignore 38 | private inputPendingProcessing: number[]; 39 | // @ts-ignore 40 | private samplesProcessed: number; 41 | // @ts-ignore 42 | private ringBufferOfSamples: RingBuffer; 43 | // @ts-ignore 44 | private fftOutputs: RingBuffer; 45 | // @ts-ignore 46 | private spreadFFTsOutput: RingBuffer; 47 | // @ts-ignore 48 | private nextSignature: DecodedMessage; 49 | 50 | private initFields(){ 51 | this.ringBufferOfSamples = new RingBuffer(2048, 0); 52 | this.fftOutputs = new RingBuffer(256, () => new Float64Array(Array(1025).fill(0))); 53 | this.spreadFFTsOutput = new RingBuffer(256, () => new Float64Array(Array(1025).fill(0))); 54 | this.nextSignature = new DecodedMessage(); 55 | this.nextSignature.sampleRateHz = 16000; 56 | this.nextSignature.numberSamples = 0; 57 | this.nextSignature.frequencyBandToSoundPeaks = {}; 58 | } 59 | 60 | constructor(){ 61 | this.inputPendingProcessing = []; 62 | this.samplesProcessed = 0; 63 | 64 | this.initFields(); 65 | } 66 | 67 | feedInput(s16leMonoSamples: number[]){ 68 | this.inputPendingProcessing = this.inputPendingProcessing.concat(s16leMonoSamples); 69 | } 70 | 71 | getNextSignature(): DecodedMessage | null { 72 | if(this.inputPendingProcessing.length - this.samplesProcessed < 128){ 73 | return null; 74 | } 75 | 76 | while( 77 | ((this.inputPendingProcessing.length - this.samplesProcessed) >= 128) && 78 | (((this.nextSignature.numberSamples / this.nextSignature.sampleRateHz) < MAX_TIME_SECONDS) || 79 | (Object.values(this.nextSignature.frequencyBandToSoundPeaks).map(e => e.length).reduce((a, b) => a+b, 0) < MAX_PEAKS)) 80 | ){ 81 | this.processInput(this.inputPendingProcessing.slice(this.samplesProcessed, this.samplesProcessed + 128)); 82 | this.samplesProcessed += 128; 83 | } 84 | let returnedSignature = this.nextSignature; 85 | this.initFields(); 86 | 87 | return returnedSignature; 88 | } 89 | 90 | processInput(s16leMonoSamples: number[]){ 91 | this.nextSignature.numberSamples += s16leMonoSamples.length; 92 | for(let positionOfChunk = 0; positionOfChunk < s16leMonoSamples.length; positionOfChunk += 128){ 93 | this.doFFT(s16leMonoSamples.slice(positionOfChunk, positionOfChunk + 128)); 94 | this.doPeakSpreadingAndRecognition(); 95 | } 96 | } 97 | 98 | doFFT(batchOf128S16leMonoSamples: number[]){ 99 | this.ringBufferOfSamples.list.splice( 100 | this.ringBufferOfSamples.position, 101 | batchOf128S16leMonoSamples.length, 102 | ...batchOf128S16leMonoSamples 103 | ); 104 | 105 | this.ringBufferOfSamples.position += batchOf128S16leMonoSamples.length; 106 | this.ringBufferOfSamples.position %= 2048; 107 | this.ringBufferOfSamples.written += batchOf128S16leMonoSamples.length; 108 | 109 | let excerptFromRingBuffer = ([ 110 | ...this.ringBufferOfSamples.list.slice(this.ringBufferOfSamples.position), 111 | ...this.ringBufferOfSamples.list.slice(0, this.ringBufferOfSamples.position), 112 | ] as number[]); 113 | 114 | const fft = new FFT(excerptFromRingBuffer.length); 115 | const out = fft.createComplexArray(); 116 | fft.realTransform(out, excerptFromRingBuffer.map((v, i) => (v * HANNING_MATRIX[i]))); 117 | // The premultiplication of the array is for applying a windowing function before the DFT (slighty rounded Hanning without zeros at edges) 118 | let results = Array(1025).fill(0); 119 | for(let i = 0; i= 46) 136 | this.doPeakRecognition(); 137 | } 138 | 139 | doPeakSpreading(){ 140 | let originLastFFT = this.fftOutputs.list[pyMod(this.fftOutputs.position - 1, this.fftOutputs.bufferSize)]!, 141 | spreadLastFFT = new Float64Array(originLastFFT); 142 | for(let position = 0; position < 1025; position++){ 143 | if(position < 1023){ 144 | spreadLastFFT[position] = Math.max(...spreadLastFFT.slice(position, position + 3)); 145 | } 146 | 147 | let maxValue = spreadLastFFT[position]; 148 | for(let formerFftNum of [-1, -3, -6]){ 149 | let formerFftOutput = this.spreadFFTsOutput.list[pyMod(this.spreadFFTsOutput.position + formerFftNum, this.spreadFFTsOutput.bufferSize)]!; 150 | if(isNaN(formerFftOutput[position])) continue; 151 | formerFftOutput[position] = maxValue = Math.max(formerFftOutput[position], maxValue); 152 | } 153 | } 154 | this.spreadFFTsOutput.append(spreadLastFFT); 155 | } 156 | 157 | doPeakRecognition(){ 158 | let fftMinus46 = this.fftOutputs.list[pyMod(this.fftOutputs.position - 46, this.fftOutputs.bufferSize)]!; 159 | let fftMinus49 = this.spreadFFTsOutput.list[pyMod(this.spreadFFTsOutput.position - 49, this.spreadFFTsOutput.bufferSize)]!; 160 | 161 | const range = (a: number, b: number, c: number = 1) => { 162 | let out = []; 163 | for(let i = a; i < b; i += c) out.push(i); 164 | return out; 165 | } 166 | 167 | for(let binPosition = 10; binPosition < 1015; binPosition++){ 168 | // Ensire that the bin is large enough to be a peak 169 | if((fftMinus46[binPosition] >= 1/64) && (fftMinus46[binPosition] >= fftMinus49[binPosition - 1])){ 170 | let maxNeighborInFftMinus49 = 0; 171 | for(let neighborOffset of [...range(-10, -3, 3), -3, 1, ...range(2, 9, 3)]){ 172 | const candidate = fftMinus49[binPosition + neighborOffset]; 173 | if(isNaN(candidate)) continue; 174 | maxNeighborInFftMinus49 = Math.max(candidate, maxNeighborInFftMinus49); 175 | } 176 | if(fftMinus46[binPosition] > maxNeighborInFftMinus49){ 177 | let maxNeighborInOtherAdjacentFFTs = maxNeighborInFftMinus49; 178 | for(let otherOffset of [-53, -45, ...range(165, 201, 7), ...range(214, 250, 7)]){ 179 | const candidate = this.spreadFFTsOutput.list[pyMod(this.spreadFFTsOutput.position + otherOffset, this.spreadFFTsOutput.bufferSize)]![binPosition - 1]; 180 | if(isNaN(candidate)) continue; 181 | maxNeighborInOtherAdjacentFFTs = Math.max( 182 | candidate, 183 | maxNeighborInOtherAdjacentFFTs 184 | ); 185 | } 186 | 187 | if(fftMinus46[binPosition] > maxNeighborInOtherAdjacentFFTs){ 188 | // This is a peak. Store the peak 189 | 190 | let fftNumber = this.spreadFFTsOutput.written - 46; 191 | 192 | let peakMagnitude = Math.log(Math.max(1 / 64, fftMinus46[binPosition])) * 1477.3 + 6144, 193 | peakMagnitudeBefore = Math.log(Math.max(1 / 64, fftMinus46[binPosition-1])) * 1477.3 + 6144, 194 | peakMagnitudeAfter = Math.log(Math.max(1 / 64, fftMinus46[binPosition+1])) * 1477.3 + 6144; 195 | 196 | let peakVariation1 = peakMagnitude * 2 - peakMagnitudeBefore - peakMagnitudeAfter, 197 | peakVariation2 = (peakMagnitudeAfter - peakMagnitudeBefore) * 32 / peakVariation1; 198 | 199 | let correctedPeakFrequencyBin = binPosition * 64 + peakVariation2; 200 | if(peakVariation1 <= 0){ 201 | console.log("Assert 2 failed - " + peakVariation1); 202 | } 203 | 204 | let frequencyHz = correctedPeakFrequencyBin * (16000 / 2 / 1024 / 64); 205 | let band; 206 | if(frequencyHz < 250){ 207 | continue; 208 | } else if(frequencyHz < 520){ 209 | band = FrequencyBand._250_520; 210 | } else if(frequencyHz < 1450){ 211 | band = FrequencyBand._520_1450; 212 | } else if(frequencyHz < 3500){ 213 | band = FrequencyBand._1450_3500; 214 | } else if(frequencyHz <= 5500){ 215 | band = FrequencyBand._3500_5500; 216 | } else continue; 217 | 218 | if(!Object.keys(this.nextSignature.frequencyBandToSoundPeaks).includes(FrequencyBand[band])){ 219 | this.nextSignature.frequencyBandToSoundPeaks[FrequencyBand[band]] = []; 220 | } 221 | this.nextSignature.frequencyBandToSoundPeaks[FrequencyBand[band]].push( 222 | new FrequencyPeak(fftNumber, Math.round(peakMagnitude), Math.round(correctedPeakFrequencyBin), 16000) 223 | ); 224 | } 225 | } 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { SignatureGenerator } from "./algorithm"; 2 | import { DecodedMessage } from "./signature-format"; 3 | import nFetch from 'node-fetch'; 4 | import { ShazamRoot } from "./types"; 5 | 6 | const TIME_ZONE = "Europe/Paris"; 7 | 8 | function uuidv4() { 9 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 10 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 11 | return v.toString(16); 12 | }).toUpperCase(); 13 | } 14 | 15 | 16 | export class Endpoint{ 17 | static SCHEME = "https"; 18 | static HOSTNAME = "amp.shazam.com"; 19 | 20 | constructor(public timezone: string){}; 21 | url(){ 22 | return `${Endpoint.SCHEME}://${Endpoint.HOSTNAME}/discovery/v5/en/US/iphone/-/tag/${uuidv4()}/${uuidv4()}`; 23 | } 24 | params(){ 25 | return { 26 | 'sync': 'true', 27 | 'webv3': 'true', 28 | 'sampling': 'true', 29 | 'connected': '', 30 | 'shazamapiversion': 'v3', 31 | 'sharehub': 'true', 32 | 'hubv5minorversion': 'v5.1', 33 | 'hidelb': 'true', 34 | 'video': 'v3' 35 | }; 36 | } 37 | headers(){ 38 | return { 39 | "X-Shazam-Platform": "IPHONE", 40 | "X-Shazam-AppVersion": "14.1.0", 41 | "Accept": "*/*", 42 | "Content-Type": "application/json", 43 | "Accept-Encoding": "gzip, deflate", 44 | "Accept-Language": "en", 45 | "User-Agent": "Shazam/3685 CFNetwork/1197 Darwin/20.0.0" 46 | } 47 | } 48 | 49 | async sendRecognizeRequest(url: string, body: string){ 50 | const fetch = global.fetch ?? nFetch; 51 | return await (await fetch(url, { body, headers: this.headers(), method: "POST" })).json(); 52 | } 53 | 54 | async formatAndSendRecognizeRequest(signature: DecodedMessage): Promise{ 55 | let data = { 56 | 'timezone': this.timezone, 57 | 'signature': { 58 | 'uri': signature.encodeToUri(), 59 | 'samplems': Math.round(signature.numberSamples / signature.sampleRateHz * 1000) 60 | }, 61 | 'timestamp': new Date().getTime(), 62 | 'context': {}, 63 | 'geolocation': {} 64 | }; 65 | const url = new URL(this.url()); 66 | Object.entries(this.params()).forEach(([a, b]) => url.searchParams.append(a, b)); 67 | 68 | let response = await this.sendRecognizeRequest(url.toString(), JSON.stringify(data)); 69 | if(response.matches.length === 0) return null; 70 | 71 | return response as ShazamRoot; 72 | } 73 | } 74 | 75 | export class Shazam{ 76 | static MAX_TIME_SCEONDS = 8; 77 | 78 | public endpoint: Endpoint; 79 | constructor(timeZone?: string){ 80 | this.endpoint = new Endpoint(timeZone ?? TIME_ZONE); 81 | } 82 | 83 | async recognizeSong(samples: number[], callback?: ((state: "generating" | "transmitting") => void)){ 84 | let response = await this.fullRecognizeSong(samples, callback); 85 | if(!response) return null; 86 | 87 | const 88 | trackData = response.track, 89 | mainSection = trackData.sections.find((e: any) => e.type === "SONG")!; 90 | const 91 | title = trackData.title, 92 | artist = trackData.subtitle, 93 | album = mainSection.metadata!.find(e => e.title === "Album")?.text, 94 | year = mainSection.metadata!.find(e => e.title === "Released")?.text; 95 | return { title, artist, album, year }; 96 | 97 | } 98 | 99 | async fullRecognizeSong(samples: number[], callback?: ((state: "generating" | "transmitting") => void)){ 100 | callback?.("generating"); 101 | let generator = this.createSignatureGenerator(samples); 102 | while(true){ 103 | callback?.("generating"); 104 | const signature = generator.getNextSignature(); 105 | if(!signature){ 106 | break; 107 | } 108 | callback?.("transmitting"); 109 | let results = await this.endpoint.formatAndSendRecognizeRequest(signature); 110 | if(results !== null) return results; 111 | } 112 | return null; 113 | } 114 | 115 | createSignatureGenerator(samples: number[]){ 116 | let signatureGenerator = new SignatureGenerator(); 117 | signatureGenerator.feedInput(samples); 118 | return signatureGenerator; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './utils'; 3 | -------------------------------------------------------------------------------- /src/signature-format.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | var crc32 = function(str: number[]) { 4 | var c; 5 | var crcTable = []; 6 | for(var n =0; n < 256; n++){ 7 | c = n; 8 | for(var k =0; k < 8; k++){ 9 | c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); 10 | } 11 | crcTable[n] = c; 12 | } 13 | 14 | var crc = 0 ^ (-1); 15 | 16 | for (var i = 0; i < str.length; i++ ) { 17 | crc = (crc >>> 8) ^ crcTable[(crc ^ str[i]) & 0xFF]; 18 | } 19 | 20 | return (crc ^ (-1)) >>> 0; 21 | }; 22 | export enum FrequencyBand{ 23 | _0_250 = -1, 24 | _250_520 = 0, 25 | _520_1450 = 1, 26 | _1450_3500 = 2, 27 | _3500_5500 = 3, 28 | }; 29 | 30 | export enum SampleRate{ 31 | _8000 = 1, 32 | _11025 = 2, 33 | _16000 = 3, 34 | _32000 = 4, 35 | _44100 = 5, 36 | _48000 = 6, 37 | }; 38 | 39 | const DATA_URI_PREFIX = 'data:audio/vnd.shazam.sig;base64,'; 40 | 41 | export class FrequencyPeak{ 42 | constructor(public fftPassNumber: number, public peakMagnitude: number, public correctedPeakFrequencyBin: number, public sampleRateHz: number){} 43 | 44 | getFrequencyHz(){ 45 | return this.correctedPeakFrequencyBin * (this.sampleRateHz / 2 / 1024 / 64); 46 | } 47 | 48 | getAmplitudePcm(){ 49 | return Math.sqrt(Math.exp((this.peakMagnitude - 6144) / 1477.3) * (1 << 17) / 2) / 1024; 50 | } 51 | 52 | getSeconds(){ 53 | return (this.fftPassNumber * 128) / this.sampleRateHz; 54 | } 55 | } 56 | 57 | export interface RawSignatureHeader{ 58 | magic1: number; 59 | crc32: number; 60 | sizeMinusHeader: number; 61 | magic2: number; 62 | shiftedSampleRateId: number; 63 | numberSamplesPlusDividedSampleRate: number; 64 | fixedValue: number; 65 | } 66 | 67 | const readUint32 = (data: Uint8Array) => { 68 | return ( 69 | data[0] >> 24 | 70 | data[1] >> 16 | 71 | data[2] >> 8 | 72 | data[3] 73 | ); 74 | } 75 | const padTo32 = (data: Uint8Array) => new Uint8Array([...data, ...Array(4 - data.length).fill(0)]); 76 | const readInt32 = (data: Uint8Array) => new Int32Array(data)[0]; 77 | const writeUint32 = (e: number) => [e & 0xff, (e >> 8) & 0xff, (e >> 16) & 0xff, (e >> 24) & 0xff]; 78 | const writeInt32 = (e: number) => { 79 | let q = new DataView(new ArrayBuffer(4), 0); 80 | q.setInt32(0, e, true); 81 | return new Uint8Array(q.buffer); 82 | } 83 | const writeInt16 = (e: number) => { 84 | let q = new DataView(new ArrayBuffer(2), 0); 85 | q.setInt16(0, e, true); 86 | return new Uint8Array(q.buffer); 87 | } 88 | 89 | export function readRawSignatureHeader(read: ((e?: number) => Uint8Array)){ 90 | const _readUint32 = () => readUint32(read(4)); 91 | const clear = (e: number) => Array(e).fill(0).map(readUint32); 92 | const 93 | magic1 = _readUint32(), 94 | crc32 = _readUint32(), 95 | sizeMinusHeader = _readUint32(), 96 | magic2 = _readUint32(), 97 | _a = clear(3), 98 | shiftedSampleRateId = _readUint32(), 99 | _b = clear(2), 100 | numberSamplesPlusDividedSampleRate = _readUint32(), 101 | fixedValue = _readUint32(); 102 | return {magic1, crc32, sizeMinusHeader, magic2, shiftedSampleRateId, numberSamplesPlusDividedSampleRate, fixedValue}; 103 | } 104 | 105 | export function writeRawSignatureHeader(rsh: RawSignatureHeader){ 106 | let buffer: number[] = []; 107 | let _writeUint32 = (e: number) => buffer.push(...writeUint32(e)); 108 | 109 | _writeUint32(rsh.magic1); 110 | _writeUint32(rsh.crc32); 111 | _writeUint32(rsh.sizeMinusHeader); 112 | _writeUint32(rsh.magic2); 113 | _writeUint32(0); 114 | _writeUint32(0); 115 | _writeUint32(0); 116 | _writeUint32(rsh.shiftedSampleRateId); 117 | _writeUint32(0); 118 | _writeUint32(0); 119 | _writeUint32(rsh.numberSamplesPlusDividedSampleRate); 120 | _writeUint32(rsh.fixedValue); 121 | 122 | return new Uint8Array(buffer); 123 | } 124 | 125 | export class DecodedMessage{ 126 | sampleRateHz: number = 0; 127 | numberSamples: number = 0; 128 | frequencyBandToSoundPeaks: {[key: string]: FrequencyPeak[]} = {}; 129 | 130 | static decodeFromBinary(bytes: Uint8Array){ 131 | let self = new DecodedMessage(); 132 | 133 | let ptr = 0; 134 | const read = (e?: number) => e === undefined ? bytes.slice(ptr, ptr = bytes.length) : bytes.slice(ptr, ptr += e); 135 | const seek = (e: number) => ptr = e; 136 | 137 | seek(8); 138 | const checksummableData = read(); 139 | seek(0); 140 | const header: RawSignatureHeader = readRawSignatureHeader(read); 141 | 142 | if(header.magic1 != 0xcafe2580){ 143 | console.log("ASSERT 3 FAIL"); 144 | } 145 | // TODO: Other asserts 146 | 147 | self.sampleRateHz = parseInt(SampleRate[header.shiftedSampleRateId >> 27].substring(1)); 148 | self.numberSamples = Math.round(header.numberSamplesPlusDividedSampleRate - self.sampleRateHz * 0.24); 149 | 150 | while(true){ 151 | const tlvHeader = read(8); 152 | if(tlvHeader.length === 0) break; 153 | 154 | let frequencyBandId = readInt32(tlvHeader.slice(0, 4)), 155 | frequencyPeaksSize = readInt32(tlvHeader.slice(4)); 156 | let frequencyPeaksPadding = 4 + (-frequencyPeaksSize % 4); 157 | read(frequencyPeaksPadding); 158 | 159 | let frequencyBand = (frequencyBandId - 0x60030040) as FrequencyBand; 160 | let fftPassNumber = 0; 161 | self.frequencyBandToSoundPeaks[FrequencyBand[frequencyBand]] = []; 162 | 163 | while(true){ 164 | let rawFftPass = read(1); 165 | if(rawFftPass.length === 0) break; 166 | 167 | let fftPassOffset = rawFftPass[0]; 168 | if(fftPassOffset === 0xff){ 169 | fftPassNumber = readInt32(read(4)); 170 | continue; 171 | }else{ 172 | fftPassNumber += fftPassOffset 173 | } 174 | 175 | let peakMagnitude = readInt32(padTo32(read(2))); 176 | let correctedPeakFrequencyBin = readInt32(padTo32(read(2))); 177 | 178 | self.frequencyBandToSoundPeaks[FrequencyBand[frequencyBand]].push( 179 | new FrequencyPeak(fftPassNumber, peakMagnitude, correctedPeakFrequencyBin, self.sampleRateHz) 180 | ); 181 | } 182 | } 183 | 184 | return self; 185 | } 186 | 187 | static decodeFromUri(uri: string){ 188 | if(!uri.startsWith(DATA_URI_PREFIX)){ 189 | throw new Error("assert 4"); 190 | } 191 | return this.decodeFromBinary(Buffer.from(uri.replace(DATA_URI_PREFIX, ""), "base64")); 192 | } 193 | 194 | encodeToBinary(){ 195 | let header: RawSignatureHeader = { 196 | magic1: 0xcafe2580, 197 | magic2: 0x94119c00, 198 | shiftedSampleRateId: SampleRate[`_${this.sampleRateHz}` as any] as unknown as number << 27, 199 | fixedValue: ((15 << 19) + 0x40000), 200 | numberSamplesPlusDividedSampleRate: Math.round(this.numberSamples + this.sampleRateHz * 0.24), 201 | 202 | crc32: -1, 203 | sizeMinusHeader: -1 204 | }; 205 | 206 | let contentsBuf: number[] = []; 207 | 208 | for(let x of Object.entries(this.frequencyBandToSoundPeaks).map(a => [FrequencyBand[a[0] as any] as unknown as number, a[1]]).sort((a, b) => ((a[0] as number) - (b[0] as number)))){ 209 | const 210 | frequencyBand = x[0] as number, 211 | frequencyPeaks = x[1] as FrequencyPeak[]; 212 | 213 | let peaksBuffer: number[] = []; 214 | 215 | let fftPassNumber = 0; 216 | 217 | for(let frequencyPeak of frequencyPeaks){ 218 | if(frequencyPeak.fftPassNumber < fftPassNumber){ 219 | throw new Error("Assert 5"); 220 | } 221 | 222 | if((frequencyPeak.fftPassNumber - fftPassNumber) >= 0xff){ 223 | peaksBuffer.push(0xff); 224 | peaksBuffer.push(...writeInt32(frequencyPeak.fftPassNumber)); 225 | fftPassNumber = frequencyPeak.fftPassNumber; 226 | } 227 | 228 | peaksBuffer.push(frequencyPeak.fftPassNumber - fftPassNumber); 229 | peaksBuffer.push(...writeInt16(frequencyPeak.peakMagnitude - 1)); 230 | peaksBuffer.push(...writeInt16(frequencyPeak.correctedPeakFrequencyBin - 1)); 231 | fftPassNumber = frequencyPeak.fftPassNumber; 232 | } 233 | contentsBuf.push(...writeInt32(0x60030040 + frequencyBand)); 234 | contentsBuf.push(...writeInt32(peaksBuffer.length)); 235 | contentsBuf = contentsBuf.concat(peaksBuffer); 236 | let paddingCount = 4 - (peaksBuffer.length % 4); 237 | if(paddingCount < 4) contentsBuf.push(...Array(paddingCount).fill(0)); 238 | } 239 | 240 | header.sizeMinusHeader = contentsBuf.length + 8; 241 | 242 | let buf: number[] = []; 243 | buf.push(...writeRawSignatureHeader(header)); 244 | buf.push(...writeInt32(0x40000000)); 245 | buf.push(...writeInt32(contentsBuf.length + 8)); 246 | buf = buf.concat(contentsBuf); 247 | header.crc32 = crc32(buf.slice(8)); 248 | let newHeader = writeRawSignatureHeader(header); 249 | buf.splice(0, newHeader.length, ...newHeader); 250 | 251 | return buf; 252 | } 253 | 254 | encodeToUri(){ 255 | const bin = this.encodeToBinary(); 256 | return DATA_URI_PREFIX + Buffer.from(bin).toString('base64'); 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface ShazamRoot { 2 | matches: ShazamMatch[] 3 | location: { 4 | accuracy: number 5 | } 6 | timestamp: number 7 | timezone: string 8 | track: ShazamTrack 9 | tagid: string 10 | } 11 | 12 | export interface ShazamMatch { 13 | id: string 14 | offset: number 15 | timeskew: number 16 | frequencyskew: number 17 | } 18 | 19 | export interface ShazamTrack { 20 | layout: string 21 | type: string 22 | key: string 23 | title: string 24 | subtitle: string 25 | images: ShazamImages 26 | share: ShazamShare 27 | hub: ShazamHub 28 | sections: ShazamSection[] 29 | url: string 30 | artists: ShazamArtist[] 31 | isrc: string 32 | genres: ShazamGenres 33 | urlparams: ShazamUrlparams 34 | myshazam: { 35 | apple: { 36 | actions: { 37 | name: string 38 | type: string 39 | uri: string 40 | } [] 41 | } 42 | } 43 | highlightsurls: ShazamHighlightsurls 44 | relatedtracksurl: string 45 | albumadamid: string 46 | } 47 | 48 | export interface ShazamImages { 49 | background: string 50 | coverart: string 51 | coverarthq: string 52 | joecolor: string 53 | } 54 | 55 | export interface ShazamShare { 56 | subject: string 57 | text: string 58 | href: string 59 | image: string 60 | twitter: string 61 | html: string 62 | avatar: string 63 | snapchat: string 64 | } 65 | 66 | export interface ShazamHub { 67 | type: string 68 | image: string 69 | actions: ShazamAction[] 70 | options: ShazamOption[] 71 | providers: ShazamProvider[] 72 | explicit: boolean 73 | displayname: string 74 | } 75 | 76 | export interface ShazamAction { 77 | name: string 78 | type: string 79 | id?: string 80 | uri?: string 81 | } 82 | 83 | export interface ShazamOption { 84 | caption: string 85 | actions: { 86 | type: string 87 | uri: string 88 | name?: string 89 | } [] 90 | beacondata: ShazamBeacondata 91 | image: string 92 | type: string 93 | listcaption: string 94 | overflowimage: string 95 | colouroverflowimage: boolean 96 | providername: string 97 | } 98 | 99 | export interface ShazamBeacondata { 100 | type: string 101 | providername: string 102 | } 103 | 104 | export interface ShazamProvider { 105 | caption: string 106 | images: { 107 | overflow: string, 108 | default: string 109 | } 110 | actions: { 111 | name: string 112 | type: string 113 | uri: string 114 | } [] 115 | type: string 116 | } 117 | 118 | export interface ShazamSection { 119 | type: string 120 | metapages?: ShazamMetapage[] 121 | tabname: string 122 | metadata?: ShazamMetadata[] 123 | text?: string[] 124 | url?: string 125 | footer?: string 126 | beacondata?: { 127 | lyricsid: string 128 | providername: string 129 | commontrackid: string 130 | } 131 | youtubeurl?: string 132 | } 133 | 134 | export interface ShazamMetapage { 135 | image: string 136 | caption: string 137 | } 138 | 139 | export interface ShazamMetadata { 140 | title: string 141 | text: string 142 | } 143 | 144 | 145 | export interface ShazamArtist { 146 | id: string 147 | adamid: string 148 | } 149 | 150 | export interface ShazamGenres { 151 | primary: string 152 | } 153 | 154 | export interface ShazamUrlparams { 155 | "{tracktitle}": string 156 | "{trackartist}": string 157 | } 158 | 159 | export interface ShazamHighlightsurls { 160 | artisthighlightsurl: string 161 | trackhighlighturl: string 162 | } 163 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function s16LEToSamplesArray(rawSamples: Uint8Array){ 2 | const samplesArray: number[] = []; 3 | for(let i = 0; i's from expanding the number of files TypeScript should add to a project. */ 39 | 40 | /* JavaScript Support */ 41 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 42 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 43 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 44 | 45 | /* Emit */ 46 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 47 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 48 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 49 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 50 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 51 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 52 | // "removeComments": true, /* Disable emitting comments. */ 53 | // "noEmit": true, /* Disable emitting files from a compilation. */ 54 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 55 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 56 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 57 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 60 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 61 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 62 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 63 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 64 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 65 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 66 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 67 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 68 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 69 | 70 | /* Interop Constraints */ 71 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 72 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 73 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 74 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 75 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 76 | 77 | /* Type Checking */ 78 | "strict": true, /* Enable all strict type-checking options. */ 79 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 80 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 81 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 82 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 83 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 84 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 85 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 86 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 87 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 88 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 89 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 90 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 91 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 92 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 93 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 94 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 95 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 96 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 97 | 98 | /* Completeness */ 99 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 100 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 101 | } 102 | } 103 | --------------------------------------------------------------------------------