VGM playback library for Common Lisp
Révision | 98c8a177535578ebde97c5052456e395662d4d34 (tree) |
---|---|
l'heure | 2023-05-24 17:52:21 |
Auteur | Remilia Scarlet <remilia@post...> |
Commiter | Remilia Scarlet |
Initial import
@@ -0,0 +1,661 @@ | ||
1 | + GNU AFFERO GENERAL PUBLIC LICENSE | |
2 | + Version 3, 19 November 2007 | |
3 | + | |
4 | + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> | |
5 | + Everyone is permitted to copy and distribute verbatim copies | |
6 | + of this license document, but changing it is not allowed. | |
7 | + | |
8 | + Preamble | |
9 | + | |
10 | + The GNU Affero General Public License is a free, copyleft license for | |
11 | +software and other kinds of works, specifically designed to ensure | |
12 | +cooperation with the community in the case of network server software. | |
13 | + | |
14 | + The licenses for most software and other practical works are designed | |
15 | +to take away your freedom to share and change the works. By contrast, | |
16 | +our General Public Licenses are intended to guarantee your freedom to | |
17 | +share and change all versions of a program--to make sure it remains free | |
18 | +software for all its users. | |
19 | + | |
20 | + When we speak of free software, we are referring to freedom, not | |
21 | +price. Our General Public Licenses are designed to make sure that you | |
22 | +have the freedom to distribute copies of free software (and charge for | |
23 | +them if you wish), that you receive source code or can get it if you | |
24 | +want it, that you can change the software or use pieces of it in new | |
25 | +free programs, and that you know you can do these things. | |
26 | + | |
27 | + Developers that use our General Public Licenses protect your rights | |
28 | +with two steps: (1) assert copyright on the software, and (2) offer | |
29 | +you this License which gives you legal permission to copy, distribute | |
30 | +and/or modify the software. | |
31 | + | |
32 | + A secondary benefit of defending all users' freedom is that | |
33 | +improvements made in alternate versions of the program, if they | |
34 | +receive widespread use, become available for other developers to | |
35 | +incorporate. Many developers of free software are heartened and | |
36 | +encouraged by the resulting cooperation. However, in the case of | |
37 | +software used on network servers, this result may fail to come about. | |
38 | +The GNU General Public License permits making a modified version and | |
39 | +letting the public access it on a server without ever releasing its | |
40 | +source code to the public. | |
41 | + | |
42 | + The GNU Affero General Public License is designed specifically to | |
43 | +ensure that, in such cases, the modified source code becomes available | |
44 | +to the community. It requires the operator of a network server to | |
45 | +provide the source code of the modified version running there to the | |
46 | +users of that server. Therefore, public use of a modified version, on | |
47 | +a publicly accessible server, gives the public access to the source | |
48 | +code of the modified version. | |
49 | + | |
50 | + An older license, called the Affero General Public License and | |
51 | +published by Affero, was designed to accomplish similar goals. This is | |
52 | +a different license, not a version of the Affero GPL, but Affero has | |
53 | +released a new version of the Affero GPL which permits relicensing under | |
54 | +this license. | |
55 | + | |
56 | + The precise terms and conditions for copying, distribution and | |
57 | +modification follow. | |
58 | + | |
59 | + TERMS AND CONDITIONS | |
60 | + | |
61 | + 0. Definitions. | |
62 | + | |
63 | + "This License" refers to version 3 of the GNU Affero General Public License. | |
64 | + | |
65 | + "Copyright" also means copyright-like laws that apply to other kinds of | |
66 | +works, such as semiconductor masks. | |
67 | + | |
68 | + "The Program" refers to any copyrightable work licensed under this | |
69 | +License. Each licensee is addressed as "you". "Licensees" and | |
70 | +"recipients" may be individuals or organizations. | |
71 | + | |
72 | + To "modify" a work means to copy from or adapt all or part of the work | |
73 | +in a fashion requiring copyright permission, other than the making of an | |
74 | +exact copy. The resulting work is called a "modified version" of the | |
75 | +earlier work or a work "based on" the earlier work. | |
76 | + | |
77 | + A "covered work" means either the unmodified Program or a work based | |
78 | +on the Program. | |
79 | + | |
80 | + To "propagate" a work means to do anything with it that, without | |
81 | +permission, would make you directly or secondarily liable for | |
82 | +infringement under applicable copyright law, except executing it on a | |
83 | +computer or modifying a private copy. Propagation includes copying, | |
84 | +distribution (with or without modification), making available to the | |
85 | +public, and in some countries other activities as well. | |
86 | + | |
87 | + To "convey" a work means any kind of propagation that enables other | |
88 | +parties to make or receive copies. Mere interaction with a user through | |
89 | +a computer network, with no transfer of a copy, is not conveying. | |
90 | + | |
91 | + An interactive user interface displays "Appropriate Legal Notices" | |
92 | +to the extent that it includes a convenient and prominently visible | |
93 | +feature that (1) displays an appropriate copyright notice, and (2) | |
94 | +tells the user that there is no warranty for the work (except to the | |
95 | +extent that warranties are provided), that licensees may convey the | |
96 | +work under this License, and how to view a copy of this License. If | |
97 | +the interface presents a list of user commands or options, such as a | |
98 | +menu, a prominent item in the list meets this criterion. | |
99 | + | |
100 | + 1. Source Code. | |
101 | + | |
102 | + The "source code" for a work means the preferred form of the work | |
103 | +for making modifications to it. "Object code" means any non-source | |
104 | +form of a work. | |
105 | + | |
106 | + A "Standard Interface" means an interface that either is an official | |
107 | +standard defined by a recognized standards body, or, in the case of | |
108 | +interfaces specified for a particular programming language, one that | |
109 | +is widely used among developers working in that language. | |
110 | + | |
111 | + The "System Libraries" of an executable work include anything, other | |
112 | +than the work as a whole, that (a) is included in the normal form of | |
113 | +packaging a Major Component, but which is not part of that Major | |
114 | +Component, and (b) serves only to enable use of the work with that | |
115 | +Major Component, or to implement a Standard Interface for which an | |
116 | +implementation is available to the public in source code form. A | |
117 | +"Major Component", in this context, means a major essential component | |
118 | +(kernel, window system, and so on) of the specific operating system | |
119 | +(if any) on which the executable work runs, or a compiler used to | |
120 | +produce the work, or an object code interpreter used to run it. | |
121 | + | |
122 | + The "Corresponding Source" for a work in object code form means all | |
123 | +the source code needed to generate, install, and (for an executable | |
124 | +work) run the object code and to modify the work, including scripts to | |
125 | +control those activities. However, it does not include the work's | |
126 | +System Libraries, or general-purpose tools or generally available free | |
127 | +programs which are used unmodified in performing those activities but | |
128 | +which are not part of the work. For example, Corresponding Source | |
129 | +includes interface definition files associated with source files for | |
130 | +the work, and the source code for shared libraries and dynamically | |
131 | +linked subprograms that the work is specifically designed to require, | |
132 | +such as by intimate data communication or control flow between those | |
133 | +subprograms and other parts of the work. | |
134 | + | |
135 | + The Corresponding Source need not include anything that users | |
136 | +can regenerate automatically from other parts of the Corresponding | |
137 | +Source. | |
138 | + | |
139 | + The Corresponding Source for a work in source code form is that | |
140 | +same work. | |
141 | + | |
142 | + 2. Basic Permissions. | |
143 | + | |
144 | + All rights granted under this License are granted for the term of | |
145 | +copyright on the Program, and are irrevocable provided the stated | |
146 | +conditions are met. This License explicitly affirms your unlimited | |
147 | +permission to run the unmodified Program. The output from running a | |
148 | +covered work is covered by this License only if the output, given its | |
149 | +content, constitutes a covered work. This License acknowledges your | |
150 | +rights of fair use or other equivalent, as provided by copyright law. | |
151 | + | |
152 | + You may make, run and propagate covered works that you do not | |
153 | +convey, without conditions so long as your license otherwise remains | |
154 | +in force. You may convey covered works to others for the sole purpose | |
155 | +of having them make modifications exclusively for you, or provide you | |
156 | +with facilities for running those works, provided that you comply with | |
157 | +the terms of this License in conveying all material for which you do | |
158 | +not control copyright. Those thus making or running the covered works | |
159 | +for you must do so exclusively on your behalf, under your direction | |
160 | +and control, on terms that prohibit them from making any copies of | |
161 | +your copyrighted material outside their relationship with you. | |
162 | + | |
163 | + Conveying under any other circumstances is permitted solely under | |
164 | +the conditions stated below. Sublicensing is not allowed; section 10 | |
165 | +makes it unnecessary. | |
166 | + | |
167 | + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |
168 | + | |
169 | + No covered work shall be deemed part of an effective technological | |
170 | +measure under any applicable law fulfilling obligations under article | |
171 | +11 of the WIPO copyright treaty adopted on 20 December 1996, or | |
172 | +similar laws prohibiting or restricting circumvention of such | |
173 | +measures. | |
174 | + | |
175 | + When you convey a covered work, you waive any legal power to forbid | |
176 | +circumvention of technological measures to the extent such circumvention | |
177 | +is effected by exercising rights under this License with respect to | |
178 | +the covered work, and you disclaim any intention to limit operation or | |
179 | +modification of the work as a means of enforcing, against the work's | |
180 | +users, your or third parties' legal rights to forbid circumvention of | |
181 | +technological measures. | |
182 | + | |
183 | + 4. Conveying Verbatim Copies. | |
184 | + | |
185 | + You may convey verbatim copies of the Program's source code as you | |
186 | +receive it, in any medium, provided that you conspicuously and | |
187 | +appropriately publish on each copy an appropriate copyright notice; | |
188 | +keep intact all notices stating that this License and any | |
189 | +non-permissive terms added in accord with section 7 apply to the code; | |
190 | +keep intact all notices of the absence of any warranty; and give all | |
191 | +recipients a copy of this License along with the Program. | |
192 | + | |
193 | + You may charge any price or no price for each copy that you convey, | |
194 | +and you may offer support or warranty protection for a fee. | |
195 | + | |
196 | + 5. Conveying Modified Source Versions. | |
197 | + | |
198 | + You may convey a work based on the Program, or the modifications to | |
199 | +produce it from the Program, in the form of source code under the | |
200 | +terms of section 4, provided that you also meet all of these conditions: | |
201 | + | |
202 | + a) The work must carry prominent notices stating that you modified | |
203 | + it, and giving a relevant date. | |
204 | + | |
205 | + b) The work must carry prominent notices stating that it is | |
206 | + released under this License and any conditions added under section | |
207 | + 7. This requirement modifies the requirement in section 4 to | |
208 | + "keep intact all notices". | |
209 | + | |
210 | + c) You must license the entire work, as a whole, under this | |
211 | + License to anyone who comes into possession of a copy. This | |
212 | + License will therefore apply, along with any applicable section 7 | |
213 | + additional terms, to the whole of the work, and all its parts, | |
214 | + regardless of how they are packaged. This License gives no | |
215 | + permission to license the work in any other way, but it does not | |
216 | + invalidate such permission if you have separately received it. | |
217 | + | |
218 | + d) If the work has interactive user interfaces, each must display | |
219 | + Appropriate Legal Notices; however, if the Program has interactive | |
220 | + interfaces that do not display Appropriate Legal Notices, your | |
221 | + work need not make them do so. | |
222 | + | |
223 | + A compilation of a covered work with other separate and independent | |
224 | +works, which are not by their nature extensions of the covered work, | |
225 | +and which are not combined with it such as to form a larger program, | |
226 | +in or on a volume of a storage or distribution medium, is called an | |
227 | +"aggregate" if the compilation and its resulting copyright are not | |
228 | +used to limit the access or legal rights of the compilation's users | |
229 | +beyond what the individual works permit. Inclusion of a covered work | |
230 | +in an aggregate does not cause this License to apply to the other | |
231 | +parts of the aggregate. | |
232 | + | |
233 | + 6. Conveying Non-Source Forms. | |
234 | + | |
235 | + You may convey a covered work in object code form under the terms | |
236 | +of sections 4 and 5, provided that you also convey the | |
237 | +machine-readable Corresponding Source under the terms of this License, | |
238 | +in one of these ways: | |
239 | + | |
240 | + a) Convey the object code in, or embodied in, a physical product | |
241 | + (including a physical distribution medium), accompanied by the | |
242 | + Corresponding Source fixed on a durable physical medium | |
243 | + customarily used for software interchange. | |
244 | + | |
245 | + b) Convey the object code in, or embodied in, a physical product | |
246 | + (including a physical distribution medium), accompanied by a | |
247 | + written offer, valid for at least three years and valid for as | |
248 | + long as you offer spare parts or customer support for that product | |
249 | + model, to give anyone who possesses the object code either (1) a | |
250 | + copy of the Corresponding Source for all the software in the | |
251 | + product that is covered by this License, on a durable physical | |
252 | + medium customarily used for software interchange, for a price no | |
253 | + more than your reasonable cost of physically performing this | |
254 | + conveying of source, or (2) access to copy the | |
255 | + Corresponding Source from a network server at no charge. | |
256 | + | |
257 | + c) Convey individual copies of the object code with a copy of the | |
258 | + written offer to provide the Corresponding Source. This | |
259 | + alternative is allowed only occasionally and noncommercially, and | |
260 | + only if you received the object code with such an offer, in accord | |
261 | + with subsection 6b. | |
262 | + | |
263 | + d) Convey the object code by offering access from a designated | |
264 | + place (gratis or for a charge), and offer equivalent access to the | |
265 | + Corresponding Source in the same way through the same place at no | |
266 | + further charge. You need not require recipients to copy the | |
267 | + Corresponding Source along with the object code. If the place to | |
268 | + copy the object code is a network server, the Corresponding Source | |
269 | + may be on a different server (operated by you or a third party) | |
270 | + that supports equivalent copying facilities, provided you maintain | |
271 | + clear directions next to the object code saying where to find the | |
272 | + Corresponding Source. Regardless of what server hosts the | |
273 | + Corresponding Source, you remain obligated to ensure that it is | |
274 | + available for as long as needed to satisfy these requirements. | |
275 | + | |
276 | + e) Convey the object code using peer-to-peer transmission, provided | |
277 | + you inform other peers where the object code and Corresponding | |
278 | + Source of the work are being offered to the general public at no | |
279 | + charge under subsection 6d. | |
280 | + | |
281 | + A separable portion of the object code, whose source code is excluded | |
282 | +from the Corresponding Source as a System Library, need not be | |
283 | +included in conveying the object code work. | |
284 | + | |
285 | + A "User Product" is either (1) a "consumer product", which means any | |
286 | +tangible personal property which is normally used for personal, family, | |
287 | +or household purposes, or (2) anything designed or sold for incorporation | |
288 | +into a dwelling. In determining whether a product is a consumer product, | |
289 | +doubtful cases shall be resolved in favor of coverage. For a particular | |
290 | +product received by a particular user, "normally used" refers to a | |
291 | +typical or common use of that class of product, regardless of the status | |
292 | +of the particular user or of the way in which the particular user | |
293 | +actually uses, or expects or is expected to use, the product. A product | |
294 | +is a consumer product regardless of whether the product has substantial | |
295 | +commercial, industrial or non-consumer uses, unless such uses represent | |
296 | +the only significant mode of use of the product. | |
297 | + | |
298 | + "Installation Information" for a User Product means any methods, | |
299 | +procedures, authorization keys, or other information required to install | |
300 | +and execute modified versions of a covered work in that User Product from | |
301 | +a modified version of its Corresponding Source. The information must | |
302 | +suffice to ensure that the continued functioning of the modified object | |
303 | +code is in no case prevented or interfered with solely because | |
304 | +modification has been made. | |
305 | + | |
306 | + If you convey an object code work under this section in, or with, or | |
307 | +specifically for use in, a User Product, and the conveying occurs as | |
308 | +part of a transaction in which the right of possession and use of the | |
309 | +User Product is transferred to the recipient in perpetuity or for a | |
310 | +fixed term (regardless of how the transaction is characterized), the | |
311 | +Corresponding Source conveyed under this section must be accompanied | |
312 | +by the Installation Information. But this requirement does not apply | |
313 | +if neither you nor any third party retains the ability to install | |
314 | +modified object code on the User Product (for example, the work has | |
315 | +been installed in ROM). | |
316 | + | |
317 | + The requirement to provide Installation Information does not include a | |
318 | +requirement to continue to provide support service, warranty, or updates | |
319 | +for a work that has been modified or installed by the recipient, or for | |
320 | +the User Product in which it has been modified or installed. Access to a | |
321 | +network may be denied when the modification itself materially and | |
322 | +adversely affects the operation of the network or violates the rules and | |
323 | +protocols for communication across the network. | |
324 | + | |
325 | + Corresponding Source conveyed, and Installation Information provided, | |
326 | +in accord with this section must be in a format that is publicly | |
327 | +documented (and with an implementation available to the public in | |
328 | +source code form), and must require no special password or key for | |
329 | +unpacking, reading or copying. | |
330 | + | |
331 | + 7. Additional Terms. | |
332 | + | |
333 | + "Additional permissions" are terms that supplement the terms of this | |
334 | +License by making exceptions from one or more of its conditions. | |
335 | +Additional permissions that are applicable to the entire Program shall | |
336 | +be treated as though they were included in this License, to the extent | |
337 | +that they are valid under applicable law. If additional permissions | |
338 | +apply only to part of the Program, that part may be used separately | |
339 | +under those permissions, but the entire Program remains governed by | |
340 | +this License without regard to the additional permissions. | |
341 | + | |
342 | + When you convey a copy of a covered work, you may at your option | |
343 | +remove any additional permissions from that copy, or from any part of | |
344 | +it. (Additional permissions may be written to require their own | |
345 | +removal in certain cases when you modify the work.) You may place | |
346 | +additional permissions on material, added by you to a covered work, | |
347 | +for which you have or can give appropriate copyright permission. | |
348 | + | |
349 | + Notwithstanding any other provision of this License, for material you | |
350 | +add to a covered work, you may (if authorized by the copyright holders of | |
351 | +that material) supplement the terms of this License with terms: | |
352 | + | |
353 | + a) Disclaiming warranty or limiting liability differently from the | |
354 | + terms of sections 15 and 16 of this License; or | |
355 | + | |
356 | + b) Requiring preservation of specified reasonable legal notices or | |
357 | + author attributions in that material or in the Appropriate Legal | |
358 | + Notices displayed by works containing it; or | |
359 | + | |
360 | + c) Prohibiting misrepresentation of the origin of that material, or | |
361 | + requiring that modified versions of such material be marked in | |
362 | + reasonable ways as different from the original version; or | |
363 | + | |
364 | + d) Limiting the use for publicity purposes of names of licensors or | |
365 | + authors of the material; or | |
366 | + | |
367 | + e) Declining to grant rights under trademark law for use of some | |
368 | + trade names, trademarks, or service marks; or | |
369 | + | |
370 | + f) Requiring indemnification of licensors and authors of that | |
371 | + material by anyone who conveys the material (or modified versions of | |
372 | + it) with contractual assumptions of liability to the recipient, for | |
373 | + any liability that these contractual assumptions directly impose on | |
374 | + those licensors and authors. | |
375 | + | |
376 | + All other non-permissive additional terms are considered "further | |
377 | +restrictions" within the meaning of section 10. If the Program as you | |
378 | +received it, or any part of it, contains a notice stating that it is | |
379 | +governed by this License along with a term that is a further | |
380 | +restriction, you may remove that term. If a license document contains | |
381 | +a further restriction but permits relicensing or conveying under this | |
382 | +License, you may add to a covered work material governed by the terms | |
383 | +of that license document, provided that the further restriction does | |
384 | +not survive such relicensing or conveying. | |
385 | + | |
386 | + If you add terms to a covered work in accord with this section, you | |
387 | +must place, in the relevant source files, a statement of the | |
388 | +additional terms that apply to those files, or a notice indicating | |
389 | +where to find the applicable terms. | |
390 | + | |
391 | + Additional terms, permissive or non-permissive, may be stated in the | |
392 | +form of a separately written license, or stated as exceptions; | |
393 | +the above requirements apply either way. | |
394 | + | |
395 | + 8. Termination. | |
396 | + | |
397 | + You may not propagate or modify a covered work except as expressly | |
398 | +provided under this License. Any attempt otherwise to propagate or | |
399 | +modify it is void, and will automatically terminate your rights under | |
400 | +this License (including any patent licenses granted under the third | |
401 | +paragraph of section 11). | |
402 | + | |
403 | + However, if you cease all violation of this License, then your | |
404 | +license from a particular copyright holder is reinstated (a) | |
405 | +provisionally, unless and until the copyright holder explicitly and | |
406 | +finally terminates your license, and (b) permanently, if the copyright | |
407 | +holder fails to notify you of the violation by some reasonable means | |
408 | +prior to 60 days after the cessation. | |
409 | + | |
410 | + Moreover, your license from a particular copyright holder is | |
411 | +reinstated permanently if the copyright holder notifies you of the | |
412 | +violation by some reasonable means, this is the first time you have | |
413 | +received notice of violation of this License (for any work) from that | |
414 | +copyright holder, and you cure the violation prior to 30 days after | |
415 | +your receipt of the notice. | |
416 | + | |
417 | + Termination of your rights under this section does not terminate the | |
418 | +licenses of parties who have received copies or rights from you under | |
419 | +this License. If your rights have been terminated and not permanently | |
420 | +reinstated, you do not qualify to receive new licenses for the same | |
421 | +material under section 10. | |
422 | + | |
423 | + 9. Acceptance Not Required for Having Copies. | |
424 | + | |
425 | + You are not required to accept this License in order to receive or | |
426 | +run a copy of the Program. Ancillary propagation of a covered work | |
427 | +occurring solely as a consequence of using peer-to-peer transmission | |
428 | +to receive a copy likewise does not require acceptance. However, | |
429 | +nothing other than this License grants you permission to propagate or | |
430 | +modify any covered work. These actions infringe copyright if you do | |
431 | +not accept this License. Therefore, by modifying or propagating a | |
432 | +covered work, you indicate your acceptance of this License to do so. | |
433 | + | |
434 | + 10. Automatic Licensing of Downstream Recipients. | |
435 | + | |
436 | + Each time you convey a covered work, the recipient automatically | |
437 | +receives a license from the original licensors, to run, modify and | |
438 | +propagate that work, subject to this License. You are not responsible | |
439 | +for enforcing compliance by third parties with this License. | |
440 | + | |
441 | + An "entity transaction" is a transaction transferring control of an | |
442 | +organization, or substantially all assets of one, or subdividing an | |
443 | +organization, or merging organizations. If propagation of a covered | |
444 | +work results from an entity transaction, each party to that | |
445 | +transaction who receives a copy of the work also receives whatever | |
446 | +licenses to the work the party's predecessor in interest had or could | |
447 | +give under the previous paragraph, plus a right to possession of the | |
448 | +Corresponding Source of the work from the predecessor in interest, if | |
449 | +the predecessor has it or can get it with reasonable efforts. | |
450 | + | |
451 | + You may not impose any further restrictions on the exercise of the | |
452 | +rights granted or affirmed under this License. For example, you may | |
453 | +not impose a license fee, royalty, or other charge for exercise of | |
454 | +rights granted under this License, and you may not initiate litigation | |
455 | +(including a cross-claim or counterclaim in a lawsuit) alleging that | |
456 | +any patent claim is infringed by making, using, selling, offering for | |
457 | +sale, or importing the Program or any portion of it. | |
458 | + | |
459 | + 11. Patents. | |
460 | + | |
461 | + A "contributor" is a copyright holder who authorizes use under this | |
462 | +License of the Program or a work on which the Program is based. The | |
463 | +work thus licensed is called the contributor's "contributor version". | |
464 | + | |
465 | + A contributor's "essential patent claims" are all patent claims | |
466 | +owned or controlled by the contributor, whether already acquired or | |
467 | +hereafter acquired, that would be infringed by some manner, permitted | |
468 | +by this License, of making, using, or selling its contributor version, | |
469 | +but do not include claims that would be infringed only as a | |
470 | +consequence of further modification of the contributor version. For | |
471 | +purposes of this definition, "control" includes the right to grant | |
472 | +patent sublicenses in a manner consistent with the requirements of | |
473 | +this License. | |
474 | + | |
475 | + Each contributor grants you a non-exclusive, worldwide, royalty-free | |
476 | +patent license under the contributor's essential patent claims, to | |
477 | +make, use, sell, offer for sale, import and otherwise run, modify and | |
478 | +propagate the contents of its contributor version. | |
479 | + | |
480 | + In the following three paragraphs, a "patent license" is any express | |
481 | +agreement or commitment, however denominated, not to enforce a patent | |
482 | +(such as an express permission to practice a patent or covenant not to | |
483 | +sue for patent infringement). To "grant" such a patent license to a | |
484 | +party means to make such an agreement or commitment not to enforce a | |
485 | +patent against the party. | |
486 | + | |
487 | + If you convey a covered work, knowingly relying on a patent license, | |
488 | +and the Corresponding Source of the work is not available for anyone | |
489 | +to copy, free of charge and under the terms of this License, through a | |
490 | +publicly available network server or other readily accessible means, | |
491 | +then you must either (1) cause the Corresponding Source to be so | |
492 | +available, or (2) arrange to deprive yourself of the benefit of the | |
493 | +patent license for this particular work, or (3) arrange, in a manner | |
494 | +consistent with the requirements of this License, to extend the patent | |
495 | +license to downstream recipients. "Knowingly relying" means you have | |
496 | +actual knowledge that, but for the patent license, your conveying the | |
497 | +covered work in a country, or your recipient's use of the covered work | |
498 | +in a country, would infringe one or more identifiable patents in that | |
499 | +country that you have reason to believe are valid. | |
500 | + | |
501 | + If, pursuant to or in connection with a single transaction or | |
502 | +arrangement, you convey, or propagate by procuring conveyance of, a | |
503 | +covered work, and grant a patent license to some of the parties | |
504 | +receiving the covered work authorizing them to use, propagate, modify | |
505 | +or convey a specific copy of the covered work, then the patent license | |
506 | +you grant is automatically extended to all recipients of the covered | |
507 | +work and works based on it. | |
508 | + | |
509 | + A patent license is "discriminatory" if it does not include within | |
510 | +the scope of its coverage, prohibits the exercise of, or is | |
511 | +conditioned on the non-exercise of one or more of the rights that are | |
512 | +specifically granted under this License. You may not convey a covered | |
513 | +work if you are a party to an arrangement with a third party that is | |
514 | +in the business of distributing software, under which you make payment | |
515 | +to the third party based on the extent of your activity of conveying | |
516 | +the work, and under which the third party grants, to any of the | |
517 | +parties who would receive the covered work from you, a discriminatory | |
518 | +patent license (a) in connection with copies of the covered work | |
519 | +conveyed by you (or copies made from those copies), or (b) primarily | |
520 | +for and in connection with specific products or compilations that | |
521 | +contain the covered work, unless you entered into that arrangement, | |
522 | +or that patent license was granted, prior to 28 March 2007. | |
523 | + | |
524 | + Nothing in this License shall be construed as excluding or limiting | |
525 | +any implied license or other defenses to infringement that may | |
526 | +otherwise be available to you under applicable patent law. | |
527 | + | |
528 | + 12. No Surrender of Others' Freedom. | |
529 | + | |
530 | + If conditions are imposed on you (whether by court order, agreement or | |
531 | +otherwise) that contradict the conditions of this License, they do not | |
532 | +excuse you from the conditions of this License. If you cannot convey a | |
533 | +covered work so as to satisfy simultaneously your obligations under this | |
534 | +License and any other pertinent obligations, then as a consequence you may | |
535 | +not convey it at all. For example, if you agree to terms that obligate you | |
536 | +to collect a royalty for further conveying from those to whom you convey | |
537 | +the Program, the only way you could satisfy both those terms and this | |
538 | +License would be to refrain entirely from conveying the Program. | |
539 | + | |
540 | + 13. Remote Network Interaction; Use with the GNU General Public License. | |
541 | + | |
542 | + Notwithstanding any other provision of this License, if you modify the | |
543 | +Program, your modified version must prominently offer all users | |
544 | +interacting with it remotely through a computer network (if your version | |
545 | +supports such interaction) an opportunity to receive the Corresponding | |
546 | +Source of your version by providing access to the Corresponding Source | |
547 | +from a network server at no charge, through some standard or customary | |
548 | +means of facilitating copying of software. This Corresponding Source | |
549 | +shall include the Corresponding Source for any work covered by version 3 | |
550 | +of the GNU General Public License that is incorporated pursuant to the | |
551 | +following paragraph. | |
552 | + | |
553 | + Notwithstanding any other provision of this License, you have | |
554 | +permission to link or combine any covered work with a work licensed | |
555 | +under version 3 of the GNU General Public License into a single | |
556 | +combined work, and to convey the resulting work. The terms of this | |
557 | +License will continue to apply to the part which is the covered work, | |
558 | +but the work with which it is combined will remain governed by version | |
559 | +3 of the GNU General Public License. | |
560 | + | |
561 | + 14. Revised Versions of this License. | |
562 | + | |
563 | + The Free Software Foundation may publish revised and/or new versions of | |
564 | +the GNU Affero General Public License from time to time. Such new versions | |
565 | +will be similar in spirit to the present version, but may differ in detail to | |
566 | +address new problems or concerns. | |
567 | + | |
568 | + Each version is given a distinguishing version number. If the | |
569 | +Program specifies that a certain numbered version of the GNU Affero General | |
570 | +Public License "or any later version" applies to it, you have the | |
571 | +option of following the terms and conditions either of that numbered | |
572 | +version or of any later version published by the Free Software | |
573 | +Foundation. If the Program does not specify a version number of the | |
574 | +GNU Affero General Public License, you may choose any version ever published | |
575 | +by the Free Software Foundation. | |
576 | + | |
577 | + If the Program specifies that a proxy can decide which future | |
578 | +versions of the GNU Affero General Public License can be used, that proxy's | |
579 | +public statement of acceptance of a version permanently authorizes you | |
580 | +to choose that version for the Program. | |
581 | + | |
582 | + Later license versions may give you additional or different | |
583 | +permissions. However, no additional obligations are imposed on any | |
584 | +author or copyright holder as a result of your choosing to follow a | |
585 | +later version. | |
586 | + | |
587 | + 15. Disclaimer of Warranty. | |
588 | + | |
589 | + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |
590 | +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |
591 | +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | |
592 | +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | |
593 | +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
594 | +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | |
595 | +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | |
596 | +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | |
597 | + | |
598 | + 16. Limitation of Liability. | |
599 | + | |
600 | + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
601 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | |
602 | +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |
603 | +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | |
604 | +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | |
605 | +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |
606 | +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | |
607 | +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | |
608 | +SUCH DAMAGES. | |
609 | + | |
610 | + 17. Interpretation of Sections 15 and 16. | |
611 | + | |
612 | + If the disclaimer of warranty and limitation of liability provided | |
613 | +above cannot be given local legal effect according to their terms, | |
614 | +reviewing courts shall apply local law that most closely approximates | |
615 | +an absolute waiver of all civil liability in connection with the | |
616 | +Program, unless a warranty or assumption of liability accompanies a | |
617 | +copy of the Program in return for a fee. | |
618 | + | |
619 | + END OF TERMS AND CONDITIONS | |
620 | + | |
621 | + How to Apply These Terms to Your New Programs | |
622 | + | |
623 | + If you develop a new program, and you want it to be of the greatest | |
624 | +possible use to the public, the best way to achieve this is to make it | |
625 | +free software which everyone can redistribute and change under these terms. | |
626 | + | |
627 | + To do so, attach the following notices to the program. It is safest | |
628 | +to attach them to the start of each source file to most effectively | |
629 | +state the exclusion of warranty; and each file should have at least | |
630 | +the "copyright" line and a pointer to where the full notice is found. | |
631 | + | |
632 | + <one line to give the program's name and a brief idea of what it does.> | |
633 | + Copyright (C) <year> <name of author> | |
634 | + | |
635 | + This program is free software: you can redistribute it and/or modify | |
636 | + it under the terms of the GNU Affero General Public License as published by | |
637 | + the Free Software Foundation, either version 3 of the License, or | |
638 | + (at your option) any later version. | |
639 | + | |
640 | + This program is distributed in the hope that it will be useful, | |
641 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | |
642 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
643 | + GNU Affero General Public License for more details. | |
644 | + | |
645 | + You should have received a copy of the GNU Affero General Public License | |
646 | + along with this program. If not, see <https://www.gnu.org/licenses/>. | |
647 | + | |
648 | +Also add information on how to contact you by electronic and paper mail. | |
649 | + | |
650 | + If your software can interact with users remotely through a computer | |
651 | +network, you should also make sure that it provides a way for users to | |
652 | +get its source. For example, if your program is a web application, its | |
653 | +interface could display a "Source" link that leads users to an archive | |
654 | +of the code. There are many ways you could offer source, and different | |
655 | +solutions will be better for different programs; see section 13 for the | |
656 | +specific requirements. | |
657 | + | |
658 | + You should also get your employer (if you work as a programmer) or school, | |
659 | +if any, to sign a "copyright disclaimer" for the program, if necessary. | |
660 | +For more information on this, and how to apply and follow the GNU AGPL, see | |
661 | +<https://www.gnu.org/licenses/>. |
@@ -0,0 +1,54 @@ | ||
1 | +(require 'asdf) | |
2 | +(in-package :asdf-user) | |
3 | + | |
4 | +#-sbcl (error "SatouSynth only supports SBCL.") | |
5 | + | |
6 | +(defun %do-block-compile (thunk) | |
7 | + #-(or satou-debug satou-verbose-debug) | |
8 | + (apply thunk (when (uiop:featurep :sbcl) '(:block-compile t)))) | |
9 | + | |
10 | +(asdf:defsystem satou | |
11 | + :long-name "SatouSynth" | |
12 | + :description "VGM playback library in pure Common Lisp" | |
13 | + :version "0.1.0" | |
14 | + :license "AGPLv3 (see LICENSE for details)" | |
15 | + | |
16 | + :maintainer "Remilia Scarlet" | |
17 | + :author "Remilia Scarlet" | |
18 | + | |
19 | + :depends-on (:cl-sdm | |
20 | + ;;:cffi | |
21 | + ;;:waaf-cffi | |
22 | + :chipz | |
23 | + :flexi-streams | |
24 | + :closer-mop) | |
25 | + | |
26 | + :serial t | |
27 | + :components | |
28 | + ((:module | |
29 | + "src" | |
30 | + :components | |
31 | + ((:file "package") | |
32 | + (:file "common") | |
33 | + | |
34 | + (:file "gd3-tag" :around-compile %do-block-compile) | |
35 | + (:file "vgm-decompression" :around-compile %do-block-compile) | |
36 | + (:file "vgmfile" :around-compile %do-block-compile) | |
37 | + (:file "abstract-chip" :around-compile %do-block-compile) | |
38 | + | |
39 | + (:module | |
40 | + "chips" | |
41 | + :components | |
42 | + ((:file "common") | |
43 | + (:file "emu-huc6280-ootake" :around-compile %do-block-compile) | |
44 | + (:file "chip-huc6280" :around-compile %do-block-compile) | |
45 | + | |
46 | + (:file "emu-c352-mame" :around-compile %do-block-compile) | |
47 | + (:file "chip-c352" :around-compile %do-block-compile) | |
48 | + | |
49 | + (:file "emu-ym2151-mame" :around-compile %do-block-compile))) | |
50 | + | |
51 | + (:file "resampler" :around-compile %do-block-compile) | |
52 | + (:file "dac-controller" :around-compile %do-block-compile) | |
53 | + (:file "vgm-player-settings") | |
54 | + (:file "vgm-player" :around-compile %do-block-compile))))) |
@@ -0,0 +1,291 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +(defgeneric chip-type (chip)) | |
19 | +(defgeneric chip-name (chip)) | |
20 | +(defgeneric chip-short-name (chip)) | |
21 | +(defgeneric chip-id (chip)) | |
22 | +(defgeneric chip-default-emu-core (chip)) | |
23 | +(defgeneric chip-base-volume (chip)) | |
24 | +(defgeneric chip-start (chip clock &optional flags)) | |
25 | +(defgeneric chip-clock (chip)) | |
26 | +(defgeneric chip-start-flags (chip vgm)) | |
27 | +(defgeneric chip-update (chip outputs start samples)) | |
28 | +(defgeneric chip-update-paired (chip outputs start samples)) | |
29 | +(defgeneric chip-reset (chip)) | |
30 | +(defgeneric chip-read (chip offset)) | |
31 | +(defgeneric chip-write (chip offset data &optional port)) | |
32 | +(defgeneric (setf chip-mute-mask) (value chip)) | |
33 | +(defgeneric chip-write-dac (chip port command data)) | |
34 | +(defgeneric (setf chip-stereo-mask) (value chip)) | |
35 | +(defgeneric chip-volume-modifier (chip)) | |
36 | +(defgeneric chip-init (chip vgm playback-sample-rate sampling-mode player-sample-rate &key emu-core flags)) | |
37 | +(defgeneric chip-write-rom (chip rom-size data-start data-length rom-data)) | |
38 | + | |
39 | +(defclass chip-flags () | |
40 | + ()) | |
41 | + | |
42 | +(defclass abstract-chip () | |
43 | + ((sample-rate | |
44 | + :initform 0 | |
45 | + :type t/uint32 | |
46 | + :accessor chip-sample-rate) | |
47 | + | |
48 | + (player-sample-rate | |
49 | + :initform 0 | |
50 | + :type t/uint32 | |
51 | + :reader chip-player-sample-rate) | |
52 | + | |
53 | + (clock-from-header | |
54 | + :initform 0 | |
55 | + :type t/uint32) | |
56 | + | |
57 | + (volume | |
58 | + :initform 0 | |
59 | + :type t/uint16 | |
60 | + :accessor chip-volume) | |
61 | + | |
62 | + (resampler-type | |
63 | + :initform :copy | |
64 | + :type t/resampler-type | |
65 | + :accessor chip-resampler-type) | |
66 | + | |
67 | + (cur-sample-num | |
68 | + :initform 0 | |
69 | + :type t/uint32) | |
70 | + | |
71 | + (last-sample-num | |
72 | + :initform 0 | |
73 | + :type t/uint32) | |
74 | + | |
75 | + (next-sample-num | |
76 | + :initform 0 | |
77 | + :type t/uint32) | |
78 | + | |
79 | + (last-sample | |
80 | + :initform (vector 0 0) | |
81 | + :type t/sample) | |
82 | + | |
83 | + (next-sample | |
84 | + :initform (vector 0 0) | |
85 | + :type t/sample) | |
86 | + | |
87 | + (paired | |
88 | + :initform nil | |
89 | + :type (or null abstract-chip)) | |
90 | + | |
91 | + (is-paired? | |
92 | + :initform nil | |
93 | + :type boolean | |
94 | + :reader chip-is-paired-p) | |
95 | + | |
96 | + (core | |
97 | + :initform :unknown | |
98 | + :type keyword | |
99 | + :reader chip-emu-core) | |
100 | + | |
101 | + (emu | |
102 | + :initform nil | |
103 | + :reader chip-emu) | |
104 | + | |
105 | + (update-fn | |
106 | + :initform nil | |
107 | + :type (or null function)))) | |
108 | + | |
109 | +(defmethod chip-start-flags ((chip abstract-chip) vgm) | |
110 | + (declare (ignore vgm)) | |
111 | + nil) | |
112 | + | |
113 | +(defmethod chip-update-paired ((chip abstract-chip) outputs start samples) | |
114 | + (declare (ignore outputs start samples)) | |
115 | + nil) | |
116 | + | |
117 | +(defmethod chip-write-dac ((chip abstract-chip) (port integer) (command integer) (data integer)) | |
118 | + (chip-write chip command data (coerce-to-uint8 port))) | |
119 | + | |
120 | +(defmethod chip-volume-modifier ((chip abstract-chip)) | |
121 | + (slot-value chip 'volume)) | |
122 | + | |
123 | +(defmethod chip-clock ((chip abstract-chip)) | |
124 | + (slot-value chip 'clock-from-header)) | |
125 | + | |
126 | +(define-typed-fn get-chip-volume ((abstract-chip chip) (vgm-file vgm) (t/chip chip-type) (fixnum chip-count) | |
127 | + &optional paired) | |
128 | + (t/uint16) | |
129 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
130 | + (check-type paired (or null t/chip)) | |
131 | + | |
132 | + (with-typed-slots ((vgm-file-header header) | |
133 | + ((or null extra-header) extra-header) | |
134 | + (gd3-tag gd3)) | |
135 | + vgm | |
136 | + (let ((volume (chip-base-volume chip)) | |
137 | + (count chip-count)) | |
138 | + (declare (type t/uint16 volume)) | |
139 | + | |
140 | + (case chip-type | |
141 | + (:sn76489 ;; And SN76496 | |
142 | + ;; If T6W28, then the volume divider is 1 | |
143 | + (when (flag? (the t/uint32 (vgm-header-sn76489-clock header)) #x80000000) | |
144 | + (setf count 1))) | |
145 | + | |
146 | + (:oki-m6295 | |
147 | + ;; Check for CP System 1, which needs a different volume. | |
148 | + (when (or (string-starts-with (gd3-tag-system-name-en gd3) "CP") | |
149 | + (string-starts-with (gd3-tag-system-name-jp gd3) "CP")) | |
150 | + (dlog "Using CP System 1 volume hack for OKI MSM6295") | |
151 | + (setf volume 110))) | |
152 | + | |
153 | + (:ym2203 | |
154 | + ;; Check for YM2203's integrated AY-1-8910 | |
155 | + (when (eq paired :ay8910) | |
156 | + (setf volume (truncate volume 2))))) | |
157 | + | |
158 | + ;; Adjust for the number of chips | |
159 | + (when (> count 1) | |
160 | + (setf volume (truncate volume chip-count))) | |
161 | + | |
162 | + ;; There may be a modification to the volume in an extra header, for check | |
163 | + ;; for the presence of one now. | |
164 | + (when extra-header | |
165 | + ;; Extra header is present. | |
166 | + ;; | |
167 | + ;; Determine which chip we're modifying. This is needed because | |
168 | + ;; CHIP-TYPE is always the parent chip, and PAIRED is only non_NIL if | |
169 | + ;; we're retrieving the volume for the paired chip. | |
170 | + ;; | |
171 | + ;; This is just for debugging output. | |
172 | + (let ((mod-chip (if (null paired) | |
173 | + (string chip-type) | |
174 | + (format nil "~a's paired ~a" chip-type paired))) | |
175 | + (vol nil) | |
176 | + (vol-val 0)) | |
177 | + (declare (type (or null (vector extra-header-volume)) vol) | |
178 | + (t/uint16 vol-val) | |
179 | + (ignorable mod-chip)) | |
180 | + (dlog "Checking for extra header volume adjustment for ~a" mod-chip) | |
181 | + | |
182 | + ;; The value in the extra header is still looked up via the parent | |
183 | + ;; chip. | |
184 | + (setf vol (gethash (t/chip->int chip-type) (extra-header-volumes | |
185 | + extra-header))) | |
186 | + | |
187 | + ;; Might be the paired chip. | |
188 | + (when (and (null vol) paired) | |
189 | + (setf vol (gethash (t/chip->int paired) (extra-header-volumes extra-header)))) | |
190 | + | |
191 | + ;; Did the extra header have a volume modification? | |
192 | + (cond | |
193 | + (vol | |
194 | + (setf vol-val (extra-header-volume-value (muffling (elt vol 0)))) | |
195 | + | |
196 | + ;; Bit 15 indicates whether the extra header volume value for this | |
197 | + ;; chip is an absolute volume, or relative. | |
198 | + ;; | |
199 | + ;; Bit 15 = 0 means absolute. | |
200 | + ;; Bit 15 = 1 means relative. | |
201 | + (cond | |
202 | + ((flag? vol-val #x8000) | |
203 | + (dlog " Found, relative volume adjustment: ~a" (logand vol-val #x7FFF)) | |
204 | + (setf volume (coerce-to-uint16 (ash (+ (* volume (logand vol-val #x7FFF)) #x80) -8)))) | |
205 | + | |
206 | + (t | |
207 | + (dlog " Found, absolute volume adjustment: ~a" vol-val) | |
208 | + (setf volume vol-val) | |
209 | + | |
210 | + ;; TODO handle option to double the SSG volume. | |
211 | + )) | |
212 | + | |
213 | + ;; Return early for debug message reasons. | |
214 | + (dlog "Determined volume for ~a of ~a chip~:p: ~a" mod-chip count volume) | |
215 | + (return-from get-chip-volume volume)) | |
216 | + | |
217 | + (t | |
218 | + ;; There was no extra header modification found for this chip (VOL is NIL). | |
219 | + (dlog " None found"))))) | |
220 | + | |
221 | + ;; Return the determined volume value. | |
222 | + (dlog "Determined volume for ~a of ~a chip~:p: ~a" chip-type count volume) | |
223 | + volume))) | |
224 | + | |
225 | +(define-typed-fn chip-change-sample-rate ((abstract-chip chip) (t/uint32 new-sample-rate)) | |
226 | + (null) | |
227 | + "Changes the sample rate for CHIP." | |
228 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0))) | |
229 | + | |
230 | + (with-typed-slots ((t/uint32 cur-sample-num next-sample-num last-sample-num | |
231 | + sample-rate player-sample-rate)) | |
232 | + chip | |
233 | + ;; This is based on code from VGMPlay. | |
234 | + (dlog "Sample rate change for ~a: ~a => ~a" (type-of chip) sample-rate new-sample-rate) | |
235 | + | |
236 | + (when (= sample-rate new-sample-rate) | |
237 | + ;; No change | |
238 | + (return-from chip-change-sample-rate)) | |
239 | + | |
240 | + (setf sample-rate new-sample-rate) | |
241 | + | |
242 | + ;; Adjust the resampler type by comparing the new sample rate to the playback | |
243 | + ;; rate. | |
244 | + (setf (chip-resampler-type chip) | |
245 | + (cond | |
246 | + ((< sample-rate player-sample-rate) :upsampling) | |
247 | + ((> sample-rate player-sample-rate) :downsampling) | |
248 | + (t :copy))) | |
249 | + | |
250 | + (setf cur-sample-num 1) | |
251 | + (decf next-sample-num last-sample-num) | |
252 | + (setf last-sample-num 0)) | |
253 | + nil) | |
254 | + | |
255 | +(defmacro with-each-chip ((chip table) &body forms) | |
256 | + (with-gensyms (key val len i) | |
257 | + `(maphash #'(lambda (,key ,val) | |
258 | + (declare (type (vector abstract-chip) ,val) | |
259 | + (ignore ,key)) | |
260 | + (loop with ,len fixnum = (muffling (length ,val)) | |
261 | + for ,i fixnum from 0 below ,len | |
262 | + for ,chip = (muffling (aref ,val ,i)) | |
263 | + do (progn ,@forms))) | |
264 | + ,table))) | |
265 | + | |
266 | +(define-typed-fn make-chip-instance ((t/chip ct) (vgm-file vgm) (t/uint32 sample-rate) (t/uint8 sampling-mode) | |
267 | + (t/uint32 player-sample-rate) settings) | |
268 | + ((or null abstract-chip)) | |
269 | + (let ((chip nil) | |
270 | + (core :unknown)) | |
271 | + (declare (type (or null abstract-chip) chip) | |
272 | + (type keyword core)) | |
273 | + | |
274 | + (case ct | |
275 | + (:huc6280 | |
276 | + (setf chip (make-instance 'satou-chips:huc6280)) | |
277 | + (setf core (settings-huc6280-core settings))) | |
278 | + | |
279 | + (:c352 | |
280 | + (setf chip (make-instance 'satou-chips:c352))) | |
281 | + | |
282 | + (:unknown (error "Attempted to start the :UNKNOWN chip")) | |
283 | + (otherwise nil)) | |
284 | + | |
285 | + (when chip | |
286 | + (chip-init chip vgm sample-rate sampling-mode player-sample-rate | |
287 | + :emu-core (if (eq core :unknown) | |
288 | + (chip-default-emu-core chip) | |
289 | + core) | |
290 | + :flags (chip-start-flags chip vgm))) | |
291 | + chip)) |
@@ -0,0 +1,138 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou-chips) | |
17 | + | |
18 | +(defconst +chip-id/c352+ #x27) | |
19 | + | |
20 | +(deftype t/c352-core () | |
21 | + '(member :mame)) | |
22 | + | |
23 | +(defclass c352 (abstract-chip) | |
24 | + ((sampling-mode | |
25 | + :initform 0 | |
26 | + :type t/uint8) | |
27 | + | |
28 | + (chip-sample-rate | |
29 | + :initform 0 | |
30 | + :type t/uint32))) | |
31 | + | |
32 | +(defclass c352-flags (chip-flags) | |
33 | + ((flags | |
34 | + :initarg :flags | |
35 | + :initform #x01 ;; Disabling the rear outputs is the default. | |
36 | + :type t/uint8) | |
37 | + | |
38 | + (clock-div | |
39 | + :initarg :clock-div | |
40 | + :initform 1 | |
41 | + :type t/uint32))) | |
42 | + | |
43 | +(defmethod chip-init ((chip c352) (vgm vgm-file) (playback-sample-rate integer) (new-sampling-mode integer) | |
44 | + (new-player-sample-rate integer) &key emu-core flags) | |
45 | + (unless (typep emu-core 't/c352-core) | |
46 | + (satou-error () "Bad emulation core for C352: ~a" emu-core)) | |
47 | + | |
48 | + (setf (slot-value chip 'satou::clock-from-header) | |
49 | + (logand (vgm-header-c352-clock (vgm-file-header vgm)) #xBFFFFFFF)) | |
50 | + (setf (slot-value chip 'satou::player-sample-rate) playback-sample-rate) | |
51 | + | |
52 | + (let ((chip-count (if (flag? (vgm-header-c352-clock (vgm-file-header vgm)) #x40000000) 2 1))) | |
53 | + (%c352-init-fields chip vgm emu-core playback-sample-rate new-sampling-mode chip-count)) | |
54 | + chip) | |
55 | + | |
56 | +(defun %c352-init-fields (chip vgm emu-core playback-sample-rate new-sampling-mode chip-count) | |
57 | + (setf (slot-value chip 'satou::volume) (satou:get-chip-volume chip vgm :c352 chip-count)) | |
58 | + (setf (slot-value chip 'satou::core) emu-core) | |
59 | + | |
60 | + (ecase emu-core | |
61 | + (:mame | |
62 | + (setf (slot-value chip 'sampling-mode) new-sampling-mode) | |
63 | + (setf (slot-value chip 'chip-sample-rate) (if (<= (slot-value chip 'satou::clock-from-header) 0) | |
64 | + playback-sample-rate | |
65 | + (slot-value chip 'satou::clock-from-header))))) | |
66 | + | |
67 | + (when (>= chip-count 2) | |
68 | + ;; Treat the second one as a paired chip. | |
69 | + (setf (slot-value chip 'satou::paired) (make-instance 'c352)) | |
70 | + (%c352-init-fields (slot-value chip 'satou::paired) vgm emu-core playback-sample-rate new-sampling-mode | |
71 | + (1- chip-count))) | |
72 | + nil) | |
73 | + | |
74 | +(defmethod satou:chip-type ((chip c352)) | |
75 | + :c352) | |
76 | + | |
77 | +(defmethod satou:chip-name ((chip c352)) | |
78 | + ;; Wrap this in a RETURN-FROM so the editor doesn't think it's a doc string. | |
79 | + (return-from chip-name "Namco C352")) | |
80 | + | |
81 | +(defmethod satou:chip-short-name ((chip c352)) | |
82 | + ;; Wrap this in a RETURN-FROM so the editor doesn't think it's a doc string. | |
83 | + (return-from chip-short-name "C352")) | |
84 | + | |
85 | +(defmethod satou:chip-id ((chip c352)) | |
86 | + +chip-id/c352+) | |
87 | + | |
88 | +(defmethod satou:chip-default-emu-core ((chip c352)) | |
89 | + :mame) | |
90 | + | |
91 | +(defmethod satou::chip-start ((chip c352) (clock integer) &optional flags) | |
92 | + (ecase (slot-value chip 'satou::core) | |
93 | + (:mame | |
94 | + (unless (typep flags 'c352-flags) | |
95 | + (error "C352 requires start flags")) | |
96 | + | |
97 | + (let ((emu (satou-chips/c352-mame::make-emu-c352 clock (* (slot-value flags 'clock-div) 4)))) | |
98 | + (setf (slot-value chip 'satou::emu) emu) | |
99 | + (satou-chips/c352-mame::emu-set-options emu (slot-value flags 'flags)) | |
100 | + (satou-chips/c352-mame::emu-unmute-all emu) | |
101 | + (setf (slot-value chip 'satou::sample-rate) (satou-chips/c352-mame::emu-start emu))))) | |
102 | + | |
103 | + (satou::dlog "C352 Internal Sample Rate: ~a" (slot-value chip 'satou::sample-rate)) | |
104 | + (slot-value chip 'satou::sample-rate)) | |
105 | + | |
106 | +(defmethod satou::chip-start-flags ((chip c352) (vgm vgm-file)) | |
107 | + (make-instance 'c352-flags :clock-div (vgm-header-c352-clock-div (vgm-file-header vgm)))) | |
108 | + | |
109 | +(defmethod satou::chip-update ((chip c352) outputs (start integer) (samples integer)) | |
110 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
111 | + (satou-chips/c352-mame::emu-update (satou:chip-emu chip) outputs start samples) | |
112 | + nil) | |
113 | + | |
114 | +(defmethod satou::chip-reset ((chip c352)) | |
115 | + (satou-chips/c352-mame::emu-reset (satou:chip-emu chip)) | |
116 | + nil) | |
117 | + | |
118 | +(defmethod satou::chip-read ((chip c352) (offset integer)) | |
119 | + (satou-chips/c352-mame::emu-read (satou:chip-emu chip) offset)) | |
120 | + | |
121 | +(defmethod satou::chip-write ((chip c352) (offset integer) (data integer) &optional port) | |
122 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0)) | |
123 | + (ignore port)) | |
124 | + (satou-chips/c352-mame::emu-write (satou:chip-emu chip) offset (coerce-to-uint16 data))) | |
125 | + | |
126 | +(defmethod satou::chip-write-dac ((chip c352) port command data) | |
127 | + (sdm-log:warn! "C352 does not yet support DAC write commands")) | |
128 | + | |
129 | +(defmethod satou::chip-volume-modifier ((chip c352)) | |
130 | + (* (slot-value chip 'satou::volume) 8)) | |
131 | + | |
132 | +(defmethod satou::chip-base-volume ((chip c352)) | |
133 | + #x40) | |
134 | + | |
135 | +(defmethod satou::chip-write-rom ((chip c352) (rom-size integer) (data-start integer) (data-length integer) | |
136 | + (rom-data vector)) | |
137 | + (satou-chips/c352-mame::emu-write-rom (satou:chip-emu chip) rom-size data-start data-length rom-data) | |
138 | + nil) |
@@ -0,0 +1,111 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou-chips) | |
17 | + | |
18 | +(defconst +chip-id/huc6280+ #x1B) | |
19 | + | |
20 | +(deftype t/huc6280-core () | |
21 | + '(member :ootake)) | |
22 | + | |
23 | +(defclass huc6280 (abstract-chip) | |
24 | + ((sampling-mode | |
25 | + :initform 0 | |
26 | + :type t/uint8) | |
27 | + | |
28 | + (chip-sample-rate | |
29 | + :initform 0 | |
30 | + :type t/uint32))) | |
31 | + | |
32 | +(defmethod chip-init ((chip huc6280) (vgm vgm-file) (playback-sample-rate integer) (new-sampling-mode integer) | |
33 | + (new-player-sample-rate integer) &key emu-core flags) | |
34 | + (unless (typep emu-core 't/huc6280-core) | |
35 | + (satou-error () "Bad emulation core for HuC6280: ~a" emu-core)) | |
36 | + (when flags | |
37 | + (sdm-log:warn! "HuC6280 does not take any init flags, ignoring")) | |
38 | + | |
39 | + (setf (slot-value chip 'satou::clock-from-header) | |
40 | + (logand (vgm-header-huc6280-clock (vgm-file-header vgm)) #x3FFFFFFF)) | |
41 | + (setf (slot-value chip 'satou::player-sample-rate) playback-sample-rate) | |
42 | + | |
43 | + (let ((chip-count (if (flag? (vgm-header-huc6280-clock (vgm-file-header vgm)) #x40000000) 2 1))) | |
44 | + (%huc6280-init-fields chip vgm emu-core playback-sample-rate new-sampling-mode chip-count)) | |
45 | + chip) | |
46 | + | |
47 | +(defun %huc6280-init-fields (chip vgm emu-core playback-sample-rate new-sampling-mode chip-count) | |
48 | + (setf (slot-value chip 'satou::volume) (satou:get-chip-volume chip vgm :huc6280 chip-count)) | |
49 | + (setf (slot-value chip 'satou::core) emu-core) | |
50 | + | |
51 | + (ecase emu-core | |
52 | + (:ootake | |
53 | + (setf (slot-value chip 'sampling-mode) new-sampling-mode) | |
54 | + (setf (slot-value chip 'chip-sample-rate) (if (<= (slot-value chip 'satou::clock-from-header) 0) | |
55 | + playback-sample-rate | |
56 | + (slot-value chip 'satou::clock-from-header))))) | |
57 | + | |
58 | + (when (>= chip-count 2) | |
59 | + ;; Treat the second one as a paired chip. | |
60 | + (setf (slot-value chip 'satou::paired) (make-instance 'huc6280)) | |
61 | + (%huc6280-init-fields (slot-value chip 'satou::paired) vgm emu-core playback-sample-rate new-sampling-mode | |
62 | + (1- chip-count))) | |
63 | + nil) | |
64 | + | |
65 | +(defmethod satou:chip-type ((chip huc6280)) | |
66 | + :huc6280) | |
67 | + | |
68 | +(defmethod satou:chip-name ((chip huc6280)) | |
69 | + ;; Wrap this in a RETURN-FROM so the editor doesn't think it's a doc string. | |
70 | + (return-from chip-name "Hudson HuC6280")) | |
71 | + | |
72 | +(defmethod satou:chip-short-name ((chip huc6280)) | |
73 | + ;; Wrap this in a RETURN-FROM so the editor doesn't think it's a doc string. | |
74 | + (return-from chip-short-name "HuC6280")) | |
75 | + | |
76 | +(defmethod satou:chip-id ((chip huc6280)) | |
77 | + +chip-id/huc6280+) | |
78 | + | |
79 | +(defmethod satou:chip-default-emu-core ((chip huc6280)) | |
80 | + :ootake) | |
81 | + | |
82 | +(defmethod satou::chip-start ((chip huc6280) (clock integer) &optional flags) | |
83 | + (when flags | |
84 | + (sdm-log:warn! "HuC6280 does not take any init flags, ignoring")) | |
85 | + | |
86 | + (ecase (slot-value chip 'satou::core) | |
87 | + (:ootake | |
88 | + (setf (slot-value chip 'satou::sample-rate) (slot-value chip 'satou::player-sample-rate)) | |
89 | + (setf (slot-value chip 'satou::emu) | |
90 | + (satou-chips/huc6280-ootake::make-emu-ootake clock (slot-value chip 'satou::sample-rate))))) | |
91 | + (slot-value chip 'satou::sample-rate)) | |
92 | + | |
93 | +(defmethod satou::chip-update ((chip huc6280) outputs (start integer) (samples integer)) | |
94 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
95 | + (satou-chips/huc6280-ootake::ootake-update (satou:chip-emu chip) outputs start samples) | |
96 | + nil) | |
97 | + | |
98 | +(defmethod satou::chip-reset ((chip huc6280)) | |
99 | + (satou-chips/huc6280-ootake::ootake-reset (satou:chip-emu chip)) | |
100 | + nil) | |
101 | + | |
102 | +(defmethod satou::chip-read ((chip huc6280) (offset integer)) | |
103 | + (satou-chips/huc6280-ootake::ootake-read (satou:chip-emu chip) offset)) | |
104 | + | |
105 | +(defmethod satou::chip-write ((chip huc6280) (offset integer) (data integer) &optional port) | |
106 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0)) | |
107 | + (ignore port)) | |
108 | + (satou-chips/huc6280-ootake::ootake-write (satou:chip-emu chip) offset data)) | |
109 | + | |
110 | +(defmethod satou::chip-base-volume ((chip huc6280)) | |
111 | + 256) |
@@ -0,0 +1,28 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou-chips) | |
17 | + | |
18 | +(defmacro with-output-buffers ((left right output-buffers samples emu-name) &body forms) | |
19 | + `(let ((,left (svref ,output-buffers 0)) | |
20 | + (,right (svref ,output-buffers 1))) | |
21 | + (declare (type t/int32-vector ,left ,right)) | |
22 | + | |
23 | + (when (or (< (length ,left) ,samples) | |
24 | + (< (length ,right) ,samples)) | |
25 | + (error "~a: Bad number of samples (expected ~a, but buffers have ~a and ~a)" | |
26 | + ,emu-name ,samples (length ,left) (length ,right))) | |
27 | + | |
28 | + ,@forms)) |
@@ -0,0 +1,355 @@ | ||
1 | +(in-package :satou-chips) | |
2 | + | |
3 | +(defpackage :satou-chips/c352-mame | |
4 | + (:use :common-lisp :cl-sdm :satou :satou-chips) | |
5 | + (:nicknames :c352-mame) | |
6 | + (:shadowing-import-from :common-lisp #:write-string)) | |
7 | + | |
8 | +(in-package :satou-chips/c352-mame) | |
9 | + | |
10 | +(defining-consts | |
11 | + (+flag-busy+ #x8000) ;; Channel is busy | |
12 | + (+flag-key-on+ #x4000) ;; Key on | |
13 | + (+flag-key-off+ #x2000) ;; Key off | |
14 | + (+flag-loop-trig+ #x1000) ;; Loop trigger | |
15 | + (+flag-loop-hist+ #x0800) ;; Loop history | |
16 | + (+flag-fm+ #x0400) ;; Frequency modulation | |
17 | + (+flag-phase-rl+ #x0200) ;; Rear/Left invert phase 180 degrees | |
18 | + (+flag-phase-fl+ #x0100) ;; Front/Left invert phase 180 degrees | |
19 | + (+flag-phase-fr+ #x0080) ;; Invert phase 180 degrees (flip sign of samples) | |
20 | + (+flag-ldir+ #x0040) ;; Loop direction | |
21 | + (+flag-link+ #x0020) ;; "long-format" sample (can't loop) | |
22 | + (+flag-noise+ #x0010) ;; Play noise instead of the sample | |
23 | + (+flag-mulaw+ #x0008) ;; Sample is µLaw encoded | |
24 | + (+flag-no-filter+ #x0004) ;; Don't apply filter | |
25 | + (+flag-revloop+ #x0003) ;; Loop backward | |
26 | + (+flag-loop+ #x0002) ;; Loop forward | |
27 | + (+flag-reverse+ #x0001) ;; Play the sample backwards | |
28 | + | |
29 | + (+num-voices+ 32)) | |
30 | + | |
31 | +(defstruct (voice (:conc-name %voice-)) | |
32 | + (pos 0 :type t/uint32) | |
33 | + (counter 0 :type t/uint32) | |
34 | + (sample 0 :type t/int16) | |
35 | + (last-sample 0 :type t/int16) | |
36 | + (vol-f 0 :type t/uint16) | |
37 | + (vol-r 0 :type t/uint16) | |
38 | + (cur-volume (new-array 4 t/uint8) :type (simple-array t/uint8 (4))) | |
39 | + (freq 0 :type t/uint16) | |
40 | + (flags 0 :type t/uint16) | |
41 | + (wave-bank 0 :type t/uint16) | |
42 | + (wave-start 0 :type t/uint16) | |
43 | + (wave-end 0 :type t/uint16) | |
44 | + (wave-loop 0 :type t/uint16) | |
45 | + (mute 0 :type t/uint8)) | |
46 | + | |
47 | +(define-typed-fn %ramp-volume ((voice voice) (fixnum channel) (t/uint8 val)) | |
48 | + (null t) | |
49 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
50 | + (let ((vol-delta (- (aref (%voice-cur-volume voice) channel) val))) | |
51 | + (unless (zerop vol-delta) | |
52 | + (incf (aref (%voice-cur-volume voice) channel) | |
53 | + (if (plusp vol-delta) -1 1)))) | |
54 | + nil) | |
55 | + | |
56 | +(defstruct (emu-c352 (:conc-name %emu-) | |
57 | + (:constructor %make-emu-c352)) | |
58 | + (sample-rate-base 0 :type t/uint32) | |
59 | + (divider 0 :type t/uint16) | |
60 | + (voices nil :type (or null (simple-array voice))) | |
61 | + (random 0 :type t/uint16) | |
62 | + (control 0 :type t/uint16) | |
63 | + (wave (new-array 0 t/uint8) :type t/uint8-array) | |
64 | + (wave-mask 0 :type t/uint32) | |
65 | + (mute-rear 0 :type t/uint8) ;; This is derived from a flag in the VGM header | |
66 | + (mulaw-table (new-array 256 t/int16) :type (simple-array t/int16 (256))) | |
67 | + (mute-all-rear 0 :type t/uint8) | |
68 | + (temp-buf (new-array 4 t/int32) :type (simple-array t/int32 (4)))) | |
69 | + | |
70 | +(defmethod print-object ((obj emu-c352) out) | |
71 | + (print-unreadable-object (obj out :type t) | |
72 | + (format out "Sample rate: ~a" (%emu-sample-rate-base obj)))) | |
73 | + | |
74 | +(defmacro make-voice-array () | |
75 | + `(make-array +num-voices+ :element-type 'voice | |
76 | + :initial-contents (loop repeat +num-voices+ | |
77 | + collect (make-voice)))) | |
78 | + | |
79 | +(define-typed-fn make-emu-c352 ((t/uint32 clock clock-div)) | |
80 | + (emu-c352) | |
81 | + (let* ((divider (if (/= clock-div 0) | |
82 | + (coerce-to-uint16 clock-div) | |
83 | + 288)) | |
84 | + (ret (%make-emu-c352 :divider divider | |
85 | + :sample-rate-base (truncate (logand clock #x7FFFFFFF) divider) | |
86 | + :mute-rear (ash (logand clock #x80000000) -31) | |
87 | + :voices (make-voice-array))) | |
88 | + (j 0)) | |
89 | + (declare (type t/uint16 j)) | |
90 | + (dotimes (i 128) | |
91 | + (setf (aref (%emu-mulaw-table ret) i) | |
92 | + (coerce-to-int16 (ash j 5))) | |
93 | + (cond | |
94 | + ((< i 16) (incf j)) | |
95 | + ((< i 24) (incf j 2)) | |
96 | + ((< i 48) (incf j 4)) | |
97 | + ((< i 100) (incf j 8)) | |
98 | + (t (incf j 16))) | |
99 | + (setf j (coerce-to-uint16 j))) | |
100 | + | |
101 | + (loop for i fixnum from 128 below 256 do | |
102 | + (setf (aref (%emu-mulaw-table ret) i) | |
103 | + (coerce-to-int16 (logand (lognot (aref (%emu-mulaw-table ret) (- i 128))) #xFFE0)))) | |
104 | + ret)) | |
105 | + | |
106 | +(define-typed-fn %fetch-sample ((emu-c352 emu) (voice voice)) | |
107 | + (null t) | |
108 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
109 | + | |
110 | + (setf (%voice-last-sample voice) (%voice-sample voice)) | |
111 | + | |
112 | + (cond | |
113 | + ((flag? (%voice-flags voice) +flag-noise+) | |
114 | + (setf (%emu-random emu) (coerce-to-uint16 | |
115 | + (logxor (ash (%emu-random emu) -1) | |
116 | + (logand (logand (- (coerce-to-int16 (%emu-random emu))) 1) #xFFF6)))) | |
117 | + (setf (%voice-sample voice) (coerce-to-int16 (%emu-random emu)))) | |
118 | + | |
119 | + (t | |
120 | + (let ((smp (aref (%emu-wave emu) (logand (%voice-pos voice) (%emu-wave-mask emu)))) | |
121 | + (pos 0)) | |
122 | + (declare (type t/uint32 pos) | |
123 | + (type t/uint8 smp)) | |
124 | + | |
125 | + (setf (%voice-sample voice) (coerce-to-int16 (ash smp 8))) | |
126 | + (when (flag? (%voice-flags voice) +flag-mulaw+) | |
127 | + (setf (%voice-sample voice) (aref (%emu-mulaw-table emu) smp))) | |
128 | + | |
129 | + (setf pos (logand (%voice-pos voice) #xFFFF)) | |
130 | + | |
131 | + (cond | |
132 | + ((and (flag? (%voice-flags voice) +flag-loop+) (flag? (%voice-flags voice) +flag-reverse+)) | |
133 | + ;; Backwards -> Forwards | |
134 | + (cond | |
135 | + ((and (flag? (%voice-flags voice) +flag-ldir+) | |
136 | + (= pos (%voice-wave-loop voice))) | |
137 | + (logandf (%voice-flags voice) (lognot +flag-ldir+))) | |
138 | + ((and (not (flag? (%voice-flags voice) +flag-ldir+)) | |
139 | + (= pos (%voice-wave-end voice))) | |
140 | + (logiorf (%voice-flags voice) +flag-ldir+))) | |
141 | + | |
142 | + (incf (%voice-pos voice) (if (flag? (%voice-flags voice) +flag-ldir+) -1 1))) | |
143 | + | |
144 | + ((= pos (%voice-wave-end voice)) | |
145 | + (cond | |
146 | + ((and (flag? (%voice-flags voice) +flag-link+) | |
147 | + (flag? (%voice-flags voice) +flag-loop+)) | |
148 | + (setf (%voice-pos voice) (coerce-to-uint16 (logior (ash (%voice-wave-start voice) 16) | |
149 | + (%voice-wave-loop voice)))) | |
150 | + (logiorf (%voice-flags voice) +flag-loop-hist+)) | |
151 | + | |
152 | + ((flag? (%voice-flags voice) +flag-loop+) | |
153 | + (setf (%voice-pos voice) (logior (logand (%voice-pos voice) #xFF0000) | |
154 | + (%voice-wave-loop voice))) | |
155 | + (logiorf (%voice-flags voice) +flag-loop-hist+)) | |
156 | + | |
157 | + (t | |
158 | + (logiorf (%voice-flags voice) +flag-key-off+) | |
159 | + (logandf (%voice-flags voice) (lognot +flag-busy+)) | |
160 | + (setf (%voice-sample voice) 0)))) | |
161 | + | |
162 | + (t | |
163 | + (incf (%voice-pos voice) (if (flag? (%voice-flags voice) +flag-reverse+) -1 1))))))) | |
164 | + nil) | |
165 | + | |
166 | +(define-typed-fn emu-update ((emu-c352 emu) (satou::t/output-buffer outputs) (t/uint32 start samples)) | |
167 | + (null) | |
168 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0))) | |
169 | + (with-output-buffers (out-l out-r outputs samples "C352") | |
170 | + (let ((smp 0) | |
171 | + (next-counter 0) | |
172 | + (out-buf (%emu-temp-buf emu))) | |
173 | + (declare (type t/int16 smp) | |
174 | + (type t/uint32 next-counter)) | |
175 | + | |
176 | + ;; Clear output buffers | |
177 | + (fill out-l 0 :start start :end (+ start samples)) | |
178 | + (fill out-r 0 :start start :end (+ start samples)) | |
179 | + | |
180 | + ;; Render | |
181 | + (loop for i fixnum from start below (+ samples start) do | |
182 | + ;; Clear temporary buffer | |
183 | + (fill out-buf 0) | |
184 | + | |
185 | + ;; Render each voice | |
186 | + (loop for voice across (%emu-voices emu) | |
187 | + for vflags = (%voice-flags voice) | |
188 | + do (setf smp 0) | |
189 | + | |
190 | + ;; Only look at voices that are doing something | |
191 | + (when (flag? vflags +flag-busy+) | |
192 | + (setf next-counter (+ (%voice-counter voice) (%voice-freq voice))) | |
193 | + (when (flag? next-counter #x10000) | |
194 | + (%fetch-sample emu voice) | |
195 | + (setf vflags (%voice-flags voice))) | |
196 | + | |
197 | + (when (flag? (logxor next-counter (%voice-counter voice)) #x18000) | |
198 | + (%ramp-volume voice 0 (ash (%voice-vol-f voice) -8)) | |
199 | + (%ramp-volume voice 1 (logand (%voice-vol-f voice) #xFF)) | |
200 | + (%ramp-volume voice 2 (ash (%voice-vol-r voice) -8)) | |
201 | + (%ramp-volume voice 3 (logand (%voice-vol-r voice) #xFF))) | |
202 | + | |
203 | + (setf (%voice-counter voice) (logand next-counter #xFFFF)) | |
204 | + (setf smp (%voice-sample voice)) | |
205 | + | |
206 | + ;; Interpolate samples | |
207 | + (unless (flag? vflags +flag-no-filter+) | |
208 | + (setf smp (coerce-to-int16 | |
209 | + (+ (%voice-last-sample voice) | |
210 | + (ash (* (%voice-counter voice) | |
211 | + (- (%voice-sample voice) (%voice-last-sample voice))) | |
212 | + -16)))))) | |
213 | + | |
214 | + ;; Mix into temporary output buffer if the voice isn't muted | |
215 | + ;; Left first | |
216 | + (incf (aref out-buf 0) | |
217 | + (coerce-to-int16 (ash (* (if (flag? vflags +flag-phase-fl+) (- smp) smp) | |
218 | + (aref (%voice-cur-volume voice) 0)) | |
219 | + -8))) | |
220 | + (incf (aref out-buf 2) | |
221 | + (coerce-to-int16 (ash (* (if (flag? vflags +flag-phase-rl+) (- smp) smp) | |
222 | + (aref (%voice-cur-volume voice) 2)) | |
223 | + -8))) | |
224 | + | |
225 | + ;; Now the right | |
226 | + (incf (aref out-buf 1) | |
227 | + (coerce-to-int16 (ash (* (if (flag? vflags +flag-phase-fr+) (- smp) smp) | |
228 | + (aref (%voice-cur-volume voice) 1)) | |
229 | + -8))) | |
230 | + (incf (aref out-buf 3) | |
231 | + (coerce-to-int16 (ash (* (if (flag? vflags +flag-phase-fr+) (- smp) smp) | |
232 | + (aref (%voice-cur-volume voice) 3)) | |
233 | + -8)))) | |
234 | + | |
235 | + (satou::muffling | |
236 | + ;; Mix into output buffers | |
237 | + (incf (aref out-l i) (aref out-buf 0)) | |
238 | + (incf (aref out-r i) (aref out-buf 1)) | |
239 | + | |
240 | + ;; Also mix the quadraphonic sound in as stereo | |
241 | + (when (and (zerop (%emu-mute-rear emu)) | |
242 | + (zerop (%emu-mute-all-rear emu))) | |
243 | + (incf (aref out-l i) (aref out-buf 2)) | |
244 | + (incf (aref out-r i) (aref out-buf 3))))))) | |
245 | + nil) | |
246 | + | |
247 | +(define-typed-fn emu-start ((emu-c352 emu)) | |
248 | + (t/uint32) | |
249 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
250 | + (%emu-sample-rate-base emu)) | |
251 | + | |
252 | +(define-typed-fn emu-reset ((emu-c352 emu)) | |
253 | + (null) | |
254 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
255 | + ;; Clear channel states | |
256 | + (setf (%emu-voices emu) (make-voice-array)) | |
257 | + | |
258 | + ;; Init noise generator | |
259 | + (setf (%emu-random emu) #x1234) | |
260 | + (setf (%emu-control emu) 0) | |
261 | + nil) | |
262 | + | |
263 | +(define-typed-fn emu-read ((emu-c352 emu) (fixnum address)) | |
264 | + (t/uint16 t) | |
265 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
266 | + (cond | |
267 | + ((< address #x100) ;; Channel registers | |
268 | + (let ((voice (aref (%emu-voices emu) (truncate address 8)))) | |
269 | + (case (mod address 8) | |
270 | + (0 (%voice-vol-f voice)) | |
271 | + (1 (%voice-vol-r voice)) | |
272 | + (2 (%voice-freq voice)) | |
273 | + (3 (%voice-flags voice)) | |
274 | + (4 (%voice-wave-bank voice)) | |
275 | + (5 (%voice-wave-start voice)) | |
276 | + (6 (%voice-wave-end voice)) | |
277 | + (7 (%voice-wave-loop voice)) | |
278 | + (otherwise 0)))) | |
279 | + | |
280 | + ((= address #x200) | |
281 | + (%emu-control emu)) | |
282 | + | |
283 | + (t 0))) | |
284 | + | |
285 | +(define-typed-fn emu-write ((emu-c352 emu) (fixnum address) (t/uint16 val)) | |
286 | + (null t) | |
287 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
288 | + (cond | |
289 | + ((< address #x100) ;; Channel registers | |
290 | + (let ((voice (aref (%emu-voices emu) (truncate address 8)))) | |
291 | + (case (mod address 8) | |
292 | + (0 (setf (%voice-vol-f voice) val)) | |
293 | + (1 (setf (%voice-vol-r voice) val)) | |
294 | + (2 (setf (%voice-freq voice) val)) | |
295 | + (3 (setf (%voice-flags voice) val)) | |
296 | + (4 (setf (%voice-wave-bank voice) val)) | |
297 | + (5 (setf (%voice-wave-start voice) val)) | |
298 | + (6 (setf (%voice-wave-end voice) val)) | |
299 | + (7 (setf (%voice-wave-loop voice) val))))) | |
300 | + | |
301 | + ((= address #x200) | |
302 | + (setf (%emu-control emu) val)) | |
303 | + | |
304 | + ((= address #x202) ;; Key on/off | |
305 | + (loop for voice across (%emu-voices emu) do | |
306 | + (cond | |
307 | + ((flag? (%voice-flags voice) +flag-key-on+) | |
308 | + (setf (%voice-pos voice) (logior (ash (%voice-wave-bank voice) 16) (%voice-wave-start voice))) | |
309 | + (setf (%voice-sample voice) 0) | |
310 | + (setf (%voice-last-sample voice) 0) | |
311 | + (setf (%voice-counter voice) #xFFFF) | |
312 | + (logiorf (%voice-flags voice) +flag-busy+) | |
313 | + (logandf (%voice-flags voice) (lognot (logior +flag-key-on+ +flag-loop-hist+))) | |
314 | + (setf (aref (%voice-cur-volume voice) 0) 0) | |
315 | + (setf (aref (%voice-cur-volume voice) 1) 0) | |
316 | + (setf (aref (%voice-cur-volume voice) 2) 0) | |
317 | + (setf (aref (%voice-cur-volume voice) 3) 0)) | |
318 | + | |
319 | + ((flag? (%voice-flags voice) +flag-key-off+) | |
320 | + (logandf (%voice-flags voice) (lognot (logior +flag-busy+ +flag-key-off+))) | |
321 | + (setf (%voice-counter voice) #xFFFF)))))) | |
322 | + nil) | |
323 | + | |
324 | +(define-typed-fn emu-set-options ((emu-c352 emu) (t/uint8 flags)) | |
325 | + (null) | |
326 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
327 | + (setf (%emu-mute-all-rear emu) (logand flags 1)) | |
328 | + nil) | |
329 | + | |
330 | +(define-typed-fn emu-unmute-all ((emu-c352 emu)) | |
331 | + (null) | |
332 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
333 | + (loop for voice across (%emu-voices emu) do | |
334 | + (setf (%voice-mute voice) 0)) | |
335 | + nil) | |
336 | + | |
337 | +(define-typed-fn emu-write-rom ((emu-c352 emu) (t/int32 rom-size data-start data-length) (t/uint8-array rom-data)) | |
338 | + (null) | |
339 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
340 | + (when (/= (length (%emu-wave emu)) rom-size) | |
341 | + (setf (%emu-wave emu) (new-array rom-size t/uint8 #xFF)) | |
342 | + (setf (%emu-wave-mask emu) 1) | |
343 | + (loop while (< (%emu-wave-mask emu) (length (%emu-wave emu))) do | |
344 | + (setf (%emu-wave-mask emu) (ash (%emu-wave-mask emu) 1))) | |
345 | + (decf (%emu-wave-mask emu))) | |
346 | + | |
347 | + (unless (> data-start rom-size) | |
348 | + (let ((data-len (if (> (+ data-start data-length) rom-size) | |
349 | + (- rom-size data-start) | |
350 | + data-length))) | |
351 | + ;; Copy the memory | |
352 | + (dotimes (i data-len) | |
353 | + (setf (aref (%emu-wave emu) (+ i data-start)) | |
354 | + (aref rom-data i))))) | |
355 | + nil) |
@@ -0,0 +1,694 @@ | ||
1 | +;;;; Ootake PSG | |
2 | +;;;; PSGを記述するのに必要な定義および関数のプロトタイプ宣言を行ないます. | |
3 | +;;;; Copyright (C) 2004 Ki | |
4 | +;;;; Copyright(C) 2006-2012 Kitao Nakamura | |
5 | +;;;; Copyright (C) 2023 Remilia Scarlet | |
6 | +;;;; | |
7 | +;;;; This program is free software; you can redistribute it and/or modify it | |
8 | +;;;; under the terms of the GNU General Public License as published by the Free | |
9 | +;;;; Software Foundation; either version 2 of the License, or (at your option) | |
10 | +;;;; any later version. | |
11 | +;;;; | |
12 | +;;;; This program is distributed in the hope that it will be useful, but WITHOUT | |
13 | +;;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
14 | +;;;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
15 | +;;;; more details. | |
16 | +;;;; | |
17 | +;;;; You should have received a copy of the GNU General Public License along | |
18 | +;;;; with this program; if not, write to the Free Software Foundation, Inc., 59 | |
19 | +;;;; Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 | +;;;; | |
21 | +;;;; ・キューの参照処理をシンプルにした。テンポの安定性および音質の向上。 | |
22 | +;;;; ・オーバーサンプリングしないようにした。(筆者の主観もあるが、PSGの場合、響きの | |
23 | +;;;; 美しさが損なわれてしまうケースが多いため。速度的にもアップ) | |
24 | +;;;; ・ノイズの音質・音量を実機並みに調整した。v0.72 | |
25 | +;;;; ・ノイズの周波数に0x1Fが書き込まれたときは、0x1Eと同じ周波数で音量を半分にして | |
26 | +;;;; 鳴らすようにした。v0.68 | |
27 | +;;;; ・現状は再生サンプルレートは44.1KHz固定とした。(CD-DA再生時の速度アップのため) | |
28 | +;;;; ・DDA音の発声が終了したときにいきなり波形を0にせず、フェードアウトさせるように | |
29 | +;;;; し、ノイズを軽減した。v0.57 | |
30 | +;;;; ・DDAモード(サンプリング発声)のときの波形データのノイズが多く含まれている部分 | |
31 | +;;;; をカットしして、音質を上げた。音量も調節した。v0.59 | |
32 | +;;;; ・ノイズ音の音質・音量を調整して、実機の雰囲気に近づけた。v0.68 | |
33 | +;;;; ・waveIndexの初期化とDDAモード時の動作を見直して実機の動作に近づけた。v0.63 | |
34 | +;;;; ・waveIndexの初期化時にwaveテーブルも初期化するようにした。ファイヤープロレス | |
35 | +;;;; リング、F1トリプルバトルなどの音が実機に近づいた。v0.65 | |
36 | +;;;; ・waveの波形の正負を実機同様にした。v0.74 | |
37 | +;;;; ・waveの最小値が-14になるようにし音質を整えた。v0.74 | |
38 | +;;;; ・クリティカルセクションは必要ない(書き込みが同時に行われるわけではない)ような | |
39 | +;;;; ので、省略し高速化した。v1.09 | |
40 | +;;;; ・キュー処理(ApuQueue.c)をここに統合して高速化した。v1.10 | |
41 | +;;;; ・低音領域のボリュームを上げて実機並みの聞こえやすさに近づけた。v1.46 | |
42 | +;;;; ・LFO処理のの実装。"はにいいんざすかい"のOPや、フラッシュハイダースの効果音が | |
43 | +;;;; 実機の音に近づいた。v1.59 | |
44 | +(in-package :satou-chips) | |
45 | + | |
46 | +;;;; | |
47 | +;;;; Actual implementation of the HuC6280 emulator based on Ootake's implementation | |
48 | +;;;; | |
49 | +;;;; Remi: I've purposely left the comments in Japanese. | |
50 | +;;;; | |
51 | + | |
52 | +(defpackage :satou-chips/huc6280-ootake | |
53 | + (:use :common-lisp :cl-sdm :satou :satou-chips) | |
54 | + (:nicknames :huc6280-ootake) | |
55 | + (:shadowing-import-from :common-lisp #:write-string)) | |
56 | + | |
57 | +(in-package :satou-chips/huc6280-ootake) | |
58 | + | |
59 | +(defining-consts | |
60 | + (+n-channel+ 6) | |
61 | + | |
62 | + ;; Kitao更新。PSGはオーバーサンプリングすると響きの美しさが損なわれてしまうの | |
63 | + ;; でオーバーサンプリングしないようにした。速度的にもアップ。 | |
64 | + (+oversample-rate+ 1.0d0) | |
65 | + | |
66 | + ;; 21.8500。Kitao追加。PSG音量の減少値。*6.0は各チャンネル足したぶんを割る意味。 | |
67 | + ;; 大きいほど音は減る。CDDAが100%のときにちょうど良いぐらいの音量に合わせよう。 | |
68 | + ;; v2.19,v2.37,v2.39,v2.62更新 | |
69 | + (+psg-decline+ (* 21.8500d0 6.0d0)) | |
70 | + | |
71 | + ;; -1.05809999010で雀探物語2OK。Kitao追加。音量テーブルの減少値。マイナス | |
72 | + ;; -が大きいほど小さい音が聞こえづらくなる。マイナスが小さすぎると平面的な | |
73 | + ;; -音になる。v2.19,v2.37,v2.39,v2.40,v2.62,v2.65更新 | |
74 | + (+vol-table-decline+ -1.05809999010d0) | |
75 | + | |
76 | + ;; 0.30599899951。Kitao追加。サンプリング音の消音時の音の減退量。ソルジャーブ | |
77 | + ;; レイド,将棋初心者無用の音声で調整。基本的にこの値が小さいほうがノイズが減る | |
78 | + ;; (逆のケースもある)。v2.40 | |
79 | + (+sample-fade-decline+ 0.305998999951d0)) | |
80 | + | |
81 | +;;;; [DEV NOTE] | |
82 | +;;;; | |
83 | +;;;; MAL --- 0 - 15 (15 で -0[dB], 1減るごとに -3.0 [dB]) | |
84 | +;;;; AL --- 0 - 31 (31 で -0[dB], 1減るごとに -1.5 [dB]) | |
85 | +;;;; LAL/RAL --- 0 - 15 (15 で -0[dB], 1減るごとに -3.0 [dB]) | |
86 | +;;;; | |
87 | +;;;; 次のように解釈しなおす。 | |
88 | +;;;; | |
89 | +;;;; MAL*2 --- 0 - 30 (30 で -0[dB], 1減るごとに -1.5 [dB]) | |
90 | +;;;; AL --- 0 - 31 (31 で -0[dB], 1減るごとに -1.5 [dB]) | |
91 | +;;;; LAL/RAL*2 --- 0 - 30 (30 で -0[dB], 1減るごとに -1.5 [dB]) | |
92 | +;;;; | |
93 | +;;;; | |
94 | +;;;; dB = 20 * log10(OUT/IN) | |
95 | +;;;; | |
96 | +;;;; dB / 20 = log10(OUT/IN) | |
97 | +;;;; | |
98 | +;;;; OUT/IN = 10^(dB/20) | |
99 | +;;;; | |
100 | +;;;; IN(最大出力) を 1.0 とすると、 | |
101 | +;;;; | |
102 | +;;;; OUT = 10^(dB/20) | |
103 | +;;;; | |
104 | +;;;; -91 <= -(MAL*2 + AL + LAL(RAL)*2) <= 0 | |
105 | +;;;; | |
106 | +;;;; だから、最も小さい音は、 | |
107 | +;;;; | |
108 | +;;;; -91 * 1.5 [dB] = -136.5 [dB] = 10^(-136.5/20) ~= 1.496236e-7 [倍] | |
109 | +;;;; | |
110 | +;;;; となる。 | |
111 | +;;;; | |
112 | +;;;; 1e-7 オーダーの値は、固定小数点で表現しようとすると、小数部だけで | |
113 | +;;;; 24 ビット以上必要で、なおかつ16ビットの音声を扱うためには +16ビット | |
114 | +;;;; だから 24+16 = 40ビット以上必要になる。よって、32 ビットの処理系で | |
115 | +;;;; PCEの音声を固定小数点で表現するのはつらい。そこで、波形の計算は | |
116 | +;;;; float で行なうことにする。 | |
117 | +;;;; | |
118 | +;;;; float から出力形式に変換するのはAPUの仕事とする。 | |
119 | +;;;; | |
120 | +;;;; [2004.4.28] やっぱり Sint32 で実装することにした(微小な値は無視する)。 | |
121 | +;;;; | |
122 | +;;;; CPUとPSGは同じICにパッケージしてあるのだが、 | |
123 | +;;;; 実際にはPSGはCPUの1/2のクロックで動作すると考えて良いようだ。 | |
124 | +;;;; よって、PSGの動作周波数 Fpsg は、 | |
125 | +;;;; | |
126 | +;;;; Fpsg = 21.47727 [MHz] / 3 / 2 = 3.579545 [MHz] | |
127 | +;;;; | |
128 | +;;;; となる。 | |
129 | +;;;; | |
130 | +;;;; たとえば32サンプルを1周期とする波形が再生されるとき、 | |
131 | +;;;; この周波数の周期でサンプルを1つずつ拾い出すと、 | |
132 | +;;;; | |
133 | +;;;; M = 3579545 / 32 = 111860.78125 [Hz] | |
134 | +;;;; | |
135 | +;;;; というマジックナンバーが得られる(ファミコンと同じ)。 | |
136 | +;;;; ただし、再生周波数が固定では曲の演奏ができないので、 | |
137 | +;;;; FRQ なる周波数パラメータを用いて再生周波数を変化させる。 | |
138 | +;;;; FRQ はPSGのレジスタに書き込まれる12ビット長のパラメータで、 | |
139 | +;;;; ↑で得られたマジックナンバーの「割る数」になっている。 | |
140 | +;;;; | |
141 | +;;;; 上の32サンプルを1周期とする波形が再生されるとき、 | |
142 | +;;;; この波形の周波数 F は、FRQ を用いて、 | |
143 | +;;;; | |
144 | +;;;; F = M / FRQ [Hz] (FRQ != 0) | |
145 | +;;;; | |
146 | +;;;; となる。 | |
147 | +;;;; | |
148 | +;;;; PCの再生サンプリング周波数が Fpc [Hz] だとすると、 | |
149 | +;;;; 1周期32サンプルの波形の再生周波数 F2 は F2 = Fpc / 32 [Hz]。 | |
150 | +;;;; よって、PCの1サンプルに対して、PCEの1サンプルを拾い出す | |
151 | +;;;; カウンタの進み幅 I は | |
152 | +;;;; | |
153 | +;;;; I = F / F2 = 32 * F / Fpc = Fpsg / FRQ / Fpc [単位なし] | |
154 | +;;;; | |
155 | +;;;; となる。 | |
156 | +;;;; | |
157 | +;;;; [NOISE CHANNEL] | |
158 | +;;;; | |
159 | +;;;; 擬似ノイズの生成にはM系列(maximum length sequence)が用いられる。 | |
160 | +;;;; M系列のビット長は未調査につき不明。 | |
161 | +;;;; ここでは仮に15ビットとして実装を行なう。 | |
162 | +;;;; 出力は1ビットで、D0 がゼロのときは負の値、1のときは正の値とする。 | |
163 | +;;;; | |
164 | +;;;; PCの1サンプルに対して、PCEの1サンプルを拾い出す | |
165 | +;;;; カウンタの進み幅 I は、 | |
166 | +;;;; | |
167 | +;;;; I = Fpsg / 64 / FRQ / Fpc (FRQ != 0) | |
168 | +;;;; | |
169 | +;;;; となる。 | |
170 | +;;;; | |
171 | +;;;; [再生クオリティ向上について] 2004.6.22 | |
172 | +;;;; | |
173 | +;;;; エミュレータでは、PSGのレジスタにデータが書き込まれるまで、 | |
174 | +;;;; 次に発声すべき音がわからない。レジスタにデータが書き込まれたときに、 | |
175 | +;;;; サウンドバッファを更新したいのだけど、あいにく現在の実装では、 | |
176 | +;;;; サウンドバッファの更新は別スレッドで行なわれていて、 | |
177 | +;;;; エミュレーションスレッドから任意の時間に更新することができない。 | |
178 | +;;;; | |
179 | +;;;; これまでの再生では、サウンドバッファの更新時のレジスタ設定のみが | |
180 | +;;;; 有効だったが、これだと例えばサウンドバッファ更新の合間に一瞬だけ | |
181 | +;;;; 出力された音などが無視されてしまう。これは特にDDAモードやノイズが | |
182 | +;;;; リズムパートとして使用される上で問題になる。 | |
183 | +;;;; | |
184 | +;;;; レジスタに書き込まれた値をきちんと音声出力に反映させるには、 | |
185 | +;;;; 過去に書き込まれたレジスタの値(いつ、どのレジスタに、何が書き込まれたか) | |
186 | +;;;; を保存しておいて、サウンドバッファ更新時にこれを参照する方法が | |
187 | +;;;; 考えられる。どのくらい過去までレジスタの値を保存しておくかは、 | |
188 | +;;;; サウンドバッファの長さにもよると思われるが、とりあえずは試行錯誤で | |
189 | +;;;; 決めることにする。 | |
190 | +;;;; | |
191 | +;;;; PSGレジスタへの書き込み動作はエミュレーションスレッドで | |
192 | +;;;; 行なわれ、サウンドバッファ更新はその専用スレッドで行なわれる。 | |
193 | +;;;; これだと、エミュレーションスレッドがレジスタのキューに書き込みを | |
194 | +;;;; 行なっている最中に、サウンドバッファ更新スレッドがキューから | |
195 | +;;;; 読み出しを行なってしまい、アクセスが衝突する。この問題を解決するには、 | |
196 | +;;;; | |
197 | +;;;; 1.サウンドバッファの更新を別スレッドで行なわない | |
198 | +;;;; 2.キューのアクセス部分を排他処理にする | |
199 | +;;;; | |
200 | +;;;; の2とおりが考えられる。とりあえず2の方法をとることにする。 | |
201 | + | |
202 | +(defstruct (psg (:conc-name %psg-)) | |
203 | + (freq 0 :type t/uint32) | |
204 | + (on-p nil :type boolean) | |
205 | + (dda-p nil :type boolean) | |
206 | + (volume 0 :type t/uint32) | |
207 | + (volume-l 0 :type t/uint32) | |
208 | + (volume-r 0 :type t/uint32) | |
209 | + (out-volume-l 0 :type t/int32) | |
210 | + (out-volume-r 0 :type t/int32) | |
211 | + (wave (new-array 32 t/int32) :type (simple-array t/int32 (32))) | |
212 | + (wave-index 0 :type t/uint32) | |
213 | + (dda-sample 0 :type t/int32) | |
214 | + (phase 0 :type t/uint32) | |
215 | + (delta-phase 0 :type t/uint32) | |
216 | + (noise-on-p nil :type boolean) | |
217 | + (noise-freq 0 :type t/uint32) | |
218 | + (delta-noise-phase 0 :type t/uint32)) | |
219 | + | |
220 | +(defmethod print-object ((obj psg) out) | |
221 | + (print-unreadable-object (obj out :type t) | |
222 | + (format out "On: ~:[no~;yes~]" (%psg-on-p obj)))) | |
223 | + | |
224 | +(defstruct (emu-ootake (:constructor %make-emu-ootake) | |
225 | + (:conc-name %ootake-)) | |
226 | + (sample-rate 0.0d0 :type double-float) | |
227 | + (psg-freq 0.0d0 :type double-float) | |
228 | + (resample-rate 0.0d0 :type double-float) | |
229 | + | |
230 | + (psg nil :type (or null (simple-array psg (8)))) | |
231 | + (dda-fade-out-l (new-array 8 t/int32) :type (simple-array t/int32 (8))) | |
232 | + (dda-fade-out-r (new-array 8 t/int32) :type (simple-array t/int32 (8))) | |
233 | + (channel 0 :type t/uint32) ;; 0 - 5 | |
234 | + (main-volume-l 0 :type t/uint32) ;; 0 - 15 | |
235 | + (main-volume-r 0 :type t/uint32) ;; 0 - 15 | |
236 | + (lfo-freq 0 :type t/uint32) | |
237 | + (lfo-on-p nil :type boolean) | |
238 | + (lfo-ctrl 0 :type t/uint32) | |
239 | + (lfo-shift 0 :type t/uint32) | |
240 | + (psg-volume-effect 0 :type t/int32) | |
241 | + (volume 0.0d0 :type double-float) | |
242 | + (vol 0.0d0 :type double-float) | |
243 | + (psg-mute (new-array 8 boolean nil) :type (simple-array boolean (8))) | |
244 | + (port (new-array 16 t/uint8) :type (simple-array t/uint8 (16))) | |
245 | + (wave-crash-p nil :type boolean) | |
246 | + (honey-in-the-sky-p nil :type boolean) | |
247 | + (volume-table (new-array 92 t/int32) :type (simple-array t/int32 (92))) | |
248 | + (noise-table (new-array 32768 t/int32) :type (simple-array t/int32 (32768)))) | |
249 | + | |
250 | +(defmethod print-object ((obj emu-ootake) out) | |
251 | + (print-unreadable-object (obj out :type t) | |
252 | + (format out "Sample rate: ~a" (%ootake-sample-rate obj)))) | |
253 | + | |
254 | +(define-typed-fn ootake-honey-in-the-sky-p ((emu-ootake emu)) | |
255 | + (boolean t) | |
256 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
257 | + (%ootake-honey-in-the-sky-p emu)) | |
258 | + | |
259 | +(define-typed-fn (setf ootake-honey-in-the-sky-p) ((boolean value) (emu-ootake emu)) | |
260 | + (null t) | |
261 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
262 | + (setf (%ootake-honey-in-the-sky-p emu) value) | |
263 | + nil) | |
264 | + | |
265 | +(define-typed-fn make-emu-ootake ((t/int32 clock) (integer sample-rate)) | |
266 | + (emu-ootake) | |
267 | + (let ((ret (%make-emu-ootake :psg-freq (coerce (logand clock #x7FFFFFFF) 'double-float)))) | |
268 | + (setf (%ootake-psg ret) (make-array 8 :element-type 'psg | |
269 | + :initial-contents (loop repeat 8 collect (make-psg)))) | |
270 | + (setf (ootake-honey-in-the-sky-p ret) (/= (logand (ash clock -31) #x01) 0)) | |
271 | + (%set-volume ret) | |
272 | + (ootake-reset ret) | |
273 | + (%create-volume-table ret) | |
274 | + (%create-noise-table ret) | |
275 | + (setf (%ootake-sample-rate ret) (coerce sample-rate 'double-float)) | |
276 | + (setf (%ootake-resample-rate ret) (/ (%ootake-psg-freq ret) +oversample-rate+ (%ootake-sample-rate ret))) | |
277 | + ret)) | |
278 | + | |
279 | +(define-typed-fn %set-vol ((emu-ootake emu)) | |
280 | + (null) | |
281 | + (setf (%ootake-vol emu) | |
282 | + (case (%ootake-psg-volume-effect emu) | |
283 | + (0 #.(/ 1.0d0 128.0d0)) | |
284 | + (3 (/ (%ootake-volume emu) (/ (* +oversample-rate+ 4.0d0) 3.0d0))) | |
285 | + (otherwise (/ (%ootake-volume emu) (* +oversample-rate+ (%ootake-psg-volume-effect emu)))))) | |
286 | + nil) | |
287 | + | |
288 | +(define-typed-fn %set-volume ((emu-ootake emu)) | |
289 | + (null) | |
290 | + (setf (%ootake-volume emu) (/ 1.0d0 +psg-decline+)) | |
291 | + (%set-vol emu) | |
292 | + nil) | |
293 | + | |
294 | +(define-typed-fn %create-volume-table ((emu-ootake emu)) | |
295 | + (null) | |
296 | + (setf (aref (%ootake-volume-table emu) 0) 0) | |
297 | + (loop for i fixnum from 1 to 91 | |
298 | + for v double-float = (- 91.0d0 i) | |
299 | + do (setf (aref (%ootake-volume-table emu) i) | |
300 | + (coerce-to-int32 | |
301 | + (truncate (* 32768.0d0 (expt 10.0d0 (/ (* v +vol-table-decline+) 20.0d0))))))) | |
302 | + nil) | |
303 | + | |
304 | +(define-typed-fn %create-noise-table ((emu-ootake emu)) | |
305 | + (null) | |
306 | + (loop with bit0 fixnum = 0 | |
307 | + with bit1 fixnum = 0 | |
308 | + with bit14 fixnum = 0 | |
309 | + with reg fixnum = #x100 | |
310 | + for i fixnum from 0 below 32768 do | |
311 | + (setf bit0 (logand reg 1)) | |
312 | + (setf bit1 (ash (logand reg 2) -1)) | |
313 | + (setf bit14 (logxor bit0 bit1)) | |
314 | + (setf reg (ash reg -1)) | |
315 | + (setf reg (logior reg (coerce-to-uint32 (ash bit14 14)))) | |
316 | + (setf (aref (%ootake-noise-table emu) i) (if (zerop bit0) -1 -18))) | |
317 | + nil) | |
318 | + | |
319 | +(define-typed-fn %get-mute-psg-channel ((emu-ootake emu) (fixnum num)) | |
320 | + (boolean t) | |
321 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
322 | + (aref (%ootake-psg-mute emu) num)) | |
323 | + | |
324 | +(define-typed-fn %set-mute-psg-channel ((emu-ootake emu) (fixnum num) (boolean mute?)) | |
325 | + (null t) | |
326 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
327 | + (setf (aref (%ootake-psg-mute emu) num) mute?) | |
328 | + (when mute? | |
329 | + (setf (aref (%ootake-dda-fade-out-l emu) num) 0) | |
330 | + (setf (aref (%ootake-dda-fade-out-r emu) num) 0)) | |
331 | + nil) | |
332 | + | |
333 | +(define-typed-fn %reset-volume-reg ((emu-ootake emu)) | |
334 | + (null) | |
335 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
336 | + (setf (%ootake-main-volume-l emu) 0) | |
337 | + (setf (%ootake-main-volume-r emu) 0) | |
338 | + (loop for i fixnum from 0 below +n-channel+ do | |
339 | + (setf (%psg-volume (aref (%ootake-psg emu) i)) 0) | |
340 | + (setf (%psg-out-volume-l (aref (%ootake-psg emu) i)) 0) | |
341 | + (setf (%psg-out-volume-r (aref (%ootake-psg emu) i)) 0) | |
342 | + (setf (aref (%ootake-dda-fade-out-l emu) i) 0) | |
343 | + (setf (aref (%ootake-dda-fade-out-r emu) i) 0)) | |
344 | + nil) | |
345 | + | |
346 | +(define-typed-fn ootake-reset ((emu-ootake emu)) | |
347 | + (null) | |
348 | + (setf (%ootake-psg emu) (make-array 8 :element-type 'psg | |
349 | + :initial-contents (loop repeat 8 collect (make-psg)))) | |
350 | + (fill (%ootake-dda-fade-out-l emu) 0) | |
351 | + (fill (%ootake-dda-fade-out-r emu) 0) | |
352 | + (setf (%ootake-main-volume-l emu) 0) | |
353 | + (setf (%ootake-main-volume-r emu) 0) | |
354 | + (setf (%ootake-lfo-freq emu) 0) | |
355 | + (setf (%ootake-lfo-ctrl emu) 0) | |
356 | + (setf (%ootake-channel emu) 0) | |
357 | + (setf (%ootake-wave-crash-p emu) nil) | |
358 | + | |
359 | + ;; Kitao更新。v0.65.waveデータを初期化。 | |
360 | + (dotimes (i +n-channel+) | |
361 | + (dotimes (j 32) | |
362 | + ;; 最小値で初期化。ファイプロ,フォーメーションサッカー'90,F1トリプルバトルで必要。 | |
363 | + (setf (aref (%psg-wave (aref (%ootake-psg emu) i)) j) -14))) | |
364 | + | |
365 | + (dotimes (j 32) | |
366 | + ;; ch3は最大値で初期化。F1トリプルバトル。v2.65 | |
367 | + (setf (aref (%psg-wave (aref (%ootake-psg emu) 3)) j) -17)) | |
368 | + nil) | |
369 | + | |
370 | +(define-typed-fn ootake-read ((emu-ootake emu) (fixnum reg)) | |
371 | + (t/uint8 t) | |
372 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
373 | + (aref (%ootake-port emu) (logand reg 15))) | |
374 | + | |
375 | +(define-typed-fn ootake-write-reg ((emu-ootake emu) (fixnum reg data)) | |
376 | + (null t) | |
377 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
378 | + (let ((freq 0)) | |
379 | + (declare (type t/uint32 freq)) | |
380 | + | |
381 | + (setf (aref (%ootake-port emu) (logand reg 15)) data) | |
382 | + | |
383 | + (case (logand reg 15) | |
384 | + (0 ;; Register select | |
385 | + (setf (%ootake-channel emu) (logand data 7))) | |
386 | + | |
387 | + (1 ;; Main volume | |
388 | + (setf (%ootake-main-volume-l emu) (logand (ash data -4) #x0F)) | |
389 | + (setf (%ootake-main-volume-r emu) (logand data #x0F)) | |
390 | + | |
391 | + ;; LMAP, RMAL は全チャネルの音量に影響する | |
392 | + (loop for i fixnum from 0 below +n-channel+ | |
393 | + for psg-chan = (aref (%ootake-psg emu) i) | |
394 | + do (setf (%psg-out-volume-l psg-chan) | |
395 | + (aref (%ootake-volume-table emu) | |
396 | + (+ (%psg-volume psg-chan) | |
397 | + (* (+ (%ootake-main-volume-l emu) (%psg-volume-l psg-chan)) 2)))) | |
398 | + (setf (%psg-out-volume-r psg-chan) | |
399 | + (aref (%ootake-volume-table emu) | |
400 | + (+ (%psg-volume psg-chan) | |
401 | + (* (+ (%ootake-main-volume-r emu) (%psg-volume-r psg-chan)) 2)))))) | |
402 | + | |
403 | + (2 ;; Frequency low | |
404 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu)))) | |
405 | + (logandf (%psg-freq psg-chan) #.(lognot #xFF)) | |
406 | + (logiorf (%psg-freq psg-chan) data) | |
407 | + | |
408 | + ;; Kitao更新。update_frequencyは、速度アップのためサブルーチンにせず直接実行するようにした。 | |
409 | + (setf freq (logand (1- (%psg-freq psg-chan)) #xFFF)) | |
410 | + (if (/= freq 0) | |
411 | + ;; Kitao更新。速度アップのためfrq以外は定数計算にした。精度向上のた | |
412 | + ;; め、先に値の小さいOVERSAMPLE_RATEのほうで割るようにした。+0.5は | |
413 | + ;; 四捨五入で精度アップ。プチノイズ軽減のため。 | |
414 | + (setf (%psg-delta-phase psg-chan) | |
415 | + (coerce-to-uint32 | |
416 | + (truncate (+ (/ (* #.(* 65536.0d0 256.0d0 8.0d0) (%ootake-resample-rate emu)) freq) 0.5d0)))) | |
417 | + | |
418 | + (setf (%psg-delta-phase psg-chan) 0)))) | |
419 | + | |
420 | + (3 ;; Frequency high | |
421 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu)))) | |
422 | + (logandf (%psg-freq psg-chan) #.(lognot #xF00)) | |
423 | + (logiorf (%psg-freq psg-chan) (ash (logand data #x0F) 8)) | |
424 | + | |
425 | + ;; Kitao更新。update_frequencyは、速度アップのためサブルーチンにせず直接実行するようにした。 | |
426 | + (setf freq (logand (1- (%psg-freq psg-chan)) #xFFF)) | |
427 | + | |
428 | + (if (/= freq 0) | |
429 | + ;; Kitao更新。速度アップのためfrq以外は定数計算にした。精度向上のた | |
430 | + ;; め、先に値の小さいOVERSAMPLE_RATEのほうで割るようにした。+0.5は | |
431 | + ;; 四捨五入で精度アップ。プチノイズ軽減のため。 | |
432 | + (setf (%psg-delta-phase psg-chan) | |
433 | + (coerce-to-uint32 | |
434 | + (truncate (+ (/ (* #.(* 65536.0d0 256.0d0 8.0d0) (%ootake-resample-rate emu)) freq) 0.5d0)))) | |
435 | + | |
436 | + (setf (%psg-delta-phase psg-chan) 0)))) | |
437 | + | |
438 | + (4 ;; ON, DDA, AL | |
439 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu)))) | |
440 | + ;; はにいいんざすかいのポーズ時に、微妙なボリューム調整タイミングの問題 | |
441 | + ;; でプチノイズが載ってしまうので、現状はパッチ処理で対応。v2.60更新 | |
442 | + (when (%ootake-honey-in-the-sky-p emu) | |
443 | + ;; 発声中にdataが0の場合、LRボリュームも0にリセット。はにいいんざすか | |
444 | + ;; いのポーズ時のノイズが解消。(data & 0x1F)だけが0のときにリセットす | |
445 | + ;; ると、サイレントデバッガーズ等でNG。発声してない時にリセットすると | |
446 | + ;; アトミックロボでNG。v2.55 | |
447 | + (when (and (%psg-on-p psg-chan) (zerop data)) | |
448 | + ;; メインボリュームのbit0が0のときだけ処理(はにいいんざすかいでイレ | |
449 | + ;; ギュラーな0xE。他のゲームは0xF。※ヘビーユニットも0xEだった)。こ | |
450 | + ;; れがないとミズバク大冒険で音が出ない。実機の仕組みと同じかどうか | |
451 | + ;; は未確認。v2.53追加 | |
452 | + (unless (flag? (%ootake-main-volume-l emu) 1) | |
453 | + (setf (%psg-volume-l psg-chan) 0)) | |
454 | + (unless (flag? (%ootake-main-volume-r emu) 1) | |
455 | + (setf (%psg-volume-r psg-chan) 0)))) | |
456 | + | |
457 | + (setf (%psg-on-p psg-chan) (flag? data #x80)) | |
458 | + ;; DDAからWAVEへ切り替わるとき or DDAから消音するとき | |
459 | + (when (and (%psg-dda-p psg-chan) (not (flag? data #x40))) | |
460 | + ;; Kitao追加。DDAはいきなり消音すると目立つノイズが載るのでフェードアウトする。 | |
461 | + ;; | |
462 | + ;; Remi: This has been refactored to be cleaner. | |
463 | + (setf (aref (%ootake-dda-fade-out-l emu) (%ootake-channel emu)) | |
464 | + (coerce-to-int32 | |
465 | + (truncate | |
466 | + (satou-chips::muffling | |
467 | + (* (%psg-dda-sample psg-chan) (%psg-out-volume-l psg-chan) +sample-fade-decline+))))) | |
468 | + (setf (aref (%ootake-dda-fade-out-r emu) (%ootake-channel emu)) | |
469 | + (coerce-to-int32 | |
470 | + (truncate | |
471 | + (satou-chips::muffling | |
472 | + (* (%psg-dda-sample psg-chan) (%psg-out-volume-r psg-chan) +sample-fade-decline+)))))) | |
473 | + | |
474 | + (setf (%psg-dda-p psg-chan) (/= (logand data #x40) 0)) | |
475 | + | |
476 | + ;; Kitao追加。dataのbit7,6が01のときにWaveインデックスをリセットする。 | |
477 | + ;; DDAモード時にWaveデータを書き込んでいた場合はここでWaveデータを修復 | |
478 | + ;; (初期化)する。ファイヤープロレスリング。 | |
479 | + (when (= (logand data #xC0) #x40) | |
480 | + (setf (%psg-wave-index psg-chan) 0) | |
481 | + (when (%ootake-wave-crash-p emu) | |
482 | + (loop for i fixnum from 0 below 32 | |
483 | + do (setf (aref (%psg-wave psg-chan) i) -14) ;; Waveデータを最小値で初期化 | |
484 | + finally (setf (%ootake-wave-crash-p emu) nil)))) | |
485 | + | |
486 | + (setf (%psg-volume psg-chan) (logand data #x1F)) | |
487 | + (setf (%psg-out-volume-l psg-chan) | |
488 | + (aref (%ootake-volume-table emu) | |
489 | + (+ (%psg-volume psg-chan) (* (+ (%ootake-main-volume-l emu) (%psg-volume-l psg-chan)) 2)))) | |
490 | + (setf (%psg-out-volume-r psg-chan) | |
491 | + (aref (%ootake-volume-table emu) | |
492 | + (+ (%psg-volume psg-chan) (* (+ (%ootake-main-volume-r emu) (%psg-volume-r psg-chan)) 2)))))) | |
493 | + | |
494 | + (5 ;; LAL, RAL | |
495 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu)))) | |
496 | + (setf (%psg-volume-l psg-chan) (logand (ash data -4) #x0F)) | |
497 | + (setf (%psg-volume-r psg-chan) (logand data #x0F)) | |
498 | + (setf (%psg-out-volume-l psg-chan) | |
499 | + (aref (%ootake-volume-table emu) | |
500 | + (+ (%psg-volume psg-chan) (* (+ (%ootake-main-volume-l emu) (%psg-volume-l psg-chan)) 2)))) | |
501 | + (setf (%psg-out-volume-r psg-chan) | |
502 | + (aref (%ootake-volume-table emu) | |
503 | + (+ (%psg-volume psg-chan) (* (+ (%ootake-main-volume-r emu) (%psg-volume-r psg-chan)) 2)))))) | |
504 | + | |
505 | + (6 ;;wave data. Kitao更新。DDAモードのときもWaveデータを更新するよ うにし | |
506 | + ;;た。v0.63。ファイヤープロレスリング | |
507 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu))) | |
508 | + (new-data (logand data #x1F))) | |
509 | + (setf (%ootake-wave-crash-p emu) nil) | |
510 | + | |
511 | + ;; Kitao追加。音を鳴らしていないときだけWaveデータを更新する。v0.65。F1 | |
512 | + ;; トリプルバトルのエンジン音。 | |
513 | + (unless (%psg-on-p psg-chan) | |
514 | + ;; 17。Kitao更新。一番心地よく響く値に。ミズバク大冒険,モトローダー, | |
515 | + ;; ドラゴンスピリット等で調整。 | |
516 | + (setf (aref (%psg-wave psg-chan) (%psg-wave-index psg-chan)) (- 17 new-data)) | |
517 | + (incf (%psg-wave-index psg-chan)) | |
518 | + (logandf (%psg-wave-index psg-chan) #x1F)) | |
519 | + | |
520 | + (when (%psg-dda-p psg-chan) | |
521 | + ;; Kitao更新。ノイズ軽減のため6より下側の値はカットするようにした。v0.59 | |
522 | + (when (< new-data 6) ;; サイバーナイトで6に決定 | |
523 | + (setf new-data 6)) ;; ノイズが多いので小さな値はカット | |
524 | + | |
525 | + (setf (%psg-dda-sample psg-chan) (- 11 new-data)) | |
526 | + | |
527 | + ;; DDAモード時にWaveデータを書き換えた場合 | |
528 | + (unless (%psg-on-p psg-chan) | |
529 | + (setf (%ootake-wave-crash-p emu) t))))) | |
530 | + | |
531 | + (7 ;; Noise on, noise frequency | |
532 | + (when (>= (%ootake-channel emu) 4) | |
533 | + (let ((psg-chan (aref (%ootake-psg emu) (%ootake-channel emu)))) | |
534 | + (setf (%psg-noise-on-p psg-chan) (/= (logand data #x80) 0)) | |
535 | + (setf (%psg-noise-freq psg-chan) (- #x1F (logand data #x1F))) | |
536 | + (setf (%psg-delta-noise-phase psg-chan) | |
537 | + (if (zerop (%psg-noise-freq psg-chan)) | |
538 | + (coerce-to-uint32 (truncate (+ (* 2048.0d0 (%ootake-resample-rate emu)) 0.5d0))) | |
539 | + (coerce-to-uint32 (truncate (/ (* 2048.0d0 (%ootake-resample-rate emu)) | |
540 | + (+ (%psg-noise-freq psg-chan) 0.5d0))))))))) | |
541 | + | |
542 | + (8 ;; LFO frequency | |
543 | + (setf (%ootake-lfo-freq emu) data)) | |
544 | + | |
545 | + (9 ;; LFO control. Kitao更新。シンプルに実装してみた。実機で同じ動作 かは | |
546 | + ;; 未確認。はにいいんざすかいの音が似るように実装。v1.59 | |
547 | + (when (flag? data #x80) ;; bit7を立てて呼ぶと恐らくリセット | |
548 | + (setf (%psg-phase (aref (%ootake-psg emu) 1)) 0)) ;; LfoFrqは初期化しない。はにいいんざすかい。 | |
549 | + | |
550 | + (setf (%ootake-lfo-ctrl emu) (logand data 7)) ;; ドロップロックほらホラで5が使われる。v1.61更新 | |
551 | + (when (flag? (%ootake-lfo-ctrl emu) 4) | |
552 | + ;; ドロップロックほらホラ。実機で聴いた感じはLFOオフと同じ音のようなの | |
553 | + ;; でbit2が立っていた(負の数扱い?)ら0と同じこととする。 | |
554 | + (setf (%ootake-lfo-ctrl emu) 0))))) | |
555 | + nil) | |
556 | + | |
557 | +(define-typed-fn ootake-write ((emu-ootake emu) (fixnum reg) (fixnum data)) | |
558 | + (null t) | |
559 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
560 | + (ootake-write-reg emu reg data)) | |
561 | + | |
562 | +(define-typed-fn ootake-update ((emu-ootake emu) (satou-chips::t/output-buffer outputs) (t/uint32 start samples)) | |
563 | + (null) | |
564 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
565 | + (with-output-buffers (out-l out-r outputs samples "HuC6280") | |
566 | + (let ((sample 0) | |
567 | + (lfo 0) | |
568 | + | |
569 | + ;; Kitao追加。6chぶんのサンプルを足していくためのバッファ。精度を維持 | |
570 | + ;; するために必要。6chぶん合計が終わった後に、これをSint16に変換して書 | |
571 | + ;; き込むようにした。 | |
572 | + (sample-all-l 0) | |
573 | + | |
574 | + ;; Kitao追加。上のRチャンネル用 | |
575 | + (sample-all-r 0) | |
576 | + | |
577 | + ;; Kitao追加。DDA音量,ノイズ音量計算用 | |
578 | + (smp 0)) | |
579 | + (declare (type fixnum sample lfo sample-all-l sample-all-r smp)) | |
580 | + | |
581 | + (loop for j fixnum from start below (+ samples start) do | |
582 | + (setf sample-all-l 0) | |
583 | + (setf sample-all-r 0) | |
584 | + | |
585 | + (loop for i fixnum from 0 below +n-channel+ | |
586 | + for psg-chan = (aref (%ootake-psg emu) i) | |
587 | + do (when (and (%psg-on-p psg-chan) | |
588 | + (or (/= i 1) (zerop (%ootake-lfo-ctrl emu))) | |
589 | + (not (aref (%ootake-psg-mute emu) i))) | |
590 | + (cond | |
591 | + ((%psg-dda-p psg-chan) | |
592 | + ;; Kitao更新。サンプリング音の音量を実機並みに調整。 | |
593 | + ;; v2.39,v2.40,v2.62,v2.65再調整した。 | |
594 | + (setf smp (* (%psg-dda-sample psg-chan) (%psg-out-volume-l psg-chan))) | |
595 | + (incf sample-all-l (+ smp (ash smp -3) (ash smp -4) (ash smp -5) (ash smp -7) | |
596 | + (ash smp -12) (ash smp -14) (ash smp -15))) | |
597 | + (setf smp (* (%psg-dda-sample psg-chan) (%psg-out-volume-r psg-chan))) | |
598 | + (incf sample-all-r (+ smp (ash smp -3) (ash smp -4) (ash smp -5) (ash smp -7) | |
599 | + (ash smp -12) (ash smp -14) (ash smp -15)))) | |
600 | + | |
601 | + ((%psg-noise-on-p psg-chan) | |
602 | + (setf sample (aref (%ootake-noise-table emu) (ash (%psg-phase psg-chan) -17))) | |
603 | + | |
604 | + ;; Kitao追加。noiseFrq=0(dataに0x1Fが書き込まれた)のときは | |
605 | + ;; 音量が通常の半分とした。(ファイヤープロレスリング3、 | |
606 | + ;; パックランド、桃太郎活劇、がんばれゴルフボーイズなど) | |
607 | + (cond | |
608 | + ((zerop (%psg-noise-freq psg-chan)) | |
609 | + (setf smp (* sample (%psg-out-volume-l psg-chan))) | |
610 | + ;; (1/2 + 1/4096 + (1/32768 + 1/32768)) | |
611 | + (incf sample-all-l (+ (ash smp -1) (ash smp -12) (ash smp -14))) | |
612 | + (setf smp (* sample (%psg-out-volume-r psg-chan))) | |
613 | + (incf sample-all-r (+ (ash smp -1) (ash smp -12) (ash smp -14)))) | |
614 | + | |
615 | + (t | |
616 | + ;; 通常 | |
617 | + | |
618 | + ;; Kitao更新。ノイズの音量を実機並みに調整(1 + 1/2048 + | |
619 | + ;; 1/16384 + 1/32768)。この"+1/32768"で絶妙(主観。大魔 | |
620 | + ;; 界村,ソルジャーブレイドなど)になる。v2.62更新 | |
621 | + (setf smp (* sample (%psg-out-volume-l psg-chan))) | |
622 | + (incf sample-all-l (+ (ash smp -11) (ash smp -14) (ash smp -15))) | |
623 | + | |
624 | + ;; Kitao更新。ノイズの音量を実機並みに調整 | |
625 | + (setf smp (* sample (%psg-out-volume-r psg-chan))) | |
626 | + (incf sample-all-r (+ (ash smp -11) (ash smp -14) (ash smp -15))))) | |
627 | + | |
628 | + (setf (%psg-phase psg-chan) | |
629 | + (coerce-to-uint32 (+ (%psg-phase psg-chan) (%psg-delta-noise-phase psg-chan))))) | |
630 | + | |
631 | + ((/= (%psg-delta-phase psg-chan) 0) | |
632 | + ;; Kitao更新。オーバーサンプリングしないようにした。 | |
633 | + (setf sample (aref (%psg-wave psg-chan) | |
634 | + (ash (%psg-phase psg-chan) -27))) | |
635 | + (when (< (%psg-freq psg-chan) 128) | |
636 | + ;; 低周波域の音量を制限。ブラッドギアのスタート時などで実機と同 | |
637 | + ;; 様の音に。ソルジャーブレイドなども実機に近くなった。v2.03 | |
638 | + (decf sample (ash sample -2))) | |
639 | + | |
640 | + (incf sample-all-l (* sample (%psg-out-volume-l psg-chan))) | |
641 | + (incf sample-all-r (* sample (%psg-out-volume-r psg-chan))) | |
642 | + | |
643 | + ;; Kitao更新。Lfoオンが有効になるようにし、Lfoの掛かり具合 | |
644 | + ;; を実機に近づけた。v1.59 | |
645 | + (cond | |
646 | + ((and (zerop i) (> (%ootake-lfo-ctrl emu) 0)) | |
647 | + ;; _LfoCtrlが1のときに0回シフト(そのまま)で、はにいいんざすかいが実機の音に近い。 | |
648 | + ;; _LfoCtrlが3のときに4回シフトで、フラッシュハイダースが実機の音に近い。 | |
649 | + (setf lfo (ash (aref (%psg-wave (aref (%ootake-psg emu) 1)) | |
650 | + (ash (%psg-phase (aref (%ootake-psg emu) 1)) -27)) | |
651 | + (ash (1- (%ootake-lfo-ctrl emu)) 1))) | |
652 | + | |
653 | + (setf (%psg-phase (aref (%ootake-psg emu) 0)) | |
654 | + (coerce-to-uint32 | |
655 | + (+ (%psg-phase (aref (%ootake-psg emu) 0)) | |
656 | + (coerce-to-uint32 | |
657 | + (truncate (+ (/ (* #.(* 65536.0d0 256.0d0 8.0d0) (%ootake-resample-rate emu)) | |
658 | + (the fixnum (+ (%psg-freq (aref (%ootake-psg emu) 0)) lfo))) | |
659 | + 0.5d0)))))) | |
660 | + | |
661 | + (setf (%psg-phase (aref (%ootake-psg emu) 1)) | |
662 | + (coerce-to-uint32 | |
663 | + (+ (%psg-phase (aref (%ootake-psg emu) 1)) | |
664 | + (coerce-to-uint32 | |
665 | + (truncate (+ (/ (* #.(* 65536.0d0 256.0d0 8.0d0) (%ootake-resample-rate emu)) | |
666 | + (the fixnum (+ (%psg-freq (aref (%ootake-psg emu) 1)) lfo))) | |
667 | + 0.5d0))))))) | |
668 | + | |
669 | + (t | |
670 | + (setf (%psg-phase psg-chan) | |
671 | + (coerce-to-uint32 (+ (%psg-phase psg-chan) (%psg-delta-phase psg-chan))))))))) | |
672 | + | |
673 | + (cond | |
674 | + ((> (aref (%ootake-dda-fade-out-l emu) i) 0) | |
675 | + (decf (aref (%ootake-dda-fade-out-l emu) i) 0)) | |
676 | + ((< (aref (%ootake-dda-fade-out-l emu) i) 0) | |
677 | + (incf (aref (%ootake-dda-fade-out-l emu) i) 0))) | |
678 | + | |
679 | + (cond | |
680 | + ((> (aref (%ootake-dda-fade-out-r emu) i) 0) | |
681 | + (decf (aref (%ootake-dda-fade-out-r emu) i) 0)) | |
682 | + ((< (aref (%ootake-dda-fade-out-r emu) i) 0) | |
683 | + (incf (aref (%ootake-dda-fade-out-r emu) i) 0))) | |
684 | + | |
685 | + (setf sample-all-l (coerce-to-int32 (+ sample-all-l (aref (%ootake-dda-fade-out-l emu) i)))) | |
686 | + (setf sample-all-r (coerce-to-int32 (+ sample-all-r (aref (%ootake-dda-fade-out-r emu) i))))) | |
687 | + | |
688 | + ;; Kitao更新。6ch合わさったところで、ボリューム調整してバッファに書き込む。 | |
689 | + (setf sample-all-l (truncate (* sample-all-l (%ootake-vol emu)))) | |
690 | + (setf sample-all-r (truncate (* sample-all-r (%ootake-vol emu)))) | |
691 | + (satou-chips::muffling | |
692 | + (setf (aref out-l j) sample-all-l) | |
693 | + (setf (aref out-r j) sample-all-r))))) | |
694 | + nil) |
@@ -0,0 +1,732 @@ | ||
1 | +(in-package :satou-chips) | |
2 | + | |
3 | +(defpackage :satou-chips/ym2151-mame | |
4 | + (:use :common-lisp :cl-sdm :satou :satou-chips) | |
5 | + (:nicknames :ym2151-mame) | |
6 | + (:shadowing-import-from :common-lisp #:write-string)) | |
7 | + | |
8 | +(shadowing-import 'satou::dlog :satou-chips/ym2151-mame) | |
9 | + | |
10 | +(in-package :satou-chips/ym2151-mame) | |
11 | + | |
12 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
13 | +;;; | |
14 | +;;; Tables and constants. | |
15 | +;;; | |
16 | + | |
17 | +(eval-when (:compile-toplevel :load-toplevel) | |
18 | + (defining-consts | |
19 | + (+sample-bits+ 16) | |
20 | + | |
21 | + (+freq-sh+ 16) ;; 16.16 fixed point (frequency calculations) | |
22 | + (+eg-sh+ 16) ;; 16.16 fixed point (envelope generator timing) | |
23 | + (+lfo-sh+ 10) ;; 20.10 fixed point (LFO calculations) | |
24 | + (+timer-sh+ 16) ;; 16.16 fixed point (timers calculations) | |
25 | + | |
26 | + (+freq-mask+ (1- (ash 1 +freq-sh+))) | |
27 | + | |
28 | + (+env-bits+ 10) | |
29 | + (+env-len+ (ash 1 +env-bits+)) | |
30 | + (+env-step+ (/ 128.0d0 +env-len+)) | |
31 | + | |
32 | + (+max-att-index+ (1- +env-len+)) ;; 1023 | |
33 | + (+min-att-index+ 0) | |
34 | + | |
35 | + (+eg-att+ 4) | |
36 | + (+eg-dec+ 3) | |
37 | + (+eg-sus+ 2) | |
38 | + (+eg-rel+ 1) | |
39 | + (+eg-off+ 0) | |
40 | + | |
41 | + (+sin-bits+ 10) | |
42 | + (+sin-len+ (ash 1 +sin-bits+)) | |
43 | + (+sin-mask+ (1- +sin-len+)) | |
44 | + | |
45 | + (+tl-res-len+ 256) ;; 8 bit addressing (real chip) | |
46 | + | |
47 | + (+final-sh+ 0) | |
48 | + (+max-out+ 32767) | |
49 | + (+min-out+ -32768) | |
50 | + | |
51 | + (+tl-tab-len+ (* 13 2 +tl-res-len+)) | |
52 | + (+env-quiet+ (ash +tl-tab-len+ -3)) | |
53 | + | |
54 | + (+rate-steps+ 8) | |
55 | + | |
56 | + (+eg-inc+ | |
57 | + (new-array-with | |
58 | + t/uint8 | |
59 | + (vector | |
60 | + ;; cycle: 0 1 2 3 4 5 6 7 | |
61 | + 0 1 0 1 0 1 0 1 ;; rates 00..11 0 (increment by 0 or 1) | |
62 | + 0 1 0 1 1 1 0 1 ;; rates 00..11 1 | |
63 | + 0 1 1 1 0 1 1 1 ;; rates 00..11 2 | |
64 | + 0 1 1 1 1 1 1 1 ;; rates 00..11 3 | |
65 | + | |
66 | + 1 1 1 1 1 1 1 1 ;; rate 12 0 (increment by 1) | |
67 | + 1 1 1 2 1 1 1 2 ;; rate 12 1 | |
68 | + 1 2 1 2 1 2 1 2 ;; rate 12 2 | |
69 | + 1 2 2 2 1 2 2 2 ;; rate 12 3 | |
70 | + | |
71 | + 2 2 2 2 2 2 2 2 ;; rate 13 0 (increment by 2) | |
72 | + 2 2 2 4 2 2 2 4 ;; rate 13 1 | |
73 | + 2 4 2 4 2 4 2 4 ;; rate 13 2 | |
74 | + 2 4 4 4 2 4 4 4 ;; rate 13 3 | |
75 | + | |
76 | + 4 4 4 4 4 4 4 4 ;; rate 14 0 (increment by 4) | |
77 | + 4 4 4 8 4 4 4 8 ;; rate 14 1 | |
78 | + 4 8 4 8 4 8 4 8 ;; rate 14 2 | |
79 | + 4 8 8 8 4 8 8 8 ;; rate 14 3 | |
80 | + | |
81 | + 8 8 8 8 8 8 8 8 ;; rates 15 0 15 1 15 2 15 3 (increment by 8) | |
82 | + 16 16 16 16 16 16 16 16 ;; rates 15 2 15 3 for attack | |
83 | + 0 0 0 0 0 0 0 0))) ;; infinity rates for attack and decay(s) | |
84 | + | |
85 | + ;; Envelope Generator rates (32 + 64 rates + 32 RKS). | |
86 | + (+eg-rate-select+ | |
87 | + (new-array-with | |
88 | + t/uint8 | |
89 | + (vector | |
90 | + ;; 32 fake (infinite time) rates | |
91 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
92 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
93 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
94 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
95 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
96 | + (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) (* 18 +rate-steps+) | |
97 | + (* 18 +rate-steps+) (* 18 +rate-steps+) | |
98 | + | |
99 | + ;; rates 00-11 | |
100 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
101 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
102 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
103 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
104 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
105 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
106 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
107 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
108 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
109 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
110 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
111 | + (* 0 +rate-steps+) (* 1 +rate-steps+) (* 2 +rate-steps+) (* 3 +rate-steps+) | |
112 | + | |
113 | + ;; rate 12 | |
114 | + (* 4 +rate-steps+) (* 5 +rate-steps+) (* 6 +rate-steps+) (* 7 +rate-steps+) | |
115 | + | |
116 | + ;; rate 13 | |
117 | + (* 8 +rate-steps+) (* 9 +rate-steps+) (* 10 +rate-steps+) (* 11 +rate-steps+) | |
118 | + | |
119 | + ;; rate 14 | |
120 | + (* 12 +rate-steps+) (* 13 +rate-steps+) (* 14 +rate-steps+) (* 15 +rate-steps+) | |
121 | + | |
122 | + ;; rate 15 | |
123 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
124 | + | |
125 | + ;; 32 fake rates (same as 15 3) | |
126 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
127 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
128 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
129 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
130 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
131 | + (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) (* 16 +rate-steps+) | |
132 | + (* 16 +rate-steps+) (* 16 +rate-steps+)))) | |
133 | + | |
134 | + (+eg-rate-shift+ | |
135 | + (new-array-with | |
136 | + t/uint8 | |
137 | + (vector | |
138 | + ;; 32 infinite time rates | |
139 | + 0 0 0 0 0 0 0 0 | |
140 | + 0 0 0 0 0 0 0 0 | |
141 | + 0 0 0 0 0 0 0 0 | |
142 | + 0 0 0 0 0 0 0 0 | |
143 | + | |
144 | + ;; rates 00-11 | |
145 | + 11 11 11 11 | |
146 | + 10 10 10 10 | |
147 | + 9 9 9 9 | |
148 | + 8 8 8 8 | |
149 | + 7 7 7 7 | |
150 | + 6 6 6 6 | |
151 | + 5 5 5 5 | |
152 | + 4 4 4 4 | |
153 | + 3 3 3 3 | |
154 | + 2 2 2 2 | |
155 | + 1 1 1 1 | |
156 | + 0 0 0 0 | |
157 | + | |
158 | + ;; rate 12 | |
159 | + 0 0 0 0 | |
160 | + | |
161 | + ;; rate 13 | |
162 | + 0 0 0 0 | |
163 | + | |
164 | + ;; rate 14 | |
165 | + 0 0 0 0 | |
166 | + | |
167 | + ;; rate 15 | |
168 | + 0 0 0 0 | |
169 | + | |
170 | + ;; 32 dummy rates (same as 15 3) | |
171 | + 0 0 0 0 0 0 0 0 | |
172 | + 0 0 0 0 0 0 0 0 | |
173 | + 0 0 0 0 0 0 0 0 | |
174 | + 0 0 0 0 0 0 0 0))) | |
175 | + | |
176 | + ;; DT2 defines offset in cents from base note | |
177 | + ;; | |
178 | + ;; This table defines offset in frequency-deltas table. | |
179 | + ;; User's Manual page 22 | |
180 | + ;; | |
181 | + ;; Values below were calculated using formula: value = orig.val / 1.5625 | |
182 | + ;; | |
183 | + ;; DT2=0 DT2=1 DT2=2 DT2=3 | |
184 | + ;; 0 600 781 950 | |
185 | + (+dt2-tab+ (new-array-with t/uint32 #(0 384 500 608))) | |
186 | + | |
187 | + ;; DT1 defines offset in Hertz from base note. This table is converted while | |
188 | + ;; initialization... Detune table shown in YM2151 User's Manual is wrong | |
189 | + ;; (verified on the real chip). | |
190 | + (+dt1-tab+ | |
191 | + (new-array-with | |
192 | + t/uint8 | |
193 | + (vector | |
194 | + ;; DT1=0 | |
195 | + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | |
196 | + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | |
197 | + | |
198 | + ;; DT1=1 | |
199 | + 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 | |
200 | + 2 3 3 3 4 4 4 5 5 6 6 7 8 8 8 8 | |
201 | + | |
202 | + ;; DT1=2 | |
203 | + 1 1 1 1 2 2 2 2 2 3 3 3 4 4 4 5 | |
204 | + 5 6 6 7 8 8 9 10 11 12 13 14 16 16 16 16 | |
205 | + | |
206 | + ;; DT1=3 | |
207 | + 2 2 2 2 2 3 3 3 4 4 4 5 5 6 6 7 | |
208 | + 8 8 9 10 11 12 13 14 16 17 19 20 22 22 22 22))) | |
209 | + | |
210 | + (+phase-inc-rom+ | |
211 | + (new-array-with | |
212 | + t/uint16 | |
213 | + (vector | |
214 | + 1299 1300 1301 1302 1303 1304 1305 1306 | |
215 | + 1308 1309 1310 1311 1313 1314 1315 1316 | |
216 | + 1318 1319 1320 1321 1322 1323 1324 1325 | |
217 | + 1327 1328 1329 1330 1332 1333 1334 1335 | |
218 | + 1337 1338 1339 1340 1341 1342 1343 1344 | |
219 | + 1346 1347 1348 1349 1351 1352 1353 1354 | |
220 | + 1356 1357 1358 1359 1361 1362 1363 1364 | |
221 | + 1366 1367 1368 1369 1371 1372 1373 1374 | |
222 | + 1376 1377 1378 1379 1381 1382 1383 1384 | |
223 | + 1386 1387 1388 1389 1391 1392 1393 1394 | |
224 | + 1396 1397 1398 1399 1401 1402 1403 1404 | |
225 | + 1406 1407 1408 1409 1411 1412 1413 1414 | |
226 | + 1416 1417 1418 1419 1421 1422 1423 1424 | |
227 | + 1426 1427 1429 1430 1431 1432 1434 1435 | |
228 | + 1437 1438 1439 1440 1442 1443 1444 1445 | |
229 | + 1447 1448 1449 1450 1452 1453 1454 1455 | |
230 | + 1458 1459 1460 1461 1463 1464 1465 1466 | |
231 | + 1468 1469 1471 1472 1473 1474 1476 1477 | |
232 | + 1479 1480 1481 1482 1484 1485 1486 1487 | |
233 | + 1489 1490 1492 1493 1494 1495 1497 1498 | |
234 | + 1501 1502 1503 1504 1506 1507 1509 1510 | |
235 | + 1512 1513 1514 1515 1517 1518 1520 1521 | |
236 | + 1523 1524 1525 1526 1528 1529 1531 1532 | |
237 | + 1534 1535 1536 1537 1539 1540 1542 1543 | |
238 | + 1545 1546 1547 1548 1550 1551 1553 1554 | |
239 | + 1556 1557 1558 1559 1561 1562 1564 1565 | |
240 | + 1567 1568 1569 1570 1572 1573 1575 1576 | |
241 | + 1578 1579 1580 1581 1583 1584 1586 1587 | |
242 | + 1590 1591 1592 1593 1595 1596 1598 1599 | |
243 | + 1601 1602 1604 1605 1607 1608 1609 1610 | |
244 | + 1613 1614 1615 1616 1618 1619 1621 1622 | |
245 | + 1624 1625 1627 1628 1630 1631 1632 1633 | |
246 | + 1637 1638 1639 1640 1642 1643 1645 1646 | |
247 | + 1648 1649 1651 1652 1654 1655 1656 1657 | |
248 | + 1660 1661 1663 1664 1666 1667 1669 1670 | |
249 | + 1672 1673 1675 1676 1678 1679 1681 1682 | |
250 | + 1685 1686 1688 1689 1691 1692 1694 1695 | |
251 | + 1697 1698 1700 1701 1703 1704 1706 1707 | |
252 | + 1709 1710 1712 1713 1715 1716 1718 1719 | |
253 | + 1721 1722 1724 1725 1727 1728 1730 1731 | |
254 | + 1734 1735 1737 1738 1740 1741 1743 1744 | |
255 | + 1746 1748 1749 1751 1752 1754 1755 1757 | |
256 | + 1759 1760 1762 1763 1765 1766 1768 1769 | |
257 | + 1771 1773 1774 1776 1777 1779 1780 1782 | |
258 | + 1785 1786 1788 1789 1791 1793 1794 1796 | |
259 | + 1798 1799 1801 1802 1804 1806 1807 1809 | |
260 | + 1811 1812 1814 1815 1817 1819 1820 1822 | |
261 | + 1824 1825 1827 1828 1830 1832 1833 1835 | |
262 | + 1837 1838 1840 1841 1843 1845 1846 1848 | |
263 | + 1850 1851 1853 1854 1856 1858 1859 1861 | |
264 | + 1864 1865 1867 1868 1870 1872 1873 1875 | |
265 | + 1877 1879 1880 1882 1884 1885 1887 1888 | |
266 | + 1891 1892 1894 1895 1897 1899 1900 1902 | |
267 | + 1904 1906 1907 1909 1911 1912 1914 1915 | |
268 | + 1918 1919 1921 1923 1925 1926 1928 1930 | |
269 | + 1932 1933 1935 1937 1939 1940 1942 1944 | |
270 | + 1946 1947 1949 1951 1953 1954 1956 1958 | |
271 | + 1960 1961 1963 1965 1967 1968 1970 1972 | |
272 | + 1975 1976 1978 1980 1982 1983 1985 1987 | |
273 | + 1989 1990 1992 1994 1996 1997 1999 2001 | |
274 | + 2003 2004 2006 2008 2010 2011 2013 2015 | |
275 | + 2017 2019 2021 2022 2024 2026 2028 2029 | |
276 | + 2032 2033 2035 2037 2039 2041 2043 2044 | |
277 | + 2047 2048 2050 2052 2054 2056 2058 2059 | |
278 | + 2062 2063 2065 2067 2069 2071 2073 2074 | |
279 | + 2077 2078 2080 2082 2084 2086 2088 2089 | |
280 | + 2092 2093 2095 2097 2099 2101 2103 2104 | |
281 | + 2107 2108 2110 2112 2114 2116 2118 2119 | |
282 | + 2122 2123 2125 2127 2129 2131 2133 2134 | |
283 | + 2137 2139 2141 2142 2145 2146 2148 2150 | |
284 | + 2153 2154 2156 2158 2160 2162 2164 2165 | |
285 | + 2168 2170 2172 2173 2176 2177 2179 2181 | |
286 | + 2185 2186 2188 2190 2192 2194 2196 2197 | |
287 | + 2200 2202 2204 2205 2208 2209 2211 2213 | |
288 | + 2216 2218 2220 2222 2223 2226 2227 2230 | |
289 | + 2232 2234 2236 2238 2239 2242 2243 2246 | |
290 | + 2249 2251 2253 2255 2256 2259 2260 2263 | |
291 | + 2265 2267 2269 2271 2272 2275 2276 2279 | |
292 | + 2281 2283 2285 2287 2288 2291 2292 2295 | |
293 | + 2297 2299 2301 2303 2304 2307 2308 2311 | |
294 | + 2315 2317 2319 2321 2322 2325 2326 2329 | |
295 | + 2331 2333 2335 2337 2338 2341 2342 2345 | |
296 | + 2348 2350 2352 2354 2355 2358 2359 2362 | |
297 | + 2364 2366 2368 2370 2371 2374 2375 2378 | |
298 | + 2382 2384 2386 2388 2389 2392 2393 2396 | |
299 | + 2398 2400 2402 2404 2407 2410 2411 2414 | |
300 | + 2417 2419 2421 2423 2424 2427 2428 2431 | |
301 | + 2433 2435 2437 2439 2442 2445 2446 2449 | |
302 | + 2452 2454 2456 2458 2459 2462 2463 2466 | |
303 | + 2468 2470 2472 2474 2477 2480 2481 2484 | |
304 | + 2488 2490 2492 2494 2495 2498 2499 2502 | |
305 | + 2504 2506 2508 2510 2513 2516 2517 2520 | |
306 | + 2524 2526 2528 2530 2531 2534 2535 2538 | |
307 | + 2540 2542 2544 2546 2549 2552 2553 2556 | |
308 | + 2561 2563 2565 2567 2568 2571 2572 2575 | |
309 | + 2577 2579 2581 2583 2586 2589 2590 2593))) | |
310 | + | |
311 | + (+lfo-noise-waveform+ | |
312 | + (new-array-with | |
313 | + t/uint8 | |
314 | + (vector | |
315 | + #xFF #xEE #xD3 #x80 #x58 #xDA #x7F #x94 #x9E | |
316 | + #xE3 #xFA #x00 #x4D #xFA #xFF #x6A #x7A #xDE | |
317 | + #x49 #xF6 #x00 #x33 #xBB #x63 #x91 #x60 #x51 | |
318 | + #xFF #x00 #xD8 #x7F #xDE #xDC #x73 #x21 #x85 | |
319 | + #xB2 #x9C #x5D #x24 #xCD #x91 #x9E #x76 #x7F | |
320 | + #x20 #xFB #xF3 #x00 #xA6 #x3E #x42 #x27 #x69 | |
321 | + #xAE #x33 #x45 #x44 #x11 #x41 #x72 #x73 #xDF | |
322 | + #xA2 | |
323 | + | |
324 | + #x32 #xBD #x7E #xA8 #x13 #xEB #xD3 #x15 #xDD | |
325 | + #xFB #xC9 #x9D #x61 #x2F #xBE #x9D #x23 #x65 | |
326 | + #x51 #x6A #x84 #xF9 #xC9 #xD7 #x23 #xBF #x65 | |
327 | + #x19 #xDC #x03 #xF3 #x24 #x33 #xB6 #x1E #x57 | |
328 | + #x5C #xAC #x25 #x89 #x4D #xC5 #x9C #x99 #x15 | |
329 | + #x07 #xCF #xBA #xC5 #x9B #x15 #x4D #x8D #x2A | |
330 | + #x1E #x1F #xEA #x2B #x2F #x64 #xA9 #x50 #x3D | |
331 | + #xAB | |
332 | + | |
333 | + #x50 #x77 #xE9 #xC0 #xAC #x6D #x3F #xCA #xCF | |
334 | + #x71 #x7D #x80 #xA6 #xFD #xFF #xB5 #xBD #x6F | |
335 | + #x24 #x7B #x00 #x99 #x5D #xB1 #x48 #xB0 #x28 | |
336 | + #x7F #x80 #xEC #xBF #x6F #x6E #x39 #x90 #x42 | |
337 | + #xD9 #x4E #x2E #x12 #x66 #xC8 #xCF #x3B #x3F | |
338 | + #x10 #x7D #x79 #x00 #xD3 #x1F #x21 #x93 #x34 | |
339 | + #xD7 #x19 #x22 #xA2 #x08 #x20 #xB9 #xB9 #xEF | |
340 | + #x51 | |
341 | + | |
342 | + #x99 #xDE #xBF #xD4 #x09 #x75 #xE9 #x8A #xEE | |
343 | + #xFD #xE4 #x4E #x30 #x17 #xDF #xCE #x11 #xB2 | |
344 | + #x28 #x35 #xC2 #x7C #x64 #xEB #x91 #x5F #x32 | |
345 | + #x0C #x6E #x00 #xF9 #x92 #x19 #xDB #x8F #xAB | |
346 | + #xAE #xD6 #x12 #xC4 #x26 #x62 #xCE #xCC #x0A | |
347 | + #x03 #xE7 #xDD #xE2 #x4D #x8A #xA6 #x46 #x95 | |
348 | + #x0F #x8F #xF5 #x15 #x97 #x32 #xD4 #x28 #x1E | |
349 | + #x55))))) | |
350 | + | |
351 | +(declaim (type (simple-array t/uint32 (#.+tl-tab-len+)) *tl-tab*)) | |
352 | +(defparameter *tl-tab* (new-array +tl-tab-len+ t/uint32)) | |
353 | + | |
354 | +;; Sine wave table in decibel scale. | |
355 | +(declaim (type (simple-array t/uint32 (#.+sin-len+)) *sin-tab*)) | |
356 | +(defparameter *sin-tab* (new-array +sin-len+ t/uint32)) | |
357 | + | |
358 | +;; Translate from D1L to volume index (16 D1L levels) | |
359 | +(declaim (type (simple-array t/uint32 (16)) *d1l-tab*)) | |
360 | +(defparameter *d1l-tab* (new-array 16 t/uint32)) | |
361 | + | |
362 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
363 | +;;; | |
364 | +;;; Operator Struct | |
365 | +;;; | |
366 | + | |
367 | +(defstruct (operator (:conc-name %op-)) | |
368 | + ;; Accumulated operator phase. | |
369 | + (phase 0 :type t/uint32) | |
370 | + | |
371 | + ;; Operator frequency count. | |
372 | + (freq 0 :type t/uint32) | |
373 | + | |
374 | + ;; Current detune 1 phase increment/decrement value. | |
375 | + (dt1 0 :type t/int32) | |
376 | + | |
377 | + ;; Frequency count multiply. | |
378 | + (mul 0 :type t/uint32) | |
379 | + | |
380 | + ;; DT1 index multiplied by 32. | |
381 | + (dt1i 0 :type t/uint32) | |
382 | + | |
383 | + ;; Current detune 2 value. | |
384 | + (dt2 0 :type t/uint32) | |
385 | + | |
386 | + ;; Operator output "direction". | |
387 | + (connect nil :type (or null sb-alien:alien)) | |
388 | + | |
389 | + ;;; | |
390 | + ;;; M1 (operator 0) data. Other operators don't use these. | |
391 | + ;;; | |
392 | + | |
393 | + ;; Where to put the delayed sample. Only M1 (operator 0) is filled with this | |
394 | + ;; data. | |
395 | + (mem-connect nil :type (or null sb-alien:alien)) | |
396 | + | |
397 | + ;; Delayed sample value. | |
398 | + (mem-value 0 :type t/int32) | |
399 | + | |
400 | + ;;; | |
401 | + ;;; Channel-specific data. Note: Each operator number 0 contains channel | |
402 | + ;;; specific data. | |
403 | + ;;; | |
404 | + | |
405 | + ;; Feedback shift value for operator 0 in each channel. | |
406 | + (fb-shift 0 :type t/uint32) | |
407 | + | |
408 | + ;; Current operator feedback value (only used by operator 0). | |
409 | + (fb-out-current 0 :type t/int32) | |
410 | + | |
411 | + ;; Previous operator feedback value (only used by operator 0). | |
412 | + (fb-out-prev 0 :type t/int32) | |
413 | + | |
414 | + ;; Channel KC (copied to all operators). | |
415 | + (kc 0 :type t/uint32) | |
416 | + (kci 0 :type t/uint32) ;; For speedup | |
417 | + | |
418 | + ;; Channel PMS. | |
419 | + (pms 0 :type t/uint32) | |
420 | + | |
421 | + ;; Channel AMS. | |
422 | + (ams 0 :type t/uint32) | |
423 | + | |
424 | + ;;; | |
425 | + ;;; End of channel-specific data. | |
426 | + ;;; | |
427 | + | |
428 | + ;; LFO amplitude modulation enable mask. | |
429 | + (am-mask 0 :type t/uint32) | |
430 | + | |
431 | + ;; Envelope state: 4 = attack(AR), 3 =decay(D1R), 2 = sustain(D2R), 1 = release(RR), 0 = off. | |
432 | + (state 0 :type t/uint32) | |
433 | + | |
434 | + ;; Attack state. | |
435 | + (eg-sh-ar 0 :type t/uint8) | |
436 | + | |
437 | + ;; Attack state. | |
438 | + (eg-sel-ar 0 :type t/uint8) | |
439 | + | |
440 | + ;; Total attenuation level. | |
441 | + (tl 0 :type t/uint32) | |
442 | + | |
443 | + ;; Current envelope attenuation level. | |
444 | + (volume +max-att-index+ :type t/int32) | |
445 | + | |
446 | + ;; Decay state. | |
447 | + (eg-sh-d1r 0 :type t/uint8) | |
448 | + | |
449 | + ;; Decay state. | |
450 | + (eg-sel-d1r 0 :type t/uint8) | |
451 | + | |
452 | + ;; Envelope switches to sustain state after reaching this level. | |
453 | + (d1l 0 :type t/uint32) | |
454 | + | |
455 | + ;; Sustain state. | |
456 | + (eg-sh-d2r 0 :type t/uint8) | |
457 | + | |
458 | + ;; Sustain state | |
459 | + (eg-sel-d2r 0 :type t/uint8) | |
460 | + | |
461 | + ;; Release state | |
462 | + (eg-sh-rr 0 :type t/uint8) | |
463 | + | |
464 | + ;; Release state | |
465 | + (eg-sel-rr 0 :type t/uint8) | |
466 | + | |
467 | + ;; 0 = last key was KEY OFF, 1 = last key was KEY ON. | |
468 | + (key 0 :type t/uint32) | |
469 | + | |
470 | + ;; Key scale. | |
471 | + (ks 0 :type t/uint32) | |
472 | + | |
473 | + ;; Attack rate. | |
474 | + (ar 0 :type t/uint32) | |
475 | + | |
476 | + ;; Decay rate. | |
477 | + (d1r 0 :type t/uint32) | |
478 | + | |
479 | + ;; Sustain rate. | |
480 | + (d2r 0 :type t/uint32) | |
481 | + | |
482 | + ;; Release rate. | |
483 | + (rr 0 :type t/uint32)) | |
484 | + | |
485 | +(define-typed-fn %op-key-on ((operator op) (fixnum key-set eg-count)) | |
486 | + (null t) | |
487 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0) | |
488 | + #+(or satou-debug satou-verbose-debug) (safety 1))) | |
489 | + (when (zerop (%op-key op)) | |
490 | + (setf (%op-phase op) 0) ;; Clear phase | |
491 | + (setf (%op-state op) +eg-att+) ;; Key On = Attack | |
492 | + (incf (%op-volume op) (ash (* (lognot (%op-volume op)) | |
493 | + (aref +eg-inc+ (+ (%op-eg-sel-ar op) | |
494 | + (logand (ash eg-count (- (%op-eg-sh-ar op))) 7)))) | |
495 | + -4)) | |
496 | + | |
497 | + (when (<= (%op-volume op) +min-att-index+) | |
498 | + (setf (%op-volume op) +min-att-index+) | |
499 | + (setf (%op-state op) +eg-dec+))) | |
500 | + | |
501 | + (logiorf (%op-key op) key-set) | |
502 | + nil) | |
503 | + | |
504 | +(define-typed-fn %op-key-off ((operator op) (fixnum key-clr)) | |
505 | + (null t) | |
506 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0) | |
507 | + #+(or satou-debug satou-verbose-debug) (safety 1))) | |
508 | + (unless (zerop (%op-key op)) | |
509 | + (logandf (%op-key op) key-clr) | |
510 | + (when (and (zerop (%op-key op)) | |
511 | + (> (%op-state op) +eg-rel+)) ;; Key Off = Release | |
512 | + (setf (%op-state op) +eg-rel+))) | |
513 | + nil) | |
514 | + | |
515 | +(defstruct (emu-ym2151 (:conc-name %emu-) | |
516 | + (:constructor %make-emu-ym2151)) | |
517 | + | |
518 | + ;; The 32 operators. | |
519 | + (operators (make-array 32 :element-type 'operator | |
520 | + :initial-contents (loop repeat 32 collect (make-operator))) | |
521 | + :type (simple-array operator (32))) | |
522 | + | |
523 | + ;; Channel output masks (#xFFFFFFFF = enable). | |
524 | + (pan (new-array 16 t/uint32) :type (simple-array t/uint32 (16))) | |
525 | + | |
526 | + ;; Used for muting. | |
527 | + (muted (new-array 8 t/uint8) :type (simple-array t/uint8 (8))) | |
528 | + | |
529 | + ;; Global envelope generator counter. | |
530 | + (eg-count 0 :type t/uint32) | |
531 | + | |
532 | + ;; Global envelope generator counter works at frequency = chipclock / 64 / 3 | |
533 | + (eg-timer 0 :type t/uint32) | |
534 | + | |
535 | + ;; Step for egTimer | |
536 | + (eg-timer-add 0 :type t/uint32) | |
537 | + | |
538 | + ;; Envelope generator timer overflows every 3 samples (on real chips). | |
539 | + (eg-timer-overflow 0 :type t/uint32) | |
540 | + | |
541 | + ;; Accumulated LFO phase (0 to 255) | |
542 | + (lfo-phase 0 :type t/uint32) | |
543 | + | |
544 | + ;; LFO timer | |
545 | + (lfo-timer 0 :type t/uint32) | |
546 | + | |
547 | + ;; Step of lfoTimer | |
548 | + (lfo-timer-add 0 :type t/uint32) | |
549 | + | |
550 | + ;; LFO generates new output when lfoTimer reaches this value. | |
551 | + (lfo-overflow 0 :type t/uint32) | |
552 | + | |
553 | + ;; LFO phase increment counter. | |
554 | + (lfo-counter 0 :type t/uint32) | |
555 | + | |
556 | + ;; Step for lfoCounter | |
557 | + (lfo-counter-add 0 :type t/uint32) | |
558 | + | |
559 | + ;; LFO waveform select (0 = saw, 1 = square, 2 = triangle, 3 = random noise). | |
560 | + (lfo-wsel 0 :type t/uint8) | |
561 | + | |
562 | + ;; LFO amplitude modulation depth. | |
563 | + (amd 0 :type t/uint8) | |
564 | + | |
565 | + ;; LFO phase modulation depth. | |
566 | + (pmd 0 :type t/int8) | |
567 | + | |
568 | + ;; LFO current AM output. | |
569 | + (lfa 0 :type t/uint32) | |
570 | + | |
571 | + ;; LFO current PM output. | |
572 | + (lfp 0 :type t/int32) | |
573 | + | |
574 | + ;; TEST register. | |
575 | + (test 0 :type t/uint8) | |
576 | + | |
577 | + ;; Output control pins (bit1 = CT2, bit0 = CT1). | |
578 | + (ct 0 :type t/uint8) | |
579 | + | |
580 | + ;; Noise enable/period register (bit 7 = noise enable, bits 4-0 = noise period). | |
581 | + (noise 0 :type t/uint32) | |
582 | + | |
583 | + ;; 17 bit noise shift register. | |
584 | + (noise-rng 0 :type t/uint32) | |
585 | + | |
586 | + ;; Current noise 'phase'. | |
587 | + (noise-phase 0 :type t/uint32) | |
588 | + | |
589 | + ;; Current noise period. | |
590 | + (noise-f 0 :type t/uint32) | |
591 | + | |
592 | + ;; CSM key on/key off sequence request. | |
593 | + (csm-req 0 :type t/uint32) | |
594 | + | |
595 | + ;; IRQ enable for timer B (bit 3) and timer A (bit 2). Bit 7 = CSM mode | |
596 | + ;; (key on to all slots, everytime timer A overflows). | |
597 | + (irq-enable 0 :type t/uint32) | |
598 | + | |
599 | + ;; Chip status (BUSY, IRQ Flags, etc.) | |
600 | + (status 0 :type t/uint32) | |
601 | + | |
602 | + ;; Channels connections. | |
603 | + (connect (new-array 8 t/uint8) :type (simple-array t/uint8 (8))) | |
604 | + | |
605 | + ;; Timer A enable (0 = disabled) | |
606 | + (tim-a 0 :type t/uint8) | |
607 | + | |
608 | + ;; Timer B enable (0 = disabled) | |
609 | + (tim-b 0 :type t/uint8) | |
610 | + | |
611 | + ;; Current value of timer A. | |
612 | + (tim-a-val 0 :type t/int32) | |
613 | + | |
614 | + ;; Current value of timer B. | |
615 | + (tim-b-val 0 :type t/int32) | |
616 | + | |
617 | + ;; Timer A deltas | |
618 | + (tim-a-tab (new-array 1024 t/uint32) :type (simple-array t/uint32 (1024))) | |
619 | + | |
620 | + ;; Timer B deltas | |
621 | + (tim-b-tab (new-array 256 t/uint32) :type (simple-array t/uint32 (256))) | |
622 | + | |
623 | + ;; Timer A index. | |
624 | + (timer-a-index 0 :type t/uint32) | |
625 | + | |
626 | + ;; Timer B index. | |
627 | + (timer-b-index 0 :type t/uint32) | |
628 | + | |
629 | + ;; Timer A previous index. | |
630 | + (timer-a-index-old 0 :type t/uint32) | |
631 | + | |
632 | + ;; Timer B previous index. | |
633 | + (timer-b-index-old 0 :type t/uint32) | |
634 | + | |
635 | + ;; Frequency-deltas to get the closest frequency possible. | |
636 | + ;; There are 11 octaves because of DT2 (max 950 cents over base frequency) | |
637 | + ;; and LFO phase modulation (max 800 cents below AND over base frequency) | |
638 | + ;; Summary: octave explanation | |
639 | + ;; 0 note code - LFO PM | |
640 | + ;; 1 note code | |
641 | + ;; 2 note code | |
642 | + ;; 3 note code | |
643 | + ;; 4 note code | |
644 | + ;; 5 note code | |
645 | + ;; 6 note code | |
646 | + ;; 7 note code | |
647 | + ;; 8 note code | |
648 | + ;; 9 note code + DT2 + LFO PM | |
649 | + ;; 10 note code + DT2 + LFO PM | |
650 | + ;; | |
651 | + ;; 11 octaves, 768 cents per octave | |
652 | + (freq (new-array #.(* 11 768) t/uint32) :type (simple-array t/uint32 (#.(* 11 768)))) | |
653 | + | |
654 | + ;; Frequency deltas for DT1. These deltas alter operator frequency after | |
655 | + ;; it has been taken from the frequency-deltas table. | |
656 | + (dt1-freq (new-array #.(* 8 32) t/int32) :type (simple-array t/int32 (#.(* 8 32)))) | |
657 | + | |
658 | + ;; 17-bit noise generator periods. | |
659 | + (noise-tab (new-array 32 t/uint32) :type (simple-array t/uint32 (32))) | |
660 | + | |
661 | + ;; Chip clock in hertz. | |
662 | + (clock 0 :type t/uint32) | |
663 | + | |
664 | + ;; Chip sampling frequency in hertz. | |
665 | + (samp-freq 0 :type t/uint32) | |
666 | + | |
667 | + ;; | |
668 | + ;; These are for speedup purposes only. | |
669 | + ;; | |
670 | + | |
671 | + (chan-out (new-array 8 t/int32) :type (simple-array t/int32 (8))) | |
672 | + (m2 nil :type (or null sb-alien:alien)) | |
673 | + (c1 nil :type (or null sb-alien:alien)) | |
674 | + (c2 nil :type (or null sb-alien:alien)) | |
675 | + (mem nil :type (or null sb-alien:alien))) | |
676 | + | |
677 | +(define-typed-fn make-emu-ym2151 ((t/uint32 clock rate)) | |
678 | + (emu-ym2151) | |
679 | + (let (m2 c1 c2 mem) | |
680 | + (handler-bind | |
681 | + ((error (lambda (err) | |
682 | + ;; Handle any errors that occur during the constructor. We'll | |
683 | + ;; need to ensure the pointers are deallocated here. We don't | |
684 | + ;; handle the error itself, so it's still passed up the stack. | |
685 | + (sdm-log:error! "Error making EMU-YM2151, cleaning up memory: ~a" (type-of err)) | |
686 | + (when (and (boundp m2) | |
687 | + (not (sb-alien:null-alien m2))) | |
688 | + (sb-alien:free-alien m2)) | |
689 | + (when (and (boundp c1) | |
690 | + (not (sb-alien:null-alien c1))) | |
691 | + (sb-alien:free-alien c1)) | |
692 | + (when (and (boundp c2) | |
693 | + (not (sb-alien:null-alien c2))) | |
694 | + (sb-alien:free-alien c2)) | |
695 | + (when (and (boundp mem) | |
696 | + (not (sb-alien:null-alien mem))) | |
697 | + (sb-alien:free-alien mem))))) | |
698 | + | |
699 | + ;; Allocate the pointers | |
700 | + (setf m2 (sb-alien:make-alien (sb-alien:* (integer 32)) 1)) | |
701 | + (setf c1 (sb-alien:make-alien (sb-alien:* (integer 32)) 1)) | |
702 | + (setf c2 (sb-alien:make-alien (sb-alien:* (integer 32)) 1)) | |
703 | + (setf mem (sb-alien:make-alien (sb-alien:* (integer 32)) 1)) | |
704 | + | |
705 | + ;; Create the emulator and initialize some values. | |
706 | + (let ((ret (%make-emu-ym2151 :m2 m2 :c1 c1 :c2 c2 :mem mem :clock clock))) | |
707 | + (%emu-init-tables ret) | |
708 | + (setf (%emu-samp-freq ret) (if (/= rate 0) rate 44100)) | |
709 | + (%emu-init-chip-tables ret) | |
710 | + | |
711 | + (setf (%emu-lfo-timer-add ret) | |
712 | + (coerce-to-uint32 (truncate (/ (* (ash 1 +lfo-sh+) (/ clock 64.0d0)) (%emu-samp-freq ret))))) | |
713 | + | |
714 | + (setf (%emu-eg-timer-add ret) | |
715 | + (coerce-to-uint32 (truncate (/ (* (ash 1 +eg-sh+) (/ clock 64.0d0)) (%emu-samp-freq ret))))) | |
716 | + | |
717 | + (setf (%emu-eg-timer-overflow ret) (coerce-to-uint32 (* 3 (ash 1 +eg-sh+)))) | |
718 | + | |
719 | + (%emu-unmute-all ret) | |
720 | + | |
721 | + ;; Setup a finalizer, then return | |
722 | + (sb-ext:finalize ret #'(lambda () | |
723 | + #+satou-verbose-debug (dlog "Running EMU-YM2151 finalizer") | |
724 | + (when (not (sb-alien:null-alien m2)) | |
725 | + (sb-alien:free-alien m2)) | |
726 | + (when (not (sb-alien:null-alien c1)) | |
727 | + (sb-alien:free-alien c1)) | |
728 | + (when (not (sb-alien:null-alien c2)) | |
729 | + (sb-alien:free-alien c2)) | |
730 | + (when (not (sb-alien:null-alien mem)) | |
731 | + (sb-alien:free-alien mem)))) | |
732 | + ret)))) |
@@ -0,0 +1,15 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. |
@@ -0,0 +1,231 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +(defining-consts | |
19 | + (+default-sample-rate+ 44100 :documentation "The default output sample rate.") | |
20 | + (+min-sample-rate+ 8000 :documentation "The minimum supported output sample rate.") | |
21 | + (+volume-modifier-wrap+ #xC0) | |
22 | + (+vgm-sample-rate+ 44100 :documentation "The rate that commands are sampled in VGM files.")) | |
23 | + | |
24 | +(deftype t/stream-sample () | |
25 | + 't/int32) | |
26 | + | |
27 | +(deftype t/output-buffer () | |
28 | + '(simple-vector 2)) | |
29 | + | |
30 | +(defmacro with-output-buffers ((left right buffers) &body forms) | |
31 | + `(let ((,left (svref ,buffers 0)) | |
32 | + (,right (svref ,buffers 1))) | |
33 | + (declare (type t/int32-array ,left ,right)) | |
34 | + ,@forms)) | |
35 | + | |
36 | +(declaim (type t/output-buffer *fake-buf*)) | |
37 | +(defparameter *fake-buf* (vector (new-array 0 t/stream-sample) | |
38 | + (new-array 0 t/stream-sample))) | |
39 | + | |
40 | +(define-condition satou-error (simple-error) | |
41 | + () | |
42 | + (:documentation "Base class for all error conditions in SatouSynth.")) | |
43 | + | |
44 | +(defmacro satou-error ((&optional (type ''satou-error)) msg &rest fmt-args) | |
45 | + "Raises a new SATOU-ERROR of the given TYPE, setting the FORMAT-CONTROL and | |
46 | +FORMAT-ARGUMENTS of the new condition automatically. | |
47 | + | |
48 | +TYPE can be quoted or not quoted, but must be SATOU-ERROR or a valid subtype | |
49 | +known at expansion time." | |
50 | + (let ((type (if (listp type) type (list 'quote type)))) | |
51 | + (unless (subtypep (cadr type) 'satou-error) | |
52 | + (error "Not a subtype of SATOU-ERROR: ~a" type)) | |
53 | + `(error ,type :format-control ,msg :format-arguments (list ,@fmt-args)))) | |
54 | + | |
55 | +;; This is re-defined from CL-SDM so that we can setup debugging easier | |
56 | +;; throughout the library. | |
57 | +(defmacro define-typed-fn (name (&rest arg-list) (ret-type &optional inline) &body forms) | |
58 | + `(sdm:define-typed-fn ,name (,@arg-list) | |
59 | + ;; Override the INLINE parameter. If :SATOU-VERBOSE-DEBUG is in *FEATURES*, | |
60 | + ;; then we always explicitly say :NO _except_ when it's equal to :ALWAYS. | |
61 | + (,ret-type #+satou-verbose-debug ,(if (eq inline :always) t :no) | |
62 | + #-satou-verbose-debug ,inline) | |
63 | + ,@forms)) | |
64 | + | |
65 | +(defmacro dlog (msg &rest fmt-args) | |
66 | + (declare (ignorable msg fmt-args)) | |
67 | + #+satou-verbose-debug `(sdm-log:dlog! ,msg ,@fmt-args)) | |
68 | + | |
69 | +(deftype t/sample () | |
70 | + 'simple-vector) | |
71 | + | |
72 | +(defmacro sample-left (smp) | |
73 | + `(the t/int32 (svref ,smp 0))) | |
74 | + | |
75 | +(defmacro sample-right (smp) | |
76 | + `(the t/int32 (svref ,smp 1))) | |
77 | + | |
78 | +(define-pseudo-enum t/chip integer | |
79 | + ;; Texas Instruments SN76489 | |
80 | + (:sn76489 0) | |
81 | + | |
82 | + ;; Yamaha YM2413 (OPLL) | |
83 | + (:ym2413 1) | |
84 | + | |
85 | + ;; Yamaha YM2612 (OPN2) | |
86 | + (:ym2612 2) | |
87 | + | |
88 | + ;; Yamaha YM2151 (OPM) | |
89 | + (:ym2151 3) | |
90 | + | |
91 | + ;; Sega SegaPCM (315-5218) | |
92 | + (:sega-pcm 4) | |
93 | + | |
94 | + ;; Ricoh RF5C69 (aka RF5C164, RF5C105). RF5C164 has a separate entry. | |
95 | + (:rf5c69 5) | |
96 | + | |
97 | + ;; Yamaha YM2203 (OPN) | |
98 | + (:ym2203 6) | |
99 | + | |
100 | + ;; Yamaha YM2608 (OPNA) | |
101 | + (:ym2608 7) | |
102 | + | |
103 | + ;; Yamaha YM2610 and YM2610B (OPNB) | |
104 | + (:ym2610 8) | |
105 | + | |
106 | + ;; Yamaha YM3812 (OPL2) | |
107 | + (:ym3812 9) | |
108 | + | |
109 | + ;; Yamaha YM3526 (OPL) | |
110 | + (:ym3526 10) | |
111 | + | |
112 | + ;; Yamaha Y8950 (MSX-Audio) | |
113 | + (:y8950 11) | |
114 | + | |
115 | + ;; Yamaha YMF262 (OPL3) | |
116 | + (:ymf262 12) | |
117 | + | |
118 | + ;; Yamaha YMF278B (OPL4) | |
119 | + (:ymf278b 13) | |
120 | + | |
121 | + ;; Yamaha YMF271 (OPX) | |
122 | + (:ymf271 14) | |
123 | + | |
124 | + ;; Yamaha YMZ280B (PCMD8) | |
125 | + (:ymz280b 15) | |
126 | + | |
127 | + ;; Ricoh RF5C164 | |
128 | + (:rf5c164 16) | |
129 | + | |
130 | + ;; Sega PWM (32x) | |
131 | + (:pwm 17) | |
132 | + | |
133 | + ;; General Instruments AY-1-8910 / AY-3-8910 / AY-3-8912 / AY-3-8913 / Yamaha YM2149 / Yamaha YM2149F | |
134 | + (:ay8910 18) | |
135 | + | |
136 | + ;; Nintendo DMG (GameBoy/GameBoy Color) | |
137 | + (:dmg 19) | |
138 | + | |
139 | + ;; Nintendo APU (NES) | |
140 | + (:nes-apu 20) | |
141 | + | |
142 | + ;; Sega MultiPCM / Yamaha YMW258-F | |
143 | + (:multi-pcm 21) | |
144 | + | |
145 | + ;; NEC µPD7759 | |
146 | + (:upd7759 22) | |
147 | + | |
148 | + ;; OKI M6258 | |
149 | + (:oki-m6258 23) | |
150 | + | |
151 | + ;; OKI M6295 | |
152 | + (:oki-m6295 24) | |
153 | + | |
154 | + ;; Konami 051649 | |
155 | + (:k051649 25) | |
156 | + | |
157 | + ;; Konami 054539 | |
158 | + (:k054539 26) | |
159 | + | |
160 | + ;; Hudson HuC6280 | |
161 | + (:huc6280 27) | |
162 | + | |
163 | + ;; Namco C140 / C219 | |
164 | + (:c140 28) | |
165 | + | |
166 | + ;; Konami 053260 | |
167 | + (:k053260 29) | |
168 | + | |
169 | + ;; Atari POKEY | |
170 | + (:pokey 30) | |
171 | + | |
172 | + ;; QSound DSP16A (Capcom CP System II) | |
173 | + (:qsound 31) | |
174 | + | |
175 | + ;; Sega Saturn Custom Sound Processor / Yamaha YMF292 | |
176 | + (:scsp 32) | |
177 | + | |
178 | + ;; Bandai Wonderswan/Wonderswan Color | |
179 | + (:wonderswan 33) | |
180 | + | |
181 | + ;; Nintendo VSU-VUE (Virtual Boy) | |
182 | + (:vsu 34) | |
183 | + | |
184 | + ;; Philips SAA1099 | |
185 | + (:saa1099 35) | |
186 | + | |
187 | + ;; Ensoniq ES5503 | |
188 | + (:es5503 36) | |
189 | + | |
190 | + ;; Ensoniq ES5506 | |
191 | + (:es5506 37) | |
192 | + | |
193 | + ;; Namco C352 | |
194 | + (:c352 38) | |
195 | + | |
196 | + ;; Sega X1-010 | |
197 | + (:x1-010 39) | |
198 | + | |
199 | + ;; Irem GA20 | |
200 | + (:ga20 40) | |
201 | + | |
202 | + ;; A special value indicating an uninitialized value. | |
203 | + (:unknown 255)) | |
204 | + | |
205 | +(defmacro muffling (&body forms) | |
206 | + `(locally | |
207 | + (declare #-(or satou-debug satou-verbose-debug) (sb-ext:muffle-conditions sb-ext:compiler-note)) | |
208 | + ,@forms)) | |
209 | + | |
210 | +(define-typed-fn get-int32-le ((t/uint8-vector data)) | |
211 | + (t/int32 t) | |
212 | + (declare (optimize (speed 3) (safety 0) (compilation-speed 0) | |
213 | + #+satou-debug (debug 1) | |
214 | + #-satou-debug (debug 0))) | |
215 | + (muffling | |
216 | + (coerce-to-int32 (logior (ash (aref data 3) 24) | |
217 | + (ash (aref data 2) 16) | |
218 | + (ash (aref data 1) 8) | |
219 | + (aref data 0))))) | |
220 | + | |
221 | +(define-typed-fn get-int16-le ((t/uint8-vector data)) | |
222 | + (t/int16 t) | |
223 | + (declare (optimize (speed 3) (safety 0) (compilation-speed 0) | |
224 | + #+satou-debug (debug 1) | |
225 | + #-satou-debug (debug 0))) | |
226 | + (muffling | |
227 | + (coerce-to-int16 (logior (ash (aref data 1) 8) | |
228 | + (aref data 0))))) | |
229 | + | |
230 | +(deftype t/resampler-type () | |
231 | + '(member :old :upsampling :copy :downsampling)) |
@@ -0,0 +1,416 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; Based on VGMPlay, Copyright (C) Valley Bell | |
4 | +;;;; | |
5 | +;;;; This program is free software: you can redistribute it and/or | |
6 | +;;;; modify it under the terms of the GNU Affero General Public | |
7 | +;;;; License as published by the Free Software Foundation, either | |
8 | +;;;; version 3 of the License, or (at your option) any later version. | |
9 | +;;;; | |
10 | +;;;; This program is distributed in the hope that it will be useful, | |
11 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | +;;;; Affero General Public License for more details. | |
14 | +;;;; | |
15 | +;;;; You should have received a copy of the GNU Affero General Public License | |
16 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
17 | +(in-package :satou) | |
18 | + | |
19 | +;;;; | |
20 | +;;;; DAC Controller | |
21 | +;;;; | |
22 | +;;;; This code implements the VGM specification for the "DAC Stream Control | |
23 | +;;;; Driver". This is used to stream data from data blocks in a VGM file to the | |
24 | +;;;; chip emulators via chip writes. | |
25 | +;;;; | |
26 | + | |
27 | +(defpackage :satou/dac | |
28 | + (:use :common-lisp :cl-sdm :satou) | |
29 | + (:shadowing-import-from :common-lisp #:write-string) | |
30 | + (:export | |
31 | + #:dac-controller | |
32 | + #:dac-update | |
33 | + #:dac-get-dac-from-pcm-bank | |
34 | + #:dac-chip-table | |
35 | + #:make-dac-controller | |
36 | + #:dac-add-pcm-data)) | |
37 | + | |
38 | +(eval-when (:compile-toplevel :load-toplevel) | |
39 | + (shadowing-import 'satou::define-typed-fn :satou/dac) | |
40 | + (import 'satou::muffling :satou/dac) | |
41 | + (import 'satou::dlog :satou/dac) | |
42 | + (import 'satou::get-int16-le :satou/dac) | |
43 | + (import 'satou::get-int32-le :satou/dac)) | |
44 | + | |
45 | +(in-package :satou/dac) | |
46 | + | |
47 | +(defining-consts | |
48 | + (+dac-pcm-bank-count+ #x40) | |
49 | + (+dac-dctrl-lmode-ignore+ 0) | |
50 | + (+dac-dctrl-lmode-cmds+ 1) | |
51 | + (+dac-dctrl-lmode-msec+ 2) | |
52 | + (+dac-dctrl-lmode-to-end+ 3) | |
53 | + (+dac-dctrl-lmode-bytes+ 15)) | |
54 | + | |
55 | +(defstruct bank-table | |
56 | + (compression-type 0 :type t/uint8) | |
57 | + (compression-sub-type 0 :type t/uint8) | |
58 | + (bit-dec 0 :type t/uint8) | |
59 | + (bit-cmp 0 :type t/uint8) | |
60 | + (entry-count 0 :type t/uint16) | |
61 | + (entries (new-array 0 t/uint8) :type t/uint8-array)) | |
62 | + | |
63 | +(defstruct pcm-data | |
64 | + (data-size 0 :type t/uint32) | |
65 | + (data (new-array 0 t/uint8) :type t/uint8-array) | |
66 | + (data-start 0 :type t/uint32)) | |
67 | + | |
68 | +(defstruct pcm-bank | |
69 | + (banks (new-vector pcm-data) :type (vector pcm-data)) | |
70 | + (data (new-array 0 t/uint8) :type t/uint8-array) | |
71 | + (data-size 0 :type t/uint32) | |
72 | + (data-pos 0 :type t/uint32) | |
73 | + (bank-pos 0 :type t/uint32)) | |
74 | + | |
75 | +(defstruct control | |
76 | + (dest-chip-type :unknown :type t/chip) | |
77 | + (dest-chip-index 0 :type t/uint8) | |
78 | + (dest-command 0 :type t/uint16) | |
79 | + (command-size 0 :type t/uint8) | |
80 | + | |
81 | + ;; The frequency at which the commands are sent, in hertz. | |
82 | + (frequency 0 :type t/uint32) | |
83 | + | |
84 | + (data-len 0 :type t/uint32) | |
85 | + (data (new-array 0 t/uint8) :type t/uint8-array) | |
86 | + (data-start 0 :type t/uint32) | |
87 | + (step-size 0 :type t/uint8) | |
88 | + (step-base 0 :type t/uint8) | |
89 | + (commands-to-send 0 :type t/uint32) | |
90 | + | |
91 | + ;; Running Bits: 0 (01) - is playing | |
92 | + ;; 2 (04) - loop sample (simple loop from start to end) | |
93 | + ;; 4 (10) - already sent this command | |
94 | + ;; 7 (80) - disabled | |
95 | + (running 0 :type t/uint8) | |
96 | + | |
97 | + (reverse 0 :type t/uint8) | |
98 | + (step 0 :type t/uint32) | |
99 | + (pos 0 :type t/uint32) | |
100 | + (remaining-commands 0 :type t/uint32) | |
101 | + (real-pos 0 :type t/uint32) | |
102 | + (data-step 0 :type t/uint8)) | |
103 | + | |
104 | +(defstruct bank-mapping | |
105 | + (chip-type :unknown :type t/chip) | |
106 | + (bank nil :type (or null pcm-bank))) | |
107 | + | |
108 | +(defstruct (dac-controller (:constructor %make-dac-controller) | |
109 | + (:conc-name %dac-)) | |
110 | + (chip-table nil :type (or null hash-table)) | |
111 | + (pcm-table (make-bank-table) :type bank-table) | |
112 | + | |
113 | + ;; Maps a bank type to a PcmBank instance. | |
114 | + (banks nil :type (or null (simple-array pcm-bank))) | |
115 | + | |
116 | + ;; Maps a stream ID to a bank. | |
117 | + (bank-map (make-hash-table) :type hash-table) | |
118 | + | |
119 | + (streams (make-hash-table) :type hash-table) | |
120 | + (sample-rate 0 :type t/uint32) | |
121 | + (bank-table (make-bank-table) :type bank-table)) | |
122 | + | |
123 | +(define-typed-fn dac-chip-table ((dac-controller dac)) | |
124 | + (hash-table t) | |
125 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
126 | + (%dac-chip-table dac)) | |
127 | + | |
128 | +(define-typed-fn (setf dac-chip-table) ((hash-table value) (dac-controller dac)) | |
129 | + (null t) | |
130 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
131 | + (setf (%dac-chip-table dac) value) | |
132 | + nil) | |
133 | + | |
134 | +(define-typed-fn %control-reset ((control ctrl)) | |
135 | + (null) | |
136 | + "Resets the instance to its initial state." | |
137 | + (dlog "Resetting CONTROL for ~a index ~a" (control-dest-chip-type ctrl) (control-dest-chip-index ctrl)) | |
138 | + (setf (control-dest-command ctrl) 0) | |
139 | + (setf (control-command-size ctrl) 0) | |
140 | + (setf (control-frequency ctrl) 0) | |
141 | + (setf (control-data-len ctrl) 0) | |
142 | + (setf (control-data ctrl) (new-array 0 t/uint8)) | |
143 | + (setf (control-data-start ctrl) 0) | |
144 | + (setf (control-step-size ctrl) 0) | |
145 | + (setf (control-step-base ctrl) 0) | |
146 | + (setf (control-running ctrl) 0) | |
147 | + (setf (control-reverse ctrl) 0) | |
148 | + (setf (control-step ctrl) 0) | |
149 | + (setf (control-pos ctrl) 0) | |
150 | + (setf (control-real-pos ctrl) 0) | |
151 | + (setf (control-remaining-commands ctrl) 0) | |
152 | + (setf (control-data-start ctrl) 0) | |
153 | + nil) | |
154 | + | |
155 | +(define-typed-fn %control-running-p ((control ctrl)) | |
156 | + (boolean t) | |
157 | + "Returns T if the instance is running, or NIL otherwise." | |
158 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
159 | + (not (flag? (control-running ctrl) #x80))) | |
160 | + | |
161 | +(define-typed-fn %dac-stream-update ((dac-controller dac) (control chip) (t/uint32 sample-count)) | |
162 | + (null t) | |
163 | + ;; Is the chip disabled? | |
164 | + (unless (%control-running-p chip) (return-from %dac-stream-update)) | |
165 | + | |
166 | + ;; Is the chip stopped? | |
167 | + (unless (flag? (control-running chip) #x01) (return-from %dac-stream-update)) | |
168 | + | |
169 | + ;; TODO | |
170 | + nil) | |
171 | + | |
172 | +(define-typed-fn %dac-read-pcm-table ((dac-controller dac) (t/uint32 data-size) (t/uint8-array data)) | |
173 | + (null) | |
174 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
175 | + (let ((bank-table (%dac-bank-table dac))) | |
176 | + (setf (bank-table-compression-type bank-table) (aref data 0)) | |
177 | + (setf (bank-table-compression-sub-type bank-table) (aref data 1)) | |
178 | + (setf (bank-table-bit-dec bank-table) (aref data 2)) | |
179 | + (setf (bank-table-bit-cmp bank-table) (aref data 3)) | |
180 | + (setf (bank-table-entry-count bank-table) (get-int16-le (subseq data 4))) | |
181 | + | |
182 | + (let* ((val-size (truncate (coerce-to-uint8 (+ (bank-table-bit-dec bank-table) 7)) 8)) | |
183 | + (table-size (coerce-to-uint64 (* (bank-table-entry-count bank-table) val-size)))) | |
184 | + (setf (bank-table-entries bank-table) (subseq data 6 (- (length data) (+ 6 table-size)))) | |
185 | + (when (< data-size (+ 6 table-size)) | |
186 | + (sdm-log:warn! "Bad PCM table length")))) | |
187 | + nil) | |
188 | + | |
189 | +(defun make-dac-controller (sample-rate) | |
190 | + "Creates a new DAC-CONTROLLER instance." | |
191 | + (let ((ret (%make-dac-controller :sample-rate sample-rate))) | |
192 | + (setf (%dac-banks ret) (make-array +dac-pcm-bank-count+ | |
193 | + :element-type 'pcm-bank | |
194 | + :initial-contents (loop repeat +dac-pcm-bank-count+ | |
195 | + collect (make-pcm-bank)))) | |
196 | + ret)) | |
197 | + | |
198 | +(define-typed-fn dac-update ((dac-controller dac) (t/uint32 sample-count)) | |
199 | + (null t) | |
200 | + "Writes data to the chips for SAMPLE-COUNT samples." | |
201 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
202 | + (loop for stream-id being the hash-values in (%dac-streams dac) do | |
203 | + (%dac-stream-update dac stream-id sample-count)) | |
204 | + nil) | |
205 | + | |
206 | +(define-typed-fn dac-reset ((dac-controller dac)) | |
207 | + (null) | |
208 | + "Resets the givewn DAC-CONTROLLER to the initial state." | |
209 | + (doseq (bank (%dac-banks dac)) | |
210 | + (when bank | |
211 | + (setf (pcm-bank-data-pos bank) 0) | |
212 | + (setf (pcm-bank-bank-pos bank) 0))) | |
213 | + (setf (bank-table-entry-count (%dac-bank-table dac)) 0) | |
214 | + nil) | |
215 | + | |
216 | +(define-typed-fn dac-stream-enabled-p ((dac-controller dac) (t/uint8 stream-id)) | |
217 | + (boolean t) | |
218 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
219 | + (hash-table-contains-p (%dac-streams dac) stream-id)) | |
220 | + | |
221 | +(define-typed-fn dac-reset-stream ((dac-controller dac) (t/uint8 stream-id)) | |
222 | + (null t) | |
223 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
224 | + (multiple-value-bind (stream found) | |
225 | + (gethash stream-id (%dac-streams dac)) | |
226 | + (when found | |
227 | + (%control-reset stream))) | |
228 | + nil) | |
229 | + | |
230 | +(define-typed-fn dac-get-dac-from-pcm-bank ((dac-controller dac)) | |
231 | + (t/uint8) | |
232 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
233 | + (let* ((bank (aref (%dac-banks dac) 0)) | |
234 | + (data-pos (pcm-bank-data-pos bank))) | |
235 | + (if (>= data-pos (pcm-bank-data-size bank)) | |
236 | + #x80 | |
237 | + (aref (pcm-bank-data bank) | |
238 | + (incf (pcm-bank-data-pos bank)))))) | |
239 | + | |
240 | +(define-typed-fn dac-setup-chip ((dac-controller dac) (t/chip chip-type) (t/uint8 chip-index stream-id) | |
241 | + (t/uint16 command)) | |
242 | + (null) | |
243 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
244 | + (dlog "Setting up DAC for type ~a, stream ID ~a, with command ~a" chip-type stream-id command) | |
245 | + | |
246 | + (unless (hash-table-contains-p (%dac-bank-map dac) stream-id) | |
247 | + (dlog "Enabling up DAC for type ~a, stream ID ~a, with command ~a" chip-type stream-id command) | |
248 | + (%dac-start-chip-stream dac chip-type chip-index stream-id) | |
249 | + (dac-reset-stream dac stream-id) | |
250 | + (setf (gethash stream-id (%dac-bank-map dac)) (make-bank-mapping :chip-type chip-type))) | |
251 | + | |
252 | + (let ((chip (gethash stream-id (%dac-streams dac)))) | |
253 | + (setf (control-dest-command chip) command) | |
254 | + | |
255 | + (case (control-dest-chip-type chip) | |
256 | + (:sn76489 | |
257 | + (setf (control-command-size chip) | |
258 | + (if (flag? (control-dest-command chip) #x10) | |
259 | + 1 ;; Volume write | |
260 | + 2))) ;; Frequency write | |
261 | + | |
262 | + (:ym2612 | |
263 | + (setf (control-command-size chip) 1)) | |
264 | + | |
265 | + ((:pwm :qsound) | |
266 | + (setf (control-command-size chip) 2)) | |
267 | + | |
268 | + (otherwise | |
269 | + (setf (control-command-size chip) 1))) | |
270 | + | |
271 | + (setf (control-data-step chip) (* (control-command-size chip) (control-step-size chip)))) | |
272 | + nil) | |
273 | + | |
274 | +(define-typed-fn dac-set-frequency ((dac-controller dac) (t/uint8 stream-id) (t/uint32 frequency)) | |
275 | + (null) | |
276 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
277 | + (multiple-value-bind (chip found) | |
278 | + (gethash stream-id (%dac-streams dac)) | |
279 | + (when (and found (%control-running-p chip)) | |
280 | + (unless (zerop frequency) | |
281 | + (setf (control-step chip) (truncate (* (control-step chip) (control-frequency chip)) frequency))) | |
282 | + (setf (control-frequency chip) frequency))) | |
283 | + nil) | |
284 | + | |
285 | +(define-typed-fn dac-add-pcm-data ((dac-controller dac) (t/uint8 data-type) (t/uint32 data-size) (t/uint8-array data)) | |
286 | + (null) | |
287 | + "Adds PCM data to the given DAC-CONTROLLER instance." | |
288 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
289 | + (let ((bank-type (logand data-type #x3F))) | |
290 | + ;;(when (> bank-type +dac-pcm-bank-count+) | |
291 | + ;; (return-from dac-add-pcm-data)) | |
292 | + | |
293 | + (when (= data-type #x7F) | |
294 | + (%dac-read-pcm-table dac data-size data) | |
295 | + (return-from dac-add-pcm-data)) | |
296 | + | |
297 | + (let ((temp-pcm (aref (%dac-banks dac) bank-type)) | |
298 | + (cur-bank 0) | |
299 | + (bank-size 0)) | |
300 | + (declare (type t/int32 cur-bank) | |
301 | + (type t/uint32 bank-size)) | |
302 | + (incf (pcm-bank-bank-pos temp-pcm)) | |
303 | + | |
304 | + ;; Speed hack for restarting playback (skip already loaded data) | |
305 | + (when (<= (pcm-bank-bank-pos temp-pcm) (length (pcm-bank-banks temp-pcm))) | |
306 | + (return-from dac-add-pcm-data)) | |
307 | + | |
308 | + (setf cur-bank (length (pcm-bank-banks temp-pcm))) | |
309 | + (vector-push-extend (make-pcm-data) (pcm-bank-banks temp-pcm)) | |
310 | + | |
311 | + (setf bank-size (if (not (flag? data-type #x40)) | |
312 | + data-size | |
313 | + (coerce-to-uint32 (get-int32-le (subseq data 1))))) | |
314 | + | |
315 | + ;; Akin to realloc | |
316 | + (setf (pcm-bank-data temp-pcm) | |
317 | + (make-array (+ (pcm-bank-data-size temp-pcm) bank-size) | |
318 | + :element-type 't/uint8 | |
319 | + :initial-contents (concatenate 'vector | |
320 | + (pcm-bank-data temp-pcm) | |
321 | + (loop repeat (- (+ (pcm-bank-data-size temp-pcm) bank-size) | |
322 | + (length (pcm-bank-data temp-pcm))) | |
323 | + collect 0)))) | |
324 | + | |
325 | + (let ((temp-bank (muffling (aref (pcm-bank-banks temp-pcm) cur-bank)))) | |
326 | + (setf (pcm-data-data-start temp-bank) (pcm-bank-data-size temp-pcm)) | |
327 | + | |
328 | + (cond | |
329 | + ((not (flag? data-type #x40)) | |
330 | + (setf (pcm-data-data-size temp-bank) data-size) | |
331 | + (setf (pcm-data-data temp-bank) (subseq (pcm-bank-data temp-pcm) (pcm-data-data-start temp-bank))) | |
332 | + (dotimes (i data-size) | |
333 | + (setf (aref (pcm-data-data temp-bank) i) (aref data i)))) | |
334 | + | |
335 | + (t | |
336 | + (setf (pcm-data-data temp-bank) (subseq (pcm-bank-data temp-pcm) (pcm-data-data-start temp-bank))) | |
337 | + (unless (%dac-decompress-data-block dac temp-bank data-size data) | |
338 | + (sdm-log:warn! "Decompression of DAC PCM data failed") | |
339 | + (setf (pcm-data-data temp-bank) (new-array 0 t/uint8)) | |
340 | + (setf (pcm-data-data-size temp-bank) 0) | |
341 | + (loop for chip being the hash-values in (%dac-streams dac) | |
342 | + do (dac-refresh-data dac chip (pcm-bank-data temp-pcm))) | |
343 | + (return-from dac-add-pcm-data)))) | |
344 | + | |
345 | + (when (/= bank-size (pcm-data-data-size temp-bank)) | |
346 | + (satou-error () "Error reading data block: data size conflict")) | |
347 | + | |
348 | + (incf (pcm-bank-data-size temp-pcm) bank-size) | |
349 | + (loop for chip being the hash-values in (%dac-streams dac) | |
350 | + do (dac-refresh-data dac chip (pcm-bank-data temp-pcm)))))) | |
351 | + nil) | |
352 | + | |
353 | +(define-typed-fn dac-bank-count ((dac-controller dac) (t/uint8 stream-id)) | |
354 | + (t/uint32 t) | |
355 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
356 | + (length (pcm-bank-banks (bank-mapping-bank (gethash stream-id (%dac-bank-map dac)))))) | |
357 | + | |
358 | +(define-typed-fn %dac-set-data ((dac-controller dac) (t/uint8 stream-id) (t/uint8-array data) | |
359 | + (t/uint8 step-size step-base)) | |
360 | + (null) | |
361 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
362 | + (let ((chip (gethash stream-id (%dac-streams dac)))) | |
363 | + (when (%control-running-p chip) | |
364 | + (cond | |
365 | + ((> (length data) 0) | |
366 | + (setf (control-data-len chip) (length data)) | |
367 | + (setf (control-data chip) data)) | |
368 | + (t | |
369 | + (setf (control-data-len chip) 0) | |
370 | + (setf (control-data chip) (new-array 0 t/uint8)))) | |
371 | + | |
372 | + (setf (control-step-size chip) (if (zerop step-size) 1 step-size)) | |
373 | + (setf (control-step-base chip) step-base) | |
374 | + (setf (control-data-step chip) (* (control-command-size chip) (control-step-size chip))))) | |
375 | + nil) | |
376 | + | |
377 | +(define-typed-fn dac-refresh-data ((dac-controller dac) (control chip) (t/uint8-array data)) | |
378 | + (null) | |
379 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0)) | |
380 | + (ignore dac)) | |
381 | + (when (%control-running-p chip) | |
382 | + (cond | |
383 | + ((> (length data) 0) | |
384 | + (setf (control-data-len chip) (length data)) | |
385 | + (setf (control-data chip) data)) | |
386 | + (t | |
387 | + (setf (control-data-len chip) 0) | |
388 | + (setf (control-data chip) (new-array 0 t/uint8))))) | |
389 | + nil) | |
390 | + | |
391 | +(define-typed-fn dac-assign-pcm-data ((dac-controller dac) (t/uint8 stream-id bank-id step-size step-base)) | |
392 | + (null) | |
393 | + "Assigns PCM data from the given bank ID to the given stream ID." | |
394 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
395 | + (let ((bank (aref (%dac-banks dac) (if (>= bank-id +dac-pcm-bank-count+) 0 bank-id)))) | |
396 | + (setf (bank-mapping-bank (gethash stream-id (%dac-bank-map dac))) bank) | |
397 | + (%dac-set-data dac stream-id (pcm-bank-data bank) step-size step-base)) | |
398 | + nil) | |
399 | + | |
400 | + | |
401 | +;; TODO playBlock | |
402 | + | |
403 | +(define-typed-fn %dac-start-chip-stream ((dac-controller dac) (t/chip chip-type) (t/uint8 chip-index stream-id)) | |
404 | + (null) | |
405 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
406 | + (let ((ctrl (make-control :dest-chip-type chip-type | |
407 | + :dest-chip-index chip-index | |
408 | + :dest-command 0 | |
409 | + :running #xFF))) ;; Disables all actions | |
410 | + (setf (gethash stream-id (%dac-streams dac)) ctrl)) | |
411 | + nil) | |
412 | + | |
413 | +(define-typed-fn %dac-decompress-data-block ((dac-controller dac) (pcm-data bank) (t/uint32 data-size) | |
414 | + (t/uint8-array data)) | |
415 | + (boolean) | |
416 | + nil) |
@@ -0,0 +1,157 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +(defining-consts | |
19 | + (+gd3-magic+ "Gd3 " :documentation "Magic bytes for the GD3 tag data.") | |
20 | + (+gd3-version+ (bytes->uint '(0 1 0 0)) :documentation "The supported version of the GD3 tag specification.")) | |
21 | + | |
22 | +(defclass gd3-tag () | |
23 | + ((version | |
24 | + :initform +gd3-version+ | |
25 | + :type t/uint32 | |
26 | + :reader gd3-tag-version | |
27 | + :documentation "The version of this GD3 tag data.") | |
28 | + | |
29 | + (track-name-en | |
30 | + :initarg :track-name-en | |
31 | + :initform "" | |
32 | + :type string | |
33 | + :accessor gd3-tag-track-name-en | |
34 | + :documentation "The name of the track, in English.") | |
35 | + | |
36 | + (track-name-jp | |
37 | + :initarg :track-name-jp | |
38 | + :initform "" | |
39 | + :type string | |
40 | + :accessor gd3-tag-track-name-jp | |
41 | + :documentation "The name of the track, in Japanese.") | |
42 | + | |
43 | + (game-name-en | |
44 | + :initarg :game-name-en | |
45 | + :initform "" | |
46 | + :type string | |
47 | + :accessor gd3-tag-game-name-en | |
48 | + :documentation "The name of the game, in English.") | |
49 | + | |
50 | + (game-name-jp | |
51 | + :initarg :game-name-jp | |
52 | + :initform "" | |
53 | + :type string | |
54 | + :accessor gd3-tag-game-name-jp | |
55 | + :documentation "The name of the game, in Japanese.") | |
56 | + | |
57 | + (system-name-en | |
58 | + :initarg :system-name-en | |
59 | + :initform "" | |
60 | + :type string | |
61 | + :accessor gd3-tag-system-name-en | |
62 | + :documentation "The name of the system, in English.") | |
63 | + | |
64 | + (system-name-jp | |
65 | + :initarg :system-name-jp | |
66 | + :initform "" | |
67 | + :type string | |
68 | + :accessor gd3-tag-system-name-jp | |
69 | + :documentation "The name of the system, in Japanese.") | |
70 | + | |
71 | + (author-name-en | |
72 | + :initarg :author-name-en | |
73 | + :initform "" | |
74 | + :type string | |
75 | + :accessor gd3-tag-author-name-en | |
76 | + :documentation "The name of the author, in English.") | |
77 | + | |
78 | + (author-name-jp | |
79 | + :initarg :author-name-jp | |
80 | + :initform "" | |
81 | + :type string | |
82 | + :accessor gd3-tag-author-name-jp | |
83 | + :documentation "The name of the author, in Japanese.") | |
84 | + | |
85 | + (release-date | |
86 | + :initarg :release-date | |
87 | + :initform "" | |
88 | + :type string | |
89 | + :accessor gd3-tag-release-date | |
90 | + :documentation "The track's original release date.") | |
91 | + | |
92 | + (creator | |
93 | + :initarg :creator | |
94 | + :initform "" | |
95 | + :type string | |
96 | + :accessor gd3-tag-creator | |
97 | + :documentation "The creator of the VGM file.") | |
98 | + | |
99 | + (notes | |
100 | + :initarg :notes | |
101 | + :initform "" | |
102 | + :type string | |
103 | + :accessor gd3-tag-notes | |
104 | + :documentation "Any additional notes about the song or VGM file.")) | |
105 | + (:documentation "a GD3 tag stores metadata for a VGM file. It is analogous to | |
106 | +an ID3 tag in an MP3 file.")) | |
107 | + | |
108 | +(define-condition gd3-tag-error (satou-error) | |
109 | + ()) | |
110 | + | |
111 | +(define-typed-fn %read-wchar-string ((stream stream)) | |
112 | + (simple-string t) | |
113 | + "Reads a C++-like wchar string from STREAM." | |
114 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
115 | + ;; The COERCE might not be needed here? | |
116 | + (coerce (with-output-to-string (out) | |
117 | + (loop for ch fixnum = (read-uint16 stream) | |
118 | + until (= ch 0) do | |
119 | + (write-char (code-char ch) out))) | |
120 | + 'simple-string)) | |
121 | + | |
122 | +(defun read-gd3-tag (stream) | |
123 | + "Creates a new GD3-TAG instance by reading data from STREAM, which should | |
124 | +already be at the position of the raw GD3 data." | |
125 | + (unless (equalp (read-string stream (length +gd3-magic+)) +gd3-magic+) | |
126 | + (satou-error (gd3-tag-error) "No valid GD3 tag header found")) | |
127 | + | |
128 | + (let ((ret (make-instance 'gd3-tag)) | |
129 | + (len 0) | |
130 | + (started-at 0)) | |
131 | + (declare (type t/uint32 len) | |
132 | + (type fixnum started-at)) | |
133 | + (unless (= (setf (slot-value ret 'version) (read-uint32 stream)) +gd3-version+) | |
134 | + (sdm-log:warn! "GD3 tag version doesn't match what is expected: read $~4,'0x, expected $~4,'0x" | |
135 | + (slot-value ret 'version) | |
136 | + +gd3-version+)) | |
137 | + | |
138 | + (setf len (read-uint32 stream)) | |
139 | + (setf started-at (file-position stream)) | |
140 | + | |
141 | + (setf (gd3-tag-track-name-en ret) (%read-wchar-string stream)) | |
142 | + (setf (gd3-tag-track-name-jp ret) (%read-wchar-string stream)) | |
143 | + (setf (gd3-tag-game-name-en ret) (%read-wchar-string stream)) | |
144 | + (setf (gd3-tag-game-name-jp ret) (%read-wchar-string stream)) | |
145 | + (setf (gd3-tag-system-name-en ret) (%read-wchar-string stream)) | |
146 | + (setf (gd3-tag-system-name-jp ret) (%read-wchar-string stream)) | |
147 | + (setf (gd3-tag-author-name-en ret) (%read-wchar-string stream)) | |
148 | + (setf (gd3-tag-author-name-jp ret) (%read-wchar-string stream)) | |
149 | + (setf (gd3-tag-release-date ret) (%read-wchar-string stream)) | |
150 | + (setf (gd3-tag-creator ret) (%read-wchar-string stream)) | |
151 | + (setf (gd3-tag-notes ret) (%read-wchar-string stream)) | |
152 | + | |
153 | + (muffling | |
154 | + (unless (= (file-position stream) (the fixnum (+ started-at len))) | |
155 | + (sdm-log:warn! "GD3 tag data size mismatch: ~a != ~a" len (- (file-position stream) started-at)))) | |
156 | + | |
157 | + ret)) |
@@ -0,0 +1,209 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | + | |
17 | +(defpackage :satou | |
18 | + (:use :common-lisp :cl-sdm) | |
19 | + (:shadowing-import-from :common-lisp #:write-string) | |
20 | + (:shadow #:define-typed-fn) | |
21 | + (:export | |
22 | + ;; common.lisp | |
23 | + #:satou-error | |
24 | + #:t/chip | |
25 | + | |
26 | + ;; abstract-chip | |
27 | + #:chip-type | |
28 | + #:chip-name | |
29 | + #:chip-short-name | |
30 | + #:chip-id | |
31 | + #:chip-emu-core | |
32 | + #:chip-default-emu-core | |
33 | + #:chip-base-volume | |
34 | + #:chip-emu | |
35 | + #:chip-start | |
36 | + #:chip-clock | |
37 | + #:chip-start-flags | |
38 | + #:chip-update | |
39 | + #:chip-update-paired | |
40 | + #:chip-reset | |
41 | + #:chip-read | |
42 | + #:chip-write | |
43 | + #:chip-mute-mask | |
44 | + #:chip-write-dac | |
45 | + #:chip-stereo-mask | |
46 | + #:chip-volume-modifier | |
47 | + #:chip-init | |
48 | + #:chip-write-rom | |
49 | + #:chip-flags | |
50 | + #:abstract-chip | |
51 | + #:chip-sample-rate | |
52 | + #:chip-player-sample-rate | |
53 | + #:chip-volume | |
54 | + #:chip-resampler-type | |
55 | + #:chip-is-paired-p | |
56 | + #:get-chip-volume | |
57 | + #:change-chip-sample-rate | |
58 | + | |
59 | + ;; gd3-tag.lisp | |
60 | + #:gd3-tag | |
61 | + #:gd3-tag-version | |
62 | + #:gd3-tag-track-name-en | |
63 | + #:gd3-tag-track-name-jp | |
64 | + #:gd3-tag-game-name-en | |
65 | + #:gd3-tag-game-name-jp | |
66 | + #:gd3-tag-system-name-en | |
67 | + #:gd3-tag-system-name-jp | |
68 | + #:gd3-tag-author-name-en | |
69 | + #:gd3-tag-author-name-jp | |
70 | + #:gd3-tag-release-date | |
71 | + #:gd3-tag-creator | |
72 | + #:gd3-tag-notes | |
73 | + #:gd3-tag-error | |
74 | + #:read-gd3-tag | |
75 | + | |
76 | + ;; vgmfile.lisp | |
77 | + #:vgm-error | |
78 | + #:valid-vgm-p | |
79 | + #:read-vgm-file | |
80 | + #:extra-header-clock | |
81 | + #:extra-header-clock-chip-id | |
82 | + #:extra-header-clock-value | |
83 | + #:extra-header-volume | |
84 | + #:extra-header-volume-chip-id | |
85 | + #:extra-header-volume-flags | |
86 | + #:extra-header-volume-value | |
87 | + #:extra-header | |
88 | + #:extra-header-clocks | |
89 | + #:extra-header-volumes | |
90 | + #:vgm-file-header | |
91 | + #:vgm-header-version | |
92 | + #:vgm-header-sn76489-clock | |
93 | + #:vgm-header-ym2413-clock | |
94 | + #:vgm-header-gd3-offset | |
95 | + #:vgm-header-total-samples | |
96 | + #:vgm-header-loop-offset | |
97 | + #:vgm-header-loop-samples | |
98 | + #:vgm-header-rate | |
99 | + #:vgm-header-sn76489-feedback | |
100 | + #:vgm-header-sn76489-shift-register-width | |
101 | + #:vgm-header-sn76489-flags | |
102 | + #:vgm-header-ym2612-clock | |
103 | + #:vgm-header-ym2151-clock | |
104 | + #:vgm-header-data-offset | |
105 | + #:vgm-header-spcm-clock | |
106 | + #:vgm-header-spcm-interface-reg | |
107 | + #:vgm-header-rf5c69-clock | |
108 | + #:vgm-header-ym2203-clock | |
109 | + #:vgm-header-ym2608-clock | |
110 | + #:vgm-header-ym2610-clock | |
111 | + #:vgm-header-ym3812-clock | |
112 | + #:vgm-header-ym3526-clock | |
113 | + #:vgm-header-y8950-clock | |
114 | + #:vgm-header-ymf262-clock | |
115 | + #:vgm-header-ymf278b-clock | |
116 | + #:vgm-header-ymf271-clock | |
117 | + #:vgm-header-ymz280b-clock | |
118 | + #:vgm-header-rf5c164-clock | |
119 | + #:vgm-header-pwm-clock | |
120 | + #:vgm-header-ay8910-clock | |
121 | + #:vgm-header-ay8910-chip-type | |
122 | + #:vgm-header-ay8910-flags | |
123 | + #:vgm-header-ay-ym2203-flags | |
124 | + #:vgm-header-ay-ym2608-flags | |
125 | + #:vgm-header-volume-modifier | |
126 | + #:vgm-header-loop-base | |
127 | + #:vgm-header-loop-modifier | |
128 | + #:vgm-header-dmg-clock | |
129 | + #:vgm-header-nes-apu-clock | |
130 | + #:vgm-header-multi-pcm-clock | |
131 | + #:vgm-header-upd7759-clock | |
132 | + #:vgm-header-oki-m6258-clock | |
133 | + #:vgm-header-oki-m6258-flags | |
134 | + #:vgm-header-k054539-flags | |
135 | + #:vgm-header-c140-chip-type | |
136 | + #:vgm-header-oki-m6295-clock | |
137 | + #:vgm-header-k051649-clock | |
138 | + #:vgm-header-k054539-clock | |
139 | + #:vgm-header-huc6280-clock | |
140 | + #:vgm-header-c140-clock | |
141 | + #:vgm-header-k053260-clock | |
142 | + #:vgm-header-pokey-clock | |
143 | + #:vgm-header-qsound-clock | |
144 | + #:vgm-header-scsp-clock | |
145 | + #:vgm-header-extra-header-offset | |
146 | + #:vgm-header-wonderswan-clock | |
147 | + #:vgm-header-vsu-clock | |
148 | + #:vgm-header-saa1099-clock | |
149 | + #:vgm-header-es5503-clock | |
150 | + #:vgm-header-es5506-clock | |
151 | + #:vgm-header-es5503-num-channels | |
152 | + #:vgm-header-es5506-num-channels | |
153 | + #:vgm-header-c352-clock-div | |
154 | + #:vgm-header-x10-010-clock | |
155 | + #:vgm-header-c352-clock | |
156 | + #:vgm-header-ga20-clock | |
157 | + #:vgm-file | |
158 | + #:vgm-file-header | |
159 | + #:vgm-file-gd3 | |
160 | + #:vgm-file-data | |
161 | + #:vgm-file-data-offset | |
162 | + #:vgm-file-extra-header | |
163 | + #:vgm-file-version | |
164 | + #:make-vgm-file | |
165 | + #:vgm-file-chip-used-p | |
166 | + #:vgm-file-get-chip-clock | |
167 | + #:vgm-file-chips-used | |
168 | + | |
169 | + ;; vgm-player.lisp | |
170 | + #:vgm-player | |
171 | + #:make-vgm-player | |
172 | + #:vgm-player-reset | |
173 | + #:vgm-player-play | |
174 | + #:vgm-player-render | |
175 | + #:vgm-player-stop | |
176 | + #:vgm-player-get-chip-names | |
177 | + #:vgm-player-chips-used | |
178 | + #:vgm-player-settings | |
179 | + #:vgm-player-vgm | |
180 | + #:vgm-player-sample-rate | |
181 | + #:vgm-player-min-buffer-size | |
182 | + #:vgm-player-play-time | |
183 | + #:vgm-player-main-volume | |
184 | + #:vgm-player-at-end-p | |
185 | + #:vgm-player-times-played | |
186 | + #:vgm-player-chip-table | |
187 | + #:vgm-player-samples-per-buffer | |
188 | + #:vgm-player-volume-modifier | |
189 | + #:vgm-player-playing-p | |
190 | + #:calc-resampling-values | |
191 | + #:calc-resampling-values*)) | |
192 | + | |
193 | +(defpackage :satou-chips | |
194 | + (:use :common-lisp :cl-sdm :satou) | |
195 | + (:shadowing-import-from :common-lisp #:write-string) | |
196 | + (:export | |
197 | + #:with-output-buffers | |
198 | + | |
199 | + #:+chip-id/huc6280+ | |
200 | + #:t/huc6280-core | |
201 | + #:huc6280 | |
202 | + | |
203 | + #:+chip-id/c352+ | |
204 | + #:t/c352-core | |
205 | + #:c352)) | |
206 | + | |
207 | +(shadowing-import 'satou::define-typed-fn :satou-chips) | |
208 | +(import 'satou::t/output-buffer :satou-chips) | |
209 | +(import 'satou::muffling :satou-chips) |
@@ -0,0 +1,356 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; Based on VGMPlay, Copyright (C) Valley Bell | |
4 | +;;;; | |
5 | +;;;; This program is free software: you can redistribute it and/or | |
6 | +;;;; modify it under the terms of the GNU Affero General Public | |
7 | +;;;; License as published by the Free Software Foundation, either | |
8 | +;;;; version 3 of the License, or (at your option) any later version. | |
9 | +;;;; | |
10 | +;;;; This program is distributed in the hope that it will be useful, | |
11 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | +;;;; Affero General Public License for more details. | |
14 | +;;;; | |
15 | +;;;; You should have received a copy of the GNU Affero General Public License | |
16 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
17 | +(in-package :satou) | |
18 | + | |
19 | +;;;; | |
20 | +;;;; Fixed-point resampler. This implements four modes: low-quality (but fast) | |
21 | +;;;; resampling, upsampling, downsampling, and copying. | |
22 | +;;;; | |
23 | +;;;; The resampler is where SatouSynth actually calls the update functions for | |
24 | +;;;; the chips. | |
25 | +;;;; | |
26 | +;;;; Note: Nearly all of this code was ported from VGMPlay. | |
27 | +;;;; | |
28 | + | |
29 | +(deftype t/slint () | |
30 | + 't/uint32) | |
31 | + | |
32 | +(declaim (type t/uint32 +resampler-bits+ +resampler-fact+ +resampler-mask+)) | |
33 | +(defining-consts | |
34 | + ;;; | |
35 | + ;;; Fixed Point Constants for Resampler | |
36 | + ;;; | |
37 | + | |
38 | + ;; Could be changed. 11 bits seems good and accurate, though. Change the | |
39 | + ;; SLInt alias to UInt64 if this goes above 11, though. Also change the | |
40 | + ;; .to_u32! code in the resamplers as needed. | |
41 | + (+resampler-bits+ 11) | |
42 | + | |
43 | + (+resampler-fact+ (ash 1 +resampler-bits+)) | |
44 | + (+resampler-mask+ (1- +resampler-fact+))) | |
45 | + | |
46 | +(defstruct resampler | |
47 | + ;; Playback sample rate | |
48 | + (sample-rate 0 :type t/uint32) | |
49 | + | |
50 | + ;; PCM data from the chips get put into this buffer when we updae the chips. | |
51 | + (stream-buffers nil :type (or null t/output-buffer)) | |
52 | + | |
53 | + ;; Holds sub buffers (see WITH-STREAM-POINT) | |
54 | + (stream-point (vector nil nil) :type (simple-vector 2))) | |
55 | + | |
56 | +;;; | |
57 | +;;; Fixed Point Functions and Utilities | |
58 | +;;; | |
59 | + | |
60 | +(define-typed-fn %get-fraction ((t/uint32 x)) | |
61 | + (t/uint32 t) | |
62 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
63 | + (logand x +resampler-mask+)) | |
64 | + | |
65 | +(define-typed-fn %get-n-fraction ((t/uint32 x)) | |
66 | + (t/uint32 t) | |
67 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
68 | + (logand (- +resampler-fact+ x) +resampler-mask+)) | |
69 | + | |
70 | +(define-typed-fn %fp2i-floor ((t/uint32 x)) | |
71 | + (t/uint32 t) | |
72 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
73 | + (truncate x +resampler-fact+)) | |
74 | + | |
75 | +(define-typed-fn %fp2i-ceil ((t/uint32 x)) | |
76 | + (t/uint32 t) | |
77 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
78 | + (truncate (+ x +resampler-mask+) +resampler-fact+)) | |
79 | + | |
80 | +(defmacro with-stream-point ((var left right rs) &body forms) | |
81 | + `(let ((,var (resampler-stream-point ,rs))) | |
82 | + (setf (svref ,var 0) ,left) | |
83 | + (setf (svref ,var 1) ,right) | |
84 | + ,@forms)) | |
85 | + | |
86 | +;;; | |
87 | +;;; Resampling Functions | |
88 | +;;; | |
89 | + | |
90 | +(define-typed-fn %resampler-old ((resampler rs) (abstract-chip chip) (simple-vector smp) (t/uint32 len)) | |
91 | + (null) | |
92 | + "Low-quality, but fast, resampling." | |
93 | + (declare (ignore rs chip smp len)) | |
94 | + (error "Not implemented") | |
95 | + nil) | |
96 | + | |
97 | +(define-typed-fn %resampler-upsample ((resampler rs) (abstract-chip chip) (simple-vector smp) (t/uint32 len)) | |
98 | + (null) | |
99 | + "High quality upsampling." | |
100 | + (declare (ignore len) | |
101 | + (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
102 | + (with-output-buffers (cur-buf-l cur-buf-r (resampler-stream-buffers rs)) | |
103 | + (with-typed-slots ((t/uint32 cur-sample-num next-sample-num last-sample-num) | |
104 | + (t/uint16 volume) | |
105 | + (t/sample last-sample next-sample) | |
106 | + ((or null function) update-fn)) | |
107 | + chip | |
108 | + (let* ((chip-sample-rate (chip-sample-rate chip)) | |
109 | + (in-pos-l (coerce-to-uint32 | |
110 | + (truncate (muffling | |
111 | + (* +resampler-fact+ cur-sample-num chip-sample-rate)) | |
112 | + (resampler-sample-rate rs)))) | |
113 | + (in-pre (%fp2i-floor in-pos-l)) | |
114 | + (in-now (%fp2i-ceil in-pos-l)) | |
115 | + (in-pos 0) | |
116 | + (sample-fraction 0) | |
117 | + (temp-sample-l 0) | |
118 | + (temp-sample-r 0) | |
119 | + (in-base 0) | |
120 | + (sample-count +resampler-fact+)) | |
121 | + (declare (type t/uint64 chip-sample-rate) | |
122 | + (type t/slint in-pos-l) | |
123 | + (type t/uint32 in-pre in-now in-pos sample-fraction in-base sample-count) | |
124 | + (type t/int64 temp-sample-l temp-sample-r)) | |
125 | + | |
126 | + (setf (aref cur-buf-l 0) (sample-left last-sample)) | |
127 | + (setf (aref cur-buf-r 0) (sample-right last-sample)) | |
128 | + (setf (aref cur-buf-l 1) (sample-left next-sample)) | |
129 | + (setf (aref cur-buf-r 1) (sample-right next-sample)) | |
130 | + | |
131 | + (with-stream-point (stream-point cur-buf-l cur-buf-r rs) | |
132 | + (if update-fn | |
133 | + (funcall (the function update-fn) chip stream-point 2 (- in-now next-sample-num)) | |
134 | + (chip-update chip stream-point 2 (- in-now next-sample-num))) | |
135 | + | |
136 | + (setf in-base (coerce-to-uint32 (+ +resampler-fact+ (- in-pos-l (* next-sample-num +resampler-fact+))))) | |
137 | + (setf last-sample-num in-pre) | |
138 | + (setf next-sample-num in-now) | |
139 | + | |
140 | + ;; The original code expects us to have an array of return samples. | |
141 | + ;; However, the resampler is only ever called with a single element. | |
142 | + ;; In the interest of efficiency, the len parameter is ignored here, | |
143 | + ;; with the original code is commented out. | |
144 | + | |
145 | + ;; (loop for out-pos fixnum from 0 below len do | |
146 | + ;; (setf in-pos (+ in-base (truncate (* +resampler-fact+ out-pos chip-sample-rate) | |
147 | + ;; (resampler-sample-rate rs)))) | |
148 | + (setf in-pos in-base) | |
149 | + (setf in-pre (%fp2i-floor in-pos)) | |
150 | + (setf in-now (%fp2i-ceil in-pos)) | |
151 | + (setf sample-fraction (%get-fraction in-pos)) | |
152 | + | |
153 | + ;; Linear interpolation | |
154 | + (setf temp-sample-l (+ (* (aref cur-buf-l in-pre) (- +resampler-fact+ sample-fraction)) | |
155 | + (* (aref cur-buf-l in-now) sample-fraction))) | |
156 | + (setf temp-sample-r (+ (* (aref cur-buf-r in-pre) (- +resampler-fact+ sample-fraction)) | |
157 | + (* (aref cur-buf-r in-now) sample-fraction))) | |
158 | + | |
159 | + ;; (incf (sample-left (aref smp out-pos)) | |
160 | + ;; (coerce-to-int32 (truncate (* temp-sample-l volume) sample-count))) | |
161 | + ;; (incf (sample-right (aref smp out-pos)) | |
162 | + ;; (coerce-to-int32 (truncate (* temp-sample-r volume) sample-count))) | |
163 | + | |
164 | + (incf (sample-left smp) (coerce-to-int32 (truncate (* temp-sample-l volume) sample-count))) | |
165 | + (incf (sample-right smp) (coerce-to-int32 (truncate (* temp-sample-r volume) sample-count))) | |
166 | + ;;) | |
167 | + | |
168 | + (setf (sample-left last-sample) (aref cur-buf-l in-pre)) | |
169 | + (setf (sample-right last-sample) (aref cur-buf-r in-pre)) | |
170 | + (setf (sample-left next-sample) (aref cur-buf-l in-now)) | |
171 | + (setf (sample-right next-sample) (aref cur-buf-r in-now)) | |
172 | + (incf cur-sample-num))))) | |
173 | + nil) | |
174 | + | |
175 | +(define-typed-fn %resampler-copy ((resampler rs) (abstract-chip chip) (simple-vector smp) (t/uint32 len)) | |
176 | + (null t) | |
177 | + "Not actually resampling, just copies data from the chip's output buffer to | |
178 | +our destination buffer and applies volume. This is used for situations where | |
179 | +the ratio of chip's output sample rate and the playback rate matches 1:1." | |
180 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
181 | + (with-output-buffers (cur-buf-l cur-buf-r (resampler-stream-buffers rs)) | |
182 | + (with-typed-slots ((t/uint32 sample-rate cur-sample-num last-sample-num next-sample-num) | |
183 | + (t/uint16 volume) | |
184 | + ((or null function) update-fn)) | |
185 | + chip | |
186 | + | |
187 | + (setf next-sample-num (coerce-to-uint32 (truncate (* cur-sample-num sample-rate) (resampler-sample-rate rs)))) | |
188 | + | |
189 | + (if update-fn | |
190 | + (funcall (the function update-fn) chip (resampler-stream-buffers rs) 0 len) | |
191 | + (chip-update chip (resampler-stream-buffers rs) 0 len)) | |
192 | + | |
193 | + ;; The original code expects us to have an array of return samples. | |
194 | + ;; However, the resampler is only ever called with a single element. | |
195 | + ;; In the interest of efficiency, the len parameter is ignored here, | |
196 | + ;; with the original code is commented out. | |
197 | + | |
198 | + ;; (loop for out-pos fixnum from 0 below len do | |
199 | + ;; (incf (sample-left (aref smp out-pos)) (* (aref cur-buf-l 0) volume)) | |
200 | + ;; (incf (sample-right (aref smp out-pos)) (* (aref cur-buf-r 0) volume)) | |
201 | + | |
202 | + (incf (sample-left smp) (* (aref cur-buf-l 0) volume)) | |
203 | + (incf (sample-right smp) (* (aref cur-buf-r 0) volume)) | |
204 | + ;;) | |
205 | + | |
206 | + (incf cur-sample-num len) | |
207 | + (setf last-sample-num next-sample-num) | |
208 | + nil))) | |
209 | + | |
210 | +(define-typed-fn %resampler-downsample ((resampler rs) (abstract-chip chip) (simple-vector smp) (t/uint32 len)) | |
211 | + (null) | |
212 | + "High quality downsampling." | |
213 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
214 | + (with-output-buffers (cur-buf-l cur-buf-r (resampler-stream-buffers rs)) | |
215 | + (with-typed-slots ((t/uint32 cur-sample-num next-sample-num last-sample-num) | |
216 | + (t/uint16 volume) | |
217 | + (t/sample last-sample next-sample) | |
218 | + ((or null function) update-fn)) | |
219 | + chip | |
220 | + (let* ((chip-sample-rate (chip-sample-rate chip)) | |
221 | + (in-pos-l (coerce-to-uint32 | |
222 | + (truncate (muffling | |
223 | + (* +resampler-fact+ (+ cur-sample-num len) chip-sample-rate)) | |
224 | + (resampler-sample-rate rs)))) | |
225 | + (sample-fraction 0) | |
226 | + (temp-sample-l 0) | |
227 | + (temp-sample-r 0) | |
228 | + (in-pre 0) | |
229 | + (in-now 0) | |
230 | + (in-base 0) | |
231 | + (in-pos-next 0) | |
232 | + (in-pos 0) | |
233 | + (sample-count +resampler-fact+)) | |
234 | + (declare (type t/uint64 chip-sample-rate) | |
235 | + (type t/slint in-pos-l) | |
236 | + (type t/uint32 in-base in-pos in-pos-next in-pre in-now sample-fraction sample-count) | |
237 | + (type t/int64 temp-sample-l temp-sample-r)) | |
238 | + | |
239 | + (setf next-sample-num (%fp2i-ceil in-pos-l)) | |
240 | + (setf (aref cur-buf-l 0) (sample-left last-sample)) | |
241 | + (setf (aref cur-buf-r 0) (sample-right last-sample)) | |
242 | + | |
243 | + (with-stream-point (stream-point cur-buf-l cur-buf-r rs) | |
244 | + (if update-fn | |
245 | + (funcall (the function update-fn) chip stream-point 1 (- next-sample-num last-sample-num)) | |
246 | + (chip-update chip stream-point 1 (- next-sample-num last-sample-num))) | |
247 | + | |
248 | + (setf in-pos-l (coerce-to-uint32 | |
249 | + (truncate (muffling (* +resampler-fact+ cur-sample-num chip-sample-rate)) | |
250 | + (resampler-sample-rate rs)))) | |
251 | + | |
252 | + ;; Add 1.0 to avoid negative indicies. | |
253 | + (setf in-base (coerce-to-uint32 (+ +resampler-fact+ (- in-pos-l (* last-sample-num +resampler-fact+))))) | |
254 | + (setf in-pos-next in-base) | |
255 | + | |
256 | + ;; The original code expects us to have an array of return samples. | |
257 | + ;; However, the resampler is only ever called with a single element. | |
258 | + ;; In the interest of efficiency, the len parameter is ignored here, | |
259 | + ;; with the original code is commented out. | |
260 | + | |
261 | + ;; (loop for out-pos fixnum from 0 below len do | |
262 | + ;; (setf in-pos (+ in-base (truncate (* +resampler-fact+ out-pos chip-sample-rate) | |
263 | + ;; (resampler-sample-rate rs)))) | |
264 | + (setf in-pos in-pos-next) | |
265 | + ;; (setf in-pos-next (+ in-base (coerce-to-uint32 | |
266 | + ;; (truncate (* +resampler-fact+ (1+ out-pos) chip-sample-rate) | |
267 | + ;; (resampler-sample-rate rs))))) | |
268 | + (setf in-pos-next (+ in-base (coerce-to-uint32 | |
269 | + (truncate (muffling (* +resampler-fact+ chip-sample-rate)) | |
270 | + (resampler-sample-rate rs))))) | |
271 | + | |
272 | + ;; First fractional sample | |
273 | + (setf sample-fraction (%get-n-fraction in-pos)) | |
274 | + (cond | |
275 | + ((not (zerop sample-fraction)) | |
276 | + (setf in-pre (%fp2i-floor in-pos)) | |
277 | + (setf temp-sample-l (* (aref cur-buf-l in-pre) sample-fraction)) | |
278 | + (setf temp-sample-r (* (aref cur-buf-r in-pre) sample-fraction))) | |
279 | + | |
280 | + (t | |
281 | + (setf temp-sample-l 0) | |
282 | + (setf temp-sample-r 0))) | |
283 | + (setf sample-count sample-fraction) | |
284 | + | |
285 | + ;; Last fractional sample | |
286 | + (setf sample-fraction (%get-fraction in-pos-next)) | |
287 | + (setf in-pre (%fp2i-floor in-pos-next)) | |
288 | + (unless (zerop sample-fraction) | |
289 | + (incf temp-sample-l (* (aref cur-buf-l in-pre) sample-fraction)) | |
290 | + (incf temp-sample-r (* (aref cur-buf-r in-pre) sample-fraction)) | |
291 | + (incf sample-count sample-fraction)) | |
292 | + | |
293 | + ;; Whole samples in-between | |
294 | + (setf in-now (%fp2i-ceil in-pos)) | |
295 | + (incf sample-count (* (- in-pre in-now) +resampler-fact+)) | |
296 | + (loop while (< in-now in-pre) do | |
297 | + (incf temp-sample-l (* (aref cur-buf-l in-now) +resampler-fact+)) | |
298 | + (incf temp-sample-r (* (aref cur-buf-r in-now) +resampler-fact+)) | |
299 | + (incf in-now)) | |
300 | + | |
301 | + ;; (incf (sample-left (aref smp out-pos)) | |
302 | + ;; (coerce-to-int32 (truncate (* temp-sample-l volume) sample-count))) | |
303 | + ;; (incf (sample-right (aref smp out-pos)) | |
304 | + ;; (coerce-to-int32 (truncate (* temp-sample-r volume) sample-count))) | |
305 | + | |
306 | + (muffling | |
307 | + (incf (sample-left smp) (coerce-to-int32 (truncate (* temp-sample-l volume) sample-count))) | |
308 | + (incf (sample-right smp) (coerce-to-int32 (truncate (* temp-sample-r volume) sample-count)))) | |
309 | + ;;) | |
310 | + | |
311 | + (setf (sample-left last-sample) (aref cur-buf-l in-pre)) | |
312 | + (setf (sample-right last-sample) (aref cur-buf-r in-pre)) | |
313 | + ;; (incf cur-sample-num len) | |
314 | + (incf cur-sample-num) | |
315 | + (setf last-sample-num next-sample-num))))) | |
316 | + nil) | |
317 | + | |
318 | +(define-typed-fn resampler-resample ((resampler rs) (hash-table chip-table) (simple-vector smp) (t/uint32 len)) | |
319 | + (null) | |
320 | + "Resamples audio data to SMP. In all cases, LEN should be 1." | |
321 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
322 | + #+satou-debug (unless (= len 1) | |
323 | + (error "LEN is not 1")) | |
324 | + | |
325 | + ;; Update and resample all of the chips. | |
326 | + (with-each-chip (chip chip-table) | |
327 | + (with-typed-slots ((t/uint32 last-sample-num sample-rate next-sample-num cur-sample-num) | |
328 | + (boolean is-paired?) | |
329 | + ((or null abstract-chip) paired)) | |
330 | + chip | |
331 | + (ecase (chip-resampler-type chip) | |
332 | + (:old (%resampler-old rs chip smp len)) | |
333 | + (:upsampling (%resampler-upsample rs chip smp len)) | |
334 | + (:copy (%resampler-copy rs chip smp len)) | |
335 | + (:downsampling (%resampler-downsample rs chip smp len))) | |
336 | + | |
337 | + (when (>= last-sample-num sample-rate) | |
338 | + (decf last-sample-num sample-rate) | |
339 | + (decf next-sample-num sample-rate) | |
340 | + (setf cur-sample-num (- cur-sample-num (resampler-sample-rate rs)))) | |
341 | + | |
342 | + ;; TODO ensure this doesn't update a chip twice. | |
343 | + (when (chip-is-paired-p chip) | |
344 | + (with-typed-slots ((t/uint32 last-sample-num sample-rate next-sample-num cur-sample-num)) | |
345 | + paired | |
346 | + (ecase (chip-resampler-type paired) | |
347 | + (:old (%resampler-old rs paired smp len)) | |
348 | + (:upsampling (%resampler-upsample rs paired smp len)) | |
349 | + (:copy (%resampler-copy rs paired smp len)) | |
350 | + (:downsampling (%resampler-downsample rs paired smp len))) | |
351 | + | |
352 | + (when (>= last-sample-num sample-rate) | |
353 | + (decf last-sample-num sample-rate) | |
354 | + (decf next-sample-num sample-rate) | |
355 | + (setf cur-sample-num (- cur-sample-num (resampler-sample-rate rs)))))))) | |
356 | + nil) |
@@ -0,0 +1,247 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +;;;; | |
19 | +;;;; VGM Decompression System | |
20 | +;;;; | |
21 | +;;;; This system is used to decompress VGM files, and allows for decompression | |
22 | +;;;; methods beyond the standard GZip compression. It is designed to be | |
23 | +;;;; extensible. | |
24 | +;;;; | |
25 | + | |
26 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
27 | +;;; | |
28 | +;;; Package Definition | |
29 | +;;; | |
30 | + | |
31 | +(defpackage :satou/decompression | |
32 | + (:use :common-lisp :satou :cl-sdm) | |
33 | + (:shadowing-import-from :common-lisp #:write-string) | |
34 | + (:export | |
35 | + #:t/extension-list | |
36 | + #:func-set | |
37 | + #:func-set-extensions | |
38 | + #:func-set-checker | |
39 | + #:func-set-decompressor | |
40 | + #:all-known-schemes | |
41 | + #:get-func-set | |
42 | + #:duplicate-func-set-error | |
43 | + #:register | |
44 | + #:maybe-decompress | |
45 | + #:get-hint)) | |
46 | + | |
47 | +(in-package :satou/decompression) | |
48 | + | |
49 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
50 | +;;; | |
51 | +;;; Core Decompression System | |
52 | +;;; | |
53 | + | |
54 | +(defun %all-simple-string-p (list) | |
55 | + (every #'(lambda (x) | |
56 | + (typep x 'simple-string)) | |
57 | + list)) | |
58 | + | |
59 | +(deftype t/extension-list () | |
60 | + '(and list (satisfies %all-simple-string-p))) | |
61 | + | |
62 | +(defstruct func-set | |
63 | + (extensions '() :type t/extension-list) | |
64 | + (checker nil :type (or null function)) | |
65 | + (decompressor nil :type (or null function))) | |
66 | + | |
67 | +(define-condition duplicate-func-set-error (satou-error) | |
68 | + ()) | |
69 | + | |
70 | +(declaim (type hash-table *functions*)) | |
71 | +(defparameter *functions* (make-hash-table :synchronized t)) | |
72 | + | |
73 | +(defun get-func-set (ident) | |
74 | + "Checks to see if IDENT is a FUNC-SET that has been registered using REGISTER. | |
75 | +IDENT should be a keyword representing a FUNC-SET, though this is not checked | |
76 | +internally." | |
77 | + (nth-value 0 (gethash ident *functions* nil))) | |
78 | + | |
79 | +(defun all-known-schemes () | |
80 | + "Returns all of the identifiers for the currently registered decompression | |
81 | +schemes as a list of KEYWORDs." | |
82 | + (loop for k being the hash-keys in *functions* | |
83 | + collect k)) | |
84 | + | |
85 | +(defun register (ident check-fn decomp-fn &rest extensions) | |
86 | + "Registers a new method for checking for compressed data, and decompressing | |
87 | +that data. IDENT can be used to reference the FUNC-SET using GET-FUNC-SET after | |
88 | +registering. | |
89 | + | |
90 | +EXTENSIONS must consist entirely of strings, and is a set of file extensions | |
91 | +that can be used by GET-HINT for this compression scheme. The extensions should | |
92 | +not include the leading period, and should be entirely lowercase, e.g. \"vgz\". | |
93 | +EXTENSIONS cannot contain the string \"vgm\", regardless of case. | |
94 | + | |
95 | +CHECK-FN must be a function that accepts a single argument (an open stream), and | |
96 | +returns a generalized boolean indicating whether the stream contains compressed | |
97 | +data or not. | |
98 | + | |
99 | +DECOMP-FN must be a function that accepts a single argument (an open stream), | |
100 | +and returns a FLEXI-STREAMS:IN-MEMORY-STREAM that is ready to be read from and | |
101 | +that contains the decompressed data. | |
102 | + | |
103 | +Note that IDENT cannot be :NONE." | |
104 | + ;; Do a few checks | |
105 | + (check-type ident symbol) | |
106 | + (check-type check-fn function) | |
107 | + (check-type decomp-fn function) | |
108 | + (unless (keywordp ident) (error "IDENT must be a keyword")) | |
109 | + (unless (every #'stringp extensions) (error "EXTENSIONS must consist entirely of strings.")) | |
110 | + (when (eq ident :none) (error "IDENT cannot be :NONE")) | |
111 | + (when (= (length extensions) 0) (error "EXTENSIONS cannot be empty")) | |
112 | + (when (find "vgm" extensions :test #'caseless-string=) | |
113 | + (error "EXTENSIONS cannot contain the extension 'vgm'")) | |
114 | + | |
115 | + (when (hash-table-contains-p *functions* ident) | |
116 | + (restart-case | |
117 | + (satou-error (duplicate-func-set-error) "Duplicate decompression function set found: ~a" ident) | |
118 | + (replace-fn-set () | |
119 | + :report "Replace current function set" | |
120 | + #+satou-verbose-debug (sdm-log:warn! "Replacing compression method: ~a" ident) | |
121 | + (remhash ident *functions*)))) | |
122 | + | |
123 | + (setf (gethash ident *functions*) (make-func-set :extensions (mapcar #'(lambda (x) | |
124 | + (coerce x 'simple-string)) | |
125 | + extensions) | |
126 | + :checker check-fn | |
127 | + :decompressor decomp-fn)) | |
128 | + | |
129 | + #+satou-verbose-debug (sdm-log:dlog! "Registered compression method: ~a (~{~a~^, ~})" ident extensions) | |
130 | + ident) | |
131 | + | |
132 | +(defun maybe-decompress (stream &optional hint) | |
133 | + "Checks to see if STREAM needs to be decompressed. If it does, this | |
134 | +decompresses the stream into RAM. This then returns either a | |
135 | +FLEXI-STREAMS:IN-MEMORY-STREAM that can be used to read uncompressed data, or | |
136 | +the original STREAM if it was not compressed. STREAM must support | |
137 | +FILE-POSITION. | |
138 | + | |
139 | +If HINT is provided and is keyword representing a known decompression | |
140 | +scheme (see GET-FUNC-SET), then that scheme will be tried first to offer a | |
141 | +speedup in load times. You can get a hint by using GET-HINT." | |
142 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0) (safety 1))) | |
143 | + (check-type hint (or null symbol)) | |
144 | + | |
145 | + (satou::muffling | |
146 | + (let ((start-pos (file-position stream))) | |
147 | + ;; If we have a hint, then see if we can decompress the data using that | |
148 | + ;; hint. | |
149 | + (when hint | |
150 | + (unless (keywordp hint) (error "HINT must be a KEYWORD")) | |
151 | + (when (eq hint :none) (error "HINT cannot be :NONE")) | |
152 | + (multiple-value-bind (hinted-set found) | |
153 | + (gethash hint *functions*) | |
154 | + | |
155 | + ;; We have a hint, let's see if the stream contains compressed data | |
156 | + ;; matching the hint. | |
157 | + (when (and found (funcall (func-set-checker hinted-set) stream)) | |
158 | + ;; Hint was successful, rewind to the initial position and decompress. | |
159 | + (file-position stream start-pos) | |
160 | + (return-from maybe-decompress (funcall (func-set-decompressor hinted-set) stream))))) | |
161 | + | |
162 | + ;; The hint was not successful, or not provided. Check all known | |
163 | + ;; compression methods. | |
164 | + (file-position stream start-pos) ;; Ensure we're at the correct position. | |
165 | + (maphash #'(lambda (key fn-set) | |
166 | + (declare (ignore key)) | |
167 | + (when (funcall (func-set-checker fn-set) stream) | |
168 | + ;; Compressed data detected, decompress and return. | |
169 | + (file-position stream start-pos) | |
170 | + (return-from maybe-decompress (funcall (func-set-decompressor fn-set) stream)))) | |
171 | + *functions*) | |
172 | + | |
173 | + ;; Not compressed with a known method, return the original stream. | |
174 | + (file-position stream start-pos) | |
175 | + stream))) | |
176 | + | |
177 | +(defun get-hint (filename) | |
178 | + "Looks at the extension of FILENAME using PATHNAME-TYPE and attempts to guess | |
179 | +the compression method (if any). This will return a KEYWORD if it was able to | |
180 | +guess, or NIL otherwise. | |
181 | + | |
182 | +If a keyword is returned, it will reference one of the registered compression | |
183 | +methods (see GET-FUNC-SET and REGISTER). This keyword can then be passed to | |
184 | +MAYBE-DECOMPRESS. | |
185 | + | |
186 | +This returns NIL if it thinks that FILENAME is an uncompressed VGM file." | |
187 | + (check-type filename (or string pathname)) | |
188 | + (let ((ext (string-downcase (pathname-type filename)))) | |
189 | + (maphash #'(lambda (ident set) | |
190 | + (when (find ext (func-set-extensions set) :test #'string=) | |
191 | + ;; Found a known extension. | |
192 | + (return-from get-hint ident))) | |
193 | + *functions*)) | |
194 | + ;; Extension was not found, or it's uncompressed. | |
195 | + nil) | |
196 | + | |
197 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
198 | +;;; | |
199 | +;;; GZip Compression Handling | |
200 | +;;; | |
201 | + | |
202 | +(defun %gzipped-p (stream) | |
203 | + (let ((buf (new-array 3 t/uint8)) | |
204 | + (pos (file-position stream))) | |
205 | + (unwind-protect | |
206 | + (read-sequence buf stream) | |
207 | + (file-position stream pos)) | |
208 | + (and (= (aref buf 0) #x1F) | |
209 | + (= (aref buf 1) #x8B) | |
210 | + (= (aref buf 2) #x08)))) | |
211 | + | |
212 | +(defun %decompress-gzipped-stream (stream) | |
213 | + (flex:make-in-memory-input-stream (chipz:decompress nil 'chipz:gzip stream))) | |
214 | + | |
215 | +(handler-bind | |
216 | + ((duplicate-func-set-error | |
217 | + (lambda (err) | |
218 | + (declare (ignore err)) | |
219 | + (invoke-restart 'replace-fn-set)))) | |
220 | + (register :gzip #'%gzipped-p #'%decompress-gzipped-stream "vgz" "gz")) | |
221 | + | |
222 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
223 | +;;; | |
224 | +;;; BZip2 Compression Handling | |
225 | +;;; | |
226 | + | |
227 | +(defun %bzip2-compressed-p (stream) | |
228 | + (let ((buf (new-array 4 t/uint8)) | |
229 | + (pos (file-position stream))) | |
230 | + (unwind-protect | |
231 | + (read-sequence buf stream) | |
232 | + (file-position stream pos)) | |
233 | + (and (= (aref buf 0) #.(char-code #\B)) | |
234 | + (= (aref buf 1) #.(char-code #\Z)) | |
235 | + (= (aref buf 2) #.(char-code #\h)) | |
236 | + (and (>= (aref buf 3) #.(char-code #\1)) | |
237 | + (<= (aref buf 3) #.(char-code #\9)))))) | |
238 | + | |
239 | +(defun %decompress-bzip2-stream (stream) | |
240 | + (flex:make-in-memory-input-stream (chipz:decompress nil 'chipz:bzip2 stream))) | |
241 | + | |
242 | +(handler-bind | |
243 | + ((duplicate-func-set-error | |
244 | + (lambda (err) | |
245 | + (declare (ignore err)) | |
246 | + (invoke-restart 'replace-fn-set)))) | |
247 | + (register :bzip2 #'%bzip2-compressed-p #'%decompress-bzip2-stream "vgb" "bz2")) |
@@ -0,0 +1,76 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +(define-condition vgm-player-settings-error (satou-error) | |
19 | + ()) | |
20 | + | |
21 | +(deftype t/ym2151-core () | |
22 | + '(member :mame)) | |
23 | + | |
24 | +(defclass vgm-player-settings () | |
25 | + ((sample-rate | |
26 | + :initarg :sample-rate | |
27 | + :initform +default-sample-rate+ | |
28 | + :type t/uint32 | |
29 | + :accessor settings-sample-rate | |
30 | + :documentation "The target sample rate to use for playback.") | |
31 | + | |
32 | + (dmg-boost-wave-chan | |
33 | + :initarg :dmg-boost-wave-chan | |
34 | + :initform t | |
35 | + :type boolean | |
36 | + :accessor settings-dmg-boost-wave-chan-p | |
37 | + :documentation "When T, then the wave channel on the DMG is boosted.") | |
38 | + | |
39 | + (ym2151-core | |
40 | + :initarg :ym2151-core | |
41 | + :initform :mame | |
42 | + :type symbol | |
43 | + :accessor settings-ym2151-core | |
44 | + :documentation "The emulation core to use for the YM2151.") | |
45 | + | |
46 | + (huc6280-core | |
47 | + :initarg :huc6280-core | |
48 | + :initform :ootake | |
49 | + :type satou-chips:t/huc6280-core | |
50 | + :accessor settings-huc6280-core | |
51 | + :documentation "The emulation core to use for the HuC6280.")) | |
52 | + (:documentation "A set of settings that controls how VGM files are played back | |
53 | +by the VGM-PLAYER class.")) | |
54 | + | |
55 | +(defmethod initialize-instance :after ((obj vgm-player-settings) &key &allow-other-keys) | |
56 | + (with-slots (sample-rate dmg-boost-wave-chan ym2151-core huc6280-core) | |
57 | + obj | |
58 | + (unless (typep sample-rate 't/uint32) (satou-error (vgm-player-settings-error) "SAMPLE-RATE must be a T/UINT32")) | |
59 | + (unless (typep dmg-boost-wave-chan 'boolean) | |
60 | + (satou-error (vgm-player-settings-error) "DMG-BOOST-WAVE-CHAN must be a BOOLEAN")) | |
61 | + (unless (typep ym2151-core 't/ym2151-core) | |
62 | + (satou-error (vgm-player-settings-error) "YM2151-CORE must be a T/YM2151-CORE")) | |
63 | + (unless (typep huc6280-core 'satou-chips:t/huc6280-core) | |
64 | + (satou-error (vgm-player-settings-error) "HUC6280-CORE must be a T/HUC6280-CORE")))) | |
65 | + | |
66 | +(defmethod (setf settings-sample-rate) :before (value (object vgm-player-settings)) | |
67 | + (check-type value t/uint32)) | |
68 | + | |
69 | +(defmethod (setf settings-dmg-boost-wave-chan-p) :before (value (object vgm-player-settings)) | |
70 | + (check-type value boolean)) | |
71 | + | |
72 | +(defmethod (setf settings-ym2151-core) :before (value (object vgm-player-settings)) | |
73 | + (check-type value t/ym2151-core)) | |
74 | + | |
75 | +(defmethod (setf settings-huc6280-core) :before (value (object vgm-player-settings)) | |
76 | + (check-type value satou-chips:t/huc6280-core)) |
@@ -0,0 +1,782 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; Based on VGMPlay, Copyright (C) Valley Bell | |
4 | +;;;; | |
5 | +;;;; This program is free software: you can redistribute it and/or | |
6 | +;;;; modify it under the terms of the GNU Affero General Public | |
7 | +;;;; License as published by the Free Software Foundation, either | |
8 | +;;;; version 3 of the License, or (at your option) any later version. | |
9 | +;;;; | |
10 | +;;;; This program is distributed in the hope that it will be useful, | |
11 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | +;;;; Affero General Public License for more details. | |
14 | +;;;; | |
15 | +;;;; You should have received a copy of the GNU Affero General Public License | |
16 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
17 | +(in-package :satou) | |
18 | + | |
19 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
20 | +;;; | |
21 | +;;; VGM Player Struct and Reader Functions | |
22 | +;;; | |
23 | + | |
24 | +(defstruct (vgm-player (:constructor %make-vgm-player) | |
25 | + (:conc-name %player-)) | |
26 | + (settings (make-instance 'vgm-player-settings) :type vgm-player-settings) | |
27 | + (vgm nil :type (or null vgm-file)) | |
28 | + (sample-rate +default-sample-rate+ :type t/uint32) | |
29 | + (min-buffer-size 100 :type t/int32) | |
30 | + (play-time 0 :type t/int32) | |
31 | + (main-volume 1.0d0 :type double-float) | |
32 | + (at-end-p nil :type boolean) | |
33 | + (times-played 0 :type t/uint32) | |
34 | + (chip-table (make-hash-table) :type hash-table) | |
35 | + (samples-per-buffer 100 :type t/int32) | |
36 | + (volume-modifier 1.0d0 :type double-float) | |
37 | + | |
38 | + ;;; | |
39 | + ;;; Playback Fields | |
40 | + ;;; | |
41 | + | |
42 | + (vgm-sample-played 0 :type t/int32) | |
43 | + (vgm-sample-pos 0 :type t/int32) | |
44 | + (vgm-playback-rate 0 :type t/uint32) | |
45 | + (vgm-playback-rate-mul 0 :type t/uint32) | |
46 | + (vgm-playback-rate-div 0 :type t/uint32) | |
47 | + (vgm-sample-rate-mul 0 :type t/uint32) | |
48 | + (vgm-sample-rate-div 0 :type t/uint32) | |
49 | + (vgm-sample-rate +vgm-sample-rate+ :type t/uint32) | |
50 | + | |
51 | + ;;; | |
52 | + ;;; Additional Fields | |
53 | + ;;; | |
54 | + | |
55 | + (resampler nil :type (or null resampler)) | |
56 | + (output-volume 1.0d0 :type double-float) | |
57 | + (ip 0 :type fixnum) | |
58 | + (stream-buffers nil :type (or null t/output-buffer)) | |
59 | + (temp-buf (vector 0 0) :type simple-vector) | |
60 | + (dac-control nil :type (or null satou/dac:dac-controller)) | |
61 | + (playing-p nil :type boolean) | |
62 | + (chip-set-created-p nil :type boolean)) | |
63 | + | |
64 | +(define-typed-fn vgm-player-settings ((vgm-player player)) | |
65 | + (vgm-player-settings t) | |
66 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
67 | + (%player-settings player)) | |
68 | + | |
69 | +(define-typed-fn vgm-player-vgm((vgm-player player)) | |
70 | + (vgm-file t) | |
71 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
72 | + (%player-vgm player)) | |
73 | + | |
74 | +(define-typed-fn vgm-player-sample-rate ((vgm-player player)) | |
75 | + (t/uint32 t) | |
76 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
77 | + (%player-sample-rate player)) | |
78 | + | |
79 | +(define-typed-fn vgm-player-min-buffer-size ((vgm-player player)) | |
80 | + (t/int32 t) | |
81 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
82 | + (%player-min-buffer-size player)) | |
83 | + | |
84 | +(define-typed-fn vgm-player-play-time ((vgm-player player)) | |
85 | + (t/int32 t) | |
86 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
87 | + (%player-play-time player)) | |
88 | + | |
89 | +(muffling | |
90 | + (define-typed-fn vgm-player-main-volume ((vgm-player player)) | |
91 | + (double-float t) | |
92 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
93 | + (%player-main-volume player))) | |
94 | + | |
95 | +(define-typed-fn vgm-player-at-end-p ((vgm-player player)) | |
96 | + (boolean t) | |
97 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
98 | + (%player-at-end-p player)) | |
99 | + | |
100 | +(define-typed-fn vgm-player-times-played ((vgm-player player)) | |
101 | + (t/uint32 t) | |
102 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
103 | + (%player-times-played player)) | |
104 | + | |
105 | +(define-typed-fn vgm-player-chip-table ((vgm-player player)) | |
106 | + (hash-table t) | |
107 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
108 | + (%player-chip-table player)) | |
109 | + | |
110 | +(define-typed-fn vgm-player-samples-per-buffer ((vgm-player player)) | |
111 | + (t/int32 t) | |
112 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
113 | + (%player-samples-per-buffer player)) | |
114 | + | |
115 | +(muffling | |
116 | + (define-typed-fn vgm-player-volume-modifier ((vgm-player player)) | |
117 | + (double-float t) | |
118 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
119 | + (%player-volume-modifier player))) | |
120 | + | |
121 | +(define-typed-fn vgm-player-playing-p ((vgm-player player)) | |
122 | + (boolean t) | |
123 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
124 | + (%player-playing-p player)) | |
125 | + | |
126 | +(defmethod print-object ((player vgm-player) out) | |
127 | + (print-unreadable-object (player out :type t) | |
128 | + (format out "Sample Rate: ~a, Playing: ~:[no~;yes~], Timed Played: ~:d" | |
129 | + (vgm-player-sample-rate player) | |
130 | + (vgm-player-playing-p player) | |
131 | + (vgm-player-times-played player)))) | |
132 | + | |
133 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
134 | +;;; | |
135 | +;;; Internal VGM Player Utility Functions | |
136 | +;;; | |
137 | + | |
138 | +(define-typed-fn %player-get-data-byte ((vgm-player player) (fixnum idx)) | |
139 | + (t/uint8 t) | |
140 | + "Returns the data byte at IDX in the data stream." | |
141 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
142 | + (aref (the t/uint8-array (vgm-file-data (%player-vgm player))) idx)) | |
143 | + | |
144 | +(define-typed-fn %player-get-chip-number ((vgm-player player)) | |
145 | + (t/uint8 t) | |
146 | + "Gets the chip number from the data stream." | |
147 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
148 | + (ash (logand (%player-get-data-byte player (1+ (%player-ip player))) #x80) -7)) | |
149 | + | |
150 | +(define-typed-fn %player-get-int16-le ((vgm-player player) (fixnum at)) | |
151 | + (fixnum t) | |
152 | + "Gets a little-endian signed 16-bit integer from the data stream." | |
153 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
154 | + (logior (ash (%player-get-data-byte player (1+ at)) 8) | |
155 | + (%player-get-data-byte player at))) | |
156 | + | |
157 | +(define-typed-fn %player-get-int16-be ((vgm-player player) (fixnum at)) | |
158 | + (fixnum t) | |
159 | + "Gets a big-endian signed 16-bit integer from the data stream." | |
160 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
161 | + (logior (%player-get-data-byte player at) | |
162 | + (ash (%player-get-data-byte player (1+ at)) 8))) | |
163 | + | |
164 | +(define-typed-fn %player-get-int32-le ((vgm-player player) (fixnum at)) | |
165 | + (fixnum t) | |
166 | + "Gets a little-endian signed 32-bit integer from the data stream." | |
167 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
168 | + (coerce-to-int32 | |
169 | + (logior (ash (%player-get-data-byte player (+ at 3)) 24) | |
170 | + (ash (%player-get-data-byte player (+ at 2)) 16) | |
171 | + (ash (%player-get-data-byte player (+ at 1)) 8) | |
172 | + (%player-get-data-byte player at)))) | |
173 | + | |
174 | +(define-typed-fn %player-pcm-sample->vgm-sample ((vgm-player player) (t/int64 sample-num)) | |
175 | + (t/uint32 t) | |
176 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
177 | + (pcm-sample->vgm-sample sample-num (%player-vgm-sample-rate-div player) (%player-vgm-sample-rate-mul player))) | |
178 | + | |
179 | +(define-typed-fn %player-vgm-sample->pcm-sample ((vgm-player player) (t/int64 sample-num)) | |
180 | + (t/uint32 t) | |
181 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
182 | + (vgm-sample->pcm-sample sample-num (%player-vgm-sample-rate-div player) (%player-vgm-sample-rate-mul player))) | |
183 | + | |
184 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
185 | +;;; | |
186 | +;;; VGM Player Instructions | |
187 | +;;; | |
188 | +;;; Each instruction returns the number of instructions that the player should | |
189 | +;;; advance after calling these functions. | |
190 | +;;; | |
191 | + | |
192 | +(defmacro define-player-instruction (name (player-argument ip-var) &body forms) | |
193 | + `(define-typed-fn ,name ((vgm-player ,player-argument)) | |
194 | + (fixnum t) | |
195 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
196 | + (let ((,ip-var (%player-ip ,player-argument))) | |
197 | + ,@forms))) | |
198 | + | |
199 | +(define-player-instruction %player-huc6280-write (player ip) | |
200 | + (let* ((cur-chip (%player-get-chip-number player)) | |
201 | + (chip (muffling (aref (the (vector abstract-chip) (gethash :huc6280 (%player-chip-table player))) cur-chip)))) | |
202 | + (chip-write chip | |
203 | + (logand (%player-get-data-byte player (+ ip 1)) #x7F) | |
204 | + (%player-get-data-byte player (+ ip 2))) | |
205 | + 3)) | |
206 | + | |
207 | +(define-player-instruction %player-c352-write (player ip) | |
208 | + (let* ((cur-chip (%player-get-chip-number player)) | |
209 | + (chip (muffling (aref (the (vector abstract-chip) (gethash :c352 (%player-chip-table player))) cur-chip))) | |
210 | + (address (coerce-to-uint32 | |
211 | + (logior (ash (logand (%player-get-data-byte player (+ ip 1)) #x7F) 8) | |
212 | + (%player-get-data-byte player (+ ip 2)))))) | |
213 | + (chip-write chip | |
214 | + address | |
215 | + (coerce-to-uint16 | |
216 | + (logior (ash (%player-get-data-byte player (+ ip 3)) 8) | |
217 | + (%player-get-data-byte player (+ ip 4))))) | |
218 | + 5)) | |
219 | + | |
220 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
221 | +;;; | |
222 | +;;; VGM Player PCM Instructions | |
223 | +;;; | |
224 | +;;; Each instruction returns the number of instructions that the player should | |
225 | +;;; advance after calling these functions. | |
226 | +;;; | |
227 | + | |
228 | +(define-typed-fn %player-load-pcm-stream ((vgm-player player)) | |
229 | + (t/int32) | |
230 | + (declare (optimize (speed 1) (debug 3) (safety 1) (compilation-speed 0))) | |
231 | + (let* ((ip (%player-ip player)) | |
232 | + (temp-byte (%player-get-data-byte player (+ ip 2))) | |
233 | + (temp-long (%player-get-int32-le player (+ ip 3))) | |
234 | + (cur-chip 0)) | |
235 | + (declare (t/uint8 temp-byte) | |
236 | + (t/int32 temp-long) | |
237 | + (type fixnum cur-chip)) | |
238 | + | |
239 | + (when (flag? temp-long #x80000000) | |
240 | + (logandf temp-long #x7FFFFFFF) | |
241 | + (setf cur-chip 1)) | |
242 | + | |
243 | + (with-typed-slots ((t/uint8-array data)) | |
244 | + (%player-vgm player) | |
245 | + | |
246 | + ;; Determine how to load the data | |
247 | + (case (logand temp-byte #xC0) | |
248 | + ((#x00 #x40) ;; Database block | |
249 | + (when (zerop (%player-times-played player)) | |
250 | + (dlog "PCM loading: Database block") | |
251 | + (satou/dac:dac-add-pcm-data (%player-dac-control player) | |
252 | + temp-byte | |
253 | + (coerce-to-uint32 temp-long) | |
254 | + (subseq data (+ ip 7))))) | |
255 | + | |
256 | + (#x80 ;; ROM/RAM dump | |
257 | + (dlog "PCM Loading: ROM/RAM dump") | |
258 | + | |
259 | + (when (zerop (%player-times-played player)) | |
260 | + (let ((rom-size (%player-get-int32-le player (+ ip 7))) | |
261 | + (data-start (%player-get-int32-le player (+ ip #x0B))) | |
262 | + (data-len (- temp-long 8)) | |
263 | + (rom-data-pos (+ ip #x0F))) | |
264 | + (dlog " PCM ROM size: ~a, data pos: ~a, data len: ~a, ROM pos: ~a" | |
265 | + rom-size data-start data-len rom-data-pos) | |
266 | + | |
267 | + ;; Write the PCM data to the correct chip | |
268 | + (case temp-byte | |
269 | + (#x92 ;; C352 ROM image | |
270 | + (dlog " Loading C352 ROM image") | |
271 | + (chip-write-rom (aref (gethash :c352 (%player-chip-table player)) cur-chip) | |
272 | + rom-size data-start data-len (subseq data (+ ip #x0F)))) | |
273 | + | |
274 | + (otherwise | |
275 | + (sdm-log:warn! "Unsupported PCM ROM image type: $~2,'0x" temp-byte)))))) | |
276 | + | |
277 | + (#xC0 ;; RAM write | |
278 | + (let* ((data-start 0) | |
279 | + (data-len 0) | |
280 | + (rom-data nil)) | |
281 | + (setf data-start (cond | |
282 | + ((flag? temp-byte #x20) | |
283 | + (setf data-start (%player-get-int16-le player (+ ip #x07))) | |
284 | + (setf data-len (- temp-long #x02)) | |
285 | + (subseq data (+ ip #x09))) | |
286 | + (t | |
287 | + (setf data-start (%player-get-int32-le player (+ ip #x07))) | |
288 | + (setf data-len (- temp-long #x04)) | |
289 | + (subseq data (+ ip #x0B))))) | |
290 | + | |
291 | + (case temp-byte | |
292 | + (otherwise | |
293 | + (sdm-log:warn! "Unsupported chip RAM write: $~2,'0x" temp-byte))))))) | |
294 | + (+ 7 temp-long))) | |
295 | + | |
296 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
297 | +;;; | |
298 | +;;; More Internal VGM Player Functions | |
299 | +;;; | |
300 | + | |
301 | +(define-typed-fn %player-interpret-vgm ((vgm-player player) (t/uint32 sample-count)) | |
302 | + (null) | |
303 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
304 | + | |
305 | + (let ((inst 0) ;; The command instruction | |
306 | + (sample-played (%player-pcm-sample->vgm-sample player (+ (%player-vgm-sample-played player) sample-count))) | |
307 | + (data (vgm-file-data (%player-vgm player))) | |
308 | + (temp-byte 0) | |
309 | + (temp-short 0) | |
310 | + (stream-id 0) | |
311 | + (header (vgm-file-header (%player-vgm player))) | |
312 | + | |
313 | + ;; TODO: Most instructions calculate this themselves. A few do not. | |
314 | + ;; This should be made consistent. | |
315 | + (cur-chip 0)) | |
316 | + (declare (type t/uint8 inst temp-byte stream-id cur-chip) | |
317 | + (t/uint16 temp-short) | |
318 | + (t/uint8-array data) | |
319 | + (t/uint32 sample-played) | |
320 | + (vgm-file-header header) | |
321 | + | |
322 | + (ignore stream-id temp-short temp-byte)) | |
323 | + | |
324 | + (loop while (<= (%player-vgm-sample-pos player) sample-played) do | |
325 | + (setf inst (aref data (%player-ip player))) | |
326 | + ;;(dlog "Next instruction: ~2,'0x" inst) | |
327 | + | |
328 | + (cond | |
329 | + ((and (>= inst #x70) (<= inst #x8F)) | |
330 | + (case (logand inst #xF0) | |
331 | + (#x70 | |
332 | + (incf (%player-vgm-sample-pos player) (1+ (logand inst #x0F)))) | |
333 | + | |
334 | + (#x80 | |
335 | + ;;(setf temp-byte (satou/dac:dac-get-dac-from-pcm-bank (%player-dac-control player))) | |
336 | + (when (/= (vgm-header-ym2612-clock header) 0) | |
337 | + ;; TODO ym2612 stuff | |
338 | + ) | |
339 | + (incf (%player-vgm-sample-pos player) (logand inst #x0F)))) | |
340 | + (incf (%player-ip player))) | |
341 | + | |
342 | + (t | |
343 | + ;; This first CASE statement may modify the instruction. | |
344 | + (case inst | |
345 | + (#xA4 | |
346 | + (when (flag? (vgm-header-ym2151-clock header) #x40000000) | |
347 | + (decf inst #x50) | |
348 | + (setf cur-chip #x01))) | |
349 | + | |
350 | + (#xA5 | |
351 | + (when (flag? (vgm-header-ym2203-clock header) #x40000000) | |
352 | + (decf inst #x50) | |
353 | + (setf cur-chip #x01))) | |
354 | + | |
355 | + ((#xA6 #xA7) | |
356 | + (when (flag? (vgm-header-ym2608-clock header) #x40000000) | |
357 | + (decf inst #x50) | |
358 | + (setf cur-chip #x01))) | |
359 | + | |
360 | + ((#xA8 #xA9) | |
361 | + (when (flag? (vgm-header-ym2610-clock header) #x40000000) | |
362 | + (decf inst #x50) | |
363 | + (setf cur-chip #x01))) | |
364 | + | |
365 | + (#xAd | |
366 | + (when (flag? (vgm-header-ymz280b-clock header) #x40000000) | |
367 | + (decf inst #x50) | |
368 | + (setf cur-chip #x01)))) | |
369 | + | |
370 | + ;; The second case statement is where the commands are dispatched. | |
371 | + (case inst | |
372 | + ;;; | |
373 | + ;;; Chip commands | |
374 | + ;;; | |
375 | + (#xB9 (incf (%player-ip player) (%player-huc6280-write player))) | |
376 | + (#xE1 (incf (%player-ip player) (%player-c352-write player))) | |
377 | + | |
378 | + ;;; | |
379 | + ;;; DAC commands | |
380 | + ;;; | |
381 | + ;; TODO | |
382 | + | |
383 | + ;;; | |
384 | + ;;; Other Commands | |
385 | + ;;; | |
386 | + | |
387 | + ;; TODO | |
388 | + (#x67 (incf (%player-ip player) (%player-load-pcm-stream player))) | |
389 | + | |
390 | + (#x61 ;; Delay sample by X amount | |
391 | + (incf (%player-vgm-sample-pos player) (%player-get-int16-le player (1+ (%player-ip player)))) | |
392 | + (incf (%player-ip player) 3)) | |
393 | + | |
394 | + (#x62 ;; Delay sample by 1/60s | |
395 | + (incf (%player-vgm-sample-pos player) 735) | |
396 | + (incf (%player-ip player))) | |
397 | + | |
398 | + (#x63 ;; Delay sample by 1/50s | |
399 | + (incf (%player-vgm-sample-pos player) 882) | |
400 | + (incf (%player-ip player))) | |
401 | + | |
402 | + (#x66 ;; End of VGM | |
403 | + (incf (%player-times-played player)) | |
404 | + | |
405 | + (cond | |
406 | + ((/= (the t/uint32 (vgm-header-loop-offset header)) 0) | |
407 | + ;; We have loop data, so do the loop. | |
408 | + (with-typed-slots ((t/uint32 loop-offset data-offset loop-samples)) | |
409 | + header | |
410 | + (setf (%player-ip player) | |
411 | + (coerce-to-int32 (- (+ loop-offset +loop-offset-offset+) data-offset))) | |
412 | + | |
413 | + (cond | |
414 | + ;; Ensure we're within bounds | |
415 | + ((and (>= (%player-ip player) 0) (< (%player-ip player) (length data))) | |
416 | + (decf (%player-vgm-sample-pos player) (coerce-to-int32 loop-samples)) | |
417 | + (decf (%player-vgm-sample-played player) (%player-vgm-sample->pcm-sample player loop-samples)) | |
418 | + (setf sample-played | |
419 | + (%player-pcm-sample->vgm-sample player (+ (%player-vgm-sample-played player) sample-count)))) | |
420 | + | |
421 | + (t | |
422 | + (sdm-log:warn! "Aborting playback, instruction pointer ended up at an odd place") | |
423 | + (setf (%player-at-end-p player) t) | |
424 | + (%player-reset-chips player) | |
425 | + (return-from %player-interpret-vgm))))) | |
426 | + | |
427 | + (t | |
428 | + ;; No loop | |
429 | + (setf (%player-at-end-p player) t) | |
430 | + (return-from %player-interpret-vgm)))) | |
431 | + | |
432 | + (otherwise | |
433 | + ;; Skip unimplemented, but reserved, instructions. | |
434 | + (incf (%player-ip player) | |
435 | + (case (logand inst #xF0) | |
436 | + ((#x00 #x10 #x20) 1) | |
437 | + (#x30 2) | |
438 | + ((#x40 #x50 #xB0) 3) | |
439 | + ((#xC0 #xD0) 4) | |
440 | + (#xF0 5) | |
441 | + (otherwise (satou-error () "Unknown VGM instruction: $~2,'0x at data offset $~8,'0x" | |
442 | + inst (%player-ip player))))))))))) | |
443 | + nil) | |
444 | + | |
445 | +(define-typed-fn %player-interpret-file ((vgm-player player) (t/uint32 sample-count)) | |
446 | + (null t) | |
447 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
448 | + (satou/dac:dac-update (%player-dac-control player) (1- sample-count)) | |
449 | + (%player-interpret-vgm player sample-count) | |
450 | + (satou/dac:dac-update (%player-dac-control player) 1) | |
451 | + (incf (%player-play-time player) sample-count) | |
452 | + (incf (%player-vgm-sample-played player) sample-count) | |
453 | + nil) | |
454 | + | |
455 | +(defun %player-setup-resampling-for-chip (player chip low-quality?) | |
456 | + (declare (type vgm-player player)) | |
457 | + | |
458 | + (dlog "Chip ~a sample rate: ~a, target sample rate: ~a" | |
459 | + (type-of chip) (chip-sample-rate chip) (%player-sample-rate player)) | |
460 | + (cond | |
461 | + (low-quality? | |
462 | + (setf (chip-resampler-type chip) :old) | |
463 | + (dlog " Old resampler mode forced")) | |
464 | + | |
465 | + (t | |
466 | + (setf (chip-resampler-type chip) | |
467 | + (cond | |
468 | + ((< (chip-sample-rate chip) (%player-sample-rate player)) :upsampling) | |
469 | + ((> (chip-sample-rate chip) (%player-sample-rate player)) :downsampling) | |
470 | + (t :copy))) | |
471 | + (dlog " Resample mode: ~a" (chip-resampler-type chip)))) | |
472 | + | |
473 | + (with-slots (cur-sample-num next-sample-num last-sample-num next-sample last-sample) | |
474 | + chip | |
475 | + (setf cur-sample-num 0) | |
476 | + (setf next-sample-num 0) | |
477 | + (setf last-sample-num 0) | |
478 | + (setf last-sample (vector 0 0)) | |
479 | + | |
480 | + (case (chip-resampler-type chip) | |
481 | + (:upsampling | |
482 | + ;; Pre-generate the first sample | |
483 | + (chip-update chip (%player-stream-buffers player) 1) | |
484 | + (setf (sample-left next-sample) (aref (svref (%player-stream-buffers player) 0) 0)) | |
485 | + (setf (sample-right next-sample) (aref (svref (%player-stream-buffers player) 1) 0))) | |
486 | + | |
487 | + (otherwise | |
488 | + (setf (sample-left next-sample) 0) | |
489 | + (setf (sample-right next-sample) 0)))) | |
490 | + nil) | |
491 | + | |
492 | +(defun %player-setup-resampling (player &optional low-quality?) | |
493 | + (declare (type vgm-player player)) | |
494 | + (setf (%player-resampler player) (make-resampler :sample-rate (%player-sample-rate player) | |
495 | + :stream-buffers (%player-stream-buffers player))) | |
496 | + | |
497 | + (with-each-chip (chip (%player-chip-table player)) | |
498 | + (%player-setup-resampling-for-chip player chip low-quality?) | |
499 | + | |
500 | + ;; Setup resampling for its paired chip as well. | |
501 | + (when (chip-is-paired-p chip) | |
502 | + (%player-setup-resampling-for-chip player (slot-value chip 'paired) low-quality?))) | |
503 | + nil) | |
504 | + | |
505 | +(define-typed-fn %player-clamp-int32-to-int16 ((t/int32 value)) | |
506 | + (t/int16 t) | |
507 | + (declare (optimize (speed 3) (debug 0) (safety 0) (compilation-speed 0))) | |
508 | + (clamp value -32768 32767)) | |
509 | + | |
510 | +(defun (setf vgm-player-main-volume) (value player) | |
511 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
512 | + (check-type value double-float) | |
513 | + (check-type player vgm-player) | |
514 | + (setf (%player-main-volume player) value) | |
515 | + (setf (%player-output-volume player) (+ (* 256.0d0 (* (%player-main-volume player) | |
516 | + (%player-volume-modifier player))) | |
517 | + 0.5d0)) | |
518 | + value) | |
519 | + | |
520 | +(define-typed-fn %player-fill-buffers ((vgm-player player) (t/int16-vector left right)) | |
521 | + (null) | |
522 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0) | |
523 | + #+satou-wd40 (safety 0) | |
524 | + #-satou-wd40 (safety 1))) | |
525 | + | |
526 | + (let ((cur-volume (truncate (%player-output-volume player))) | |
527 | + (buf (%player-temp-buf player))) | |
528 | + (declare (type fixnum cur-volume)) | |
529 | + | |
530 | + #-satou-wd40 (unless (= (length left) (length right)) | |
531 | + (error "Buffer size mismatch")) | |
532 | + | |
533 | + (loop with len fixnum = (length left) | |
534 | + for sample-num fixnum from 0 below len | |
535 | + do ;; Generate and resample the new sample. | |
536 | + (%player-interpret-file player 1) | |
537 | + (resampler-resample (%player-resampler player) (%player-chip-table player) buf 1) | |
538 | + | |
539 | + ;; Adjust the volume. | |
540 | + (setf (sample-left buf) (ash (coerce-to-int32 (* (ash (sample-left buf) -5) cur-volume)) -11)) | |
541 | + (setf (sample-right buf) (ash (coerce-to-int32 (* (ash (sample-right buf) -5) cur-volume)) -11)) | |
542 | + | |
543 | + ;; Store the new sample | |
544 | + (muffling | |
545 | + (setf (aref left sample-num) (%player-clamp-int32-to-int16 (sample-left buf))) | |
546 | + (setf (aref right sample-num) (%player-clamp-int32-to-int16 (sample-right buf)))))) | |
547 | + nil) | |
548 | + | |
549 | +(define-typed-fn %player-reset-chips ((vgm-player player)) | |
550 | + (null) | |
551 | + (with-each-chip (chip (%player-chip-table player)) | |
552 | + (chip-reset chip) | |
553 | + | |
554 | + (when (chip-is-paired-p chip) | |
555 | + (chip-reset (slot-value chip 'paired)))) | |
556 | + nil) | |
557 | + | |
558 | +(define-typed-fn %player-start-chip ((vgm-player player) (abstract-chip chip)) | |
559 | + (t/uint32) | |
560 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
561 | + (let ((start-flags (chip-start-flags chip (%player-vgm player)))) | |
562 | + ;; TODO | |
563 | + ;;(when (typep chip 'dmg) | |
564 | + ;; (if (settings-dmg-boost-wave-chan-p (%player-settings player)) | |
565 | + ;; (setf (dmg-start-flags-value start-flags) 3) | |
566 | + ;; (setf (dmg-start-flags-value start-flags) 0))) | |
567 | + | |
568 | + (chip-start chip (chip-clock chip) start-flags) | |
569 | + (the t/uint32 (chip-volume-modifier chip)))) | |
570 | + | |
571 | +(define-typed-fn %player-start-all-chips ((vgm-player player)) | |
572 | + (null) | |
573 | + "Starts all allocated chips." | |
574 | + | |
575 | + ;; Remi: This is some weird hacky stuff. | |
576 | + (let ((vol-mod 0)) | |
577 | + ;; Start each chip. Also collect the volume modification value so that we | |
578 | + ;; can ensure a proper output level for the given set of chips. | |
579 | + (with-each-chip (chip (%player-chip-table player)) | |
580 | + (incf vol-mod (%player-start-chip player chip))) | |
581 | + | |
582 | + ;; Reset the chips, then adjust their output volumes. | |
583 | + (%player-reset-chips player) | |
584 | + (loop while (and (< vol-mod #x200) (/= vol-mod 0)) do | |
585 | + (with-each-chip (chip (%player-chip-table player)) | |
586 | + (setf (chip-volume chip) (* (chip-volume chip) 2))) | |
587 | + (setf vol-mod (* vol-mod 2))) | |
588 | + | |
589 | + (loop while (> vol-mod #x300) do | |
590 | + (with-each-chip (chip (%player-chip-table player)) | |
591 | + (setf (chip-volume chip) (truncate (chip-volume chip) 2))) | |
592 | + (setf vol-mod (truncate vol-mod 2)))) | |
593 | + nil) | |
594 | + | |
595 | +(define-typed-fn %player-setup-chip-set ((vgm-player player)) | |
596 | + (null) | |
597 | + ;; May have been created with VGM-PLAYER-GET-CHIP-NAMES VGM-PLAYER-CHIPS-USED | |
598 | + (unless (%player-chip-set-created-p player) | |
599 | + (maphash #'(lambda (chip-type count) | |
600 | + (loop repeat count | |
601 | + for chip = (make-chip-instance chip-type | |
602 | + (%player-vgm player) (%player-sample-rate player) 0 | |
603 | + (%player-sample-rate player) (%player-settings player)) | |
604 | + when chip do | |
605 | + (unless (hash-table-contains-p (%player-chip-table player) chip-type) | |
606 | + (setf (gethash chip-type (%player-chip-table player)) (new-vector 'abstract-chip))) | |
607 | + (vector-push-extend chip (gethash chip-type (%player-chip-table player))))) | |
608 | + (vgm-file-chips-used (%player-vgm player))) | |
609 | + | |
610 | + (setf (satou/dac:dac-chip-table (%player-dac-control player)) (%player-chip-table player)) | |
611 | + (setf (%player-chip-set-created-p player) t)) | |
612 | + nil) | |
613 | + | |
614 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
615 | +;;; | |
616 | +;;; Public VGM Player Functions | |
617 | +;;; | |
618 | + | |
619 | +(defun make-vgm-player (vgm &optional settings) | |
620 | + "Creates a new VGM-PLAYER instance that will play the given VGM file." | |
621 | + (check-type vgm vgm-file) | |
622 | + (check-type settings (or null vgm-player-settings)) | |
623 | + | |
624 | + (let* (;; Create the default settings if none were passed in. | |
625 | + (settings (or settings (make-instance 'vgm-player-settings))) | |
626 | + | |
627 | + ;; Get a handle on the header for less typing. | |
628 | + (header (vgm-file-header vgm)) | |
629 | + | |
630 | + ;; This is used to adjust chip output volume. It is a per-VGM setting | |
631 | + ;; that VGM authors can use to adjust the volumes of their VGM files. | |
632 | + (vol-mod-base (cond | |
633 | + ((<= (vgm-header-volume-modifier header) +volume-modifier-wrap+) | |
634 | + (coerce-to-int32 (vgm-header-volume-modifier header))) | |
635 | + ((= (vgm-header-volume-modifier header) (1+ +volume-modifier-wrap+)) | |
636 | + (- +volume-modifier-wrap+ #x100)) | |
637 | + (t | |
638 | + (- (vgm-header-volume-modifier header) #x100)))) | |
639 | + | |
640 | + ;; Base for the internal buffer sizes. | |
641 | + (min-buffer-size (coerce-to-int32 (truncate (settings-sample-rate settings) (* 100 2)))) | |
642 | + | |
643 | + ;; Create the VGM-PLAYER. We still need to call (SETF | |
644 | + ;; VGM-PLAYER-MAIN-VOLUME) since it does a few extra calculations. | |
645 | + (ret (%make-vgm-player :vgm vgm | |
646 | + :settings settings | |
647 | + :sample-rate (settings-sample-rate settings) | |
648 | + :min-buffer-size min-buffer-size | |
649 | + :samples-per-buffer (truncate min-buffer-size 2) | |
650 | + :stream-buffers (vector (new-array 100 t/int32) (new-array 100 t/int32)) | |
651 | + :dac-control (satou/dac:make-dac-controller (settings-sample-rate settings)) | |
652 | + :volume-modifier (expt 2.0d0 (/ vol-mod-base #x20))))) | |
653 | + (setf (vgm-player-main-volume ret) 1.0d0) | |
654 | + ret)) | |
655 | + | |
656 | +(defun calc-resampling-values* (sample-rate vgm &key (playback-rate 0) (vgm-sample-rate +vgm-sample-rate+)) | |
657 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
658 | + (check-type sample-rate t/uint32) | |
659 | + (check-type vgm vgm-file) | |
660 | + (check-type playback-rate t/uint32) | |
661 | + (check-type vgm-sample-rate t/uint32) | |
662 | + | |
663 | + (with-typed-slots ((vgm-file-header header)) | |
664 | + vgm | |
665 | + (with-typed-slots ((t/uint32 rate)) | |
666 | + header | |
667 | + (let ((rate-mul 0) | |
668 | + (rate-div 0) | |
669 | + (playback-rate-mul 0) | |
670 | + (playback-rate-div 0)) | |
671 | + (declare (type t/uint32 rate-mul rate-div playback-rate-mul playback-rate-div)) | |
672 | + | |
673 | + (cond | |
674 | + ((or (zerop playback-rate) (zerop rate)) | |
675 | + (setf playback-rate-mul 1) | |
676 | + (setf playback-rate-div 1)) | |
677 | + (t | |
678 | + (let ((val (muffling (gcd rate playback-rate)))) | |
679 | + (setf playback-rate-mul (coerce-to-uint32 (truncate rate val))) | |
680 | + (setf playback-rate-div (coerce-to-uint32 (truncate playback-rate val)))))) | |
681 | + | |
682 | + (setf rate-mul (coerce-to-uint32 (* sample-rate playback-rate-mul))) | |
683 | + (setf rate-div (coerce-to-uint32 (* vgm-sample-rate playback-rate-div))) | |
684 | + | |
685 | + (let ((val (muffling (gcd rate-mul rate-div)))) | |
686 | + (setf rate-mul (coerce-to-uint32 (truncate rate-mul val))) | |
687 | + (setf rate-div (coerce-to-uint32 (truncate rate-div val)))) | |
688 | + | |
689 | + (values rate-mul rate-div playback-rate-mul playback-rate-div))))) | |
690 | + | |
691 | +(define-typed-fn vgm-player-reset ((vgm-player player)) | |
692 | + (null) | |
693 | + (setf (%player-ip player) 0) | |
694 | + (setf (%player-vgm-sample-played player) 0) | |
695 | + (setf (%player-vgm-sample-pos player) 0) | |
696 | + | |
697 | + (dlog "Sample rate: ~a" (%player-sample-rate player)) | |
698 | + (dlog "VGM Header Rate: ~a" (vgm-header-rate (vgm-file-header (%player-vgm player)))) | |
699 | + | |
700 | + (multiple-value-bind (rate-mul rate-div playback-mul playback-div) | |
701 | + (calc-resampling-values* (%player-sample-rate player) (%player-vgm player) | |
702 | + :playback-rate (%player-vgm-playback-rate player) | |
703 | + :vgm-sample-rate (%player-vgm-sample-rate player)) | |
704 | + (setf (%player-vgm-sample-rate-mul player) rate-mul) | |
705 | + (setf (%player-vgm-sample-rate-div player) rate-div) | |
706 | + (setf (%player-vgm-playback-rate-mul player) playback-mul) | |
707 | + (setf (%player-vgm-playback-rate-div player) playback-div) | |
708 | + | |
709 | + (dlog "VGM Playback Rate: ~a" (%player-vgm-playback-rate player)) | |
710 | + (dlog "VGM Header Rate: ~a" (vgm-header-rate (vgm-file-header (%player-vgm player)))) | |
711 | + (dlog "VGM Playback Rate Mul: ~a" (%player-vgm-playback-rate-mul player)) | |
712 | + (dlog "VGM Playback Rate Div: ~a" (%player-vgm-playback-rate-div player)) | |
713 | + (dlog "VGM Sample Rate: ~a" (%player-vgm-sample-rate player)) | |
714 | + (dlog "VGM Sample Rate Mul: ~a" (%player-vgm-sample-rate-mul player)) | |
715 | + (dlog "VGM Sample Rate Div: ~a" (%player-vgm-sample-rate-div player))) | |
716 | + nil) | |
717 | + | |
718 | +(define-typed-fn calc-resampling-values ((t/uint32 sample-rate) (vgm-file vgm)) | |
719 | + ((values t/uint32 t/uint32) t) | |
720 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
721 | + (multiple-value-bind (rate-mul rate-div) | |
722 | + (calc-resampling-values* sample-rate vgm) | |
723 | + (values rate-mul rate-div))) | |
724 | + | |
725 | +(define-typed-fn vgm-player-play ((vgm-player player) &optional low-quality-resampling?) | |
726 | + (null) | |
727 | + (unless (%player-playing-p player) | |
728 | + (%player-setup-chip-set player) | |
729 | + (%player-start-all-chips player) | |
730 | + (vgm-player-reset player) | |
731 | + (%player-setup-resampling player low-quality-resampling?) | |
732 | + (setf (%player-playing-p player)t) | |
733 | + nil)) | |
734 | + | |
735 | +(define-typed-fn vgm-player-render ((vgm-player player) (t/int16-vector left right)) | |
736 | + (null t) | |
737 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
738 | + (%player-fill-buffers player left right)) | |
739 | + | |
740 | +(define-typed-fn vgm-player-stop ((vgm-player player)) | |
741 | + (null t) | |
742 | + (setf (%player-at-end-p player) t) | |
743 | + nil) | |
744 | + | |
745 | +(define-typed-fn vgm-player-get-chip-names ((vgm-player player) &optional use-short) | |
746 | + (list) | |
747 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
748 | + | |
749 | + (%player-setup-chip-set player) | |
750 | + (let ((recorded (make-hash-table))) | |
751 | + (maphash #'(lambda (ct chips) | |
752 | + (declare (type (vector abstract-chip) chips) | |
753 | + (type t/chip ct)) | |
754 | + (when (and (not (hash-table-contains-p recorded ct)) (> (length chips) 0)) | |
755 | + (let ((name (muffling (if use-short | |
756 | + (chip-short-name (elt chips 0)) | |
757 | + (chip-name (elt chips 0)))))) | |
758 | + (if (= (length chips) 1) | |
759 | + (setf (gethash ct recorded) name) | |
760 | + (setf (gethash ct recorded) (format nil "~dx ~a" (length chips) name)))))) | |
761 | + (%player-chip-table player)) | |
762 | + | |
763 | + (loop for value being the hash-values in recorded | |
764 | + collect value))) | |
765 | + | |
766 | +(define-typed-fn vgm-player-chips-used ((vgm-player player)) | |
767 | + (list) | |
768 | + (declare (optimize (speed 3) (debug 1) (safety 1) (compilation-speed 0))) | |
769 | + | |
770 | + (%player-setup-chip-set player) | |
771 | + (let ((ret (make-hash-table))) | |
772 | + (maphash #'(lambda (ct chips) | |
773 | + (declare (type (vector abstract-chip) chips) | |
774 | + (type t/chip ct)) | |
775 | + (if (and (not (hash-table-contains-p ret ct)) (> (length chips) 0)) | |
776 | + (setf (gethash ct ret) (length chips)) | |
777 | + (incf (the fixnum (gethash ct ret)) (length chips)))) | |
778 | + (%player-chip-table player)) | |
779 | + | |
780 | + (loop for ct being the hash-keys in ret | |
781 | + using (hash-value count) | |
782 | + collect (cons ct count)))) |
@@ -0,0 +1,1201 @@ | ||
1 | +;;;; SatouSynth | |
2 | +;;;; Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp> | |
3 | +;;;; | |
4 | +;;;; This program is free software: you can redistribute it and/or | |
5 | +;;;; modify it under the terms of the GNU Affero General Public | |
6 | +;;;; License as published by the Free Software Foundation, either | |
7 | +;;;; version 3 of the License, or (at your option) any later version. | |
8 | +;;;; | |
9 | +;;;; This program is distributed in the hope that it will be useful, | |
10 | +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | +;;;; Affero General Public License for more details. | |
13 | +;;;; | |
14 | +;;;; You should have received a copy of the GNU Affero General Public License | |
15 | +;;;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | +(in-package :satou) | |
17 | + | |
18 | +;;;; | |
19 | +;;;; VGM File Handling | |
20 | +;;;; | |
21 | + | |
22 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
23 | +;;; | |
24 | +;;; Constants, Conditions, and Types | |
25 | +;;; | |
26 | + | |
27 | +(defining-consts | |
28 | + (+vgm-magic+ "Vgm " :documentation "The magic bytes at the start of a VGM file.") | |
29 | + (+loop-offset-offset+ #x1C)) | |
30 | + | |
31 | +(define-condition vgm-error (satou-error) | |
32 | + ()) | |
33 | + | |
34 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
35 | +;;; | |
36 | +;;; Metaclass Stuff | |
37 | +;;; | |
38 | + | |
39 | +(defclass vgm-file-metaclass (closer-mop:standard-class) | |
40 | + ()) | |
41 | + | |
42 | +(defclass vgm-file-metaclass-slot (closer-mop:standard-direct-slot-definition) | |
43 | + ((ignore? | |
44 | + :initarg :ignore | |
45 | + :initform nil | |
46 | + :type boolean | |
47 | + :reader %vgm-field-ignore-p) | |
48 | + | |
49 | + (since | |
50 | + :initarg :since | |
51 | + :initform 0 | |
52 | + :type fixnum | |
53 | + :reader %vgm-field-since) | |
54 | + | |
55 | + (offset | |
56 | + :initarg :offset | |
57 | + :initform 0 | |
58 | + :type fixnum | |
59 | + :reader %vgm-field-offset) | |
60 | + | |
61 | + (chip-type | |
62 | + :initarg :chip-type | |
63 | + :initform :unknown | |
64 | + :type keyword | |
65 | + :reader %vgm-field-chip-type))) | |
66 | + | |
67 | +(defclass vgm-file-metaclass-slot/effective (closer-mop:standard-effective-slot-definition) | |
68 | + ((ignore? | |
69 | + :initarg :ignore | |
70 | + :initform nil | |
71 | + :type boolean | |
72 | + :accessor %vgm-field-ignore-p) | |
73 | + | |
74 | + (since | |
75 | + :initarg :since | |
76 | + :initform 0 | |
77 | + :type fixnum | |
78 | + :accessor %vgm-field-since) | |
79 | + | |
80 | + (offset | |
81 | + :initarg :offset | |
82 | + :initform 0 | |
83 | + :type fixnum | |
84 | + :accessor %vgm-field-offset) | |
85 | + | |
86 | + (chip-type | |
87 | + :initarg :chip-type | |
88 | + :initform :unknown | |
89 | + :type keyword | |
90 | + :accessor %vgm-field-chip-type))) | |
91 | + | |
92 | +(defmethod closer-mop:validate-superclass ((class vgm-file-metaclass) (superclass standard-class)) | |
93 | + t) | |
94 | + | |
95 | +(defmethod closer-mop:direct-slot-definition-class ((class vgm-file-metaclass) &rest initargs) | |
96 | + (declare (ignore initargs)) | |
97 | + (find-class 'vgm-file-metaclass-slot)) | |
98 | + | |
99 | +(defmethod closer-mop:effective-slot-definition-class ((class vgm-file-metaclass) &rest initargs) | |
100 | + (declare (ignore initargs)) | |
101 | + (find-class 'vgm-file-metaclass-slot/effective)) | |
102 | + | |
103 | +(defmethod closer-mop:compute-effective-slot-definition ((class vgm-file-metaclass) name direct-slots) | |
104 | + (let ((slot-def (call-next-method))) | |
105 | + (loop for slot in direct-slots | |
106 | + for ignore = (%vgm-field-ignore-p slot) | |
107 | + for since = (%vgm-field-since slot) | |
108 | + for offset = (%vgm-field-offset slot) | |
109 | + for chip-type = (%vgm-field-chip-type slot) | |
110 | + do (setf | |
111 | + (%vgm-field-ignore-p slot-def) ignore | |
112 | + (%vgm-field-since slot-def) since | |
113 | + (%vgm-field-offset slot-def) offset | |
114 | + (%vgm-field-chip-type slot-def) chip-type) | |
115 | + (loop-finish)) | |
116 | + slot-def)) | |
117 | + | |
118 | +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
119 | +;;; | |
120 | +;;; VGM-FILE Classes and Methods | |
121 | +;;; | |
122 | + | |
123 | +(defgeneric valid-vgm-p (source) | |
124 | + (:documentation "Checks to see if SOURCE is a valid VGM file. SOURCE may be a | |
125 | +STRING containing a path, a PATHNAME, or an open stream. If it's a stream, it | |
126 | +must be able to seek.")) | |
127 | + | |
128 | +(defgeneric read-vgm-file (source &optional hint) | |
129 | + (:documentation "Creates a new VGM-FILE instance by reading from the given | |
130 | +source. SOURCE can be a PATHNAME, a STRING containing a path, or an open | |
131 | +stream. The data in the source may be compressed. | |
132 | + | |
133 | +HINT can be a keyword which will be passed to | |
134 | +SATOU/DECOMPRESSION:MAYBE-DECOMPRESS.")) | |
135 | + | |
136 | +(defclass extra-header-clock () | |
137 | + ((chip-id | |
138 | + :initarg :chip-id | |
139 | + :initform 0 | |
140 | + :type t/uint8 | |
141 | + :reader extra-header-clock-chip-id) | |
142 | + | |
143 | + (value | |
144 | + :initarg :value | |
145 | + :initform 0 | |
146 | + :type t/uint32 | |
147 | + :reader extra-header-clock-value))) | |
148 | + | |
149 | +(defmethod initialize-instance :after ((obj extra-header-clock) &key &allow-other-keys) | |
150 | + (check-type (slot-value obj 'chip-id) t/uint8) | |
151 | + (check-type (slot-value obj 'value) t/uint32)) | |
152 | + | |
153 | +(defclass extra-header-volume () | |
154 | + ((chip-id | |
155 | + :initarg :chip-id | |
156 | + :initform 0 | |
157 | + :type t/uint8 | |
158 | + :reader extra-header-volume-chip-id) | |
159 | + | |
160 | + (flags | |
161 | + :initarg :flags | |
162 | + :initform 0 | |
163 | + :type t/uint8 | |
164 | + :reader extra-header-volume-flags) | |
165 | + | |
166 | + (value | |
167 | + :initarg :value | |
168 | + :initform 0 | |
169 | + :type t/uint16 | |
170 | + :reader extra-header-volume-value))) | |
171 | + | |
172 | +(defmethod initialize-instance :after ((obj extra-header-volume) &key &allow-other-keys) | |
173 | + (check-type (slot-value obj 'chip-id) t/uint8) | |
174 | + (check-type (slot-value obj 'flags) t/uint8) | |
175 | + (check-type (slot-value obj 'value) t/uint16)) | |
176 | + | |
177 | +(defclass extra-header () | |
178 | + ((clocks | |
179 | + :initform (make-hash-table) | |
180 | + :type hash-table | |
181 | + :reader extra-header-clocks) | |
182 | + | |
183 | + (volumes | |
184 | + :initform (make-hash-table) | |
185 | + :type hash-table | |
186 | + :reader extra-header-volumes))) | |
187 | + | |
188 | +(defclass vgm-file-header () | |
189 | + ((relative-eof | |
190 | + :initform 0 | |
191 | + :type t/uint32 | |
192 | + :ignore t) | |
193 | + | |
194 | + (version | |
195 | + :initform 0 | |
196 | + :type t/uint32 | |
197 | + :ignore t | |
198 | + :reader vgm-header-version | |
199 | + :documentation "The VGM specification version this VGM file adheres to.") | |
200 | + | |
201 | + (sn76489-clock | |
202 | + :initform 0 | |
203 | + :type t/uint32 | |
204 | + :offset #x0C | |
205 | + :chip-type :sn76489 | |
206 | + :reader vgm-header-sn76489-clock) | |
207 | + | |
208 | + (ym2413-clock | |
209 | + :initform 0 | |
210 | + :type t/uint32 | |
211 | + :offset #x10 | |
212 | + :chip-type :ym2413 | |
213 | + :reader vgm-header-ym2413-clock) | |
214 | + | |
215 | + (gd3-offset | |
216 | + :initform 0 | |
217 | + :type t/uint32 | |
218 | + :offset #x14 | |
219 | + :reader vgm-header-gd3-offset) | |
220 | + | |
221 | + (total-samples | |
222 | + :initform 0 | |
223 | + :type t/uint32 | |
224 | + :offset #x18 | |
225 | + :reader vgm-header-total-samples) | |
226 | + | |
227 | + (loop-offset | |
228 | + :initform 0 | |
229 | + :type t/uint32 | |
230 | + :offset #x1C | |
231 | + :reader vgm-header-loop-offset) | |
232 | + | |
233 | + (loop-samples | |
234 | + :initform 0 | |
235 | + :type t/uint32 | |
236 | + :offset #x20 | |
237 | + :reader vgm-header-loop-samples) | |
238 | + | |
239 | + | |
240 | + | |
241 | + (rate | |
242 | + :initform 0 | |
243 | + :type t/uint32 | |
244 | + :offset #x24 | |
245 | + :since #x00000101 | |
246 | + :reader vgm-header-rate) | |
247 | + | |
248 | + | |
249 | + | |
250 | + (sn76489-feedback | |
251 | + :initform 0 | |
252 | + :type t/uint16 | |
253 | + :offset #x28 | |
254 | + :since #x00000110 | |
255 | + :chip-type :sn76489 | |
256 | + :reader vgm-header-sn76489-feedback) | |
257 | + | |
258 | + (sn76489-shift-register-width | |
259 | + :initform 0 | |
260 | + :type t/uint8 | |
261 | + :offset #x2A | |
262 | + :since #x00000110 | |
263 | + :chip-type :sn76489 | |
264 | + :reader vgm-header-sn76489-shift-register-width) | |
265 | + | |
266 | + (sn76489-flags | |
267 | + :initform 0 | |
268 | + :type t/uint8 | |
269 | + :offset #x2B | |
270 | + :since #x00000151 | |
271 | + :chip-type :sn76489 | |
272 | + :reader vgm-header-sn76489-flags) | |
273 | + | |
274 | + (ym2612-clock | |
275 | + :initform 0 | |
276 | + :type t/uint32 | |
277 | + :offset #x2C | |
278 | + :since #x00000110 | |
279 | + :chip-type :ym2612 | |
280 | + :reader vgm-header-ym2612-clock) | |
281 | + | |
282 | + (ym2151-clock | |
283 | + :initform 0 | |
284 | + :type t/uint32 | |
285 | + :offset #x2C | |
286 | + :since #x00000110 | |
287 | + :chip-type :ym2151 | |
288 | + :reader vgm-header-ym2151-clock) | |
289 | + | |
290 | + | |
291 | + | |
292 | + (data-offset | |
293 | + :initform 0 | |
294 | + :type t/uint32 | |
295 | + :offset #x34 | |
296 | + :since #x00000150 | |
297 | + :reader vgm-header-data-offset) | |
298 | + | |
299 | + | |
300 | + | |
301 | + (spcm-clock | |
302 | + :initform 0 | |
303 | + :type t/uint32 | |
304 | + :since #x00000151 | |
305 | + :offset #x38 | |
306 | + :chip-type :sega-pcm | |
307 | + :reader vgm-header-spcm-clock) | |
308 | + | |
309 | + (spcm-interface-reg | |
310 | + :initform 0 | |
311 | + :type t/uint32 | |
312 | + :since #x00000151 | |
313 | + :offset #x3C | |
314 | + :chip-type :sega-pcm | |
315 | + :reader vgm-header-spcm-interface-reg) | |
316 | + | |
317 | + (rf5c69-clock | |
318 | + :initform 0 | |
319 | + :type t/uint32 | |
320 | + :since #x00000151 | |
321 | + :offset #x40 | |
322 | + :chip-type :rf5c69 | |
323 | + :reader vgm-header-rf5c69-clock) | |
324 | + | |
325 | + (ym2203-clock | |
326 | + :initform 0 | |
327 | + :type t/uint32 | |
328 | + :since #x00000151 | |
329 | + :offset #x44 | |
330 | + :chip-type :ym2203 | |
331 | + :reader vgm-header-ym2203-clock) | |
332 | + | |
333 | + (ym2608-clock | |
334 | + :initform 0 | |
335 | + :type t/uint32 | |
336 | + :since #x00000151 | |
337 | + :offset #x48 | |
338 | + :chip-type :ym2608 | |
339 | + :reader vgm-header-ym2608-clock) | |
340 | + | |
341 | + (ym2610-clock | |
342 | + :initform 0 | |
343 | + :type t/uint32 | |
344 | + :since #x00000151 | |
345 | + :offset #x4C | |
346 | + :chip-type :ym2610 | |
347 | + :reader vgm-header-ym2610-clock) | |
348 | + | |
349 | + (ym3812-clock | |
350 | + :initform 0 | |
351 | + :type t/uint32 | |
352 | + :since #x00000151 | |
353 | + :offset #x50 | |
354 | + :chip-type :ym3812 | |
355 | + :reader vgm-header-ym3812-clock) | |
356 | + | |
357 | + (ym3526-clock | |
358 | + :initform 0 | |
359 | + :type t/uint32 | |
360 | + :since #x00000151 | |
361 | + :offset #x54 | |
362 | + :chip-type :ym3526 | |
363 | + :reader vgm-header-ym3526-clock) | |
364 | + | |
365 | + (y8950-clock | |
366 | + :initform 0 | |
367 | + :type t/uint32 | |
368 | + :since #x00000151 | |
369 | + :offset #x58 | |
370 | + :chip-type :y8950 | |
371 | + :reader vgm-header-y8950-clock) | |
372 | + | |
373 | + (ymf262-clock | |
374 | + :initform 0 | |
375 | + :type t/uint32 | |
376 | + :since #x00000151 | |
377 | + :offset #x5C | |
378 | + :chip-type :ymf262 | |
379 | + :reader vgm-header-ymf262-clock) | |
380 | + | |
381 | + (ymf278b-clock | |
382 | + :initform 0 | |
383 | + :type t/uint32 | |
384 | + :since #x00000151 | |
385 | + :offset #x60 | |
386 | + :chip-type :ymf278b | |
387 | + :reader vgm-header-ymf278b-clock) | |
388 | + | |
389 | + (ymf271-clock | |
390 | + :initform 0 | |
391 | + :type t/uint32 | |
392 | + :since #x00000151 | |
393 | + :offset #x64 | |
394 | + :chip-type :ymf271 | |
395 | + :reader vgm-header-ymf271-clock) | |
396 | + | |
397 | + (ymz280b-clock | |
398 | + :initform 0 | |
399 | + :type t/uint32 | |
400 | + :since #x00000151 | |
401 | + :offset #x68 | |
402 | + :chip-type :ymz280b | |
403 | + :reader vgm-header-ymz280b-clock) | |
404 | + | |
405 | + (rf5c164-clock | |
406 | + :initform 0 | |
407 | + :type t/uint32 | |
408 | + :since #x00000151 | |
409 | + :offset #x6C | |
410 | + :chip-type :rf5c164 | |
411 | + :reader vgm-header-rf5c164-clock) | |
412 | + | |
413 | + (pwm-clock | |
414 | + :initform 0 | |
415 | + :type t/uint32 | |
416 | + :since #x00000151 | |
417 | + :offset #x70 | |
418 | + :chip-type :pwm | |
419 | + :reader vgm-header-pwm-clock) | |
420 | + | |
421 | + | |
422 | + (ay8910-clock | |
423 | + :initform 0 | |
424 | + :type t/uint32 | |
425 | + :since #x00000151 | |
426 | + :offset #x74 | |
427 | + :chip-type :ay8910 | |
428 | + :reader vgm-header-ay8910-clock) | |
429 | + | |
430 | + (ay8910-chip-type | |
431 | + :initform 0 | |
432 | + :type t/uint8 | |
433 | + :since #x00000151 | |
434 | + :offset #x78 | |
435 | + :chip-type :ay8910 | |
436 | + :reader vgm-header-ay8910-chip-type) | |
437 | + | |
438 | + (ay8910-flags | |
439 | + :initform 0 | |
440 | + :type t/uint8 | |
441 | + :since #x00000151 | |
442 | + :offset #x79 | |
443 | + :chip-type :ay8910 | |
444 | + :reader vgm-header-ay8910-flags) | |
445 | + | |
446 | + (ay-ym2203-flags | |
447 | + :initform 0 | |
448 | + :type t/uint8 | |
449 | + :since #x00000151 | |
450 | + :offset #x7A | |
451 | + :chip-type :ay8910 | |
452 | + :reader vgm-header-ay-ym2203-flags) | |
453 | + | |
454 | + (ay-ym2608-flags | |
455 | + :initform 0 | |
456 | + :type t/uint8 | |
457 | + :since #x00000151 | |
458 | + :offset #x7B | |
459 | + :chip-type :ay8910 | |
460 | + :reader vgm-header-ay-ym2608-flags) | |
461 | + | |
462 | + | |
463 | + (volume-modifier | |
464 | + :initform 0 | |
465 | + :type t/uint8 | |
466 | + :since #x00000160 | |
467 | + :offset #x7C | |
468 | + :reader vgm-header-volume-modifier) | |
469 | + | |
470 | + (reserved2 | |
471 | + :initform 0 | |
472 | + :type t/uint8 | |
473 | + :since #x00000160 | |
474 | + :offset #x7D) | |
475 | + | |
476 | + (loop-base | |
477 | + :initform 0 | |
478 | + :type t/int8 | |
479 | + :since #x00000160 | |
480 | + :offset #x7E | |
481 | + :reader vgm-header-loop-base) | |
482 | + | |
483 | + (loop-modifier | |
484 | + :initform 0 | |
485 | + :type t/uint8 | |
486 | + :since #x00000151 | |
487 | + :offset #x7F | |
488 | + :reader vgm-header-loop-modifier) | |
489 | + | |
490 | + | |
491 | + (dmg-clock | |
492 | + :initform 0 | |
493 | + :type t/uint32 | |
494 | + :since #x00000161 | |
495 | + :offset #x80 | |
496 | + :chip-type :dmg | |
497 | + :reader vgm-header-dmg-clock) | |
498 | + | |
499 | + (nes-apu-clock | |
500 | + :initform 0 | |
501 | + :type t/uint32 | |
502 | + :since #x00000161 | |
503 | + :offset #x84 | |
504 | + :chip-type :nes-apu | |
505 | + :reader vgm-header-nes-apu-clock) | |
506 | + | |
507 | + (multi-pcm-clock | |
508 | + :initform 0 | |
509 | + :type t/uint32 | |
510 | + :since #x00000161 | |
511 | + :offset #x88 | |
512 | + :chip-type :multi-pcm | |
513 | + :reader vgm-header-multi-pcm-clock) | |
514 | + | |
515 | + (upd7759-clock | |
516 | + :initform 0 | |
517 | + :type t/uint32 | |
518 | + :since #x00000161 | |
519 | + :offset #x8C | |
520 | + :chip-type :upd7759 | |
521 | + :reader vgm-header-upd7759-clock) | |
522 | + | |
523 | + (oki-m6258-clock | |
524 | + :initform 0 | |
525 | + :type t/uint32 | |
526 | + :since #x00000161 | |
527 | + :offset #x90 | |
528 | + :chip-type :oki-m6258 | |
529 | + :reader vgm-header-oki-m6258-clock) | |
530 | + | |
531 | + (oki-m6258-flags | |
532 | + :initform 0 | |
533 | + :type t/uint8 | |
534 | + :since #x00000161 | |
535 | + :offset #x94 | |
536 | + :chip-type :oki-m6258 | |
537 | + :reader vgm-header-oki-m6258-flags) | |
538 | + | |
539 | + (k054539Flags | |
540 | + :initform 0 | |
541 | + :type t/uint8 | |
542 | + :since #x00000161 | |
543 | + :offset #x95 | |
544 | + :chip-type :k054539 | |
545 | + :reader vgm-header-k054539Flags) | |
546 | + | |
547 | + (c140-chip-type | |
548 | + :initform 0 | |
549 | + :type t/uint8 | |
550 | + :since #x00000161 | |
551 | + :offset #x96 | |
552 | + :chip-type :c140 | |
553 | + :reader vgm-header-c140-chip-type) | |
554 | + | |
555 | + | |
556 | + (reserved-flags | |
557 | + :initform 0 | |
558 | + :type t/uint8 | |
559 | + :since #x00000161 | |
560 | + :offset #x97 | |
561 | + :reader vgm-header-reserved-flags) | |
562 | + | |
563 | + | |
564 | + (oki-m6295-clock | |
565 | + :initform 0 | |
566 | + :type t/uint32 | |
567 | + :since #x00000161 | |
568 | + :offset #x98 | |
569 | + :chip-type :oki-m6295 | |
570 | + :reader vgm-header-oki-m6295-clock) | |
571 | + | |
572 | + (k051649-clock | |
573 | + :initform 0 | |
574 | + :type t/uint32 | |
575 | + :since #x00000161 | |
576 | + :offset #x9C | |
577 | + :chip-type :k051649 | |
578 | + :reader vgm-header-k051649-clock) | |
579 | + | |
580 | + (k054539-clock | |
581 | + :initform 0 | |
582 | + :type t/uint32 | |
583 | + :since #x00000161 | |
584 | + :offset #xA0 | |
585 | + :chip-type :k054539 | |
586 | + :reader vgm-header-k054539-clock) | |
587 | + | |
588 | + (huc6280-clock | |
589 | + :initform 0 | |
590 | + :type t/uint32 | |
591 | + :since #x00000161 | |
592 | + :offset #xA4 | |
593 | + :chip-type :huc6280 | |
594 | + :reader vgm-header-huc6280-clock) | |
595 | + | |
596 | + (c140-clock | |
597 | + :initform 0 | |
598 | + :type t/uint32 | |
599 | + :since #x00000161 | |
600 | + :offset #xA8 | |
601 | + :chip-type :c140 | |
602 | + :reader vgm-header-c140-clock) | |
603 | + | |
604 | + (k053260-clock | |
605 | + :initform 0 | |
606 | + :type t/uint32 | |
607 | + :since #x00000161 | |
608 | + :offset #xAC | |
609 | + :chip-type :k053260 | |
610 | + :reader vgm-header-k053260-clock) | |
611 | + | |
612 | + (pokey-clock | |
613 | + :initform 0 | |
614 | + :type t/uint32 | |
615 | + :since #x00000161 | |
616 | + :offset #xB0 | |
617 | + :chip-type :pokey | |
618 | + :reader vgm-header-pokey-clock) | |
619 | + | |
620 | + (qsound-clock | |
621 | + :initform 0 | |
622 | + :type t/uint32 | |
623 | + :since #x00000161 | |
624 | + :offset #xB4 | |
625 | + :chip-type :qsound | |
626 | + :reader vgm-header-qsound-clock) | |
627 | + | |
628 | + (scsp-clock | |
629 | + :initform 0 | |
630 | + :type t/uint32 | |
631 | + :since #x00000171 | |
632 | + :offset #xB8 | |
633 | + :chip-type :sega-pcm | |
634 | + :reader vgm-header-scsp-clock) | |
635 | + | |
636 | + | |
637 | + (extra-header-offset | |
638 | + :initform 0 | |
639 | + :type t/uint32 | |
640 | + :since #x00000170 | |
641 | + :offset #xBC | |
642 | + :reader vgm-header-extra-header-offset) | |
643 | + | |
644 | + (wonderswan-clock | |
645 | + :initform 0 | |
646 | + :type t/uint32 | |
647 | + :since #x00000171 | |
648 | + :offset #xC0 | |
649 | + :chip-type :wonderswan | |
650 | + :reader vgm-header-wonderswan-clock) | |
651 | + | |
652 | + (vsu-clock | |
653 | + :initform 0 | |
654 | + :type t/uint32 | |
655 | + :since #x00000171 | |
656 | + :offset #xC4 | |
657 | + :chip-type :vsu | |
658 | + :reader vgm-header-vsu-clock) | |
659 | + | |
660 | + (saa1099-clock | |
661 | + :initform 0 | |
662 | + :type t/uint32 | |
663 | + :since #x00000171 | |
664 | + :offset #xC8 | |
665 | + :chip-type :saa1099 | |
666 | + :reader vgm-header-saa1099-clock) | |
667 | + | |
668 | + (es5503-clock | |
669 | + :initform 0 | |
670 | + :type t/uint32 | |
671 | + :since #x00000171 | |
672 | + :offset #xCC | |
673 | + :chip-type :es5503 | |
674 | + :reader vgm-header-es5503-clock) | |
675 | + | |
676 | + (es5506-clock | |
677 | + :initform 0 | |
678 | + :type t/uint32 | |
679 | + :since #x00000171 | |
680 | + :offset #xD0 | |
681 | + :chip-type :es5506 | |
682 | + :reader vgm-header-es5506-clock) | |
683 | + | |
684 | + (es5503-num-channels | |
685 | + :initform 0 | |
686 | + :type t/uint8 | |
687 | + :since #x00000171 | |
688 | + :offset #xD4 | |
689 | + :chip-type :es5503 | |
690 | + :reader vgm-header-es5503-num-channels) | |
691 | + | |
692 | + (es5506-num-channels | |
693 | + :initform 0 | |
694 | + :type t/uint8 | |
695 | + :since #x00000171 | |
696 | + :offset #xD5 | |
697 | + :chip-type :es5506 | |
698 | + :reader vgm-header-es5506-num-channels) | |
699 | + | |
700 | + (c352-clock-div | |
701 | + :initform 0 | |
702 | + :type t/uint8 | |
703 | + :since #x00000171 | |
704 | + :offset #xD6 | |
705 | + :chip-type :c352 | |
706 | + :reader vgm-header-c352-clock-div) | |
707 | + | |
708 | + (reserved3 | |
709 | + :initform 0 | |
710 | + :type t/uint8 | |
711 | + :since #x00000171 | |
712 | + :offset #xD7) | |
713 | + | |
714 | + (x1-010-clock | |
715 | + :initform 0 | |
716 | + :type t/uint32 | |
717 | + :since #x00000171 | |
718 | + :offset #xD8 | |
719 | + :chip-type :x1-010 | |
720 | + :reader vgm-header-x1-010-clock) | |
721 | + | |
722 | + (c352-clock | |
723 | + :initform 0 | |
724 | + :type t/uint32 | |
725 | + :since #x00000171 | |
726 | + :offset #xDC | |
727 | + :chip-type :c352 | |
728 | + :reader vgm-header-c352-clock) | |
729 | + | |
730 | + (ga20-clock | |
731 | + :initform 0 | |
732 | + :type t/uint32 | |
733 | + :since #x00000171 | |
734 | + :offset #xE0 | |
735 | + :chip-type :ga20 | |
736 | + :reader vgm-header-ga20-clock) | |
737 | + | |
738 | + (reserved4 | |
739 | + :initform 0 | |
740 | + :type t/uint32 | |
741 | + :since #x00000171 | |
742 | + :offset #xE4)) | |
743 | + (:metaclass vgm-file-metaclass)) | |
744 | + | |
745 | +(defclass vgm-file () | |
746 | + ((header | |
747 | + :initarg :header | |
748 | + :initform (make-instance 'vgm-file-header) | |
749 | + :type vgm-file-header | |
750 | + :reader vgm-file-header | |
751 | + :documentation "Returns the VGM-FILE-HEADER associated with this VGM.") | |
752 | + | |
753 | + (gd3 | |
754 | + :initform (make-instance 'gd3-tag) | |
755 | + :type gd3-tag | |
756 | + :reader vgm-file-gd3 | |
757 | + :documentation "Returns the GD3-TAG associated with this VGM.") | |
758 | + | |
759 | + (data | |
760 | + :initform (new-array 0 t/uint8) | |
761 | + :type t/uint8-array | |
762 | + :reader vgm-file-data | |
763 | + :documentation "Returns the data (that is, the stream of instructions and | |
764 | +data bytes) associated with this VGM.") | |
765 | + | |
766 | + (data-offset | |
767 | + :initform 0 | |
768 | + :type t/uint32 | |
769 | + :reader vgm-file-data-offset) | |
770 | + | |
771 | + (extra-header | |
772 | + :initform nil | |
773 | + :type (or null extra-header) | |
774 | + :reader vgm-file-extra-header | |
775 | + :documentation "Returns the EXTRA-HEADER associated with this VGM, if any.") | |
776 | + | |
777 | + (known-supported-chips | |
778 | + :initform () | |
779 | + :type list) | |
780 | + | |
781 | + (known-used-chips | |
782 | + :initform (make-hash-table) | |
783 | + :type hash-table))) | |
784 | + | |
785 | +(defmethod initialize-instance :after ((obj vgm-file) &key &allow-other-keys) | |
786 | + (setf (slot-value obj 'data-offset) (vgm-header-data-offset (vgm-file-header obj)))) | |
787 | + | |
788 | +(defgeneric vgm-file-version (vgm)) | |
789 | + | |
790 | +(defmethod vgm-file-version ((vgm vgm-file)) | |
791 | + (vgm-header-version (vgm-file-header vgm))) | |
792 | + | |
793 | +(defmethod print-object ((obj vgm-file) stream) | |
794 | + (print-unreadable-object (obj stream :type t) | |
795 | + (format stream "Version: ~x, Data offset: $~x" | |
796 | + (vgm-file-version obj) | |
797 | + (vgm-file-data-offset obj)))) | |
798 | + | |
799 | +(defun %read-vgm-file-header (stream) | |
800 | + ;; Check for the magic bytes. | |
801 | + (unless (string= (read-string stream 4) +vgm-magic+) | |
802 | + (satou-error (vgm-error) "Not a VGM file")) | |
803 | + | |
804 | + (let ((ret (make-instance 'vgm-file-header)) | |
805 | + (klass (find-class 'vgm-file-header))) | |
806 | + ;; Read the first two fields manually. | |
807 | + (setf (slot-value ret 'relative-eof) (read-uint32 stream)) | |
808 | + (setf (slot-value ret 'version) (read-uint32 stream)) | |
809 | + | |
810 | + ;; Now read the rest of the fields. We skip any fields marked :IGNORE since | |
811 | + ;; those are read manually (like the two fields above). | |
812 | + | |
813 | + (loop with vgm-version = (vgm-header-version ret) | |
814 | + for slot-def in (closer-mop:class-slots klass) | |
815 | + | |
816 | + when (and (not (%vgm-field-ignore-p slot-def)) | |
817 | + (>= vgm-version (%vgm-field-since slot-def))) | |
818 | + do ;; Read this field | |
819 | + (file-position stream (%vgm-field-offset slot-def)) | |
820 | + (setf (closer-mop:slot-value-using-class klass ret slot-def) | |
821 | + (ecase (closer-mop:slot-definition-type slot-def) | |
822 | + (t/int8 (read-int8 stream)) | |
823 | + (t/uint8 (read-byte stream)) | |
824 | + (t/uint16 (read-uint16 stream)) | |
825 | + (t/uint32 (read-uint32 stream))))) | |
826 | + | |
827 | + ;; Adjust the data offset. This is needed because a data offset value of | |
828 | + ;; 0x0C must be reinterpreted as a hard offset of 0x40 according to the VGM | |
829 | + ;; specifications. Additionally, if it isn't 0x0C, we need to account for | |
830 | + ;; the data offset field being relative to its own position. | |
831 | + (dlog "Pre-adjustment VGM data offset in header: ~8,'0x" (vgm-header-data-offset ret)) | |
832 | + (setf (slot-value ret 'data-offset) | |
833 | + (case (slot-value ret 'data-offset) | |
834 | + ((0 #x0C) #x40) | |
835 | + (otherwise (+ (slot-value ret 'data-offset) #x34)))) | |
836 | + (dlog " Post-adjustment VGM data offset: ~8,'0x" (vgm-header-data-offset ret)) | |
837 | + | |
838 | + ;; Check Loop offset and sample count. | |
839 | + (when (and (/= (vgm-header-loop-offset ret) 0) | |
840 | + (= (vgm-header-loop-samples ret) 0)) | |
841 | + (sdm-log:warn! "Ignoring zero-sample loop") | |
842 | + (setf (slot-value ret 'loop-offset) 0)) | |
843 | + | |
844 | + ret)) | |
845 | + | |
846 | +(defun %read-vgm-file-extra-header-clock (stream) | |
847 | + (let ((ret (make-instance 'extra-header-clock))) | |
848 | + (setf (slot-value ret 'chip-id) (read-byte stream)) | |
849 | + (setf (slot-value ret 'value) (read-uint32 stream)) | |
850 | + ret)) | |
851 | + | |
852 | +(defun %read-vgm-file-extra-header-volume (stream) | |
853 | + (let ((ret (make-instance 'extra-header-volume))) | |
854 | + (setf (slot-value ret 'chip-id) (read-byte stream)) | |
855 | + (setf (slot-value ret 'flagso) (read-byte stream)) | |
856 | + (setf (slot-value ret 'value) (read-uint32 stream)) | |
857 | + ret)) | |
858 | + | |
859 | +(defun %read-vgm-file-extra-header (stream) | |
860 | + (let ((ret (make-instance 'vgm-file-extra-header)) | |
861 | + (header-size (read-uint32 stream))) | |
862 | + (when (= header-size 4) | |
863 | + ;; Nothing to read, return nil. | |
864 | + (return-from %read-vgm-file-extra-header)) | |
865 | + | |
866 | + (let* ((clock-offset-pos (file-position stream)) | |
867 | + (clock-offset (read-int32 stream)) | |
868 | + (volume-offset-pos (file-position stream)) | |
869 | + (volume-offset (read-int32 stream))) | |
870 | + | |
871 | + ;; Read clock values | |
872 | + (file-position stream (+ clock-offset-pos clock-offset)) | |
873 | + (loop with num-clocks fixnum = (read-byte stream) | |
874 | + repeat num-clocks do | |
875 | + ;; Read the clock. | |
876 | + (let ((clock (%read-vgm-file-extra-header-clock stream))) | |
877 | + (unless (hash-table-contains-p (extra-header-clocks ret) | |
878 | + (extra-header-clock-chip-id clock)) | |
879 | + ;; No clock values for this chip ID yet, so start a new vector. | |
880 | + (setf (gethash (extra-header-clock-chip-id clock) (extra-header-clocks ret)) | |
881 | + (new-vector 'extra-header-clock))) | |
882 | + | |
883 | + ;; Store the clock. | |
884 | + (vector-push-extend clock (gethash (extra-header-clock-chip-id clock) | |
885 | + (extra-header-clocks ret))))) | |
886 | + | |
887 | + ;; Read volume values | |
888 | + (file-position stream (+ volume-offset-pos volume-offset)) | |
889 | + (loop with num-volumes fixnum = (read-byte stream) | |
890 | + repeat num-volumes do | |
891 | + ;; Read the volume. | |
892 | + (let ((volume (%read-vgm-file-extra-header-volume stream))) | |
893 | + (unless (hash-table-contains-p (extra-header-volumes ret) | |
894 | + (extra-header-volume-chip-id volume)) | |
895 | + ;; No volume values for this chip ID yet, so start a new vector. | |
896 | + (setf (gethash (extra-header-volume-chip-id volume) (extra-header-volumes ret)) | |
897 | + (new-vector 'extra-header-volume))) | |
898 | + | |
899 | + ;; Store the volume. | |
900 | + (vector-push-extend volume (gethash (extra-header-volume-chip-id volume) | |
901 | + (extra-header-volumes ret))))) | |
902 | + | |
903 | + ret))) | |
904 | + | |
905 | +(defmacro %get-vgm-field-offset (obj slot) | |
906 | + `(%vgm-field-offset (find ,slot (the list (closer-mop:class-slots (class-of ,obj))) | |
907 | + :test #'eq :key #'closer-mop:slot-definition-name))) | |
908 | + | |
909 | +(defmacro %move-to-vgm-field-offset (stream obj slot &optional (extra-offset 0)) | |
910 | + `(file-position ,stream (+ ,extra-offset (the t/uint32 (%get-vgm-field-offset ,obj ,slot))))) | |
911 | + | |
912 | +(defun make-vgm-file (stream) | |
913 | + "Creates a new VGM-FILE instance by reading data from STREAM. This data must | |
914 | +not be compressed. STREAM must be able to seek." | |
915 | + (declare (optimize (speed 3) (debug 1))) | |
916 | + (let* ((start-at (file-position stream)) | |
917 | + | |
918 | + ;; This will read the file magic for us. | |
919 | + (header (%read-vgm-file-header stream)) | |
920 | + | |
921 | + ;; The instance we'll return. | |
922 | + (ret (make-instance 'vgm-file :header header))) | |
923 | + (declare (type fixnum start-at)) | |
924 | + | |
925 | + (with-typed-slots ((t/uint32 data-offset)) | |
926 | + ret | |
927 | + (with-typed-slots ((t/uint32 extra-header-offset version loop-offset relative-eof gd3-offset)) | |
928 | + header | |
929 | + | |
930 | + ;; Check the data offset. If the header reports a data offset less than | |
931 | + ;; 0x40, then we must reinterpret it as a hard offset of 0x40. | |
932 | + (when (< data-offset #x40) | |
933 | + (sdm-log:warn! "Invalid data offset: $~2,'0x" (vgm-file-data-offset ret)) | |
934 | + (setf (slot-value ret 'data-offset) #x40)) | |
935 | + | |
936 | + ;; Do we have an extra header? | |
937 | + (when (/= extra-header-offset 0) | |
938 | + (dlog "Reading extra header") | |
939 | + (%move-to-vgm-field-offset stream header 'extra-header-offset extra-header-offset) | |
940 | + (setf (slot-value ret 'extra-header) (%read-vgm-file-extra-header stream))) | |
941 | + | |
942 | + ;; Read GD3 tag | |
943 | + (%move-to-vgm-field-offset stream header 'gd3-offset gd3-offset) | |
944 | + (setf (slot-value ret 'gd3) (read-gd3-tag stream)) | |
945 | + | |
946 | + ;; If the VGm data starts at an offset that is lower than #x100, all | |
947 | + ;; overlapping header bytes have to be handled as if they were zero. So set | |
948 | + ;; these to zero now, because they're actually part of the data. | |
949 | + (when (< (the t/uint32 (vgm-header-data-offset header)) #x100) | |
950 | + (%clear-unused-header-data header)) | |
951 | + | |
952 | + ;; Read the data | |
953 | + (file-position stream (vgm-header-data-offset header)) | |
954 | + (dlog "Reading VGM data starting at $~8,'0x" (file-position stream)) | |
955 | + (flex:with-output-to-sequence (out) | |
956 | + (loop with buf = (new-array cl-sdm:*io-buffer-size* 't/uint8) | |
957 | + with num-written fixnum = 0 | |
958 | + for pos = (read-sequence buf stream) | |
959 | + until (zerop pos) do | |
960 | + (write-sequence buf out :end pos) | |
961 | + (incf num-written pos) | |
962 | + finally (setf (slot-value ret 'data) | |
963 | + (muffling | |
964 | + (make-array num-written :element-type 't/uint8 | |
965 | + :initial-contents (flex:get-output-stream-sequence out)))))) | |
966 | + | |
967 | + ;; A VGM file can, optionally, start its command data within the header | |
968 | + ;; space itself. When it does this, the unused fields are treated as if | |
969 | + ;; they were zero. So we need to determine where the command data | |
970 | + ;; *actually* starts. | |
971 | + (let ((total-size (- (the fixnum (file-position stream)) start-at))) | |
972 | + (when (or (= relative-eof 0) | |
973 | + (> relative-eof total-size)) | |
974 | + (sdm-log:warn! "Invalid VGM EOF offset: $~2,'0x (should be $~2,'0x)" | |
975 | + (slot-value header 'relative-eof) total-size) | |
976 | + (setf (slot-value header 'relative-eof) total-size))) | |
977 | + | |
978 | + ;; Adjust loop offset | |
979 | + (when (>= loop-offset relative-eof) | |
980 | + (setf (slot-value header 'loop-offset) 0)) | |
981 | + | |
982 | + ;; VGMs using the VGM spec v1.0 and earlier always set this to zero. | |
983 | + (when (< version #x101) | |
984 | + (setf (slot-value header 'rate) 0)) | |
985 | + | |
986 | + ;; Older versions of the VGM spec use the YM2413 clock for the YM2612 and | |
987 | + ;; YM2151. | |
988 | + (when (< version #x110) | |
989 | + (setf (slot-value header 'ym2612-clock) (vgm-header-ym2413-clock header)) | |
990 | + (setf (slot-value header 'ym2151-clock) (vgm-header-ym2413-clock header))) | |
991 | + | |
992 | + ;; Older versions of the VGm spec do not have these fields. | |
993 | + (when (< version #x150) | |
994 | + (setf (slot-value header 'spcm-clock) 0) | |
995 | + (setf (slot-value header 'spcm-interface-reg) 0)) | |
996 | + | |
997 | + ret)))) | |
998 | + | |
999 | +(defun %clear-unused-header-data (header) | |
1000 | + "Unsets any header value that was accidentally set. This can be caused if the | |
1001 | +data offset is < 0x100." | |
1002 | + (declare (type vgm-file-header header)) | |
1003 | + ;; NOTE At time of writing (19 May 2023), the GA20 clock field is the last | |
1004 | + ;; field in the header that is defined. All fields after are reserved, and so | |
1005 | + ;; we don't worry about those. | |
1006 | + | |
1007 | + (let ((doffset (vgm-header-data-offset header)) | |
1008 | + (klass (find-class 'vgm-file-header))) | |
1009 | + (dlog "Clearing header fields over $~8,'0x" doffset) | |
1010 | + | |
1011 | + (loop for slot-def in (closer-mop:class-slots klass) | |
1012 | + when (and (not (%vgm-field-ignore-p slot-def)) | |
1013 | + (<= doffset (%vgm-field-offset slot-def))) | |
1014 | + do ;; Clear this field | |
1015 | + (setf (closer-mop:slot-value-using-class klass header slot-def) 0))) | |
1016 | + t) | |
1017 | + | |
1018 | +(defmethod read-vgm-file ((source stream) &optional hint) | |
1019 | + (let ((src (satou/decompression:maybe-decompress source hint))) | |
1020 | + (make-vgm-file src))) | |
1021 | + | |
1022 | +(defmethod read-vgm-file ((source flex:flexi-stream) &optional hint) | |
1023 | + (let ((src (satou/decompression:maybe-decompress source hint))) | |
1024 | + (make-vgm-file src))) | |
1025 | + | |
1026 | +(defmethod read-vgm-file ((source pathname) &optional hint) | |
1027 | + (let ((real-hint (or hint (satou/decompression:get-hint source)))) | |
1028 | + (with-open-file (in source :direction :input :element-type '(unsigned-byte 8) | |
1029 | + :if-does-not-exist :error) | |
1030 | + (read-vgm-file in real-hint)))) | |
1031 | + | |
1032 | +(defmethod read-vgm-file ((source string) &optional hint) | |
1033 | + (read-vgm-file (pathname source) hint)) | |
1034 | + | |
1035 | +(define-typed-fn vgm-has-loop-info-p ((vgm-file vgm)) | |
1036 | + (boolean t) | |
1037 | + (declare (optimize (speed 3) (compilation-speed 0))) | |
1038 | + (/= (the t/uint32 (vgm-header-loop-offset (vgm-file-header vgm))) 0)) | |
1039 | + | |
1040 | +(defmethod valid-vgm-p ((source stream)) | |
1041 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1042 | + (handler-case | |
1043 | + (let ((src (satou/decompression:maybe-decompress source))) | |
1044 | + (muffling (string= (the string (read-string src 4)) +vgm-magic+))) | |
1045 | + (error () | |
1046 | + (return-from valid-vgm-p nil)))) | |
1047 | + | |
1048 | +(defmethod valid-vgm-p ((source flex:flexi-stream)) | |
1049 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1050 | + (handler-case | |
1051 | + (let ((src (satou/decompression:maybe-decompress source))) | |
1052 | + (muffling (string= (the string (read-string src 4)) +vgm-magic+))) | |
1053 | + (error () | |
1054 | + (return-from valid-vgm-p nil)))) | |
1055 | + | |
1056 | +(defmethod valid-vgm-p ((source pathname)) | |
1057 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1058 | + (with-open-file (in source :direction :input :element-type '(unsigned-byte 8) | |
1059 | + :if-does-not-exist :error) | |
1060 | + (valid-vgm-p in))) | |
1061 | + | |
1062 | +(defmethod valid-vgm-p ((source string)) | |
1063 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1064 | + (valid-vgm-p (pathname source))) | |
1065 | + | |
1066 | +(define-typed-fn vgm-file-chip-used-p ((vgm-file vgm) (t/chip chip)) | |
1067 | + (boolean) | |
1068 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1069 | + | |
1070 | + (with-typed-slots ((vgm-file-header header)) | |
1071 | + vgm | |
1072 | + (case chip | |
1073 | + (:sn76489 (/= (the t/uint32 (vgm-header-sn76489-clock header)) 0)) | |
1074 | + (:ym2413 (/= (the t/uint32 (vgm-header-ym2413-clock header)) 0)) | |
1075 | + (:ym2612 (/= (the t/uint32 (vgm-header-ym2612-clock header)) 0)) | |
1076 | + (:ym2151 (/= (the t/uint32 (vgm-header-ym2151-clock header)) 0)) | |
1077 | + (:sega-pcm (/= (the t/uint32 (vgm-header-spcm-clock header)) 0)) | |
1078 | + (:rf5c69 (/= (the t/uint32 (vgm-header-rf5c69-clock header)) 0)) | |
1079 | + (:ym2203 (/= (the t/uint32 (vgm-header-ym2203-clock header)) 0)) | |
1080 | + (:ym2608 (/= (the t/uint32 (vgm-header-ym2608-clock header)) 0)) | |
1081 | + (:ym2610 (/= (the t/uint32 (vgm-header-ym2610-clock header)) 0)) | |
1082 | + (:ym3812 (/= (the t/uint32 (vgm-header-ym3812-clock header)) 0)) | |
1083 | + (:ym3526 (/= (the t/uint32 (vgm-header-ym3526-clock header)) 0)) | |
1084 | + (:y8950 (/= (the t/uint32 (vgm-header-y8950-clock header)) 0)) | |
1085 | + (:ymf262 (/= (the t/uint32 (vgm-header-ymf262-clock header)) 0)) | |
1086 | + (:ymf278b (/= (the t/uint32 (vgm-header-ymf278b-clock header)) 0)) | |
1087 | + (:ymf271 (/= (the t/uint32 (vgm-header-ymf271-clock header)) 0)) | |
1088 | + (:ymz280b (/= (the t/uint32 (vgm-header-ymz280b-clock header)) 0)) | |
1089 | + (:rf5c164 (/= (the t/uint32 (vgm-header-rf5c164-clock header)) 0)) | |
1090 | + (:pwm (/= (the t/uint32 (vgm-header-pwm-clock header)) 0)) | |
1091 | + (:ay8910 (/= (the t/uint32 (vgm-header-ay8910-clock header)) 0)) | |
1092 | + (:dmg (/= (the t/uint32 (vgm-header-dmg-clock header)) 0)) | |
1093 | + (:nes-apu (/= (the t/uint32 (vgm-header-nes-apu-clock header)) 0)) | |
1094 | + (:multi-pcm (/= (the t/uint32 (vgm-header-multi-pcm-clock header)) 0)) | |
1095 | + (:upd7759 (/= (the t/uint32 (vgm-header-upd7759-clock header)) 0)) | |
1096 | + (:oki-m6258 (/= (the t/uint32 (vgm-header-oki-m6258-clock header)) 0)) | |
1097 | + (:oki-m6295 (/= (the t/uint32 (vgm-header-oki-m6295-clock header)) 0)) | |
1098 | + (:k051649 (/= (the t/uint32 (vgm-header-k051649-clock header)) 0)) | |
1099 | + (:k054539 (/= (the t/uint32 (vgm-header-k054539-clock header)) 0)) | |
1100 | + (:huc6280 (/= (the t/uint32 (vgm-header-huc6280-clock header)) 0)) | |
1101 | + (:c140 (/= (the t/uint32 (vgm-header-c140-clock header)) 0)) | |
1102 | + (:k053260 (/= (the t/uint32 (vgm-header-k053260-clock header)) 0)) | |
1103 | + (:pokey (/= (the t/uint32 (vgm-header-pokey-clock header)) 0)) | |
1104 | + (:qsound (/= (the t/uint32 (vgm-header-qsound-clock header)) 0)) | |
1105 | + (:scsp (/= (the t/uint32 (vgm-header-scsp-clock header)) 0)) | |
1106 | + (:wonderswan (/= (the t/uint32 (vgm-header-wonderswan-clock header)) 0)) | |
1107 | + (:vsu (/= (the t/uint32 (vgm-header-vsu-clock header)) 0)) | |
1108 | + (:saa1099 (/= (the t/uint32 (vgm-header-saa1099-clock header)) 0)) | |
1109 | + (:es5503 (/= (the t/uint32 (vgm-header-es5503-clock header)) 0)) | |
1110 | + (:es5506 (/= (the t/uint32 (vgm-header-es5506-clock header)) 0)) | |
1111 | + (:c352 (/= (the t/uint32 (vgm-header-c352-clock header)) 0)) | |
1112 | + (:x1-010 (/= (the t/uint32 (vgm-header-x1-010-clock header)) 0)) | |
1113 | + (:ga20 (/= (the t/uint32 (vgm-header-ga20-clock header)) 0)) | |
1114 | + (otherwise (error "Attempted to check for the existence of an unknown chip: ~a" chip))))) | |
1115 | + | |
1116 | +(define-typed-fn vgm-file-get-chip-clock ((vgm-file vgm) (t/chip chip)) | |
1117 | + (t/uint32) | |
1118 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1119 | + | |
1120 | + (with-typed-slots ((vgm-file-header header)) | |
1121 | + vgm | |
1122 | + (the t/uint32 | |
1123 | + (case chip | |
1124 | + (:sn76489 (vgm-header-sn76489-clock header)) | |
1125 | + (:ym2413 (vgm-header-ym2413-clock header)) | |
1126 | + (:ym2612 (vgm-header-ym2612-clock header)) | |
1127 | + (:ym2151 (vgm-header-ym2151-clock header)) | |
1128 | + (:sega-pcm (vgm-header-spcm-clock header)) | |
1129 | + (:rf5c69 (vgm-header-rf5c69-clock header)) | |
1130 | + (:ym2203 (vgm-header-ym2203-clock header)) | |
1131 | + (:ym2608 (vgm-header-ym2608-clock header)) | |
1132 | + (:ym2610 (vgm-header-ym2610-clock header)) | |
1133 | + (:ym3812 (vgm-header-ym3812-clock header)) | |
1134 | + (:ym3526 (vgm-header-ym3526-clock header)) | |
1135 | + (:y8950 (vgm-header-y8950-clock header)) | |
1136 | + (:ymf262 (vgm-header-ymf262-clock header)) | |
1137 | + (:ymf278b (vgm-header-ymf278b-clock header)) | |
1138 | + (:ymf271 (vgm-header-ymf271-clock header)) | |
1139 | + (:ymz280b (vgm-header-ymz280b-clock header)) | |
1140 | + (:rf5c164 (vgm-header-rf5c164-clock header)) | |
1141 | + (:pwm (vgm-header-pwm-clock header)) | |
1142 | + (:ay8910 (vgm-header-ay8910-clock header)) | |
1143 | + (:dmg (vgm-header-dmg-clock header)) | |
1144 | + (:nes-apu (vgm-header-nes-apu-clock header)) | |
1145 | + (:multi-pcm (vgm-header-multi-pcm-clock header)) | |
1146 | + (:upd7759 (vgm-header-upd7759-clock header)) | |
1147 | + (:oki-m6258 (vgm-header-oki-m6258-clock header)) | |
1148 | + (:oki-m6295 (vgm-header-oki-m6295-clock header)) | |
1149 | + (:k051649 (vgm-header-k051649-clock header)) | |
1150 | + (:k054539 (vgm-header-k054539-clock header)) | |
1151 | + (:huc6280 (vgm-header-huc6280-clock header)) | |
1152 | + (:c140 (vgm-header-c140-clock header)) | |
1153 | + (:k053260 (vgm-header-k053260-clock header)) | |
1154 | + (:pokey (vgm-header-pokey-clock header)) | |
1155 | + (:qsound (vgm-header-qsound-clock header)) | |
1156 | + (:scsp (vgm-header-scsp-clock header)) | |
1157 | + (:wonderswan (vgm-header-wonderswan-clock header)) | |
1158 | + (:vsu (vgm-header-vsu-clock header)) | |
1159 | + (:saa1099 (vgm-header-saa1099-clock header)) | |
1160 | + (:es5503 (vgm-header-es5503-clock header)) | |
1161 | + (:es5506 (vgm-header-es5506-clock header)) | |
1162 | + (:c352 (vgm-header-c352-clock header)) | |
1163 | + (:x1-010 (vgm-header-x1-010-clock header)) | |
1164 | + (:ga20 (vgm-header-ga20-clock header)) | |
1165 | + (otherwise (error "Attempted to check for the existence of an unknown chip: ~a" chip)))))) | |
1166 | + | |
1167 | +(define-typed-fn vgm-file-chips-used ((vgm-file vgm)) | |
1168 | + (hash-table) | |
1169 | + (declare (optimize (speed 3) (debug 1) (compilation-speed 0) (safety 1))) | |
1170 | + (with-typed-slots ((hash-table known-used-chips) | |
1171 | + (list known-supported-chips) | |
1172 | + (vgm-file-header header)) | |
1173 | + vgm | |
1174 | + (let* ((klass (find-class 'vgm-file-header)) | |
1175 | + (chip-types (if (= (length known-supported-chips) 0) | |
1176 | + ;; Memoize this | |
1177 | + (setf known-supported-chips | |
1178 | + (loop for slot in (closer-mop:class-slots klass) | |
1179 | + unless (or (%vgm-field-ignore-p slot) | |
1180 | + (eq (%vgm-field-chip-type slot) :unknown)) | |
1181 | + collect (%vgm-field-chip-type slot) into types | |
1182 | + finally (return (delete-duplicates types :test #'eql)))) | |
1183 | + | |
1184 | + ;; Use memoized value | |
1185 | + known-supported-chips))) | |
1186 | + (loop for chip-type in chip-types | |
1187 | + if (hash-table-contains-p known-used-chips chip-type) do | |
1188 | + (incf (the fixnum (gethash chip-type known-used-chips))) | |
1189 | + else if (vgm-file-chip-used-p vgm chip-type) do | |
1190 | + (setf (gethash chip-type known-used-chips) 1))) | |
1191 | + known-used-chips)) | |
1192 | + | |
1193 | +(define-typed-fn pcm-sample->vgm-sample ((t/int64 sample-num sample-rate-div sample-rate-mul)) | |
1194 | + (t/uint32 t) | |
1195 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1196 | + (muffling (coerce-to-uint32 (truncate (* sample-num sample-rate-div) sample-rate-mul)))) | |
1197 | + | |
1198 | +(define-typed-fn vgm-sample->pcm-sample ((t/int64 sample-num sample-rate-div sample-rate-mul)) | |
1199 | + (t/uint32 t) | |
1200 | + (declare (optimize (speed 3) (debug 1) (safety 0) (compilation-speed 0))) | |
1201 | + (muffling (coerce-to-uint32 (truncate (* sample-num sample-rate-mul) sample-rate-div)))) |