├── 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 |
--------------------------------------------------------------------------------