├── HISTORY.laser2wav ├── LICENSE ├── README.laser2wav ├── README.md ├── chopin8.py ├── circuit └── cld1010-efm.asc ├── efm.py ├── galois_field.py ├── laser2wav.py ├── laserbits.txt ├── ld-decode-pcm.py ├── ldd.py └── wav_format.py /HISTORY.laser2wav: -------------------------------------------------------------------------------- 1 | 2 | This file documents the history of the laser2wav package. 3 | 4 | --- version 0.0.2: 5 | 6 | * Initial release 7 | 8 | --- version 0.0.5: 9 | 10 | * fixed silly "to_signed" bug 11 | * added HISTORY 12 | * renamed source and data files 13 | * added lots of comments 14 | * cleaned up "galois field" code 15 | * emit a PCM-encoded WAV file instead of a text file 16 | * laser input is now assumed to be the raw signal 17 | (not the delta-signal as before). 18 | -------------------------------------------------------------------------------- /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 | {description} 294 | Copyright (C) {year} {fullname} 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 | {signature of Ty Coon}, 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 | 341 | -------------------------------------------------------------------------------- /README.laser2wav: -------------------------------------------------------------------------------- 1 | 2 | laser2wav 3 | --------- 4 | 5 | Version 0.0.5 as released on March 14, 2005 6 | 7 | Copyright (c) 2005 by Sidney Cadot 8 | This software is licensed according to the GNU General Public Licence (GPL). 9 | 10 | What does it do? 11 | --------------- 12 | 13 | The laser2wav package is a software-only implementation of an audio CD decoder. 14 | 15 | To decode raw audio data from laser data (sample data is provided), try 16 | 17 | python laser2wav.py laserbits.txt audio.wav 18 | 19 | However, the real target here is not so much running the program; rather, this 20 | was written to have a reference implementation for CD audio decoding, including 21 | such things as subchannel decoding, error handling, and de-interleaving. 22 | 23 | Future Plans 24 | ------------ 25 | 26 | The package could definitely use some more documentation. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cd-decode 2 | CD/LaserDisc audio decoder 3 | 4 | this is a development project that'll use a mix of megapixie's code, a tiny bit of my code, and Sidney Cadot's laser2wav code. 5 | 6 | Hopefully soon it'll be integrated and decoding audio nicely. 7 | -------------------------------------------------------------------------------- /chopin8.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import numpy as np 4 | from matplotlib import pyplot as plt 5 | from scipy.stats import itemfreq 6 | 7 | import numpy as np 8 | import scipy.signal as sps 9 | 10 | freq = (315.0 / 88.0) * 8.0 11 | 12 | def doplot(B, A): 13 | w, h = sps.freqz(B, A) 14 | 15 | fig = plt.figure() 16 | plt.title('Digital filter frequency response') 17 | 18 | db = 20 * np.log10(abs(h)) 19 | 20 | ax1 = fig.add_subplot(111) 21 | 22 | plt.plot(w * (freq/np.pi) / 2.0, 20 * np.log10(abs(h)), 'b') 23 | plt.ylabel('Amplitude [dB]', color='b') 24 | plt.xlabel('Frequency [rad/sample]') 25 | 26 | ax2 = ax1.twinx() 27 | angles = np.unwrap(np.angle(h)) 28 | plt.plot(w * (freq/np.pi) / 2.0, angles, 'g') 29 | plt.ylabel('Angle (radians)', color='g') 30 | 31 | plt.grid() 32 | plt.axis('tight') 33 | plt.show() 34 | 35 | 36 | CD_BASE_FREQUENCY = 4321800.0 # Hz 37 | SAMPLE_FREQUENCY = 28.636e6 # Hz 38 | 39 | FREQ_MHZ = (315.0 / 88.0) * 8.0 40 | NYQUIST_MHZ = FREQ_MHZ / 2 41 | FREQ_HZ = FREQ_MHZ * 1000000.0 42 | NYQUIST_HZ = FREQ_HZ / 2 43 | 44 | data = np.fromfile("chopin8.cdraw", dtype = np.uint8) 45 | 46 | # remove the first samples because they are strange (lower amplitude) 47 | data = data[2650:len(data)-5000] 48 | 49 | # This prints stuff out for LTspice 50 | 51 | #for i in range(0, len(data)): 52 | # print i / FREQ_HZ,",", (data[i] / 256.0) - .5 53 | # 54 | #exit() 55 | 56 | # without filter: 299/964 57 | # poles at 0 and 49700 hz, 3.202312738us, zero at 1.59mhz/0.100097448us 58 | 59 | # this shhould be - but doesn't work worth a darn 60 | deemp_pole = .100097448 * 1 61 | deemp_zero = 3.202312738 * 1 62 | 63 | # 21/1018 64 | # NEW MEASURE: 850/1079 65 | deemp_pole = .1100 * 1 66 | deemp_zero = 3.100 * 1 67 | lowpass_b, lowpass_a = sps.butter(2, 2.120/NYQUIST_MHZ) 68 | 69 | # 999/1050 70 | deemp_pole = .1450 * 1 71 | deemp_zero = 3.100 * 1 72 | lowpass_b, lowpass_a = sps.butter(2, 2.120/NYQUIST_MHZ) 73 | 74 | # 1177/798 75 | deemp_pole = .1450 * 1 76 | deemp_zero = 3.100 * 1 77 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 78 | 79 | # 1204/771 80 | deemp_pole = .1450 * 1 81 | deemp_zero = 3.202312738 * 1 82 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 83 | 84 | # 1204/771 85 | deemp_pole = .1450 * 1 86 | deemp_zero = 3.202312738 * 1 87 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 88 | 89 | # 1205/766 90 | deemp_pole = .1450 * 1 91 | deemp_zero = 3.30 * 1 92 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 93 | 94 | # 1663/354 95 | deemp_pole = .1450 * 1 96 | deemp_zero = 3.20 * 1 97 | lowpass_b = sps.firwin(43, [.400/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 98 | lowpass_a = [1.0] 99 | 100 | # 1664/351 101 | deemp_pole = .1480 * 1 102 | deemp_zero = 3.20 * 1 103 | lowpass_b = sps.firwin(43, [.400/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 104 | lowpass_a = [1.0] 105 | 106 | # 1664/345 107 | deemp_pole = .1480 * 1 108 | deemp_zero = 3.20 * 1 109 | lowpass_b = sps.firwin(45, [.390/NYQUIST_MHZ, 2.700/NYQUIST_MHZ], pass_zero=False) 110 | lowpass_a = [1.0] 111 | lowpass_b, lowpass_a = sps.butter(4, [0.39/NYQUIST_MHZ, 2.700/NYQUIST_MHZ], btype='bandpass') 112 | 113 | # 1664/345 114 | deemp_pole = .1480 * 1 115 | deemp_zero = 3.20 * 1 116 | lowpass_b = sps.firwin(45, [.390/NYQUIST_MHZ, 2.600/NYQUIST_MHZ], pass_zero=False) 117 | lowpass_a = [1.0] 118 | 119 | [tf_b, tf_a] = sps.zpk2tf([-deemp_pole*(10**-8)], [-deemp_zero*(10**-8)], deemp_pole / deemp_zero) 120 | [f_emp_b, f_emp_a] = sps.bilinear(tf_b, tf_a, .5/FREQ_HZ) 121 | 122 | # .295 leftover scale: 1053 frames found, 34731 good samps, 374 ERRORS 123 | bandpass = sps.firwin(55, [.335/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 124 | 125 | # .295 leftover scale: 1053 frames found, 34731 good samps, 374 ERRORS 126 | # 1647/352 127 | bandpass = sps.firwin(53, [.335/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 128 | 129 | # 1651/348 130 | bandpass = sps.firwin(53, [.355/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 131 | # 1652/343 132 | bandpass = sps.firwin(53, [.355/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 133 | # 1660/341 134 | bandpass = sps.firwin(39, [.600/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 135 | # 1660/339 136 | bandpass = sps.firwin(37, [.900/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 137 | 138 | # 1663/348 139 | bandpass = sps.firwin(43, [.880/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 140 | 141 | #doplot(f_emp_b, f_emp_a) 142 | #doplot(bandpass, [1.0]) 143 | #exit() 144 | 145 | # convert to single-precision floats 146 | #data = data.astype(np.float32) 147 | 148 | # fewer errors if we filter as double precision 149 | data = data.astype(np.float64) 150 | 151 | # subtract DC component 152 | dc = data.mean() 153 | data -= dc 154 | 155 | #plt.plot(data[5000:6000]) 156 | 157 | data = sps.lfilter(f_emp_b, f_emp_a, data) 158 | data = sps.lfilter(lowpass_b, lowpass_a, data) 159 | 160 | #data = sps.lfilter(bandpass, [1.0], data) 161 | 162 | plt.plot(data[5000:6000]) 163 | plt.show() 164 | #exit() 165 | 166 | # filter to binary signal 167 | data = (data > 0.0) 168 | 169 | #for i in range(1, len(data) - 1): 170 | # if data[i] == True and data[i - 1] == False and data[i + 1] == False: 171 | # print "err ", i 172 | # data[i] = False 173 | # if data[i] == False and data[i - 1] == True and data[i + 1] == True: 174 | # print "errp ", i 175 | # data[i] = True 176 | 177 | transition = (np.diff(data) != 0) 178 | transition = np.insert(transition, 0, False) # The first sample is never a transition. 179 | 180 | print "data", data.shape, data.dtype 181 | print "transition", transition.shape, transition.dtype 182 | 183 | runLengths = np.diff(np.where(transition)[0]) 184 | 185 | # fetch run signal values. The last transition 186 | # isn't part of a well-defined run, so we don't need it. 187 | 188 | runValues = data[transition].astype(np.int8) 189 | runValues = runValues[:-1] 190 | 191 | print "runLengths", runLengths.shape, runLengths.dtype 192 | print "runValues", runValues.shape, runValues.dtype 193 | 194 | totalRunlength0 = np.sum(runLengths[runValues == 0]) 195 | totalRunlength1 = np.sum(runLengths[runValues == 1]) 196 | 197 | bias = (totalRunlength0 - totalRunlength1) / (SAMPLE_FREQUENCY * len(runLengths)) 198 | print "bias: {} seconds".format(bias) 199 | 200 | bias = 0 201 | 202 | runDurations = runLengths / SAMPLE_FREQUENCY # to SECONDS 203 | 204 | runDurations[runValues == 0] -= bias 205 | runDurations[runValues == 1] += bias 206 | 207 | runDurations = runDurations * CD_BASE_FREQUENCY # to CD BASE FREQUENCY TICKS 208 | 209 | if False: 210 | 211 | print "plotting ..." 212 | 213 | freqAll = itemfreq(runDurations) 214 | freq0 = itemfreq(runDurations[runValues == 0]) 215 | freq1 = itemfreq(runDurations[runValues == 1]) 216 | 217 | plt.subplot(411) 218 | plt.title("All runs (bias corrected)") 219 | plt.xlim(0, 13) 220 | plt.plot(freqAll[:, 0], freqAll[:, 1], '.-') 221 | 222 | plt.subplot(412) 223 | plt.title("Bias-corrected zero runs ") 224 | plt.xlim(0, 13) 225 | plt.plot(freq0[:, 0], freq0[:, 1], '.-') 226 | 227 | plt.subplot(413) 228 | plt.title("Bias-corrected one runs") 229 | plt.xlim(0, 13) 230 | plt.plot(freq1[:, 0], freq1[:, 1], '.-') 231 | 232 | plt.subplot(414) 233 | plt.title("Bias-corrected zero/one runs, overlayed") 234 | plt.xlim(0, 13) 235 | plt.plot(freq0[:, 0], freq0[:, 1], '.-') 236 | plt.plot(freq1[:, 0], freq1[:, 1], '.-') 237 | 238 | plt.savefig("chopin8.pdf") 239 | plt.close() 240 | 241 | if True: 242 | 243 | print "writing file ..." 244 | 245 | with open("chopin8-bits.txt", "w") as f: 246 | leftover = 0 247 | for (value, duration) in zip(runValues, runDurations): 248 | #durationr = int(round(duration + (leftover * .111))) # to integer 249 | #durationr = int(round(duration + (leftover * 0.22))) # to integer 250 | #durationr = int(round(duration + (leftover * 0.24))) # to integer 251 | # durationr = int(round(duration + (leftover * 0.270))) # to integer 252 | #durationr = int(round(duration + (leftover * 0.295))) # to integer 253 | durationr = int(round(duration + (leftover * 0.299))) # to integer 254 | # durationr = int(round(duration)) # to integer 255 | leftover = duration - durationr 256 | 257 | f.write(str(value) * durationr) 258 | -------------------------------------------------------------------------------- /circuit/cld1010-efm.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happycube/cd-decode/8a5b0e6498753bd76c6c054342b3441bd25b9a98/circuit/cld1010-efm.asc -------------------------------------------------------------------------------- /efm.py: -------------------------------------------------------------------------------- 1 | # efm.py 2 | # 3 | # Copyright (c) 2005--2015 by Sidney Cadot 4 | # This software is licensed under the GNU General Public License (GPL). 5 | # 6 | # This file is part of laser2wav, a software-only implementation of 7 | # an audio CD decoder. 8 | 9 | 10 | # The lookup dictionary that defines the Eight-To-Fourteen (EFM) 11 | # decoding algorithm as used in the CD audio channel encoding. 12 | # 13 | # The compact disc channel, scanned by the laser, consists of lands and pits. 14 | # Let T be 1/4321800 seconds; then the valid length for both lands and pits is 15 | # 3*T, 4*T, 5*T, 6*T, 7*T, 8*T, 9*T, 10*T or 11*T. 16 | # 17 | # Note: the allowed physical length of the lands/pits can be deduced from the 18 | # fact that the CD is specified to be read at a constant linear velocity (CLV) 19 | # of 1.2 m/s to 1.4 m/s. This means that T corresponds to a length of 20 | # 277.662 nm (when read at 1.2 m/s) to 323.939 nm (when read at 1.4 m/s), giving 21 | # a valid pit length of between 832.986 nm and 3054.283 nm (when read at 1.2 m/s), 22 | # and between 971.817 nm and 3563.330 nm (when read at 1.4 m/s). 23 | # 24 | # CD surface: 25 | # 26 | # 3 3 4 4 5 5 6 6 7 7 8 8 27 | # -___---____----_____-----______------_______-------________-------- (etc) 28 | # 29 | # Level: 30 | # 31 | # 1000111000011110000011111000000111111000000011111110000000011111111 32 | # 33 | # The first step of decoding detects whether the sampled signal has changed 34 | # its level since the previous (1/4321800)-second interval. 35 | # 36 | # For the sample above, this results in: 37 | # 38 | # Delta-level: 39 | # 40 | # 0100100100010001000010000100000100000100000010000001000000010000000 41 | # 42 | # It is upon blocks of this form that the digital processing chain goes to work; 43 | # Specifically, synchronization to the start-of-frame marker 44 | # ("100000000001000000000010") and Fourteen-To-Eight demodulation work on 45 | # the "delta level" signal. 46 | # 47 | # The "3 to 11" length constraint for pit- and land-lengths translates to a 48 | # "2 to 10" constraint for the "number of zeroes between two successive lengths. 49 | # 50 | # Of the 16,384 14-bit patterns that exist, only 267 do not violate the constraint. 51 | # 256 are used to encode byte patterns; two are used to encode unique 52 | # synchronization patterns that are used to identify the first two frames of an 53 | # 98-frame sector; and exactly nine patterns /could/ be used, but are not: 54 | # 55 | # 00000000001000 56 | # 00000000001001 57 | # 00000000010000 58 | # 00000000010001 59 | # 00001000000000 60 | # 00010000000000 61 | # 01001000000000 62 | # 10001000000000 63 | # 10010000000000 64 | # 65 | # All 258 patterns that /are/ used are listed in the dictionary below. 66 | # 67 | # Despite its name, the EFM dictionary given below is mainly useful for fourteen-to-eight 68 | # demodulation. 69 | 70 | EFM = { 71 | "00100000000001" : 'SYNC0', # special CONTROL pattern (first frame of sector) 72 | "00000000010010" : 'SYNC1', # special CONTROL pattern (second frame of sector) 73 | "01001000100000" : 0, 74 | "10000100000000" : 1, 75 | "10010000100000" : 2, 76 | "10001000100000" : 3, 77 | "01000100000000" : 4, 78 | "00000100010000" : 5, 79 | "00010000100000" : 6, 80 | "00100100000000" : 7, 81 | "01001001000000" : 8, 82 | "10000001000000" : 9, 83 | "10010001000000" : 10, 84 | "10001001000000" : 11, 85 | "01000001000000" : 12, 86 | "00000001000000" : 13, 87 | "00010001000000" : 14, 88 | "00100001000000" : 15, 89 | "10000000100000" : 16, 90 | "10000010000000" : 17, 91 | "10010010000000" : 18, 92 | "00100000100000" : 19, 93 | "01000010000000" : 20, 94 | "00000010000000" : 21, 95 | "00010010000000" : 22, 96 | "00100010000000" : 23, 97 | "01001000010000" : 24, 98 | "10000000010000" : 25, 99 | "10010000010000" : 26, 100 | "10001000010000" : 27, 101 | "01000000010000" : 28, 102 | "00001000010000" : 29, 103 | "00010000010000" : 30, 104 | "00100000010000" : 31, 105 | "00000000100000" : 32, 106 | "10000100001000" : 33, 107 | "00001000100000" : 34, 108 | "00100100100000" : 35, 109 | "01000100001000" : 36, 110 | "00000100001000" : 37, 111 | "01000000100000" : 38, 112 | "00100100001000" : 39, 113 | "01001001001000" : 40, 114 | "10000001001000" : 41, 115 | "10010001001000" : 42, 116 | "10001001001000" : 43, 117 | "01000001001000" : 44, 118 | "00000001001000" : 45, 119 | "00010001001000" : 46, 120 | "00100001001000" : 47, 121 | "00000100000000" : 48, 122 | "10000010001000" : 49, 123 | "10010010001000" : 50, 124 | "10000100010000" : 51, 125 | "01000010001000" : 52, 126 | "00000010001000" : 53, 127 | "00010010001000" : 54, 128 | "00100010001000" : 55, 129 | "01001000001000" : 56, 130 | "10000000001000" : 57, 131 | "10010000001000" : 58, 132 | "10001000001000" : 59, 133 | "01000000001000" : 60, 134 | "00001000001000" : 61, 135 | "00010000001000" : 62, 136 | "00100000001000" : 63, 137 | "01001000100100" : 64, 138 | "10000100100100" : 65, 139 | "10010000100100" : 66, 140 | "10001000100100" : 67, 141 | "01000100100100" : 68, 142 | "00000000100100" : 69, 143 | "00010000100100" : 70, 144 | "00100100100100" : 71, 145 | "01001001000100" : 72, 146 | "10000001000100" : 73, 147 | "10010001000100" : 74, 148 | "10001001000100" : 75, 149 | "01000001000100" : 76, 150 | "00000001000100" : 77, 151 | "00010001000100" : 78, 152 | "00100001000100" : 79, 153 | "10000000100100" : 80, 154 | "10000010000100" : 81, 155 | "10010010000100" : 82, 156 | "00100000100100" : 83, 157 | "01000010000100" : 84, 158 | "00000010000100" : 85, 159 | "00010010000100" : 86, 160 | "00100010000100" : 87, 161 | "01001000000100" : 88, 162 | "10000000000100" : 89, 163 | "10010000000100" : 90, 164 | "10001000000100" : 91, 165 | "01000000000100" : 92, 166 | "00001000000100" : 93, 167 | "00010000000100" : 94, 168 | "00100000000100" : 95, 169 | "01001000100010" : 96, 170 | "10000100100010" : 97, 171 | "10010000100010" : 98, 172 | "10001000100010" : 99, 173 | "01000100100010" : 100, 174 | "00000000100010" : 101, 175 | "01000000100100" : 102, 176 | "00100100100010" : 103, 177 | "01001001000010" : 104, 178 | "10000001000010" : 105, 179 | "10010001000010" : 106, 180 | "10001001000010" : 107, 181 | "01000001000010" : 108, 182 | "00000001000010" : 109, 183 | "00010001000010" : 110, 184 | "00100001000010" : 111, 185 | "10000000100010" : 112, 186 | "10000010000010" : 113, 187 | "10010010000010" : 114, 188 | "00100000100010" : 115, 189 | "01000010000010" : 116, 190 | "00000010000010" : 117, 191 | "00010010000010" : 118, 192 | "00100010000010" : 119, 193 | "01001000000010" : 120, 194 | "00001001001000" : 121, 195 | "10010000000010" : 122, 196 | "10001000000010" : 123, 197 | "01000000000010" : 124, 198 | "00001000000010" : 125, 199 | "00010000000010" : 126, 200 | "00100000000010" : 127, 201 | "01001000100001" : 128, 202 | "10000100100001" : 129, 203 | "10010000100001" : 130, 204 | "10001000100001" : 131, 205 | "01000100100001" : 132, 206 | "00000000100001" : 133, 207 | "00010000100001" : 134, 208 | "00100100100001" : 135, 209 | "01001001000001" : 136, 210 | "10000001000001" : 137, 211 | "10010001000001" : 138, 212 | "10001001000001" : 139, 213 | "01000001000001" : 140, 214 | "00000001000001" : 141, 215 | "00010001000001" : 142, 216 | "00100001000001" : 143, 217 | "10000000100001" : 144, 218 | "10000010000001" : 145, 219 | "10010010000001" : 146, 220 | "00100000100001" : 147, 221 | "01000010000001" : 148, 222 | "00000010000001" : 149, 223 | "00010010000001" : 150, 224 | "00100010000001" : 151, 225 | "01001000000001" : 152, 226 | "10000010010000" : 153, 227 | "10010000000001" : 154, 228 | "10001000000001" : 155, 229 | "01000010010000" : 156, 230 | "00001000000001" : 157, 231 | "00010000000001" : 158, 232 | "00100010010000" : 159, 233 | "00001000100001" : 160, 234 | "10000100001001" : 161, 235 | "01000100010000" : 162, 236 | "00000100100001" : 163, 237 | "01000100001001" : 164, 238 | "00000100001001" : 165, 239 | "01000000100001" : 166, 240 | "00100100001001" : 167, 241 | "01001001001001" : 168, 242 | "10000001001001" : 169, 243 | "10010001001001" : 170, 244 | "10001001001001" : 171, 245 | "01000001001001" : 172, 246 | "00000001001001" : 173, 247 | "00010001001001" : 174, 248 | "00100001001001" : 175, 249 | "00000100100000" : 176, 250 | "10000010001001" : 177, 251 | "10010010001001" : 178, 252 | "00100100010000" : 179, 253 | "01000010001001" : 180, 254 | "00000010001001" : 181, 255 | "00010010001001" : 182, 256 | "00100010001001" : 183, 257 | "01001000001001" : 184, 258 | "10000000001001" : 185, 259 | "10010000001001" : 186, 260 | "10001000001001" : 187, 261 | "01000000001001" : 188, 262 | "00001000001001" : 189, 263 | "00010000001001" : 190, 264 | "00100000001001" : 191, 265 | "01000100100000" : 192, 266 | "10000100010001" : 193, 267 | "10010010010000" : 194, 268 | "00001000100100" : 195, 269 | "01000100010001" : 196, 270 | "00000100010001" : 197, 271 | "00010010010000" : 198, 272 | "00100100010001" : 199, 273 | "00001001000001" : 200, 274 | "10000100000001" : 201, 275 | "00001001000100" : 202, 276 | "00001001000000" : 203, 277 | "01000100000001" : 204, 278 | "00000100000001" : 205, 279 | "00000010010000" : 206, 280 | "00100100000001" : 207, 281 | "00000100100100" : 208, 282 | "10000010010001" : 209, 283 | "10010010010001" : 210, 284 | "10000100100000" : 211, 285 | "01000010010001" : 212, 286 | "00000010010001" : 213, 287 | "00010010010001" : 214, 288 | "00100010010001" : 215, 289 | "01001000010001" : 216, 290 | "10000000010001" : 217, 291 | "10010000010001" : 218, 292 | "10001000010001" : 219, 293 | "01000000010001" : 220, 294 | "00001000010001" : 221, 295 | "00010000010001" : 222, 296 | "00100000010001" : 223, 297 | "01000100000010" : 224, 298 | "00000100000010" : 225, 299 | "10000100010010" : 226, 300 | "00100100000010" : 227, 301 | "01000100010010" : 228, 302 | "00000100010010" : 229, 303 | "01000000100010" : 230, 304 | "00100100010010" : 231, 305 | "10000100000010" : 232, 306 | "10000100000100" : 233, 307 | "00001001001001" : 234, 308 | "00001001000010" : 235, 309 | "01000100000100" : 236, 310 | "00000100000100" : 237, 311 | "00010000100010" : 238, 312 | "00100100000100" : 239, 313 | "00000100100010" : 240, 314 | "10000010010010" : 241, 315 | "10010010010010" : 242, 316 | "00001000100010" : 243, 317 | "01000010010010" : 244, 318 | "00000010010010" : 245, 319 | "00010010010010" : 246, 320 | "00100010010010" : 247, 321 | "01001000010010" : 248, 322 | "10000000010010" : 249, 323 | "10010000010010" : 250, 324 | "10001000010010" : 251, 325 | "01000000010010" : 252, 326 | "00001000010010" : 253, 327 | "00010000010010" : 254, 328 | "00100000010010" : 255 329 | } 330 | -------------------------------------------------------------------------------- /galois_field.py: -------------------------------------------------------------------------------- 1 | # galois_field.py 2 | # 3 | # Copyright (c) 2005--2015 by Sidney Cadot 4 | # This software is licensed under the GNU General Public License (GPL). 5 | # 6 | # This file is part of laser2wav, a software-only implementation of 7 | # an audio CD decoder. 8 | 9 | 10 | # Elements of GF(256) are represented as 8-bit unsigned integers, with 11 | # the LSB representing 'x^0', and the MSB representing 'x^7'. 12 | # 13 | # The field is generated by the polynomial 14 | # 15 | # g(x) = x^8 + x^4 + x^3 + x^2 + 1 16 | # 17 | # The primitive element alpha corresponds to the polynomial "x" here; 18 | # which means that alpha is represented as 00000010 (bin) = 2 (dec). 19 | 20 | # the generator polynomial 21 | generator = (1<<8) + (1<<4) + (1<<3) + (1<<2) + (1<<0) 22 | 23 | # the zero element 24 | zero = 0 25 | 26 | # the primitive element alpha 27 | alpha = (1<<1) 28 | 29 | # Below are Power, Logarithm, Multiplication, and Addition functions for 30 | # GF(2^8) as used in Compact Discs for Reed-Solomon encoding/decoding. 31 | 32 | # For all integers i, gf_alpha_power_table[i%255] == (alpha) ** i, 33 | # where alpha is the primitive element represented by the byte '2'. 34 | # Note that the zero element (represented by the byte '0') cannot be 35 | # represented as a power of alpha. 36 | 37 | # This table can easily be generated as follows: 38 | # 39 | # poly = 1 40 | # alpha_power_table = [] 41 | # for i in range(255): 42 | # alpha_power_table.append(poly) 43 | # poly = poly << 1 44 | # if poly >= (1<<8): 45 | # poly = add(poly, generator) # reduce modulo (generator) 46 | 47 | alpha_power_table = [ 48 | 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 49 | 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 50 | 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 51 | 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 52 | 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 53 | 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 54 | 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 55 | 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 56 | 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 57 | 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 58 | 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 59 | 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 60 | 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 61 | 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 62 | 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 63 | 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142 ] 64 | 65 | # For all elements 'e' except the zero element, alpha ^ gf_alpha_logarithm_table[e-1] == e, 66 | # where alpha is the primitive element represented by the byte '2'. 67 | # Note that the zero element (represented by the byte '0') cannot be 68 | # represented as a power of alpha, i.e. its logarithm does not exist. 69 | # 70 | # This table can be derived from the alpha_power_table as follows: 71 | # 72 | # alpha_logarithm_table = [] 73 | # for e in range(1, 256): 74 | # alpha_logarithm_table.append(alpha_power_table.index(e)) 75 | 76 | alpha_logarithm_table = [ 77 | 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 78 | 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 79 | 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 80 | 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 81 | 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 82 | 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 83 | 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 84 | 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 85 | 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 86 | 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 87 | 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 88 | 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 89 | 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 90 | 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 91 | 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 92 | 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 ] 93 | 94 | def alpha_power(i): 95 | # note that in Python, (negative_int % positive_int) is (sensibly!) 96 | # defined to yield a nonnegative integer, so this function also works 97 | # for negative arguments. 98 | return alpha_power_table[i % 255] 99 | 100 | def alpha_logarithm(e): 101 | assert e != zero # we cannot take the logarithm of the zero element. 102 | return alpha_logarithm_table[e - 1] 103 | 104 | def add(a, b): 105 | return (a ^ b) # xor operator 106 | 107 | def multiply(a, b): 108 | if (a == 0) or (b == 0): 109 | return 0 110 | else: 111 | return alpha_power(alpha_logarithm(a) + alpha_logarithm(b)) 112 | 113 | def power(e, i): 114 | assert e != zero # leave undefined, even though we could define it. 115 | return alpha_power(alpha_logarithm(e) * i) 116 | -------------------------------------------------------------------------------- /laser2wav.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # laser2wav.py 4 | # 5 | # Copyright (c) 2005--2015 by Sidney Cadot 6 | # This software is licensed under the GNU General Public License (GPL). 7 | # 8 | # This file is part of Laser2Wav, a software-only implementation of 9 | # an audio CD decoder. 10 | 11 | 12 | import sys, wave 13 | import galois_field as gf 14 | from efm import EFM 15 | 16 | goodsym = 0 17 | 18 | def analyze_frame(frame): 19 | assert len(frame) == 588 # verify that each frame is 588 bits long 20 | sync24 = frame[0:24] 21 | merge3 = frame[24:27] # ignore 22 | assert sync24 == "100000000001000000000010" 23 | 24 | global goodsym 25 | 26 | framedata = [] 27 | for i in range(33): 28 | channel14 = frame[27+17*i:41+17*i] 29 | merge3 = frame[41+17*i:44+17*i] # ignore 30 | data8 = 0 31 | try: 32 | data8 = EFM[channel14] 33 | goodsym = goodsym + 1 34 | except KeyError: 35 | print("argh-data") 36 | data8 = 0 37 | framedata.append(data8) 38 | 39 | return (framedata[0], framedata[1:]) 40 | 41 | def invert_last_16(Q): 42 | return Q[:-16] + str.join("", [str(1 - int(x)) for x in Q[-16:]]) 43 | 44 | def valid_crc(Q): 45 | # this is a simple way to check the CRC 46 | if len(Q) == 0: 47 | return True 48 | if Q[0] == '0': 49 | return valid_crc(Q[1:]) 50 | if len(Q)<17: 51 | return False 52 | 53 | # subtract multiple of the CRC generator polynomial 54 | Q = [int(x) for x in Q] 55 | Q[ 0] = 1 - Q[ 0] 56 | Q[ 4] = 1 - Q[ 4] 57 | Q[11] = 1 - Q[11] 58 | Q[16] = 1 - Q[16] 59 | Q = str.join("", [str(x) for x in Q]) 60 | return valid_crc(Q) 61 | 62 | def to_signed(n): 63 | if n >= 32768: 64 | n -= 65536 65 | return n 66 | 67 | def to_unsigned(n): 68 | if n < 0: 69 | n += 65536 70 | return n 71 | 72 | def bcd2int(S): 73 | assert len(S)==8 74 | hi = int(S[0:4], 2) 75 | #assert (hi>=0) and (hi<=9) 76 | lo = int(S[4:8], 2) 77 | #assert (lo>=0) and (lo<=9) 78 | return hi*10+lo 79 | 80 | def analyze_control_stream(control_stream): 81 | 82 | print("Analyzing control stream ...") 83 | 84 | all_zeros = 96 * "0" 85 | all_ones = 96 * "1" 86 | ok_map = {False: "ERROR", True: "ok"} 87 | 88 | while len(control_stream) >= 98: 89 | if control_stream[0] != 'SYNC0': 90 | # discard a control byte - not part of a sector 91 | control_stream = control_stream[1:] 92 | else: 93 | if control_stream[0] != 'SYNC0': 94 | print("BAD SYNC0") 95 | if control_stream[1] != 'SYNC1': 96 | print("BAD SYNC1") 97 | 98 | for i in range(2, len(control_stream)): 99 | if (control_stream[i] == 'SYNC0') or (control_stream[i] == 'SYNC1'): 100 | control_stream[i] = 0 101 | 102 | control_sector = control_stream[2:98] 103 | control_stream = control_stream[98:] 104 | 105 | P = str.join("", [str(int((x & 0x80) != 0)) for x in control_sector]) 106 | Q = str.join("", [str(int((x & 0x40) != 0)) for x in control_sector]) 107 | R = str.join("", [str(int((x & 0x20) != 0)) for x in control_sector]) 108 | S = str.join("", [str(int((x & 0x10) != 0)) for x in control_sector]) 109 | T = str.join("", [str(int((x & 0x08) != 0)) for x in control_sector]) 110 | U = str.join("", [str(int((x & 0x04) != 0)) for x in control_sector]) 111 | V = str.join("", [str(int((x & 0x02) != 0)) for x in control_sector]) 112 | W = str.join("", [str(int((x & 0x01) != 0)) for x in control_sector]) 113 | 114 | p_ok = (P in [all_zeros, all_ones]) 115 | q_ok = valid_crc(invert_last_16(Q)) 116 | r_ok = (R == all_zeros) # for most CDs 117 | s_ok = (S == all_zeros) # for most CDs 118 | t_ok = (T == all_zeros) # for most CDs 119 | u_ok = (U == all_zeros) # for most CDs 120 | v_ok = (V == all_zeros) # for most CDs 121 | w_ok = (W == all_zeros) # for most CDs 122 | 123 | print(" Sector sub-channels:") 124 | print(" P: <{}> {}".format(P, ok_map[p_ok])) 125 | print(" Q: <{}> {}".format(Q, ok_map[q_ok])) 126 | 127 | if q_ok: 128 | q_control = Q[0:4] 129 | q_mode = int(Q[4:8], 2) 130 | q_data = Q[8:80] 131 | if q_mode == 1: 132 | (tno,index,min,sec,frac,zero,amin,asec,afrac) = [bcd2int(q_data[8*i:8*i+8]) for i in range(9)] 133 | assert zero == 0 134 | print(" CONTROL .......... : <{}> [#channels / reserved / copy-protect / pre-emphasis]".format(q_control)) 135 | print(" MODE ............. : {}".format(q_mode)) 136 | print(" DATA ............. : trackno. {:02d} indexno. {:02d} track-time {:02d}:{:02d}.{:02d} [mm:ss.ff] disc-time {:02d}:{:02d}.{:02d} [mm:ss.ff]".format(tno, index, min, sec, frac, amin, asec, afrac)) 137 | else: 138 | print(" CONTROL .......... : <{}>".format(q_control)) 139 | print(" MODE ............. : {}".format(q_mode)) 140 | print(" DATA ............. : <{}>".format(q_data)) 141 | 142 | print(" R: <{}> {}".format(R, ok_map[r_ok])) 143 | print(" S: <{}> {}".format(S, ok_map[s_ok])) 144 | print(" T: <{}> {}".format(T, ok_map[t_ok])) 145 | print(" U: <{}> {}".format(U, ok_map[u_ok])) 146 | print(" V: <{}> {}".format(V, ok_map[v_ok])) 147 | print(" W: <{}> {}".format(W, ok_map[w_ok])) 148 | 149 | def verify_data_stream(data): 150 | 151 | errorsP = 0 152 | errorsQ = 0 153 | 154 | # make the Reed-Solomon check calculations 155 | 156 | invert = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255] 157 | 158 | # check C1 a.k.a. P-parity 159 | print("Checking C1 / P parity ...") 160 | p_delay = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] 161 | for i in range(1, len(data)): 162 | z_list = [] 163 | for hp_row in range(4): 164 | z = gf.zero 165 | for j in range(32): 166 | if (data[i-p_delay[j]][j] == 'SYNC1') or (data[i-p_delay[j]][j] == 'SYNC0'): 167 | data[i-p_delay[j]][j] = 0 168 | z = gf.add(z, gf.multiply(gf.power(gf.alpha, hp_row*(31-j)), data[i-p_delay[j]][j]^invert[j])) 169 | z_list.append(z) 170 | if z_list != [gf.zero, gf.zero, gf.zero, gf.zero]: 171 | print("P PARITY: ERROR DETECTED IN FRAME # {}".format(i)) 172 | errorsP = errorsP + 1 173 | else: 174 | print("P PARITY: FRAME # {} GOOD".format(i)) 175 | 176 | 177 | # check C2 a.k.a. Q-parity 178 | print("Checking C2 / Q parity ...") 179 | q_delay = [107, 104, 99, 96, 91, 88, 83, 80, 75, 72, 67, 64, 59, 56, 51, 48, 43, 40, 35, 32, 27, 24, 19, 16, 11, 8, 3, 0] 180 | for i in range(107, len(data)): 181 | z_list = [] 182 | for hq_row in range(4): 183 | z = gf.zero 184 | for j in range(28): 185 | if (data[i-q_delay[j]][j] == 'SYNC1') or (data[i-q_delay[j]][j] == 'SYNC0'): 186 | data[i-q_delay[j]][j] = 0 187 | z = gf.add(z, gf.multiply(gf.power(gf.alpha, hq_row*(27-j)), data[i-q_delay[j]][j]^invert[j])) 188 | z_list.append(z) 189 | if z_list!= [gf.zero, gf.zero, gf.zero, gf.zero]: 190 | print("Q PARITY: ERROR DETECTED IN FRAME # {}".format(i)) 191 | errorsQ = errorsQ + 1 192 | else: 193 | print("Q PARITY: FRAME # {} GOOD".format(i)) 194 | 195 | return (errorsP, errorsQ) 196 | 197 | def extract_audio_stream(data): 198 | 199 | audio_data = [] 200 | 201 | for i in range(105, len(data)): 202 | for j in range(0, 28): 203 | if (data[i - 105][j] == 'SYNC1') or (data[i - 105][j] == 'SYNC2'): 204 | data[i - 105][j] = 0 205 | 206 | for i in range(105, len(data)): 207 | 208 | L0 = to_signed(data[i-105][ 0]*256 + data[i-102][ 1]) 209 | L2 = to_signed(data[i- 97][ 2]*256 + data[i- 94][ 3]) 210 | L4 = to_signed(data[i- 89][ 4]*256 + data[i- 86][ 5]) 211 | 212 | R0 = to_signed(data[i- 81][ 6]*256 + data[i- 78][ 7]) 213 | R2 = to_signed(data[i- 73][ 8]*256 + data[i- 70][ 9]) 214 | R4 = to_signed(data[i- 65][10]*256 + data[i- 62][11]) 215 | 216 | L1 = to_signed(data[i- 43][16]*256 + data[i- 40][17]) 217 | L3 = to_signed(data[i- 35][18]*256 + data[i- 32][19]) 218 | L5 = to_signed(data[i- 27][20]*256 + data[i- 24][21]) 219 | 220 | R1 = to_signed(data[i- 19][22]*256 + data[i- 16][23]) 221 | R3 = to_signed(data[i- 11][24]*256 + data[i- 8][25]) 222 | R5 = to_signed(data[i- 3][26]*256 + data[i- 0][27]) 223 | 224 | audio_data.extend([(L0, R0), (L1, R1), (L2, R2), (L3, R3), (L4, R4), (L5, R5)]) 225 | 226 | return audio_data 227 | 228 | def write_wav_file(filename_out, audio_data): 229 | 230 | # Python's WAV-file writer expects raw data, encoded as a string. 231 | 232 | wave_data = bytearray() 233 | for (left, right) in audio_data: 234 | left = to_unsigned(left) 235 | right = to_unsigned(right) 236 | wave_data.append(left % 256) 237 | wave_data.append(left // 256) 238 | wave_data.append(right % 256) 239 | wave_data.append(right // 256) 240 | 241 | # Write the WAV file. 242 | 243 | wf = wave.open(filename_out, "w") 244 | wf.setnchannels(2) # stereo 245 | wf.setsampwidth(2) # 16-bit signal; 2 bytes per sample 246 | wf.setframerate(44100) 247 | wf.writeframes(wave_data) 248 | wf.close() 249 | 250 | def main(): 251 | 252 | if len(sys.argv) != 3: 253 | print("Usage: python laser2wav.py ") 254 | return 255 | 256 | filename_in = sys.argv[1] 257 | filename_out = sys.argv[2] 258 | 259 | print("Reading raw signal ...") 260 | with open(filename_in, "rb") as f: 261 | raw_signal = f.read() 262 | 263 | print("Converting to delta signal...") 264 | raw_signal = [int (x) for x in raw_signal] 265 | delta_signal = [str(raw_signal[i] ^ raw_signal[i - 1]) for i in range(1, len(raw_signal))] 266 | delta_signal = str.join("", delta_signal) 267 | 268 | print("Walking frames...") 269 | control_stream = [] 270 | data_stream = [] 271 | 272 | fcount = 0 273 | 274 | prev_z = 0 275 | z = 0 276 | while True: 277 | z = delta_signal.find("100000000001000000000010", z) 278 | if z < 0: 279 | break 280 | 281 | print("delta at ", z, z - prev_z) 282 | 283 | frame = delta_signal[z:z+588] 284 | if len(frame) == 588: 285 | (control, data) = analyze_frame(frame) 286 | control_stream.append(control) 287 | data_stream.append(data) 288 | fcount = fcount + 1 289 | prev_z = z 290 | z = z + 22 291 | 292 | global goodsym 293 | print(goodsym, " good symbols") 294 | print(fcount, " frames found") 295 | 296 | analyze_control_stream(control_stream) 297 | 298 | verify_data_stream(data_stream) 299 | 300 | print("Extracting audio data ...") 301 | audio_data = extract_audio_stream(data_stream) 302 | 303 | print("Writing WAV file ...") 304 | write_wav_file(filename_out, audio_data) 305 | 306 | if __name__ == "__main__": 307 | main() 308 | -------------------------------------------------------------------------------- /ld-decode-pcm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import numpy as np 3 | import scipy.signal as sps 4 | import sys 5 | 6 | # 7 | # Sample usage: 8 | # cat ../raw_samples/ld_dig_audio.raw | ./ld-decode-pcm.py > raw-efm.raw 9 | # 10 | # Outputs what it decodes to stderr, and it's post processed output to stdout 11 | # 12 | 13 | FREQ_MHZ = (315.0 / 88.0) * 8.0 14 | FREQ_HZ = FREQ_MHZ * 1000000.0 15 | NYQUIST_MHZ = FREQ_MHZ / 2 16 | 17 | EFM_CLOCK_FREQ_MHZ = 4.3218 18 | EFM_CLOCK_FREQ_HZ = EFM_CLOCK_FREQ_MHZ * 1e6 19 | EFM_PIXEL_RATE = FREQ_MHZ / EFM_CLOCK_FREQ_MHZ 20 | 21 | SAMPLES = 2000000 # Number of samples to decode 22 | 23 | 24 | def printerr(txt): 25 | """ Avoid clogging up the stdout with logging """ 26 | sys.stderr.write(str(txt) + '\n') 27 | 28 | 29 | def coroutine(func): 30 | """ Decorate a coroutine do deal with the ugly first yield/send problem """ 31 | 32 | def start(*args, **kwargs): 33 | cr = func(*args, **kwargs) 34 | cr.next() 35 | return cr 36 | 37 | return start 38 | 39 | 40 | @coroutine 41 | def biquad_filter(a1, a2, b0, b1, b2): 42 | """ Biquadratic IIR filter (Direct form I) 43 | This is a co-routine, and uses yield and send to deliver and receive samples. 44 | Be warned some calculators transpose a and b params (we use (b)efore and (a)fter 45 | """ 46 | x1, x2, y1, y2, y = 0.0, 0.0, 0.0, 0.0, 0.0 47 | while 1: 48 | x = (yield int(y)) # Sent by send 49 | y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2 50 | x2 = x1 51 | x1 = x 52 | y2 = y1 53 | y1 = y # Shuffle samples along 54 | 55 | 56 | def edge_pll(data, expected_rate): 57 | """ Ultra-naive Phase locked loop, looking for zero crossings at the expected rate (i.e. once every N samples), 58 | adjust a bit when we find one before that. """ 59 | state = 0 60 | next_sample = 0 61 | val = 0 62 | for i, sample in enumerate(data): 63 | if (sample > 0 and not state ) or ( sample <= 0 and state ): 64 | state = sample > 0 65 | val = 1 66 | if next_sample - i > 1: 67 | next_sample = next_sample - 1 # phase locking a bit. 68 | if i == next_sample: 69 | yield int(val) 70 | val = 0 71 | next_sample = int(i + expected_rate) 72 | 73 | 74 | def run_filter(ffilter, data): 75 | """ Run the passed coroutine filter over the data - returning a generator """ 76 | for point in data: 77 | yield ffilter.send(point) 78 | 79 | 80 | def run_until_start_code(bit_gen): 81 | """ Consume bits from a bit generator until we see a frame start code """ 82 | cnt = 0 83 | flag = 0 # Need two start codes in a row 84 | while 1: 85 | bit = bit_gen.next() 86 | if not bit: 87 | cnt += 1 88 | if cnt == 11: 89 | if bit_gen.next(): 90 | if flag: 91 | bit_gen.next() # One more zero (we hope) 92 | printerr('Found start code !') 93 | return 94 | flag = 1 95 | else: 96 | flag = 0 97 | cnt = 0 98 | else: 99 | flag = 0 100 | cnt = 0 101 | 102 | 103 | def eat_three_bits(bit_gen): 104 | """ Eat the three DC correction bits """ 105 | bit_gen.next() 106 | bit_gen.next() 107 | bit_gen.next() 108 | 109 | 110 | def process_efm_frame(bit_gen, num_words=31): 111 | """ Process an EFM frame from the passed bit generator, reading the passed number of words """ 112 | buff = [] 113 | buffer_append = buff.append 114 | i = 0 115 | while i <= num_words: 116 | buffer_append(bit_gen.next()) 117 | if len(buff) == 14: 118 | printerr(buff) 119 | buff[:] = [] # Oh for list.clear in Python 2 120 | eat_three_bits(bit_gen) 121 | i += 1 122 | printerr('consumed frame') 123 | 124 | 125 | def auto_gain(data, peak, logline=None): 126 | """ Automatically apply gain to the passed nparray, with the aim of adjusting peak amplitude to passed peak """ 127 | gain = float(peak) / max(max(data), abs(min(data))) 128 | if logline: 129 | printerr('auto_gain %s: %.2f' % (logline, gain )) 130 | return data * gain 131 | 132 | 133 | def decode_efm(): 134 | """ Decode EFM from STDIN, assuming it's a 28Mhz 8bit raw stream """ 135 | datao = np.fromstring(sys.stdin.read(SAMPLES), dtype=np.uint8).astype(np.int16) 136 | datao = sps.detrend(datao, type='constant') # Remove DC 137 | 138 | datao = auto_gain(datao, 10000, 'pre-filter') # Expand before filtering, since we'll lose much of signal otherwise 139 | 140 | low_pass = sps.butter(4, 1.75 / FREQ_MHZ, btype='lowpass') # Low pass at 1.75 Mhz 141 | datao = sps.lfilter(low_pass[0], low_pass[1], datao) 142 | 143 | high_pass = sps.butter(4, 0.01333 / FREQ_MHZ, btype='highpass') # High pass at 13.333 khz 144 | datao = sps.lfilter(high_pass[0], high_pass[1], datao) 145 | 146 | # This is too slow, need to work out a way to do it in scipy 147 | de_emphasis_filter = biquad_filter(-1.8617006585639506, 0.8706642683920058, 0.947680874725466, -1.8659578411373265, 0.9187262110931641) 148 | datao = np.fromiter(run_filter(de_emphasis_filter, datao), np.int16) # De-emph - 26db below 500khz 149 | 150 | # Could tie edge_pll and run_filter together as generators, but we want to see the filter output 151 | 152 | bit_gen = edge_pll(datao, EFM_PIXEL_RATE) # This is a ultra-naive PLL that returns a bit-stream of 1 = edge, 0 = no-edge 153 | try: 154 | while 1: 155 | run_until_start_code(bit_gen) 156 | eat_three_bits(bit_gen) 157 | process_efm_frame(bit_gen, 31) # 31 14 bit EFM codes in a frame 158 | except StopIteration: 159 | printerr('Hit the end of the bitstream') 160 | 161 | datao = np.clip(datao, 0, 255).astype(np.uint8) 162 | sys.stdout.write(datao.tostring()) 163 | 164 | 165 | if __name__ == "__main__": 166 | decode_efm() -------------------------------------------------------------------------------- /ldd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import numpy as np 4 | from matplotlib import pyplot as plt 5 | from scipy.stats import itemfreq 6 | 7 | import numpy as np 8 | import scipy.signal as sps 9 | 10 | freq = (315.0 / 88.0) * 8.0 11 | 12 | def doplot(B, A): 13 | w, h = sps.freqz(B, A) 14 | 15 | fig = plt.figure() 16 | plt.title('Digital filter frequency response') 17 | 18 | db = 20 * np.log10(abs(h)) 19 | 20 | ax1 = fig.add_subplot(111) 21 | 22 | plt.plot(w * (freq/np.pi) / 2.0, 20 * np.log10(abs(h)), 'b') 23 | plt.ylabel('Amplitude [dB]', color='b') 24 | plt.xlabel('Frequency [rad/sample]') 25 | 26 | ax2 = ax1.twinx() 27 | angles = np.unwrap(np.angle(h)) 28 | plt.plot(w * (freq/np.pi) / 2.0, angles, 'g') 29 | plt.ylabel('Angle (radians)', color='g') 30 | 31 | plt.grid() 32 | plt.axis('tight') 33 | plt.show() 34 | 35 | 36 | CD_BASE_FREQUENCY = 4321800.0 # Hz 37 | SAMPLE_FREQUENCY = 28.636e6 # Hz 38 | 39 | FREQ_MHZ = (315.0 / 88.0) * 8.0 40 | NYQUIST_MHZ = FREQ_MHZ / 2 41 | FREQ_HZ = FREQ_MHZ * 1000000.0 42 | NYQUIST_HZ = FREQ_HZ / 2 43 | 44 | data = np.fromfile("ldd.raw", dtype = np.uint8) 45 | 46 | # remove the first samples because they are strange (lower amplitude) 47 | data = data[2650:len(data)-5000] 48 | 49 | #for i in range(0, len(data)): 50 | # print i / FREQ_HZ,",", (data[i] / 256.0) - .5 51 | # 52 | #exit() 53 | 54 | # without filter: 299/964 55 | # poles at 0 and 49700 hz, 3.202312738us, zero at 1.59mhz/0.100097448us 56 | 57 | # this shhould be - but doesn't work worth a darn 58 | deemp_pole = .100097448 * 1 59 | deemp_zero = 3.202312738 * 1 60 | 61 | # 21/1018 62 | # NEW MEASURE: 850/1079 63 | deemp_pole = .1100 * 1 64 | deemp_zero = 3.100 * 1 65 | lowpass_b, lowpass_a = sps.butter(2, 2.120/NYQUIST_MHZ) 66 | 67 | # 999/1050 68 | deemp_pole = .1450 * 1 69 | deemp_zero = 3.100 * 1 70 | lowpass_b, lowpass_a = sps.butter(2, 2.120/NYQUIST_MHZ) 71 | 72 | # 1177/798 73 | deemp_pole = .1450 * 1 74 | deemp_zero = 3.100 * 1 75 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 76 | 77 | # 1204/771 78 | deemp_pole = .1450 * 1 79 | deemp_zero = 3.202312738 * 1 80 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 81 | 82 | # 1204/771 83 | deemp_pole = .1450 * 1 84 | deemp_zero = 3.202312738 * 1 85 | lowpass_b, lowpass_a = sps.butter(4, 2.200/NYQUIST_MHZ) 86 | 87 | # LDD - 0 good 1593 bad 88 | deemp_pole = .1000 * 1 89 | deemp_zero = 3.202312738 * 1 90 | # below from megapixie 91 | lowpass_b, lowpass_a = sps.cheby2(16, 100., 2.15 / NYQUIST_MHZ) # Looks a bit odd, but is a reasonable tie for the spec filter (-26db at 2.0 Mhz, -50+ at 2.3Mhz) 92 | 93 | # LDD - 0 good 2185bad 31046 symbols 94 | deemp_pole = .0400 * 1 95 | deemp_zero = 3.202312738 * 1 96 | # below from megapixie 97 | lowpass_b, lowpass_a = sps.cheby2(16, 100., 2.15 / NYQUIST_MHZ) # Looks a bit odd, but is a reasonable tie for the spec filter (-26db at 2.0 Mhz, -50+ at 2.3Mhz) 98 | 99 | # LDD - 0/2521/40740 100 | deemp_pole = .0400 * 1 101 | deemp_zero = 3.202312738 * 1 102 | lowpass_b, lowpass_a = sps.cheby2(14, 100., 1.80 / NYQUIST_MHZ) 103 | 104 | # LDD - 0/2531/41244 105 | deemp_pole = .0370 * 1 106 | deemp_zero = 3.102312738 * 1 107 | lowpass_b, lowpass_a = sps.cheby2(14, 100., 1.80 / NYQUIST_MHZ) 108 | 109 | # LDD - 0/2535/41337 110 | deemp_pole = .0360 * 1 111 | deemp_zero = 3.102312738 * 1 112 | lowpass_b, lowpass_a = sps.cheby2(14, 100., 1.80 / NYQUIST_MHZ) 113 | 114 | # LDD - 0/2535/41337 115 | deemp_pole = .0360 * 1 116 | deemp_zero = 3.102312738 * 1 117 | lowpass_b, lowpass_a = sps.cheby2(14, 100., 1.800 / NYQUIST_MHZ) 118 | 119 | [tf_b, tf_a] = sps.zpk2tf([-deemp_pole*(10**-8)], [-deemp_zero*(10**-8)], deemp_zero / deemp_pole) 120 | [f_emp_b, f_emp_a] = sps.bilinear(tf_b, tf_a, .5/FREQ_HZ) 121 | 122 | # .295 leftover scale: 1053 frames found, 34731 good samps, 374 ERRORS 123 | bandpass = sps.firwin(55, [.335/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 124 | 125 | # .295 leftover scale: 1053 frames found, 34731 good samps, 374 ERRORS 126 | # 1647/352 127 | bandpass = sps.firwin(53, [.335/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 128 | 129 | # 1651/348 130 | bandpass = sps.firwin(53, [.355/NYQUIST_MHZ, 1.870/NYQUIST_MHZ], pass_zero=False) 131 | # 1652/343 132 | bandpass = sps.firwin(53, [.355/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 133 | # 1660/341 134 | bandpass = sps.firwin(39, [.600/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 135 | # 1660/339 136 | bandpass = sps.firwin(37, [.900/NYQUIST_MHZ, 2.800/NYQUIST_MHZ], pass_zero=False) 137 | 138 | #doplot(f_emp_b, f_emp_a) 139 | #doplot(bandpass, [1.0]) 140 | #exit() 141 | 142 | # convert to single-precision floats 143 | #data = data.astype(np.float32) 144 | 145 | # fewer errors if we filter as double precision 146 | data = data.astype(np.float64) 147 | 148 | # subtract DC component 149 | dc = data.mean() 150 | data -= dc 151 | 152 | #plt.plot(data[5000:6000]) 153 | 154 | data = sps.lfilter(lowpass_b, lowpass_a, data) 155 | data = sps.lfilter(f_emp_b, f_emp_a, data) 156 | 157 | #data = sps.lfilter(bandpass, [1.0], data) 158 | 159 | plt.plot(data[5000:6000]) 160 | #plt.show() 161 | #exit() 162 | 163 | # filter to binary signal 164 | data = (data > 0.0) 165 | 166 | transition = np.diff(data) != 0 167 | transition = np.insert(transition, 0, False) # The first sample is never a transition. 168 | 169 | print "data", data.shape, data.dtype 170 | print "transition", transition.shape, transition.dtype 171 | 172 | runLengths = np.diff(np.where(transition)[0]) 173 | 174 | # fetch run signal values. The last transition 175 | # isn't part of a well-defined run, so we don't need it. 176 | 177 | runValues = data[transition].astype(np.int8) 178 | runValues = runValues[:-1] 179 | 180 | print "runLengths", runLengths.shape, runLengths.dtype 181 | print "runValues", runValues.shape, runValues.dtype 182 | 183 | totalRunlength0 = np.sum(runLengths[runValues == 0]) 184 | totalRunlength1 = np.sum(runLengths[runValues == 1]) 185 | 186 | bias = (totalRunlength0 - totalRunlength1) / (SAMPLE_FREQUENCY * len(runLengths)) 187 | print "bias: {} seconds".format(bias), (totalRunlength0 - totalRunlength1) 188 | 189 | runDurations = runLengths / SAMPLE_FREQUENCY # to SECONDS 190 | 191 | runDurations[runValues == 0] -= bias 192 | runDurations[runValues == 1] += bias 193 | 194 | runDurations = runDurations * CD_BASE_FREQUENCY # to CD BASE FREQUENCY TICKS 195 | 196 | if False: 197 | 198 | print "plotting ..." 199 | 200 | freqAll = itemfreq(runDurations) 201 | freq0 = itemfreq(runDurations[runValues == 0]) 202 | freq1 = itemfreq(runDurations[runValues == 1]) 203 | 204 | plt.subplot(411) 205 | plt.title("All runs (bias corrected)") 206 | plt.xlim(0, 13) 207 | plt.plot(freqAll[:, 0], freqAll[:, 1], '.-') 208 | 209 | plt.subplot(412) 210 | plt.title("Bias-corrected zero runs ") 211 | plt.xlim(0, 13) 212 | plt.plot(freq0[:, 0], freq0[:, 1], '.-') 213 | 214 | plt.subplot(413) 215 | plt.title("Bias-corrected one runs") 216 | plt.xlim(0, 13) 217 | plt.plot(freq1[:, 0], freq1[:, 1], '.-') 218 | 219 | plt.subplot(414) 220 | plt.title("Bias-corrected zero/one runs, overlayed") 221 | plt.xlim(0, 13) 222 | plt.plot(freq0[:, 0], freq0[:, 1], '.-') 223 | plt.plot(freq1[:, 0], freq1[:, 1], '.-') 224 | 225 | plt.savefig("chopin8.pdf") 226 | plt.close() 227 | 228 | if True: 229 | 230 | print "writing file ..." 231 | 232 | with open("chopin8-bits.txt", "w") as f: 233 | leftover = 0 234 | for (value, duration) in zip(runValues, runDurations): 235 | #durationr = int(round(duration + (leftover * .111))) # to integer 236 | #durationr = int(round(duration + (leftover * 0.22))) # to integer 237 | #durationr = int(round(duration + (leftover * 0.24))) # to integer 238 | # durationr = int(round(duration + (leftover * 0.270))) # to integer 239 | durationr = int(round(duration + (leftover * 0.295))) # to integer 240 | # durationr = int(round(duration)) # to integer 241 | leftover = duration - durationr 242 | 243 | f.write(str(value) * durationr) 244 | -------------------------------------------------------------------------------- /wav_format.py: -------------------------------------------------------------------------------- 1 | # wav_format.py 2 | # 3 | # Copyright (c) 2005 by Sidney Cadot 4 | # This software is licensed under the GNU General Public License (GPL). 5 | # 6 | # This file is part of laser2wav, a software-only implementation of 7 | # an audio CD decoder. 8 | # 9 | ############################################################################### 10 | 11 | def little_endian_long(n): 12 | return [(n)&255, (n>>8)&255, (n>>16)&255, (n>>24)&255] 13 | 14 | def little_endian_short(n): 15 | return [(n)&255, (n>>8)&255] 16 | 17 | def clip(n): 18 | if (n<-32768): n = -32768 19 | if (n>32767): n = 32767 20 | return n 21 | 22 | def to_unsigned(n): 23 | if (n<0): 24 | n = n + 65536 25 | return n 26 | 27 | def wave_file_string(audio_samples): 28 | 29 | bytes = [] 30 | 31 | num_samples = len(audio_samples) 32 | subchunk1size = 16 33 | audio_format = 1 34 | num_channels = 2 35 | sample_rate = 44100 36 | bits_per_sample = 16 37 | byte_rate = sample_rate * num_channels * bits_per_sample/8 38 | block_align = num_channels * bits_per_sample/8 39 | subchunk2size = num_samples * num_channels * bits_per_sample/8 40 | 41 | chunksize = 4 + (8+subchunk1size) + (8+subchunk2size) 42 | 43 | bytes.extend([ord(x) for x in "RIFF"]) 44 | bytes.extend(little_endian_long(chunksize)) 45 | bytes.extend([ord(x) for x in "WAVE"]) 46 | bytes.extend([ord(x) for x in "fmt "]) 47 | bytes.extend(little_endian_long(subchunk1size)) 48 | bytes.extend(little_endian_short(audio_format)) 49 | bytes.extend(little_endian_short(num_channels)) 50 | bytes.extend(little_endian_long(sample_rate)) 51 | bytes.extend(little_endian_long(byte_rate)) 52 | bytes.extend(little_endian_short(block_align)) 53 | bytes.extend(little_endian_short(bits_per_sample)) 54 | bytes.extend([ord(x) for x in "data"]) 55 | bytes.extend(little_endian_long(subchunk2size)) 56 | 57 | for (L, R) in audio_samples: 58 | bytes.extend(little_endian_short(to_unsigned(clip(L)))) 59 | bytes.extend(little_endian_short(to_unsigned(clip(R)))) 60 | 61 | return str.join("", map(chr, bytes)) 62 | --------------------------------------------------------------------------------