From 223388f1ad8f3e39856a1e6a01ef2acd1d820d25 Mon Sep 17 00:00:00 2001 From: Georg Holzmann Date: Mon, 14 Nov 2005 21:06:04 +0000 Subject: initial commit svn path=/trunk/externals/grh/; revision=3898 --- threadlib/GnuGPL.txt | 290 ++++++ threadlib/INSTALL | 16 + threadlib/README | 65 ++ threadlib/doc/help-detach.pd | 105 +++ threadlib/doc/help-join.pd | 106 +++ threadlib/doc/help-sleep.pd | 88 ++ threadlib/doc/help-threadedsf.pd | 54 ++ threadlib/src/Makefile | 68 ++ threadlib/src/Makefile_darwin | 69 ++ threadlib/src/Makefile_mingw | 68 ++ threadlib/src/callbacks.c | 134 +++ threadlib/src/detach.c | 240 +++++ threadlib/src/fifo.c | 512 ++++++++++ threadlib/src/join.c | 161 ++++ threadlib/src/sleep.c | 58 ++ threadlib/src/threadedsf.c | 1919 ++++++++++++++++++++++++++++++++++++++ threadlib/src/threadlib.c | 83 ++ threadlib/src/threadlib.h | 81 ++ 18 files changed, 4117 insertions(+) create mode 100755 threadlib/GnuGPL.txt create mode 100755 threadlib/INSTALL create mode 100755 threadlib/README create mode 100755 threadlib/doc/help-detach.pd create mode 100755 threadlib/doc/help-join.pd create mode 100755 threadlib/doc/help-sleep.pd create mode 100755 threadlib/doc/help-threadedsf.pd create mode 100755 threadlib/src/Makefile create mode 100755 threadlib/src/Makefile_darwin create mode 100755 threadlib/src/Makefile_mingw create mode 100755 threadlib/src/callbacks.c create mode 100755 threadlib/src/detach.c create mode 100755 threadlib/src/fifo.c create mode 100755 threadlib/src/join.c create mode 100755 threadlib/src/sleep.c create mode 100755 threadlib/src/threadedsf.c create mode 100755 threadlib/src/threadlib.c create mode 100755 threadlib/src/threadlib.h diff --git a/threadlib/GnuGPL.txt b/threadlib/GnuGPL.txt new file mode 100755 index 0000000..fa0bef4 --- /dev/null +++ b/threadlib/GnuGPL.txt @@ -0,0 +1,290 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This General +Public License applies to most of the Free Software Foundation's +software and to any other program whose authors commit to using it. +(Some other Free Software Foundation software is covered by the +GNU Library General Public License instead.) You can apply it to your +programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone +to deny you these rights or to ask you to surrender the rights. These +restrictions translate to certain responsibilities for you if you distribute +copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis +or for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And +you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +Finally, any free program is threatened constantly by software patents. +We wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program +proprietary. To prevent this, we have made it clear that any patent must +be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS FOR +COPYING, DISTRIBUTION AND +MODIFICATION + +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed under +the terms of this General Public License. The "Program", below, refers +to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, either +verbatim or with modifications and/or translated into another language. +(Hereinafter, translation is included without limitation in the term +"modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of running +the Program is not restricted, and the output from the Program is +covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether +that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the notices +that refer to this License and to the absence of any warranty; and give +any other recipients of the Program a copy of this License along with the +Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, and +can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based on +the Program, the distribution of the whole must be on the terms of this +License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the +Program with the Program (or with a work based on the Program) on a +volume of a storage or distribution medium does not bring the other +work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding + machine-readable source code, which must be distributed under + the terms of Sections 1 and 2 above on a medium customarily + used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with + such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to control +compilation and installation of the executable. However, as a special +exception, the source code distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies the +executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy +the source code from the same place counts as distribution of the source +code, even though third parties are not compelled to copy the source +along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and will +automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full +compliance. + +5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and all +its terms and conditions for copying, distributing or modifying the +Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms +and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. You are not responsible +for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot distribute +so as to satisfy simultaneously your obligations under this License and +any other pertinent obligations, then as a consequence you may not +distribute the Program at all. For example, if a patent license would not +permit royalty-free redistribution of the Program by all those who +receive copies directly or indirectly through you, then the only way you +could satisfy both it and this License would be to refrain entirely from +distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license +practices. Many people have made generous contributions to the wide +range of software distributed through that system in reliance on +consistent application of that system; it is up to the author/donor to +decide if he or she is willing to distribute software through any other +system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be +a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an +explicit geographical distribution limitation excluding those countries, so +that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number +of this License, you may choose any version ever published by the Free +Software Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we +sometimes make exceptions for this. Our decision will be guided by the +two goals of preserving the free status of all derivatives of our free +software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF +CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, +TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT +WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD +THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE +COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW +OR AGREED TO IN WRITING WILL ANY COPYRIGHT +HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED +ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING +ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR +OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS diff --git a/threadlib/INSTALL b/threadlib/INSTALL new file mode 100755 index 0000000..0aac333 --- /dev/null +++ b/threadlib/INSTALL @@ -0,0 +1,16 @@ +threadlib installation + +1) open the right makefile for your platform/compiler + +2) adapt the makefile for your needs: + PDPATH should point to your pd installation (usually /usr/local/lib/pd or /usr/lib/pd) + PDSCR should point to the PD source diretory + +3) type "make" to compile + +4) type "make install" to install help patches and binary +(you have to be root probably) + + +in case of problems: +grh@mur.at \ No newline at end of file diff --git a/threadlib/README b/threadlib/README new file mode 100755 index 0000000..54850f1 --- /dev/null +++ b/threadlib/README @@ -0,0 +1,65 @@ +threadlib +PD library for threaded patching +heavily based on pd_devel code by Tim Blechmann +(c) 2005, Georg Holzmann, +http://grh.mur.at/software/threadlib.html + +------------------------------------------------------------------------ + +contents of threadlib: + detach run part of the patch in a helper thread + join synchronize messages to pd's main thread + sleep blocks the system for a specific time + threadedsf modified threaded soundfiler from pd_devel_0.38 + +WARNING: +these objects (especially detach/join) are very experimental and may +crash your patches if you use them in the wrong way, because some +externals/internals are not threadsafe! + +REQUIREMENT: pd >= 0.39 + +Many thanks to Tim Blechmann for his code and help! + +------------------------------------------------------------------------ + +detach: +Detach is working on control objects only, creates a helper thread +and runs all functions of the following objects in this helper thread. +If a message from that thread reaches a join object a callback for +running the outlet function will be placed in the scheduler and run in +the next scheduler loop - so you can synchronize the message with pd's +main thread again. +Detach is useful if you have a control operation that would be too +CPU-intensive and would disturb dsp. + +join: +The message to the inlet will be sent to the outlet during the +next clock callback of the scheduler. It doesn't matter, which thread +the message was coming from. Messages from the main pd thread will be +rescheduled. +Join can be used everywhere, where a message has to be rescheduled, this +is also useful to place behind any threaded external calling the outlet +functions from the helper thread, to make sure the following messages +are being run in the main pd thread. + +threadedsf: +This is the threaded soundfiler from pd devel 0.38 by Tim Blechmann, +adapted to work with main pd >= 0.39. +Instead of the idle callbacks, which are not really useable in current +main pd, it uses clock-callbacks (and also the lockfree FIFO of pd devel). + +!!! WARNING: !!! +detach/join provide the possibility of threaded patching. +beware of the risks: +- not every pd object is thread-safe (if unsure, look at the source +code or ask at the pd-list or me) +- pd is not completely thread-safe itself +- everything that is triggered by the detached message will be +detached. don't mix threaded and non-threaded message paths unless you +know what you are doing. use the join external to synchronize with the +main pd thread!!!! +- if detach/join crashes pd during a performance, don't complain ... +- both detach and join have an overhead ... so only use them, if you +really need threaded patching, e.g. have a subpatch that has to run for +quite some time and would interrupt audio or something else diff --git a/threadlib/doc/help-detach.pd b/threadlib/doc/help-detach.pd new file mode 100755 index 0000000..03895aa --- /dev/null +++ b/threadlib/doc/help-detach.pd @@ -0,0 +1,105 @@ +#N canvas 658 0 480 847 10; +#X obj 29 23 cnv 15 404 54 empty empty empty 22 25 0 18 -1 -66577 0 +; +#X obj 31 25 cnv 15 400 50 empty empty threadlib 22 25 0 18 -228992 +-66577 0; +#X text 322 34 help file of; +#X text 316 50 ::: detach :::; +#X text 39 783 =%)!(%= threadlib \, by Georg Holzmann +\, 2005; +#X text 82 817 htttp://grh.mur.at/software/threadlib.html; +#X text 29 150 Detach is working on control objects only \, creates +a helper thread and runs all functions of the following object in this +helper thread.; +#X text 27 194 If a message form that thread reaches a join object +a callback for running the outlet function will be placed in the scheduler +and run in the next scheduler loop - so you can synchronize the message +with pd's main thread again.; +#X text 173 110 ::: DETACH :::; +#X text 28 251 Detach is useful \, if you have a control operation +that would be too CPU-intensive and would disturb dsp.; +#X obj 223 717 sleep; +#X obj 178 717 join; +#X obj 273 717 threadedsf; +#X text 98 717 see also:; +#X text 163 324 see the examples:; +#X text 154 407 !!!!! WARNING !!!!!; +#X text 30 436 detach/join provide the possibility of threaded patching +\, beware of the risks:; +#X text 30 466 - not every pd object is thread-safe (if unsure \, look +at the source \, ask the pd-list or me); +#X text 31 497 - pd is not completely thread-safe itself; +#X text 31 516 - everything that is triggered by the detached message +will be detached - so don't mix threaded and non-threaded message paths +unless you know what you are doing. use join to synchronize with the +main pd thread !; +#X text 31 575 - if detach/join crashes pd during a performance \, +don't complain ...; +#X text 31 607 - both detach and join have an overhead ... so only +use them if you really need threaded patching \, e.g. have a subpatch +that has to run for quite some time and would interrupt audio or something +else; +#X text 67 800 heavily based on pd_devel code by Tim Blechmann; +#N canvas 421 0 803 576 detach_join_examples 0; +#X msg 117 105 5; +#X obj 117 133 sleep; +#X text 37 39 1) this will block the system for 5 sec:; +#X obj 117 161 print EX1_DONE; +#X text 554 112 (instead of sleep you could; +#X msg 499 73 5; +#X obj 499 123 sleep; +#X text 396 39 2) to avoid this you can run it in a helper thread: +; +#X obj 499 97 detach; +#X obj 499 184 print EX2_DONE; +#X text 560 127 of course use an other cpu; +#X text 559 143 intensive object which could; +#X text 561 158 block the main thread); +#X text 34 252 3) be aware that each object connected to; +#X text 55 267 detach will run in the helper thread:; +#X msg 127 305 5; +#X obj 127 366 sleep; +#X obj 127 330 detach; +#X obj 170 397 print EX3_DONE_1; +#X msg 127 396 5; +#X obj 127 433 sleep; +#X obj 127 514 print EX3_DONE_2; +#X text 179 436 <- also in helper thread; +#X text 164 457 (and anything else connected; +#X text 171 472 to objects which are connected; +#X text 172 487 to detach !); +#X text 392 251 4) to synchronize these objects with pd main thread +; +#X text 413 268 again you have to use the join object:; +#X text 58 56 (so you will get e.g. an 5 sec; +#X text 61 71 audio drop out !); +#X msg 503 303 5; +#X obj 503 364 sleep; +#X obj 503 328 detach; +#X msg 503 394 5; +#X obj 546 395 print EX4_DONE_1; +#X text 579 368 <- in helper thread; +#X obj 503 431 join; +#X obj 503 463 sleep; +#X obj 503 518 print EX4_DONE_2; +#X text 551 464 <- in main thread again; +#X text 551 481 (and will so block the system); +#X connect 0 0 1 0; +#X connect 1 0 3 0; +#X connect 5 0 8 0; +#X connect 6 0 9 0; +#X connect 8 0 6 0; +#X connect 15 0 17 0; +#X connect 16 0 18 0; +#X connect 16 0 19 0; +#X connect 17 0 16 0; +#X connect 19 0 20 0; +#X connect 20 0 21 0; +#X connect 30 0 32 0; +#X connect 31 0 33 0; +#X connect 31 0 34 0; +#X connect 32 0 31 0; +#X connect 33 0 36 0; +#X connect 36 0 37 0; +#X connect 37 0 38 0; +#X restore 141 343 pd detach_join_examples; diff --git a/threadlib/doc/help-join.pd b/threadlib/doc/help-join.pd new file mode 100755 index 0000000..44fe459 --- /dev/null +++ b/threadlib/doc/help-join.pd @@ -0,0 +1,106 @@ +#N canvas 280 37 480 847 10; +#X obj 29 23 cnv 15 404 54 empty empty empty 22 25 0 18 -1 -66577 0 +; +#X obj 31 25 cnv 15 400 50 empty empty threadlib 22 25 0 18 -228992 +-66577 0; +#X text 322 34 help file of; +#X text 39 783 =%)!(%= threadlib \, by Georg Holzmann +\, 2005; +#X text 82 817 htttp://grh.mur.at/software/threadlib.html; +#X obj 232 717 sleep; +#X obj 281 717 threadedsf; +#X text 95 717 see also:; +#X text 163 324 see the examples:; +#X text 154 407 !!!!! WARNING !!!!!; +#X text 30 436 detach/join provide the possibility of threaded patching +\, beware of the risks:; +#X text 30 466 - not every pd object is thread-safe (if unsure \, look +at the source \, ask the pd-list or me); +#X text 31 497 - pd is not completely thread-safe itself; +#X text 31 516 - everything that is triggered by the detached message +will be detached - so don't mix threaded and non-threaded message paths +unless you know what you are doing. use join to synchronize with the +main pd thread !; +#X text 31 575 - if detach/join crashes pd during a performance \, +don't complain ...; +#X text 31 607 - both detach and join have an overhead ... so only +use them if you really need threaded patching \, e.g. have a subpatch +that has to run for quite some time and would interrupt audio or something +else; +#X text 67 800 heavily based on pd_devel code by Tim Blechmann; +#N canvas 421 0 803 576 detach_join_examples 0; +#X msg 117 105 5; +#X obj 117 133 sleep; +#X text 37 39 1) this will block the system for 5 sec:; +#X obj 117 161 print EX1_DONE; +#X text 554 112 (instead of sleep you could; +#X msg 499 73 5; +#X obj 499 123 sleep; +#X text 396 39 2) to avoid this you can run it in a helper thread: +; +#X obj 499 97 detach; +#X obj 499 184 print EX2_DONE; +#X text 560 127 of course use an other cpu; +#X text 559 143 intensive object which could; +#X text 561 158 block the main thread); +#X text 34 252 3) be aware that each object connected to; +#X text 55 267 detach will run in the helper thread:; +#X msg 127 305 5; +#X obj 127 366 sleep; +#X obj 127 330 detach; +#X obj 170 397 print EX3_DONE_1; +#X msg 127 396 5; +#X obj 127 433 sleep; +#X obj 127 514 print EX3_DONE_2; +#X text 179 436 <- also in helper thread; +#X text 164 457 (and anything else connected; +#X text 171 472 to objects which are connected; +#X text 172 487 to detach !); +#X text 392 251 4) to synchronize these objects with pd main thread +; +#X text 413 268 again you have to use the join object:; +#X text 58 56 (so you will get e.g. an 5 sec; +#X text 61 71 audio drop out !); +#X msg 503 303 5; +#X obj 503 364 sleep; +#X obj 503 328 detach; +#X msg 503 394 5; +#X obj 546 395 print EX4_DONE_1; +#X text 579 368 <- in helper thread; +#X obj 503 431 join; +#X obj 503 463 sleep; +#X obj 503 518 print EX4_DONE_2; +#X text 551 464 <- in main thread again; +#X text 551 481 (and will so block the system); +#X connect 0 0 1 0; +#X connect 1 0 3 0; +#X connect 5 0 8 0; +#X connect 6 0 9 0; +#X connect 8 0 6 0; +#X connect 15 0 17 0; +#X connect 16 0 18 0; +#X connect 16 0 19 0; +#X connect 17 0 16 0; +#X connect 19 0 20 0; +#X connect 20 0 21 0; +#X connect 30 0 32 0; +#X connect 31 0 33 0; +#X connect 31 0 34 0; +#X connect 32 0 31 0; +#X connect 33 0 36 0; +#X connect 36 0 37 0; +#X connect 37 0 38 0; +#X restore 141 343 pd detach_join_examples; +#X text 321 50 ::: join :::; +#X text 176 109 ::: JOIN :::; +#X obj 175 717 detach; +#X text 31 150 Join synchronizes messages from all threads to the pd +main thread. The message to the inlet will be sent to the outlet during +the next clock callback of the scheduler. It doesn't matter \, which +thread the message was coming from. Messages from the main pd thread +will be rescheduled.; +#X text 30 217 Join can be used everywhere \, where a message has to +be rescheduled \, this is also useful to place behind any threaded +external calling the outlet function from a helper thread - so you +can be sure \, that the following messages are being run in the main +pd thread.; diff --git a/threadlib/doc/help-sleep.pd b/threadlib/doc/help-sleep.pd new file mode 100755 index 0000000..b9c172d --- /dev/null +++ b/threadlib/doc/help-sleep.pd @@ -0,0 +1,88 @@ +#N canvas 280 37 465 589 10; +#X obj 29 23 cnv 15 404 54 empty empty empty 22 25 0 18 -1 -66577 0 +; +#X obj 31 25 cnv 15 400 50 empty empty threadlib 22 25 0 18 -228992 +-66577 0; +#X text 322 34 help file of; +#X text 27 518 =%)!(%= threadlib \, by Georg Holzmann +\, 2005; +#X text 70 552 htttp://grh.mur.at/software/threadlib.html; +#X obj 261 452 threadedsf; +#X text 83 452 see also:; +#X text 55 535 heavily based on pd_devel code by Tim Blechmann; +#N canvas 421 0 803 576 detach_join_examples 0; +#X msg 117 105 5; +#X obj 117 133 sleep; +#X text 37 39 1) this will block the system for 5 sec:; +#X obj 117 161 print EX1_DONE; +#X text 554 112 (instead of sleep you could; +#X msg 499 73 5; +#X obj 499 123 sleep; +#X text 396 39 2) to avoid this you can run it in a helper thread: +; +#X obj 499 97 detach; +#X obj 499 184 print EX2_DONE; +#X text 560 127 of course use an other cpu; +#X text 559 143 intensive object which could; +#X text 561 158 block the main thread); +#X text 34 252 3) be aware that each object connected to; +#X text 55 267 detach will run in the helper thread:; +#X msg 127 305 5; +#X obj 127 366 sleep; +#X obj 127 330 detach; +#X obj 170 397 print EX3_DONE_1; +#X msg 127 396 5; +#X obj 127 433 sleep; +#X obj 127 514 print EX3_DONE_2; +#X text 179 436 <- also in helper thread; +#X text 164 457 (and anything else connected; +#X text 171 472 to objects which are connected; +#X text 172 487 to detach !); +#X text 392 251 4) to synchronize these objects with pd main thread +; +#X text 413 268 again you have to use the join object:; +#X text 58 56 (so you will get e.g. an 5 sec; +#X text 61 71 audio drop out !); +#X msg 503 303 5; +#X obj 503 364 sleep; +#X obj 503 328 detach; +#X msg 503 394 5; +#X obj 546 395 print EX4_DONE_1; +#X text 579 368 <- in helper thread; +#X obj 503 431 join; +#X obj 503 463 sleep; +#X obj 503 518 print EX4_DONE_2; +#X text 551 464 <- in main thread again; +#X text 551 481 (and will so block the system); +#X connect 0 0 1 0; +#X connect 1 0 3 0; +#X connect 5 0 8 0; +#X connect 6 0 9 0; +#X connect 8 0 6 0; +#X connect 15 0 17 0; +#X connect 16 0 18 0; +#X connect 16 0 19 0; +#X connect 17 0 16 0; +#X connect 19 0 20 0; +#X connect 20 0 21 0; +#X connect 30 0 32 0; +#X connect 31 0 33 0; +#X connect 31 0 34 0; +#X connect 32 0 31 0; +#X connect 33 0 36 0; +#X connect 36 0 37 0; +#X connect 37 0 38 0; +#X restore 135 372 pd detach_join_examples; +#X obj 163 452 detach; +#X text 319 50 ::: sleep :::; +#X text 176 109 ::: SLEEP :::; +#X text 31 149 Sleep simply uses the c-function sleep() and blocks +the system for a specific time.; +#X obj 122 256 sleep; +#X msg 122 226 5; +#X text 159 227 <- sleep time in seconds !; +#X obj 122 287 print SLEEP_OVER; +#X text 154 353 see more examples:; +#X obj 220 452 join; +#X connect 13 0 16 0; +#X connect 14 0 13 0; diff --git a/threadlib/doc/help-threadedsf.pd b/threadlib/doc/help-threadedsf.pd new file mode 100755 index 0000000..7d6ea64 --- /dev/null +++ b/threadlib/doc/help-threadedsf.pd @@ -0,0 +1,54 @@ +#N canvas 728 33 465 834 10; +#X obj 29 23 cnv 15 404 54 empty empty empty 22 25 0 18 -1 -66577 0 +; +#X obj 31 25 cnv 15 400 50 empty empty threadlib 22 25 0 18 -228992 +-66577 0; +#X text 314 35 help file of; +#X text 27 765 =%)!(%= threadlib \, by Georg Holzmann +\, 2005; +#X text 70 799 htttp://grh.mur.at/software/threadlib.html; +#X text 100 711 see also:; +#X text 55 782 heavily based on pd_devel code by Tim Blechmann; +#X obj 180 711 detach; +#X obj 237 711 join; +#X text 292 51 ::: threadedsf :::; +#X text 162 112 ::: THREADEDSF :::; +#X text 106 152 A threaded soundfiler for main pd.; +#X obj 274 191 soundfiler; +#X obj 278 711 sleep; +#X text 27 594 NOTE: this is the threaded soundfiler from pd devel +0.38 by Tim Blechmann \, adapted to work with main pd >= 0.39. Instead +of the idle callbacks \, which are not really useable in current main +pd \, it uses clock-callbacks (ans also the lockfree FIFO of pd devel). +; +#X obj 70 512 threadedsf; +#X obj 70 534 print THREADED_SF; +#X obj 70 489 r \$0-tsf; +#X text 101 191 USAGE: see help-file of; +#X text 148 207 (same usage); +#X obj 52 307 openpanel; +#X msg 52 329 read -resize \$1 array1; +#X obj 52 351 s \$0-tsf; +#X obj 52 287 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X obj 248 349 s \$0-tsf; +#X obj 248 285 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X msg 248 327 write -wave \$1 array1; +#X obj 248 305 savepanel; +#X obj 265 505 table array1; +#X text 48 255 the same as with soundfiler:; +#X text 73 286 <- read; +#X text 271 284 <- write; +#X obj 262 437 s \$0-tsf; +#X text 46 409 additional: threaded resize->; +#X msg 262 411 resize array1 4410; +#X connect 15 0 16 0; +#X connect 17 0 15 0; +#X connect 20 0 21 0; +#X connect 21 0 22 0; +#X connect 23 0 20 0; +#X connect 25 0 27 0; +#X connect 26 0 24 0; +#X connect 27 0 26 0; +#X connect 34 0 32 0; diff --git a/threadlib/src/Makefile b/threadlib/src/Makefile new file mode 100755 index 0000000..0579345 --- /dev/null +++ b/threadlib/src/Makefile @@ -0,0 +1,68 @@ +# ------------------------------------------------- +# adjust the next 2 pathes to your system: + +# this should point to the directory which contains +# m_pd.h and g_canvas.h +PDSCR=/home/holzi/pd-0.39-1test1/src + +# this is the pd directory, here the files will be +# installed +PDPATH=/usr/lib/pd + +# -------------------------------------------------- + +TARGET=threadlib.pd_linux + +OBJ=fifo.o callbacks.o threadlib.o sleep.o detach.o \ + join.o threadedsf.o + +CC = gcc +LD = gcc +INCLUDE=-I$(PDSCR) -I. +LIB=-lc -lm +CC_FLAGS = -DPD -DUNIX -c -fPIC \ + -Wall -Wno-parentheses -Wno-switch -O3 \ + -funroll-loops -fomit-frame-pointer -pthread +LD_FLAGS = --export-dynamic -shared -o + +# -------------------------------------------------- + +all: pd_linux + +pd_linux: $(TARGET) + +$(TARGET): $(OBJ) + $(LD) $(LD_FLAGS) $(TARGET) $(OBJ) $(LIB) + strip --strip-unneeded $(TARGET) + chmod 755 $(TARGET) + +threadlib.o: threadlib.h threadlib.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadlib.c + +fifo.o: threadlib.o fifo.c + $(CC) $(CC_FLAGS) $(INCLUDE) fifo.c + +callbacks.o: fifo.o threadlib.o callbacks.c + $(CC) $(CC_FLAGS) $(INCLUDE) callbacks.c + +sleep.o: threadlib.o sleep.c + $(CC) $(CC_FLAGS) $(INCLUDE) sleep.c + +detach.o: threadlib.o fifo.o detach.c + $(CC) $(CC_FLAGS) $(INCLUDE) detach.c + +join.o: threadlib.o callbacks.o join.c + $(CC) $(CC_FLAGS) $(INCLUDE) join.c + +threadedsf.o: threadlib.o callbacks.o threadedsf.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadedsf.c + +# -------------------------------------------------- + +clean: + rm -f $(OBJ) $(TARGET) + +install: + @test -d $(PDPATH)/extra || mkdir -p $(PDPATH)/extra + install $(TARGET) $(PDPATH)/extra + install ../doc/*.pd $(PDPATH)/doc/5.reference diff --git a/threadlib/src/Makefile_darwin b/threadlib/src/Makefile_darwin new file mode 100755 index 0000000..a254ff0 --- /dev/null +++ b/threadlib/src/Makefile_darwin @@ -0,0 +1,69 @@ +# ------------------------------------------------- +# adjust the next 2 pathes to your system: + +# this should point to the directory which contains +# m_pd.h and g_canvas.h +PDSCR=/home/holzi/pd-0.39-1test1/src + +# this is the pd directory, here the files will be +# installed +PDPATH=/usr/lib/pd + +# -------------------------------------------------- + +TARGET=threadlib.pd_linux + +OBJ=fifo.o callbacks.o threadlib.o sleep.o detach.o \ + join.o threadedsf.o + +CC = gcc +LD = gcc +INCLUDE=-I$(PDSCR) -I. +LIB=-lc -lm +CC_FLAGS = -DPD -c \ + -Wall -Wno-parentheses -Wno-switch -O3 \ + -funroll-loops -fomit-frame-pointer -pthread +LD_FLAGS = -bundle -bundle_loader $(PDPATH)/bin/pd \ + --export-dynamic -o + +# -------------------------------------------------- + +all: pd_linux + +pd_linux: $(TARGET) + +$(TARGET): $(OBJ) + $(LD) $(LD_FLAGS) $(TARGET) $(OBJ) $(LIB) + #strip --strip-unneeded $(TARGET) + chmod 755 $(TARGET) + +threadlib.o: threadlib.h threadlib.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadlib.c + +fifo.o: threadlib.o fifo.c + $(CC) $(CC_FLAGS) $(INCLUDE) fifo.c + +callbacks.o: fifo.o threadlib.o callbacks.c + $(CC) $(CC_FLAGS) $(INCLUDE) callbacks.c + +sleep.o: threadlib.o sleep.c + $(CC) $(CC_FLAGS) $(INCLUDE) sleep.c + +detach.o: threadlib.o fifo.o detach.c + $(CC) $(CC_FLAGS) $(INCLUDE) detach.c + +join.o: threadlib.o callbacks.o join.c + $(CC) $(CC_FLAGS) $(INCLUDE) join.c + +threadedsf.o: threadlib.o callbacks.o threadedsf.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadedsf.c + +# -------------------------------------------------- + +clean: + rm -f $(OBJ) $(TARGET) + +install: + @test -d $(PDPATH)/extra || mkdir -p $(PDPATH)/extra + install $(TARGET) $(PDPATH)/extra + install ../doc/*.pd $(PDPATH)/doc/5.reference diff --git a/threadlib/src/Makefile_mingw b/threadlib/src/Makefile_mingw new file mode 100755 index 0000000..c110eec --- /dev/null +++ b/threadlib/src/Makefile_mingw @@ -0,0 +1,68 @@ +# ------------------------------------------------- +# adjust the next 2 pathes to your system: + +# this should point to the directory which contains +# m_pd.h and g_canvas.h +PDSCR=c:/pd/src + +# this is the pd directory, here the files will be +# installed +PDPATH=c:/pd + +# -------------------------------------------------- + +TARGET=threadlib.pd_linux + +OBJ=fifo.o callbacks.o threadlib.o sleep.o detach.o \ + join.o threadedsf.o + +CC = gcc +LD = gcc +INCLUDE=-I$(PDSCR) -I. +LIB=$(PD-PATH)/bin/pd.dll +CC_FLAGS = -DPD -DWINDOWS -c -mms-bitfields \ + -Wall -Wno-parentheses -Wno-switch -O3 \ + -funroll-loops -fomit-frame-pointer -pthread +LD_FLAGS = --export-dynamic -shared -o + +# -------------------------------------------------- + +all: pd_linux + +pd_linux: $(TARGET) + +$(TARGET): $(OBJ) + $(LD) $(LD_FLAGS) $(TARGET) $(OBJ) $(LIB) + strip --strip-unneeded $(TARGET) + chmod 755 $(TARGET) + +threadlib.o: threadlib.h threadlib.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadlib.c + +fifo.o: threadlib.o fifo.c + $(CC) $(CC_FLAGS) $(INCLUDE) fifo.c + +callbacks.o: fifo.o threadlib.o callbacks.c + $(CC) $(CC_FLAGS) $(INCLUDE) callbacks.c + +sleep.o: threadlib.o sleep.c + $(CC) $(CC_FLAGS) $(INCLUDE) sleep.c + +detach.o: threadlib.o fifo.o detach.c + $(CC) $(CC_FLAGS) $(INCLUDE) detach.c + +join.o: threadlib.o callbacks.o join.c + $(CC) $(CC_FLAGS) $(INCLUDE) join.c + +threadedsf.o: threadlib.o callbacks.o threadedsf.c + $(CC) $(CC_FLAGS) $(INCLUDE) threadedsf.c + +# -------------------------------------------------- + +clean: + rm -f $(OBJ) $(TARGET) + +install: + @test -d $(PDPATH)/extra || mkdir -p $(PDPATH)/extra + install $(TARGET) $(PDPATH)/extra + install ../doc/*.pd $(PDPATH)/doc/5.reference diff --git a/threadlib/src/callbacks.c b/threadlib/src/callbacks.c new file mode 100755 index 0000000..1385992 --- /dev/null +++ b/threadlib/src/callbacks.c @@ -0,0 +1,134 @@ +/* +* +* callbacks.c +* implementation of the callback FIFO +* +* this is the (modified) FIFO of the idle callbacks from pd_devel_0.38 +* (implemented in m_sched.c) +*/ + +#include "threadlib.h" + +// global callback fifo +t_fifo * h_callback_fifo = NULL; + +// global clock callback to trigger +// the callback fifo +t_clock *h_callback_clock = NULL; + +/* linked list of callbacks + * callback will be freed after returning 0 */ +typedef struct _sched_callback +{ + struct _sched_callback* next; /* next callback in ringbuffer / in fifo */ + t_int (*function) (t_int* argv); + t_int* argv; + t_int argc; +} t_sched_callback; + +// forward declaration +static void h_run_callbacks(); + +void h_init_callbacks() +{ + h_callback_fifo = fifo_init(); + h_callback_clock = clock_new(NULL, (t_method)h_run_callbacks); +} + +void h_free_callbacks() +{ + clock_free(h_callback_clock); + fifo_destroy(h_callback_fifo); +} + +void h_set_callback(t_int (*callback) (t_int* argv), t_int* argv, t_int argc) +{ + t_sched_callback* new = (t_sched_callback*) getbytes + (sizeof(t_sched_callback)); + + new->function = callback; + new->argv = (t_int*) copybytes (argv, argc * sizeof (t_int)); + new->argc = argc; + new->next = NULL; + + fifo_put(h_callback_fifo, new); + + // TODO find solution without lock + sys_lock(); + clock_delay(h_callback_clock, 0); + sys_unlock(); +} + +static t_sched_callback *ringbuffer_head; + +void h_run_callbacks() +{ + t_sched_callback * new_callback; + + sys_unlock(); + + /* append idle callback to ringbuffer */ + + while ( (new_callback = (t_sched_callback*) fifo_get(h_callback_fifo)) ) + { + t_sched_callback * next; + + /* set the next field to NULL ... it might be set in the fifo */ + new_callback->next = NULL; + if (ringbuffer_head == NULL) + { + ringbuffer_head = new_callback; + } + else + { + next = ringbuffer_head; + while (next->next != 0) + next = next->next; + next->next = new_callback; + } + } + + if (ringbuffer_head != NULL) + { + t_sched_callback * idle_callback = ringbuffer_head; + t_sched_callback * last = NULL; + t_sched_callback * next; + + do + { + int status; + + sys_lock(); + status = (idle_callback->function)(idle_callback->argv); + sys_unlock(); + + switch (status) + { + /* callbacks returning 0 will be deleted */ + case 0: + next = idle_callback->next; + freebytes (idle_callback->argv, idle_callback->argc); + freebytes ((void*)idle_callback, sizeof(t_sched_callback)); + + if (last == NULL) + ringbuffer_head = next; + else + last->next = next; + + idle_callback = next; + + /* callbacks returning 1 will be run again */ + case 1: + break; + + /* callbacks returning 2 will be run during the next idle callback */ + case 2: + last = idle_callback; + idle_callback = idle_callback->next; + } + } + while (idle_callback != NULL); + } + + sys_lock(); +} diff --git a/threadlib/src/detach.c b/threadlib/src/detach.c new file mode 100755 index 0000000..b92c7af --- /dev/null +++ b/threadlib/src/detach.c @@ -0,0 +1,240 @@ +/* +* +* detach +* Copyright (C) 2005 Georg Holzmann, +* Copyright (C) 2005 Tim Blechmann +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; see the file COPYING. If not, write to +* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#include "threadlib.h" + +static t_class *detach_class; + +typedef t_fifo fifo_t; /* for emacs syntax highlighting */ + +typedef struct _detach +{ + t_object x_obj; + + t_outlet * x_outlet; + + pthread_t x_thread; + pthread_mutex_t x_mutex; + pthread_cond_t x_cond; + + fifo_t * x_fifo; + +} detach_t; + +typedef struct _detach_content +{ + struct _detach_content_t * next; + enum { BANG, + POINTER, + FLOAT, + SYMBOL, + LIST, + ANYTHING, + CANCEL} type; + int argc; + t_atom * argv; + t_symbol * symbol; +} detach_content_t; + +static void detach_thread(detach_t* x) +{ + detach_content_t * me; + while(1) + { + pthread_cond_wait(&x->x_cond, &x->x_mutex); + + me = (detach_content_t*) fifo_get(x->x_fifo); + + while (me != NULL) + { + /* run function */ + switch (me->type) + { + case BANG: + outlet_bang(x->x_outlet); + break; + case FLOAT: + outlet_float(x->x_outlet, atom_getfloat(me->argv)); + break; + case SYMBOL: + outlet_symbol(x->x_outlet, atom_getsymbol(me->argv)); + break; + case LIST: + outlet_list(x->x_outlet, 0, me->argc, me->argv); + freebytes(me->argv, me->argc * sizeof(t_atom)); + break; + case POINTER: + outlet_pointer(x->x_outlet, me->argv->a_w.w_gpointer); + break; + case ANYTHING: + outlet_anything(x->x_outlet, me->symbol, me->argc, me->argv); + freebytes(me->argv, me->argc * sizeof(t_atom)); + break; + case CANCEL: + goto done; + } + + /* free */ + if (me->argc) + freebytes(me->argv, me->argc * sizeof (t_atom)); + freebytes (me, sizeof(detach_content_t)); + me = (detach_content_t*) fifo_get(x->x_fifo); + } + } + + done: /* free the fifo and exit */ + + do + { + if (me->argc) + freebytes(me->argv, me->argc * sizeof (t_atom)); + freebytes (me, sizeof(detach_content_t)); + } + while ( (me = (detach_content_t*) fifo_get(x->x_fifo)) ); + + fifo_destroy(x->x_fifo); + pthread_mutex_destroy(&x->x_mutex); + pthread_cond_destroy(&x->x_cond); + return; +} + +/* todo: take argument for thread priority */ +static detach_t * detach_new() +{ + detach_t *x = (detach_t*) pd_new(detach_class); + int status; + + x->x_outlet = outlet_new(&x->x_obj, NULL); + x->x_fifo = fifo_init(); + + /* thread initialisation */ + pthread_mutex_init (&x->x_mutex,NULL); + pthread_mutex_unlock (&x->x_mutex); + pthread_cond_init (&x->x_cond,NULL); + + status = pthread_create(&x->x_thread, NULL, + (void*)detach_thread, x); + + if (status == 0) + post("detaching thread"); + + return x; +} + +static void detach_free(detach_t * x) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = CANCEL; + me->argc = 0; + fifo_put(x->x_fifo, me); + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_bang(detach_t * x) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = BANG; + me->argc = 0; + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_float(detach_t * x, t_float f) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = FLOAT; + me->argc = 1; + me->argv = getbytes(sizeof(t_atom)); + SETFLOAT(me->argv, f); + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_pointer(detach_t * x, t_gpointer* gp) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = POINTER; + me->argc = 1; + me->argv = getbytes(sizeof(t_atom)); + SETPOINTER(me->argv, gp); + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_symbol(detach_t * x, t_symbol * s) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = SYMBOL; + me->argc = 1; + me->argv = getbytes(sizeof(t_atom)); + SETSYMBOL(me->argv, s); + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_list(detach_t * x, t_symbol * s, int argc, t_atom* argv) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = LIST; + me->argc = argc; + me->argv = copybytes(argv, argc * sizeof(t_atom)); + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +static void detach_anything(detach_t * x, t_symbol * s, int argc, t_atom* argv) +{ + detach_content_t * me = getbytes(sizeof(detach_content_t)); + + me->type = ANYTHING; + me->argc = argc; + me->argv = copybytes(argv, argc * sizeof(t_atom)); + me->symbol = s; + fifo_put(x->x_fifo, me); + + pthread_cond_broadcast(&x->x_cond); +} + +void detach_setup(void) +{ + detach_class = class_new(gensym("detach"), (t_newmethod)detach_new, + (t_method)detach_free, sizeof(detach_t), + CLASS_DEFAULT, 0); + + class_addbang(detach_class, detach_bang); + class_addfloat(detach_class, detach_float); + class_addpointer(detach_class, detach_pointer); + class_addsymbol(detach_class, detach_symbol); + class_addlist(detach_class, detach_list); + class_addanything(detach_class, detach_anything); +} diff --git a/threadlib/src/fifo.c b/threadlib/src/fifo.c new file mode 100755 index 0000000..daad265 --- /dev/null +++ b/threadlib/src/fifo.c @@ -0,0 +1,512 @@ +/* + * fifo.c + * this is the lockfree fifo implementation of pd_devel_0.39 + * + * Copyright (c) 2004, Tim Blechmann + * supported by vibrez.net + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt" in this distribution. */ + + +#include "threadlib.h" +#include "stddef.h" + + +#ifndef THREADLIB_LOCKFREE + +/* we always have the implementation for posix systems with threadlocks */ + +#include "errno.h" + +typedef struct _fifocell +{ + struct _fifocell* next; + void* data; /* pointer to our data */ +} t_fifocell; + +struct _fifo +{ + t_fifocell * head; + t_fifocell * tail; + pthread_mutex_t mutex; +}; + + +t_fifo * fifo_init() +{ + t_fifo* ret = (t_fifo*) getbytes(sizeof (t_fifo)); + t_fifocell * fifo_begin = (t_fifocell*) getbytes (sizeof (t_fifocell) ); + + fifo_begin->data = NULL; + fifo_begin->next = NULL; + + ret->head = fifo_begin; + ret->tail = fifo_begin; + + pthread_mutex_init(&ret->mutex, NULL); + + pthread_mutex_unlock(&ret->mutex); + + return ret; +} + +void fifo_destroy(t_fifo* fifo) +{ + void * data; + + do + { + data = fifo_get(fifo); + } + while (data != NULL); + + pthread_mutex_lock(&fifo->mutex); + pthread_mutex_destroy(&fifo->mutex); + + freebytes(fifo, sizeof(t_fifo)); + return; +} + +/* fifo_put and fifo_get are the only threadsafe functions!!! */ +void fifo_put(t_fifo* fifo, void* data) +{ + if (data != NULL) + { + t_fifocell * cell = (t_fifocell*) getbytes(sizeof(t_fifocell)); + + cell->data = data; + cell->next = NULL; + + pthread_mutex_lock(&fifo->mutex); + + fifo->tail->next = cell; + fifo->tail = cell; + + pthread_mutex_unlock(&fifo->mutex); + } + return; +} + + +/* this fifo_get returns NULL if the fifo is empty + * or locked by another thread */ +void* fifo_get(t_fifo* fifo) +{ + t_fifocell * cell; + void* data; + + if(pthread_mutex_trylock(&fifo->mutex) != EBUSY) + { + cell = fifo->head->next; + + if (cell != NULL) + { + fifo->head->next = cell->next; + if(cell == fifo->tail) + fifo->tail = fifo->head; + data = cell->data; + + freebytes (cell, sizeof(t_fifocell)); + } + else + data = NULL; + + pthread_mutex_unlock(&fifo->mutex); + } + else + data = NULL; + return data; +} + +#else /* THREADLIB_LOCKFREE */ + +/* + lockfree fifo adapted from the midishare: Copyright © Grame 1999 + Grame Research Laboratory, 9, rue du Garet 69001 Lyon - France + grame@rd.grame.fr +*/ + + + +typedef struct _fifocell +{ + struct _fifocell* next; + void* data; /* pointer to our data */ +} t_fifocell; + +typedef struct _lifo +{ + unsigned long ic; /* operation counter */ + t_fifocell* top; /* stack pointer */ + unsigned long oc; /* operation counter */ +#ifdef __POWERPC__ + long unused [5]; /* lifo size must be at least 32 bytes */ + /* to avoid livelock in multiprocessor */ +#endif +} t_lifo; + +struct _fifo +{ + t_lifo in; + t_lifo out; +}; + +/* platform dependent code */ + +#ifdef __SMP__ +#define LOCK lock ; +#else +#define LOCK +#endif + +#if defined(__GNUC__) && defined(__POWERPC__) + +static void* lifo_pop(t_lifo* lifo) +{ + register void * data; + register volatile long a, b; + register long c=0; + asm volatile ( + "# LFPOP \n" + "0: \n" + " lwarx %4, %1, %2 \n" /* creates a reservation on lf */ + " cmpwi %4, 0 \n" /* test if the lifo is empty */ + " beq- 1f \n" + " lwz %5, 0(%4) \n" /* next cell in b */ + " sync \n" /* synchronize instructions */ + " stwcx. %5, %1, %2 \n" /* if the reservation is not altered */ + /* modify lifo top */ + " bne- 0b \n" /* otherwise: loop and try again */ + "0: \n" + " lwarx %5, %1, %3 \n" /* creates a reservation on lf->count */ + " addi %5, %5, -1 \n" /* dec count */ + " sync \n" /* synchronize instructions */ + " stwcx. %5, %1, %3 \n" /* conditionnal store */ + " bne- 0b \n" + "1: \n" + " mr %0, %4 \n" + :"=r" (data), "=r" (c) + : "r" (&lifo->top), "r" (&lifo->oc), "r" (a), "r" (b), "1" (c) + : "r0" /* prevents using r0 because of the ambiguity of 'addi' coding: */ + /* gcc version 2.95.3 20010315 (release - Linux-Mandrake 8.0 for PPC) */ + /* compiles the instruction "addi 0, 0, n" as li 0, n */ + ); + return data; +} + +static void* lifo_push(register t_lifo* lifo, register void* data) +{ + register volatile long t1; + register long t2=0; + asm volatile ( + "# LFPUSH \n" + "0: \n" + " lwarx %0, %3, %1 \n" + " stw %0, 0(%2) \n" + " sync \n" + " stwcx. %2, %3, %1 \n" + " bne- 0b \n" + "0: \n" + " lwarx %0, %3, %4 \n" + " addi %0, %0, 1 \n" + " sync \n" + " stwcx. %0, %3, %4 \n" + " bne- 0b \n" + : "=r" (t1) + : "r" (&lifo->top), "r" (data), "r" (t2), "r" (&lifo->oc), "0" (t1) + : "r0" /* prevents using r0 because of the ambiguity of 'addi' coding: */ + /* gcc version 2.95.3 20010315 (release - Linux-Mandrake 8.0 for PPC) */ + /* compiles the instruction "addi 0, 0, n" as li 0, n */ + ); +} + +#elif defined(__Macintosh__) || defined(__MacOSX__) + +static void* lifo_pop(t_lifo* lifo) +{ + register cell * data; + register long a, b; + asm { + addi lifo, lifo, 4 + loop: + lwarx a, 0, lifo /* creates a reservation on lifo */ + cmpwi a, 0 /* test if the lifo is empty */ + beq- empty + lwz b, 0(a) /* next cell in b */ + sync /* synchronize instructions */ + stwcx. b, 0, lifo /* if the reservation is not altered */ + /* modify lifo top */ + bne- loop /* otherwise: loop and try again */ + + addi lifo, lifo, 4 + dec: + lwarx b, 0, lifo /* creates a reservation on lifo->count */ + addi b, b, -1 /* dec count */ + sync /* synchronize instructions */ + stwcx. b, 0, lifo /* conditionnal store */ + bne- dec + + empty: + mr data, a + } + return data; +} + +static void lifo_push (register t_lifo * lifo, register void * data) +{ + register long tmp; + asm { + addi lifo, lifo, 4 + loop: + lwarx tmp, 0, lifo /* creates a reservation on lifo */ + stw tmp, 0(data) /* link the new cell to the lifo */ + sync /* synchronize instructions */ + stwcx. data, 0, lifo /* if the reservation is not altered */ + /* modify lifo top */ + bne- loop /* otherwise: loop and try again */ + + addi lifo, lifo, 4 + inc: + lwarx tmp, 0, lifo /* creates a reservation on lifo->count */ + addi tmp, tmp, 1 /* inc count */ + sync /* synchronize instructions */ + stwcx. tmp, 0, lifo /* conditionnal store */ + bne- inc + } +} + + + +#elif defined(__GNUC__) && (defined(_X86_) || defined(__i386__) || defined(__i586__) || defined(__i686__)) + +static void* lifo_pop(t_lifo* lifo) +{ + void * data = 0; + __asm__ __volatile__ ( + "# LFPOP \n\t" + "pushl %%ebx \n\t" + "pushl %%ecx \n\t" + "movl 4(%%esi), %%edx \n\t" + "movl (%%esi), %%eax \n\t" + "testl %%eax, %%eax \n\t" + "jz 20f \n" + "10:\t" + "movl (%%eax), %%ebx \n\t" + "movl %%edx, %%ecx \n\t" + "incl %%ecx \n\t" + LOCK "cmpxchg8b (%%esi) \n\t" + "jz 20f \n\t" + "testl %%eax, %%eax \n\t" + "jnz 10b \n" + "20:\t" + "popl %%ecx \n\t" + "popl %%ebx \n\t" + :"=a" (data) + :"S" (&lifo->top) + :"memory", "edx"); + return data; +} + +static void lifo_push(t_lifo * lifo, void * data) +{ + __asm__ __volatile__ ( + "# LFPUSH \n\t" + "pushl %%ebx \n\t" + "pushl %%ecx \n\t" + "movl 0(%%esi), %%eax \n\t" + "movl 4(%%esi), %%edx \n" + "1:\t" + "movl %%eax, %%ebx \n\t" + "incl %%ebx \n\t" + "movl %%edx, (%%ecx) \n\t" + LOCK "cmpxchg8b (%%esi) \n\t" + "jnz 1b \n\t" + "popl %%ecx \n\t" + "popl %%ebx \n\t" + :/* no output */ + :"S" (lifo), "c" (data) + :"memory", "eax", "edx"); +} + +#elif defined(__GNUC__) && defined(__x86_64__) + +/* this will not work for all revisions of the amd64 architecture ... */ + +static void* lifo_pop(t_lifo* lifo) +{ + void * data = 0; + __asm__ __volatile__ ( + "# LFPOP \n\t" + "push %%rbx \n\t" + "push %%rcx \n\t" + "mov 8(%%rdi), %%rdx \n\t" + "mov (%%rdi), %%rax \n\t" + "test %%rax, %%rax \n\t" + "jz 20f \n" + "10:\t" + "mov (%%rax), %%rbx \n\t" + "mov %%rdx, %%rcx \n\t" + "inc %%rcx \n\t" + LOCK "cmpxchg16b (%%rdi) \n\t" + "jz 20f \n\t" + "test %%rax, %%rax \n\t" + "jnz 10b \n" + "20:\t" + "pop %%rcx \n\t" + "pop %%rbx \n\t" + :"=a" (data) + :"D" (&lifo->top) + :"memory", "rdx"); + return data; +} + +static void lifo_push(t_lifo * lifo, void * data) +{ + __asm__ __volatile__ ( + "# LFPUSH \n\t" + "push %%rbx \n\t" + "push %%rcx \n\t" + "mov 0(%%rdi), %%rax \n\t" + "mov 8(%%rdi), %%rdx \n" + "1:\t" + "mov %%rax, %%rbx \n\t" + "inc %%rbx \n\t" + "mov %%rdx, (%%rcx) \n\t" + LOCK "cmpxchg16b (%%rdi) \n\t" + "jnz 1b \n\t" + "pop %%rcx \n\t" + "pop %%rbx \n\t" + :/* no output */ + :"D" (lifo), "c" (data) + :"memory", "rax", "rdx"); +} + +#elif defined(_WIN32) && defined(_MSC_VER) + +static void* lifo_pop(t_lifo* lifo) +{ + __asm + { + push ebx + push ecx + push edx + push esi + mov esi, lifo + add esi, 4 + mov edx, dword ptr [esi+4] + mov eax, dword ptr [esi] + test eax, eax + jz _end + _loop: + mov ebx, dword ptr [eax] + mov ecx, edx + inc ecx + LOCK cmpxchg8b qword ptr [esi] + jz _end + test eax, eax + jnz _loop + _end: + pop esi + pop edx + pop ecx + pop ebx + } +} + +static void lifo_push(t_lifo * lifo, void * data) +{ + __asm + { + push eax + push ebx + push ecx + push edx + push esi + mov esi, lifo + mov eax, dword ptr [esi] + mov ecx, data + mov edx, dword ptr 4[esi] + _loop: + mov ebx, eax + inc ebx + mov [ecx], edx + LOCK cmpxchg8b qword ptr [esi] + jnz _loop + pop esi + pop edx + pop ecx + pop ebx + pop eax + } +} + + +#else +#error lockfree fifos not available on this platform +#endif + + + +static void lifo_init(t_lifo* lifo) +{ + lifo->ic = 0; + lifo->top = NULL; + lifo->oc = 0; +} + +t_fifo* fifo_init(void) +{ + t_fifo* ret = (t_fifo*) getbytes(sizeof(t_fifo)); + + lifo_init(&ret->in); + lifo_init(&ret->out); + + return ret; +} + + +void fifo_destroy(t_fifo* fifo) +{ + void * data; + do + { + data = fifo_get(fifo); + } + while (data != NULL); + + freebytes(fifo, sizeof(t_fifo)); + return; +} + +void fifo_put(t_fifo* fifo, void* data) +{ + lifo_push(&fifo->in, data); +} + +void* fifo_get(t_fifo* fifo) +{ + void * data; + t_lifo *out = &fifo->out; + + data = lifo_pop(out); + + if (!data) + { + void * tmp; + t_lifo *in = &fifo->in; + data = lifo_pop(in); + + if (data) + { + while((tmp = lifo_pop(in))) + { + lifo_push(out, data); + data = tmp; + } + } + + } + return data; +} + +#endif diff --git a/threadlib/src/join.c b/threadlib/src/join.c new file mode 100755 index 0000000..5e3f688 --- /dev/null +++ b/threadlib/src/join.c @@ -0,0 +1,161 @@ +/* +* +* join +* Copyright (C) 2005 Georg Holzmann, +* Copyright (C) 2005 Tim Blechmann +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; see the file COPYING. If not, write to +* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#include "threadlib.h" +#include + +static t_class *join_class; + +typedef struct _join +{ + t_object x_obj; + t_outlet * x_outlet; +} join_t; + +static join_t * join_new(void) +{ + join_t *x = (join_t*) pd_new(join_class); + x->x_outlet = outlet_new(&x->x_obj, NULL); + return x; +} + +static t_int join_bang_callback(t_int * argv) +{ + outlet_bang((t_outlet*)argv[0]); + return 0; +} + +static void join_bang(join_t * x) +{ + t_int* argv = getbytes(sizeof(t_int*)); + argv[0] = (t_int)x->x_outlet; + + h_set_callback(join_bang_callback, argv, 1); +} + +static t_int join_pointer_callback(t_int * argv) +{ + outlet_pointer((t_outlet*)argv[0], (t_gpointer*)argv[1]); + return 0; +} + +static void join_pointer(join_t * x, t_gpointer * gp) +{ + t_int* argv = getbytes(2*sizeof(t_int*)); + argv[0] = (t_int)x->x_outlet; + argv[1] = (t_int)gp; + + h_set_callback(join_pointer_callback, argv, 2); +} + +static t_int join_float_callback(t_int * argv) +{ + outlet_float((t_outlet*)argv[0], (t_float)argv[1]); + return 0; +} + +static void join_float(join_t * x, t_float f) +{ + t_int* argv = getbytes(2*sizeof(t_int*)); + argv[0] = (t_int)x->x_outlet; + argv[1] = (t_int)f; + + h_set_callback(join_float_callback, argv, 2); +} + +static t_int join_symbol_callback(t_int * argv) +{ + outlet_symbol((t_outlet*)argv[0], (t_symbol*)argv[1]); + return 0; +} + +static void join_symbol(join_t * x, t_symbol * s) +{ + t_int* argv = getbytes(2*sizeof(t_int*)); + argv[0] = (t_int)x->x_outlet; + argv[1] = (t_int)s; + + h_set_callback(join_symbol_callback, argv, 2); +} + +static t_int join_list_callback(t_int * argv) +{ + outlet_list((t_outlet*)argv[0], 0, (int)argv[1], (t_atom*)argv[2]); + freebytes ((t_atom*)argv[2], (int)argv[1] * sizeof(t_atom)); + return 0; +} + +static void join_list(join_t * x, t_symbol * s, int argc, t_atom* largv) +{ + t_int* argv = getbytes(3*sizeof(t_int*)); + t_atom* copied_argv = copybytes(largv, argc * sizeof(t_atom)); + + argv[0] = (t_int)x->x_outlet; + argv[1] = (t_int)argc; + argv[2] = (t_int)copied_argv; + + h_set_callback(join_list_callback, argv, 3); +} + +static t_int join_anything_callback(t_int * argv) +{ + outlet_anything((t_outlet*)argv[0], &s_list, + (int)argv[1], (t_atom*)argv[2]); + freebytes ((t_atom*)argv[2], (int)argv[1] * sizeof(t_atom)); + return 0; +} + +static void join_anything(join_t * x, t_symbol * s, int argc, t_atom* largv) +{ + t_int* argv = getbytes(3*sizeof(t_int*)); + + // also copy selector symbol + int copied_argc = argc+1; + t_atom *copied_argv; + copied_argv = (t_atom*)getbytes(copied_argc * sizeof(t_atom)); + + if(copied_argc) + { + memcpy(copied_argv, s, sizeof(t_atom)); + SETSYMBOL(copied_argv, s); + memcpy(copied_argv+1, largv, argc * sizeof(t_atom)); + } + + argv[0] = (t_int)x->x_outlet; + argv[1] = (t_int)copied_argc; + argv[2] = (t_int)copied_argv; + + h_set_callback(join_anything_callback, argv, 3); +} + +void join_setup(void) +{ + join_class = class_new(gensym("join"), (t_newmethod)join_new, + 0, sizeof(join_t), CLASS_DEFAULT, 0); + + class_addbang(join_class, join_bang); + class_addfloat(join_class, join_float); + class_addpointer(join_class, join_pointer); + class_addsymbol(join_class, join_symbol); + class_addlist(join_class, join_list); + class_addanything(join_class, join_anything); +} diff --git a/threadlib/src/sleep.c b/threadlib/src/sleep.c new file mode 100755 index 0000000..f800074 --- /dev/null +++ b/threadlib/src/sleep.c @@ -0,0 +1,58 @@ +/* +* +* sleep +* like the c function sleep - blocks the system for a specific time +* Copyright (C) 2005 Georg Holzmann +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; see the file COPYING. If not, write to +* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#include "threadlib.h" +#include + +static t_class *sleep_class; + +typedef struct _sleep +{ + t_object x_obj; + t_outlet * x_outlet; +} t_sleep; + +static void sleep_float(t_sleep * x, t_float f) +{ + int time = (int)(f<0?0:f); + sleep(time); + outlet_bang(x->x_outlet); +} + +static void *sleep_new(void) +{ + t_sleep *x = (t_sleep *)pd_new(sleep_class); + + x->x_outlet = outlet_new(&x->x_obj,&s_float); + + return (void *)x; +} + +void sleep_setup(void) +{ + sleep_class = class_new(gensym("sleep"), + (t_newmethod)sleep_new, + 0, sizeof(t_sleep), + CLASS_DEFAULT, 0); + + class_addfloat(sleep_class, sleep_float); +} diff --git a/threadlib/src/threadedsf.c b/threadlib/src/threadedsf.c new file mode 100755 index 0000000..c9d0b9f --- /dev/null +++ b/threadlib/src/threadedsf.c @@ -0,0 +1,1919 @@ +/* +* +* threadedsf +* +* this is a little bit hacked version of the +* threaded soundfiler of pd_devel_0.38 by Tim Blechmann +* +* (c) 2005, Georg Holzmann, +*/ + +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* this file contains, first, a collection of soundfile access routines, a +sort of soundfile library. Second, the "soundfiler" object is defined which +uses the routines to read or write soundfiles, synchronously, from garrays. +These operations are not to be done in "real time" as they may have to wait +for disk accesses (even the write routine.) Finally, the realtime objects +readsf~ and writesf~ are defined which confine disk operations to a separate +thread so that they can be used in real time. The readsf~ and writesf~ +objects use Posix-like threads. */ + +/* threaded soundfiler by Tim Blechmann */ + + +#include "threadlib.h" + +#ifndef MSW +#include +#include +#endif +#ifdef MSW +#include +#endif +#include +#include +#include + +#include "g_canvas.h" + +#define MAXSFCHANS 64 + + +// forward declarations + +struct _garray +{ + t_gobj x_gobj; + t_scalar *x_scalar; /* scalar "containing" the array */ + t_glist *x_glist; /* containing glist */ + t_symbol *x_name; /* unexpanded name (possibly with leading '$') */ + t_symbol *x_realname; /* expanded name (symbol we're bound to) */ + char x_usedindsp; /* true if some DSP routine is using this */ + char x_saveit; /* true if we should save this with parent */ + char x_listviewing; /* true if list view window is open */ +}; + +t_array *garray_getarray(t_garray *x); +int garray_ambigendian(void); + + + +/***************** soundfile header structures ************************/ + +typedef unsigned short uint16; +typedef unsigned int uint32; /* long isn't 32-bit on amd64 */ + +#define FORMAT_WAVE 0 +#define FORMAT_AIFF 1 +#define FORMAT_NEXT 2 + +/* the NeXTStep sound header structure; can be big or little endian */ + +typedef struct _nextstep +{ + char ns_fileid[4]; /* magic number '.snd' if file is big-endian */ + uint32 ns_onset; /* byte offset of first sample */ + uint32 ns_length; /* length of sound in bytes */ + uint32 ns_format; /* format; see below */ + uint32 ns_sr; /* sample rate */ + uint32 ns_nchans; /* number of channels */ + char ns_info[4]; /* comment */ +} t_nextstep; + +#define NS_FORMAT_LINEAR_16 3 +#define NS_FORMAT_LINEAR_24 4 +#define NS_FORMAT_FLOAT 6 +#define SCALE (1./(1024. * 1024. * 1024. * 2.)) + +/* the WAVE header. All Wave files are little endian. We assume + the "fmt" chunk comes first which is usually the case but perhaps not + always; same for AIFF and the "COMM" chunk. */ + +typedef struct _wave +{ + char w_fileid[4]; /* chunk id 'RIFF' */ + uint32 w_chunksize; /* chunk size */ + char w_waveid[4]; /* wave chunk id 'WAVE' */ + char w_fmtid[4]; /* format chunk id 'fmt ' */ + uint32 w_fmtchunksize; /* format chunk size */ + uint16 w_fmttag; /* format tag (WAV_INT etc) */ + uint16 w_nchannels; /* number of channels */ + uint32 w_samplespersec; /* sample rate in hz */ + uint32 w_navgbytespersec; /* average bytes per second */ + uint16 w_nblockalign; /* number of bytes per frame */ + uint16 w_nbitspersample; /* number of bits in a sample */ + char w_datachunkid[4]; /* data chunk id 'data' */ + uint32 w_datachunksize; /* length of data chunk */ +} t_wave; + +typedef struct _fmt /* format chunk */ +{ + uint16 f_fmttag; /* format tag, 1 for PCM */ + uint16 f_nchannels; /* number of channels */ + uint32 f_samplespersec; /* sample rate in hz */ + uint32 f_navgbytespersec; /* average bytes per second */ + uint16 f_nblockalign; /* number of bytes per frame */ + uint16 f_nbitspersample; /* number of bits in a sample */ +} t_fmt; + +typedef struct _wavechunk /* ... and the last two items */ +{ + char wc_id[4]; /* data chunk id, e.g., 'data' or 'fmt ' */ + uint32 wc_size; /* length of data chunk */ +} t_wavechunk; + +#define WAV_INT 1 +#define WAV_FLOAT 3 + +/* the AIFF header. I'm assuming AIFC is compatible but don't really know + that. */ + +typedef struct _datachunk +{ + char dc_id[4]; /* data chunk id 'SSND' */ + uint32 dc_size; /* length of data chunk */ +} t_datachunk; + +typedef struct _comm +{ + uint16 c_nchannels; /* number of channels */ + uint16 c_nframeshi; /* # of sample frames (hi) */ + uint16 c_nframeslo; /* # of sample frames (lo) */ + uint16 c_bitspersamp; /* bits per sample */ + unsigned char c_samprate[10]; /* sample rate, 80-bit float! */ +} t_comm; + + /* this version is more convenient for writing them out: */ +typedef struct _aiff +{ + char a_fileid[4]; /* chunk id 'FORM' */ + uint32 a_chunksize; /* chunk size */ + char a_aiffid[4]; /* aiff chunk id 'AIFF' */ + char a_fmtid[4]; /* format chunk id 'COMM' */ + uint32 a_fmtchunksize; /* format chunk size, 18 */ + uint16 a_nchannels; /* number of channels */ + uint16 a_nframeshi; /* # of sample frames (hi) */ + uint16 a_nframeslo; /* # of sample frames (lo) */ + uint16 a_bitspersamp; /* bits per sample */ + unsigned char a_samprate[10]; /* sample rate, 80-bit float! */ +} t_aiff; + +#define AIFFHDRSIZE 38 /* probably not what sizeof() gives */ + + +#define AIFFPLUS (AIFFHDRSIZE + 8) /* header size including first chunk hdr */ + +#define WHDR1 sizeof(t_nextstep) +#define WHDR2 (sizeof(t_wave) > WHDR1 ? sizeof (t_wave) : WHDR1) +#define WRITEHDRSIZE (AIFFPLUS > WHDR2 ? AIFFPLUS : WHDR2) + +#define READHDRSIZE (16 > WHDR2 + 2 ? 16 : WHDR2 + 2) + +#define OBUFSIZE MAXPDSTRING /* assume MAXPDSTRING is bigger than headers */ + +#ifdef MSW +#include +#define BINCREATE _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY +#else +#define BINCREATE O_WRONLY | O_CREAT | O_TRUNC +#endif + +/* this routine returns 1 if the high order byte comes at the lower +address on our architecture (big-endianness.). It's 1 for Motorola, +0 for Intel: */ + +extern int garray_ambigendian(void); + +/* byte swappers */ + +static uint32 swap4(uint32 n, int doit) +{ + if (doit) + return (((n & 0xff) << 24) | ((n & 0xff00) << 8) | + ((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24)); + else return (n); +} + +static uint16 swap2(uint32 n, int doit) +{ + if (doit) + return (((n & 0xff) << 8) | ((n & 0xff00) >> 8)); + else return (n); +} + +static void swapstring(char *foo, int doit) +{ + if (doit) + { + char a = foo[0], b = foo[1], c = foo[2], d = foo[3]; + foo[0] = d; foo[1] = c; foo[2] = b; foo[3] = a; + } +} + +/******************** soundfile access routines **********************/ + +/* This routine opens a file, looks for either a nextstep or "wave" header, +* seeks to end of it, and fills in bytes per sample and number of channels. +* Only 2- and 3-byte fixed-point samples and 4-byte floating point samples +* are supported. If "headersize" is nonzero, the +* caller should supply the number of channels, endinanness, and bytes per +* sample; the header is ignored. Otherwise, the routine tries to read the +* header and fill in the properties. +*/ + +int open_soundfile(const char *dirname, const char *filename, int headersize, + int *p_bytespersamp, int *p_bigendian, int *p_nchannels, long *p_bytelimit, + long skipframes) +{ + char buf[OBUFSIZE], *bufptr; + int fd, format, nchannels, bigendian, bytespersamp, swap, sysrtn; + long bytelimit = 0x7fffffff; + errno = 0; + fd = open_via_path(dirname, filename, + "", buf, &bufptr, MAXPDSTRING, 1); + if (fd < 0) + return (-1); + if (headersize >= 0) /* header detection overridden */ + { + bigendian = *p_bigendian; + nchannels = *p_nchannels; + bytespersamp = *p_bytespersamp; + bytelimit = *p_bytelimit; + } + else + { + int bytesread = read(fd, buf, READHDRSIZE); + int format; + if (bytesread < 4) + goto badheader; + if (!strncmp(buf, ".snd", 4)) + format = FORMAT_NEXT, bigendian = 1; + else if (!strncmp(buf, "dns.", 4)) + format = FORMAT_NEXT, bigendian = 0; + else if (!strncmp(buf, "RIFF", 4)) + { + if (bytesread < 12 || strncmp(buf + 8, "WAVE", 4)) + goto badheader; + format = FORMAT_WAVE, bigendian = 0; + } + else if (!strncmp(buf, "FORM", 4)) + { + if (bytesread < 12 || strncmp(buf + 8, "AIFF", 4)) + goto badheader; + format = FORMAT_AIFF, bigendian = 1; + } + else + goto badheader; + swap = (bigendian != garray_ambigendian()); + if (format == FORMAT_NEXT) /* nextstep header */ + { + uint32 param; + if (bytesread < (int)sizeof(t_nextstep)) + goto badheader; + nchannels = swap4(((t_nextstep *)buf)->ns_nchans, swap); + format = swap4(((t_nextstep *)buf)->ns_format, swap); + headersize = swap4(((t_nextstep *)buf)->ns_onset, swap); + if (format == NS_FORMAT_LINEAR_16) + bytespersamp = 2; + else if (format == NS_FORMAT_LINEAR_24) + bytespersamp = 3; + else if (format == NS_FORMAT_FLOAT) + bytespersamp = 4; + else goto badheader; + bytelimit = 0x7fffffff; + } + else if (format == FORMAT_WAVE) /* wave header */ + { + /* This is awful. You have to skip over chunks, + except that if one happens to be a "fmt" chunk, you want to + find out the format from that one. The case where the + "fmt" chunk comes after the audio isn't handled. */ + headersize = 12; + if (bytesread < 20) + goto badheader; + /* First we guess a number of channels, etc., in case there's + no "fmt" chunk to follow. */ + nchannels = 1; + bytespersamp = 2; + /* copy the first chunk header to beginnning of buffer. */ + memcpy(buf, buf + headersize, sizeof(t_wavechunk)); + /* post("chunk %c %c %c %c", + ((t_wavechunk *)buf)->wc_id[0], + ((t_wavechunk *)buf)->wc_id[1], + ((t_wavechunk *)buf)->wc_id[2], + ((t_wavechunk *)buf)->wc_id[3]); */ + /* read chunks in loop until we get to the data chunk */ + while (strncmp(((t_wavechunk *)buf)->wc_id, "data", 4)) + { + long chunksize = swap4(((t_wavechunk *)buf)->wc_size, + swap), seekto = headersize + chunksize + 8, seekout; + + if (!strncmp(((t_wavechunk *)buf)->wc_id, "fmt ", 4)) + { + long commblockonset = headersize + 8; + seekout = lseek(fd, commblockonset, SEEK_SET); + if (seekout != commblockonset) + goto badheader; + if (read(fd, buf, sizeof(t_fmt)) < (int) sizeof(t_fmt)) + goto badheader; + nchannels = swap2(((t_fmt *)buf)->f_nchannels, swap); + format = swap2(((t_fmt *)buf)->f_nbitspersample, swap); + if (format == 16) + bytespersamp = 2; + else if (format == 24) + bytespersamp = 3; + else if (format == 32) + bytespersamp = 4; + else goto badheader; + } + seekout = lseek(fd, seekto, SEEK_SET); + if (seekout != seekto) + goto badheader; + if (read(fd, buf, sizeof(t_wavechunk)) < + (int) sizeof(t_wavechunk)) + goto badheader; + /* post("new chunk %c %c %c %c at %d", + ((t_wavechunk *)buf)->wc_id[0], + ((t_wavechunk *)buf)->wc_id[1], + ((t_wavechunk *)buf)->wc_id[2], + ((t_wavechunk *)buf)->wc_id[3], seekto); */ + headersize = seekto; + } + bytelimit = swap4(((t_wavechunk *)buf)->wc_size, swap); + headersize += 8; + } + else + { + /* AIFF. same as WAVE; actually predates it. Disgusting. */ + headersize = 12; + if (bytesread < 20) + goto badheader; + /* First we guess a number of channels, etc., in case there's + no COMM block to follow. */ + nchannels = 1; + bytespersamp = 2; + /* copy the first chunk header to beginnning of buffer. */ + memcpy(buf, buf + headersize, sizeof(t_datachunk)); + /* read chunks in loop until we get to the data chunk */ + while (strncmp(((t_datachunk *)buf)->dc_id, "SSND", 4)) + { + long chunksize = swap4(((t_datachunk *)buf)->dc_size, + swap), seekto = headersize + chunksize + 8, seekout; + /* post("chunk %c %c %c %c seek %d", + ((t_datachunk *)buf)->dc_id[0], + ((t_datachunk *)buf)->dc_id[1], + ((t_datachunk *)buf)->dc_id[2], + ((t_datachunk *)buf)->dc_id[3], seekto); */ + if (!strncmp(((t_datachunk *)buf)->dc_id, "COMM", 4)) + { + long commblockonset = headersize + 8; + seekout = lseek(fd, commblockonset, SEEK_SET); + if (seekout != commblockonset) + goto badheader; + if (read(fd, buf, sizeof(t_comm)) < + (int) sizeof(t_comm)) + goto badheader; + nchannels = swap2(((t_comm *)buf)->c_nchannels, swap); + format = swap2(((t_comm *)buf)->c_bitspersamp, swap); + if (format == 16) + bytespersamp = 2; + else if (format == 24) + bytespersamp = 3; + else goto badheader; + } + seekout = lseek(fd, seekto, SEEK_SET); + if (seekout != seekto) + goto badheader; + if (read(fd, buf, sizeof(t_datachunk)) < + (int) sizeof(t_datachunk)) + goto badheader; + headersize = seekto; + } + bytelimit = swap4(((t_datachunk *)buf)->dc_size, swap); + headersize += 8; + } + } + /* seek past header and any sample frames to skip */ + sysrtn = lseek(fd, nchannels * bytespersamp * skipframes + headersize, 0); + if (sysrtn != nchannels * bytespersamp * skipframes + headersize) + return (-1); + bytelimit -= nchannels * bytespersamp * skipframes; + if (bytelimit < 0) + bytelimit = 0; + /* copy sample format back to caller */ + *p_bigendian = bigendian; + *p_nchannels = nchannels; + *p_bytespersamp = bytespersamp; + *p_bytelimit = bytelimit; + return (fd); +badheader: + /* the header wasn't recognized. We're threadable here so let's not + print out the error... */ + errno = EIO; + return (-1); +} + +static void soundfile_xferin(int sfchannels, int nvecs, float **vecs, + long itemsread, unsigned char *buf, int nitems, int bytespersamp, + int bigendian) +{ + int i, j; + unsigned char *sp, *sp2; + float *fp; + int nchannels = (sfchannels < nvecs ? sfchannels : nvecs); + int bytesperframe = bytespersamp * sfchannels; + for (i = 0, sp = buf; i < nchannels; i++, sp += bytespersamp) + { + if (bytespersamp == 2) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[0] << 24) | (sp2[1] << 16)); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[1] << 24) | (sp2[0] << 16)); + } + } + else if (bytespersamp == 3) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[0] << 24) | (sp2[1] << 16) + | (sp2[2] << 8)); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *fp = SCALE * ((sp2[2] << 24) | (sp2[1] << 16) + | (sp2[0] << 8)); + } + } + else if (bytespersamp == 4) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *(long *)fp = ((sp2[0] << 24) | (sp2[1] << 16) + | (sp2[2] << 8) | sp2[3]); + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + itemsread; + j < nitems; j++, sp2 += bytesperframe, fp++) + *(long *)fp = ((sp2[3] << 24) | (sp2[2] << 16) + | (sp2[1] << 8) | sp2[0]); + } + } + } + /* zero out other outputs */ + for (i = sfchannels; i < nvecs; i++) + for (j = nitems, fp = vecs[i]; j--; ) + *fp++ = 0; + +} + + /* soundfiler_write ... + + usage: write [flags] filename table ... + flags: + -nframes + -skip + -bytes + -normalize + -nextstep + -wave + -big + -little + */ + + /* the routine which actually does the work should LATER also be called + from garray_write16. */ + + + /* Parse arguments for writing. The "obj" argument is only for flagging + errors. For streaming to a file the "normalize", "onset" and "nframes" + arguments shouldn't be set but the calling routine flags this. */ + +static int soundfiler_writeargparse(void *obj, int *p_argc, t_atom **p_argv, + t_symbol **p_filesym, + int *p_filetype, int *p_bytespersamp, int *p_swap, int *p_bigendian, + int *p_normalize, long *p_onset, long *p_nframes, float *p_rate) +{ + int argc = *p_argc; + t_atom *argv = *p_argv; + int bytespersamp = 2, bigendian = 0, + endianness = -1, swap, filetype = -1, normalize = 0; + long onset = 0, nframes = 0x7fffffff; + t_symbol *filesym; + float rate = -1; + + while (argc > 0 && argv->a_type == A_SYMBOL && + *argv->a_w.w_symbol->s_name == '-') + { + char *flag = argv->a_w.w_symbol->s_name + 1; + if (!strcmp(flag, "skip")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((onset = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "nframes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((nframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "bytes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((bytespersamp = argv[1].a_w.w_float) < 2) || + bytespersamp > 4) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "normalize")) + { + normalize = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "wave")) + { + filetype = FORMAT_WAVE; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "nextstep")) + { + filetype = FORMAT_NEXT; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "aiff")) + { + filetype = FORMAT_AIFF; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "big")) + { + endianness = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "little")) + { + endianness = 0; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "r") || !strcmp(flag, "rate")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((rate = argv[1].a_w.w_float) <= 0)) + goto usage; + argc -= 2; argv += 2; + } + else goto usage; + } + if (!argc || argv->a_type != A_SYMBOL) + goto usage; + filesym = argv->a_w.w_symbol; + + /* check if format not specified and fill in */ + if (filetype < 0) + { + if (strlen(filesym->s_name) >= 5 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".aif") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".AIF"))) + filetype = FORMAT_AIFF; + if (strlen(filesym->s_name) >= 6 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".aiff") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".AIFF"))) + filetype = FORMAT_AIFF; + if (strlen(filesym->s_name) >= 5 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".snd") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".SND"))) + filetype = FORMAT_NEXT; + if (strlen(filesym->s_name) >= 4 && + (!strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".au") || + !strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".AU"))) + filetype = FORMAT_NEXT; + if (filetype < 0) + filetype = FORMAT_WAVE; + } + /* don't handle AIFF floating point samples */ + if (bytespersamp == 4) + { + if (filetype == FORMAT_AIFF) + { + pd_error(obj, "AIFF floating-point file format unavailable"); + goto usage; + } + } + /* for WAVE force little endian; for nextstep use machine native */ + if (filetype == FORMAT_WAVE) + { + bigendian = 0; + if (endianness == 1) + pd_error(obj, "WAVE file forced to little endian"); + } + else if (filetype == FORMAT_AIFF) + { + bigendian = 1; + if (endianness == 0) + pd_error(obj, "AIFF file forced to big endian"); + } + else if (endianness == -1) + { + bigendian = garray_ambigendian(); + } + else bigendian = endianness; + swap = (bigendian != garray_ambigendian()); + + argc--; argv++; + + *p_argc = argc; + *p_argv = argv; + *p_filesym = filesym; + *p_filetype = filetype; + *p_bytespersamp = bytespersamp; + *p_swap = swap; + *p_normalize = normalize; + *p_onset = onset; + *p_nframes = nframes; + *p_bigendian = bigendian; + *p_rate = rate; + return (0); +usage: + return (-1); +} + +static int create_soundfile(t_canvas *canvas, const char *filename, + int filetype, int nframes, int bytespersamp, + int bigendian, int nchannels, int swap, float samplerate) +{ + char filenamebuf[MAXPDSTRING], buf2[MAXPDSTRING]; + char headerbuf[WRITEHDRSIZE]; + t_wave *wavehdr = (t_wave *)headerbuf; + t_nextstep *nexthdr = (t_nextstep *)headerbuf; + t_aiff *aiffhdr = (t_aiff *)headerbuf; + int fd, headersize = 0; + + strncpy(filenamebuf, filename, MAXPDSTRING-10); + filenamebuf[MAXPDSTRING-10] = 0; + + if (filetype == FORMAT_NEXT) + { + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".snd")) + strcat(filenamebuf, ".snd"); + if (bigendian) + strncpy(nexthdr->ns_fileid, ".snd", 4); + else strncpy(nexthdr->ns_fileid, "dns.", 4); + nexthdr->ns_onset = swap4(sizeof(*nexthdr), swap); + nexthdr->ns_length = 0; + nexthdr->ns_format = swap4((bytespersamp == 3 ? NS_FORMAT_LINEAR_24 : + (bytespersamp == 4 ? NS_FORMAT_FLOAT : NS_FORMAT_LINEAR_16)), swap); + nexthdr->ns_sr = swap4(samplerate, swap); + nexthdr->ns_nchans = swap4(nchannels, swap); + strcpy(nexthdr->ns_info, "Pd "); + swapstring(nexthdr->ns_info, swap); + headersize = sizeof(t_nextstep); + } + else if (filetype == FORMAT_AIFF) + { + long datasize = nframes * nchannels * bytespersamp; + long longtmp; + static unsigned char dogdoo[] = + {0x40, 0x0e, 0xac, 0x44, 0, 0, 0, 0, 0, 0, 'S', 'S', 'N', 'D'}; + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".aif") && + strcmp(filenamebuf + strlen(filenamebuf)-5, ".aiff")) + strcat(filenamebuf, ".aif"); + strncpy(aiffhdr->a_fileid, "FORM", 4); + aiffhdr->a_chunksize = swap4(datasize + sizeof(*aiffhdr) + 4, swap); + strncpy(aiffhdr->a_aiffid, "AIFF", 4); + strncpy(aiffhdr->a_fmtid, "COMM", 4); + aiffhdr->a_fmtchunksize = swap4(18, swap); + aiffhdr->a_nchannels = swap2(nchannels, swap); + longtmp = swap4(nframes, swap); + memcpy(&aiffhdr->a_nframeshi, &longtmp, 4); + aiffhdr->a_bitspersamp = swap2(8 * bytespersamp, swap); + memcpy(aiffhdr->a_samprate, dogdoo, sizeof(dogdoo)); + longtmp = swap4(datasize, swap); + memcpy(aiffhdr->a_samprate + sizeof(dogdoo), &longtmp, 4); + headersize = AIFFPLUS; + } + else /* WAVE format */ + { + long datasize = nframes * nchannels * bytespersamp; + if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".wav")) + strcat(filenamebuf, ".wav"); + strncpy(wavehdr->w_fileid, "RIFF", 4); + wavehdr->w_chunksize = swap4(datasize + sizeof(*wavehdr) - 8, swap); + strncpy(wavehdr->w_waveid, "WAVE", 4); + strncpy(wavehdr->w_fmtid, "fmt ", 4); + wavehdr->w_fmtchunksize = swap4(16, swap); + wavehdr->w_fmttag = + swap2((bytespersamp == 4 ? WAV_FLOAT : WAV_INT), swap); + wavehdr->w_nchannels = swap2(nchannels, swap); + wavehdr->w_samplespersec = swap4(samplerate, swap); + wavehdr->w_navgbytespersec = + swap4((int)(samplerate * nchannels * bytespersamp), swap); + wavehdr->w_nblockalign = swap2(nchannels * bytespersamp, swap); + wavehdr->w_nbitspersample = swap2(8 * bytespersamp, swap); + strncpy(wavehdr->w_datachunkid, "data", 4); + wavehdr->w_datachunksize = swap4(datasize, swap); + headersize = sizeof(t_wave); + } + + canvas_makefilename(canvas, filenamebuf, buf2, MAXPDSTRING); + sys_bashfilename(buf2, buf2); + if ((fd = open(buf2, BINCREATE, 0666)) < 0) + return (-1); + + if (write(fd, headerbuf, headersize) < headersize) + { + close (fd); + return (-1); + } + return (fd); +} + +static void soundfile_finishwrite(void *obj, char *filename, int fd, + int filetype, long nframes, long itemswritten, int bytesperframe, int swap) +{ + if (itemswritten < nframes) + { + if (nframes < 0x7fffffff) + pd_error(obj, "soundfiler_write: %d out of %d bytes written", + itemswritten, nframes); + /* try to fix size fields in header */ + if (filetype == FORMAT_WAVE) + { + long datasize = itemswritten * bytesperframe, mofo; + + if (lseek(fd, + ((char *)(&((t_wave *)0)->w_chunksize)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(datasize + sizeof(t_wave) - 8, swap); + if (write(fd, (char *)(&mofo), 4) < 4) + goto baddonewrite; + if (lseek(fd, + ((char *)(&((t_wave *)0)->w_datachunksize)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(datasize, swap); + if (write(fd, (char *)(&mofo), 4) < 4) + goto baddonewrite; + } + if (filetype == FORMAT_AIFF) + { + long mofo; + if (lseek(fd, + ((char *)(&((t_aiff *)0)->a_nframeshi)) - (char *)0, + SEEK_SET) == 0) + goto baddonewrite; + mofo = swap4(nframes, swap); + if (write(fd, (char *)(&mofo), 4) < 4) + goto baddonewrite; + } + if (filetype == FORMAT_NEXT) + { + /* do it the lazy way: just set the size field to 'unknown size'*/ + uint32 nextsize = 0xffffffff; + if (lseek(fd, 8, SEEK_SET) == 0) + { + goto baddonewrite; + } + if (write(fd, &nextsize, 4) < 4) + { + goto baddonewrite; + } + } + } + return; +baddonewrite: + post("%s: %s", filename, strerror(errno)); +} + +static void soundfile_xferout(int nchannels, float **vecs, + unsigned char *buf, int nitems, long onset, int bytespersamp, + int bigendian, float normalfactor) +{ + int i, j; + unsigned char *sp, *sp2; + float *fp; + int bytesperframe = bytespersamp * nchannels; + long xx; + for (i = 0, sp = buf; i < nchannels; i++, sp += bytespersamp) + { + if (bytespersamp == 2) + { + float ff = normalfactor * 32768.; + if (bigendian) + { + for (j = 0, sp2 = sp, fp = vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 32768. + (*fp * ff); + xx -= 32768; + if (xx < -32767) + xx = -32767; + if (xx > 32767) + xx = 32767; + sp2[0] = (xx >> 8); + sp2[1] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 32768. + (*fp * ff); + xx -= 32768; + if (xx < -32767) + xx = -32767; + if (xx > 32767) + xx = 32767; + sp2[1] = (xx >> 8); + sp2[0] = xx; + } + } + } + else if (bytespersamp == 3) + { + float ff = normalfactor * 8388608.; + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 8388608. + (*fp * ff); + xx -= 8388608; + if (xx < -8388607) + xx = -8388607; + if (xx > 8388607) + xx = 8388607; + sp2[0] = (xx >> 16); + sp2[1] = (xx >> 8); + sp2[2] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + int xx = 8388608. + (*fp * ff); + xx -= 8388608; + if (xx < -8388607) + xx = -8388607; + if (xx > 8388607) + xx = 8388607; + sp2[2] = (xx >> 16); + sp2[1] = (xx >> 8); + sp2[0] = xx; + } + } + } + else if (bytespersamp == 4) + { + if (bigendian) + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + float f2 = *fp * normalfactor; + xx = *(long *)&f2; + sp2[0] = (xx >> 24); sp2[1] = (xx >> 16); + sp2[2] = (xx >> 8); sp2[3] = xx; + } + } + else + { + for (j = 0, sp2 = sp, fp=vecs[i] + onset; + j < nitems; j++, sp2 += bytesperframe, fp++) + { + float f2 = *fp * normalfactor; + xx = *(long *)&f2; + sp2[3] = (xx >> 24); sp2[2] = (xx >> 16); + sp2[1] = (xx >> 8); sp2[0] = xx; + } + } + } + } +} + + + +/* ------- threadedsf - reads and writes soundfiles to/from "garrays" ---- */ + +// in pd's soundfiler maxsize is 4000000 +// threaded we should be able to hande a bit more +#define DEFMAXSIZE 40000000 +#define SAMPBUFSIZE 1024 + + +static t_class *threadedsf_class; + +typedef struct _threadedsf +{ + t_object x_obj; + t_canvas *x_canvas; +} t_threadedsf; + +#include + +#ifdef _POSIX_MEMLOCK +#include +#endif /* _POSIX_MEMLOCK */ + +#ifdef DEBUG +#define SFDEBUG +#endif + +static pthread_t sf_thread_id; /* id of soundfiler thread */ +//static pthread_attr_t sf_attr; + +typedef struct _sfprocess +{ + void (* process) (t_threadedsf *,t_symbol *, + int, t_atom *); /* function to call */ + t_threadedsf * x; /* soundfiler */ + int argc; + t_atom * argv; + struct _sfprocess * next; /* next object in queue */ + pthread_mutex_t mutex; +} t_sfprocess; + +/* this is the queue for all threadedsf objects */ +typedef struct _sfqueue +{ + t_sfprocess * begin; + t_sfprocess * end; + pthread_mutex_t mutex; + pthread_cond_t cond; +} t_sfqueue; + +static t_sfqueue * threadedsf_queue; + + +/* we fill the queue */ +void threadedsf_queue_add(void (* process) (t_threadedsf *,t_symbol *, + int,t_atom *), void * x, + int argc, t_atom * argv) +{ + /* preparing entry */ + t_sfprocess * last_entry = (t_sfprocess*)getbytes(sizeof(t_sfprocess)); + +#ifdef SFDEBUG + post("adding process to queue"); +#endif + + pthread_mutex_init(&(last_entry->mutex), NULL); + pthread_mutex_lock(&(last_entry->mutex)); + last_entry->process = process; + last_entry->x = x; + last_entry->argc = argc; + last_entry->argv = copybytes (argv, argc * sizeof(t_atom)); + last_entry->next = NULL; + pthread_mutex_unlock(&(last_entry->mutex)); + + /* add new entry to queue */ + pthread_mutex_lock(&(threadedsf_queue->mutex)); + + if (threadedsf_queue->begin==NULL) + { + threadedsf_queue->begin=last_entry; + threadedsf_queue->end=last_entry; + } + else + { + pthread_mutex_lock(&(threadedsf_queue->end->mutex)); + threadedsf_queue->end->next=last_entry; + pthread_mutex_unlock(&(threadedsf_queue->end->mutex)); + threadedsf_queue->end=last_entry; + } + + + if ( threadedsf_queue->begin == threadedsf_queue->end ) + { +#ifdef DEBUG + post("signaling"); +#endif + pthread_mutex_unlock(&(threadedsf_queue->mutex)); + + /* and signal the helper thread */ + pthread_cond_signal(&(threadedsf_queue->cond)); + } + else + { +#ifdef DEBUG + post("not signaling"); +#endif + pthread_mutex_unlock(&(threadedsf_queue->mutex)); + } + return; +} + +/* global threadedsf thread ... sleeping until signaled */ +void threadedsf_thread(void) +{ + t_sfprocess * me; + t_sfprocess * next; + +#ifdef DEBUG + post("threadedsf_thread ID: %d", pthread_self()); +#endif + while (1) + { +#ifdef DEBUG + post("Soundfiler sleeping"); +#endif + pthread_cond_wait(&threadedsf_queue->cond, &threadedsf_queue->mutex); +#ifdef DEBUG + post("Soundfiler awake"); +#endif + + /* work on the queue */ + while (threadedsf_queue->begin!=NULL) + { + post("threadedsf: locked queue"); + /* locking process */ + pthread_mutex_lock(&(threadedsf_queue->begin->mutex)); + + me = threadedsf_queue->begin; + + pthread_mutex_unlock(&(me->mutex)); + pthread_mutex_unlock(&(threadedsf_queue->mutex)); +#ifdef DEBUG + post("threadedsf: mutex unlocked, running process"); +#endif + + /* running the specific function */ + me->process(me->x, NULL, me->argc, me->argv); +#ifdef DEBUG + post("threadedsf: process done, locking mutex"); +#endif + + pthread_mutex_lock(&(threadedsf_queue->mutex)); + pthread_mutex_lock(&(me->mutex)); + + /* freeing the argument vector */ + freebytes(me->argv, sizeof(t_atom) * me->argc); + + /* the process struct */ + next=me->next; + threadedsf_queue->begin=next; + freebytes(me, sizeof(t_sfprocess)); + }; + threadedsf_queue->end=NULL; + } +} + +extern int sys_hipriority; /* real-time flag, true if priority boosted */ + +/* create soundfiler thread */ +void sys_start_sfthread(void) +{ + pthread_attr_t sf_attr; + struct sched_param sf_param; + + int status; + + //initialize queue + threadedsf_queue = getbytes (sizeof(t_sfqueue)); + + pthread_mutex_init (&threadedsf_queue->mutex,NULL); + pthread_cond_init (&threadedsf_queue->cond,NULL); + + threadedsf_queue->begin=threadedsf_queue->end=NULL; +/* pthread_mutex_unlock(&(threadedsf_queue->mutex)); */ + + // initialize thread + pthread_attr_init(&sf_attr); + +//#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + sf_param.sched_priority=sched_get_priority_min(SCHED_OTHER); + pthread_attr_setschedparam(&sf_attr,&sf_param); +/* pthread_attr_setinheritsched(&sf_attr,PTHREAD_EXPLICIT_SCHED); */ + +#ifdef UNIX + if (sys_hipriority == 1 && getuid() == 0) + { + sf_param.sched_priority=sched_get_priority_min(SCHED_RR); + pthread_attr_setschedpolicy(&sf_attr,SCHED_RR); + } + else + { +/* pthread_attr_setschedpolicy(&sf_attr,SCHED_OTHER); */ +/* sf_param.sched_priority=sched_get_priority_min(SCHED_OTHER); */ + } +#endif /* UNIX */ + +//#endif /* _POSIX_THREAD_PRIORITY_SCHEDULING */ + + //start thread + status = pthread_create(&sf_thread_id, &sf_attr, + (void *) threadedsf_thread,NULL); + if (status != 0) + error("Couldn't create threadedsf thread: %d",status); +#ifdef DEBUG + else + post("global threadedsf thread launched, priority: %d", + sf_param.sched_priority); +#endif +} + +static void threadedsf_t_write(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_write_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_write,(void *)x,argc, argv); +} + +static void threadedsf_t_read(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_read_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_read,(void *)x,argc, argv); +} + + + /* threadedsf_read ... + + usage: read [flags] filename table ... + flags: + -skip ... frames to skip in file + -nframes + -onset ... onset in table to read into (NOT DONE YET) + -raw + -resize + -maxsize + + TB: adapted for threaded use + */ + +/* callback prototypes */ +static t_int threadedsf_read_update_garray(t_int * w); +static t_int threadedsf_read_update_graphics(t_int * w); +static t_int threadedsf_read_output(t_int * w); + + +static void threadedsf_t_read(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + int headersize = -1, channels = 0, bytespersamp = 0, bigendian = 0, + resize = 0, i, j; + long skipframes = 0, + nframes = 0, + finalsize = 0, /* new size */ + maxsize = DEFMAXSIZE, + itemsread = 0, + bytelimit = 0x7fffffff; + int fd = -1; + char endianness, *filename; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; /* the old array */ + t_float *nvecs[MAXSFCHANS]; /* the new array */ + int vecsize[MAXSFCHANS]; /* the old array size */ + char sampbuf[SAMPBUFSIZE]; + int bufframes, nitems; + FILE *fp; + pthread_cond_t resume_after_callback = PTHREAD_COND_INITIALIZER; + pthread_mutex_t resume_after_callback_mutex = PTHREAD_MUTEX_INITIALIZER; /* dummy */ + t_int* outargs; + + while (argc > 0 && argv->a_type == A_SYMBOL && + *argv->a_w.w_symbol->s_name == '-') + { + char *flag = argv->a_w.w_symbol->s_name + 1; + if (!strcmp(flag, "skip")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((skipframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "nframes")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((nframes = argv[1].a_w.w_float) < 0)) + goto usage; + argc -= 2; argv += 2; + } + else if (!strcmp(flag, "raw")) + { + if (argc < 5 || + argv[1].a_type != A_FLOAT || + ((headersize = argv[1].a_w.w_float) < 0) || + argv[2].a_type != A_FLOAT || + ((channels = argv[2].a_w.w_float) < 1) || + (channels > MAXSFCHANS) || + argv[3].a_type != A_FLOAT || + ((bytespersamp = argv[3].a_w.w_float) < 2) || + (bytespersamp > 4) || + argv[4].a_type != A_SYMBOL || + ((endianness = argv[4].a_w.w_symbol->s_name[0]) != 'b' + && endianness != 'l' && endianness != 'n')) + goto usage; + if (endianness == 'b') + bigendian = 1; + else if (endianness == 'l') + bigendian = 0; + else + bigendian = garray_ambigendian(); + argc -= 5; argv += 5; + } + else if (!strcmp(flag, "resize")) + { + resize = 1; + argc -= 1; argv += 1; + } + else if (!strcmp(flag, "maxsize")) + { + if (argc < 2 || argv[1].a_type != A_FLOAT || + ((maxsize = argv[1].a_w.w_float) < 0)) + goto usage; + resize = 1; /* maxsize implies resize. */ + argc -= 2; argv += 2; + } + else goto usage; + } + if (argc < 2 || argc > MAXSFCHANS + 1 || argv[0].a_type != A_SYMBOL) + goto usage; + filename = argv[0].a_w.w_symbol->s_name; + argc--; argv++; + + for (i = 0; i < argc; i++) + { + if (argv[i].a_type != A_SYMBOL) + goto usage; + if (!(garrays[i] = + (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class))) + { + pd_error(x, "%s: no such table", argv[i].a_w.w_symbol->s_name); + goto done; + } + else if (!garray_getfloatarray(garrays[i], &vecsize[i], &vecs[i])) + error("%s: bad template for tabwrite", + argv[i].a_w.w_symbol->s_name); + if (finalsize && finalsize != vecsize[i] && !resize) + { + post("threadedsf_read: arrays have different lengths; resizing..."); + resize = 1; + } + finalsize = vecsize[i]; + } + fd = open_soundfile(canvas_getdir(x->x_canvas)->s_name, filename, + headersize, &bytespersamp, &bigendian, &channels, &bytelimit, + skipframes); + + if (fd < 0) + { + pd_error(x, "threadedsf_read: %s: %s", filename, (errno == EIO ? + "unknown or bad header format" : strerror(errno))); + goto done; + } + + if (resize) + { + /* figure out what to resize to */ + long poswas, eofis, framesinfile; + + poswas = lseek(fd, 0, SEEK_CUR); + eofis = lseek(fd, 0, SEEK_END); + if (poswas < 0 || eofis < 0) + { + pd_error(x, "lseek failed"); + goto done; + } + lseek(fd, poswas, SEEK_SET); + framesinfile = (eofis - poswas) / (channels * bytespersamp); + if (framesinfile > maxsize) + { + pd_error(x, "threadedsf_read: truncated to %d elements", maxsize); + framesinfile = maxsize; + } + if (framesinfile > bytelimit / (channels * bytespersamp)) + framesinfile = bytelimit / (channels * bytespersamp); + finalsize = framesinfile; + + } + if (!finalsize) finalsize = 0x7fffffff; + if (finalsize > bytelimit / (channels * bytespersamp)) + finalsize = bytelimit / (channels * bytespersamp); + fp = fdopen(fd, "rb"); + bufframes = SAMPBUFSIZE / (channels * bytespersamp); + +#ifdef SFDEBUG + post("buffers: %d", argc); + post("channels: %d", channels); +#endif + +#ifdef _POSIX_MEMLOCK + munlockall(); +#endif + + /* allocate memory for new array */ + if (resize) + for (i = 0; i < argc; i++) + { +#ifdef SFDEBUG + post("allocating buffer %d",i); +#endif + nvecs[i]=getbytes (finalsize * sizeof(t_float)); + /* if we are out of memory, free it again and quit */ + if (nvecs[i]==0) + { + pd_error(x, "resize failed"); + for (j=0; j!=i;++j) + /* if the resizing fails, we'll have to free all arrays again */ + freebytes (nvecs[i],finalsize * sizeof(t_float)); + goto done; + } + /* zero samples */ + if(i > channels) + { + memset(nvecs[i],0,vecsize[i] * sizeof(t_float)); + } + } + else + for (i = 0; i < argc; i++) + { + nvecs[i] = getbytes (vecsize[i] * sizeof(t_float)); + /* if we are out of memory, free it again and quit */ + if (nvecs[i]==0) + { + pd_error(x, "resize failed"); + for (j=0; j!=i;++j) + /* if the resizing fails, we'll have to free all arrays again */ + freebytes (nvecs[i],vecsize[i] * sizeof(t_float)); + goto done; + } + /* zero samples */ + if(i > channels) + memset(nvecs[i],0,vecsize[i] * sizeof(t_float)); + } + +#ifdef SFDEBUG + post("transfer soundfile"); +#endif + + for (itemsread = 0; itemsread < finalsize; ) + { + int thisread = finalsize - itemsread; + thisread = (thisread > bufframes ? bufframes : thisread); + nitems = fread(sampbuf, channels * bytespersamp, thisread, fp); + if (nitems <= 0) break; + soundfile_xferin(channels, argc, nvecs, itemsread, + (unsigned char *)sampbuf, nitems, bytespersamp, bigendian); + itemsread += nitems; + } + +#ifdef SFDEBUG + post("zeroing remaining elements"); +#endif + /* zero out remaining elements of vectors */ + for (i = 0; i < argc; i++) + { + for (j = itemsread; j < finalsize; j++) + nvecs[i][j] = 0; + } + + /* set idle callback to switch pointers */ +#ifdef SFDEBUG + post("locked"); +#endif + for (i = 0; i < argc; i++) + { + t_int* w = (t_int*)getbytes(4*sizeof(t_int)); + + w[0] = (t_int)(garrays[i]); + w[1] = (t_int)nvecs[i]; + w[2] = (t_int)finalsize; + w[3] = (t_int)(&resume_after_callback); + + h_set_callback(&threadedsf_read_update_garray, w, 4); + + pthread_cond_wait(&resume_after_callback, &resume_after_callback_mutex); + } + +#ifdef SFDEBUG + post("unlocked, doing graphics updates"); +#endif + + /* do all graphics updates + * run this in the main thread via callback */ + for (i = 0; i < argc; i++) + { + t_int* w = (t_int*)getbytes(2*sizeof(t_int)); + + w[0] = (t_int)(garrays[i]); + w[1] = (t_int)finalsize; + + h_set_callback(&threadedsf_read_update_graphics, w, 2); + } + + /* free the old arrays */ + for (i = 0; i < argc; i++) + freebytes(vecs[i], vecsize[i] * sizeof(t_float)); + + fclose(fp); + fd = -1; + goto done; + + usage: + pd_error(x, "usage: read [flags] filename tablename..."); + post("flags: -skip -nframes -resize -maxsize ..."); + post("-raw ."); + + done: + if (fd >= 0) + close (fd); + +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + + outargs = (t_int*)getbytes(2*sizeof(t_int)); + outargs[0] = (t_int)x->x_obj.ob_outlet; + outargs[1] = (t_int)itemsread; + h_set_callback(&threadedsf_read_output, outargs, 2); +} + +/* idle callback for threadsafe synchronisation */ +static t_int threadedsf_read_update_garray(t_int * w) +{ + t_garray* garray = (t_garray*)w[0]; + t_array *data = garray_getarray(garray); + t_int nvec = w[1]; + t_int finalsize = w[2]; + pthread_cond_t * conditional = (pthread_cond_t*) w[3]; + +#ifdef SFDEBUG + post("lock array %p", garray); +#endif + + // no garray_locks in current pd + //garray_lock(garray); + + data->a_vec = (char *) nvec; + data->a_n = finalsize; + if (garray->x_usedindsp) canvas_update_dsp(); + +#ifdef SFDEBUG + post("unlock array %p", garray); +#endif + + // no garray_locks in current pd + //garray_unlock(garray); + + /* signal helper thread */ + pthread_cond_broadcast(conditional); + + return 0; +} + +static t_int threadedsf_read_update_graphics(t_int * w) +{ + t_garray* garray = (t_garray*) w[0]; + t_glist *gl; + int n = w[1]; + /* if this is the only array in the graph, + reset the graph's coordinates */ + +#ifdef SFDEBUG + post("redraw array %p", garray); +#endif + + gl = garray->x_glist; + if (gl->gl_list == &garray->x_gobj && !garray->x_gobj.g_next) + { + vmess(&gl->gl_pd, gensym("bounds"), "ffff", + 0., gl->gl_y1, (double)(n > 1 ? n-1 : 1), gl->gl_y2); + /* close any dialogs that might have the wrong info now... */ + gfxstub_deleteforkey(gl); + } + else + garray_redraw(garray); + + return 0; +} + + +static t_int threadedsf_read_output(t_int * w) +{ + t_outlet* outlet = (t_outlet*) w[0]; + float itemsread = (float) w[1]; + +#ifdef SFDEBUG + post("bang %p", outlet); +#endif + + outlet_float (outlet, itemsread); + + return 0; +} + + + + + /* this is broken out from threadedsf_write below so garray_write can + call it too... not done yet though. */ + +long threadedsf_t_dowrite(void *obj, t_canvas *canvas, + int argc, t_atom *argv) +{ + int bytespersamp, bigendian, + swap, filetype, normalize, i, j, nchannels; + long onset, nframes, itemswritten = 0; + t_garray *garrays[MAXSFCHANS]; + t_float *vecs[MAXSFCHANS]; + char sampbuf[SAMPBUFSIZE]; + int bufframes; + int fd = -1; + float normfactor, biggest = 0, samplerate; + t_symbol *filesym; + + if (soundfiler_writeargparse(obj, &argc, &argv, &filesym, &filetype, + &bytespersamp, &swap, &bigendian, &normalize, &onset, &nframes, + &samplerate)) + goto usage; + nchannels = argc; + if (nchannels < 1 || nchannels > MAXSFCHANS) + goto usage; + if (samplerate < 0) + samplerate = sys_getsr(); + for (i = 0; i < nchannels; i++) + { + int vecsize; + if (argv[i].a_type != A_SYMBOL) + goto usage; + if (!(garrays[i] = + (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class))) + { + pd_error(obj, "%s: no such table", argv[i].a_w.w_symbol->s_name); + goto fail; + } + else if (!garray_getfloatarray(garrays[i], &vecsize, &vecs[i])) + error("%s: bad template for tabwrite", + argv[i].a_w.w_symbol->s_name); + if (nframes > vecsize - onset) + nframes = vecsize - onset; + + for (j = 0; j < vecsize; j++) + { + if (vecs[i][j] > biggest) + biggest = vecs[i][j]; + else if (-vecs[i][j] > biggest) + biggest = -vecs[i][j]; + } + } + if (nframes <= 0) + { + pd_error(obj, "threadedsf_write: no samples at onset %ld", onset); + goto fail; + } + + if ((fd = create_soundfile(canvas, filesym->s_name, filetype, + nframes, bytespersamp, bigendian, nchannels, + swap, samplerate)) < 0) + { + post("%s: %s\n", filesym->s_name, strerror(errno)); + goto fail; + } + if (!normalize) + { + if ((bytespersamp != 4) && (biggest > 1)) + { + post("%s: normalizing max amplitude %f to 1", filesym->s_name, biggest); + normalize = 1; + } + else post("%s: biggest amplitude = %f", filesym->s_name, biggest); + } + if (normalize) + normfactor = (biggest > 0 ? 32767./(32768. * biggest) : 1); + else normfactor = 1; + + bufframes = SAMPBUFSIZE / (nchannels * bytespersamp); + + for (itemswritten = 0; itemswritten < nframes; ) + { + int thiswrite = nframes - itemswritten, nbytes; + thiswrite = (thiswrite > bufframes ? bufframes : thiswrite); + soundfile_xferout(argc, vecs, (unsigned char *)sampbuf, thiswrite, + onset, bytespersamp, bigendian, normfactor); + nbytes = write(fd, sampbuf, nchannels * bytespersamp * thiswrite); + if (nbytes < nchannels * bytespersamp * thiswrite) + { + post("%s: %s", filesym->s_name, strerror(errno)); + if (nbytes > 0) + itemswritten += nbytes / (nchannels * bytespersamp); + break; + } + itemswritten += thiswrite; + onset += thiswrite; + } + if (fd >= 0) + { + soundfile_finishwrite(obj, filesym->s_name, fd, + filetype, nframes, itemswritten, nchannels * bytespersamp, swap); + close (fd); + } + return ((float)itemswritten); +usage: + pd_error(obj, "usage: write [flags] filename tablename..."); + post("flags: -skip -nframes -bytes -wave -aiff -nextstep ..."); + post("-big -little -normalize"); + post("(defaults to a 16-bit wave file)."); +fail: + if (fd >= 0) + close (fd); + return (0); +} + +static void threadedsf_t_write(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + long bozo = threadedsf_t_dowrite(x, x->x_canvas, + argc, argv); + sys_lock(); + outlet_float(x->x_obj.ob_outlet, (float)bozo); + sys_lock(); +} + +static void threadedsf_t_resize(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv); + +static void threadedsf_t_resize_addq(t_threadedsf *x, t_symbol *s, + int argc, t_atom *argv) +{ + threadedsf_queue_add(threadedsf_t_resize,(void *)x,argc, argv); +} + + + /* TB: threadedsf_t_resize ... + usage: resize table size + adapted from garray_resize + */ +static void threadedsf_t_resize(t_threadedsf *y, t_symbol *s, + int argc, t_atom *argv) +{ + int was, elemsize; /* array contains was elements of size elemsize */ + t_float * vec; /* old array */ + t_glist *gl; + int n; /* resize of n elements */ + char *nvec; /* new array */ + + t_garray * x = (t_garray *)pd_findbyclass(argv[0].a_w.w_symbol, garray_class); + t_array *data = garray_getarray(x); // TODO muss der im lock sein ? + + if (!(x)) + { + pd_error(y, "%s: no such table", argv[0].a_w.w_symbol->s_name); + goto usage; + } + + vec = (t_float*) data->a_vec; + was = data->a_n; + + if ((argv+1)->a_type == A_FLOAT) + { + n = (int) (argv+1)->a_w.w_float; + } + else + { + goto usage; + } + + if (n == was) + { + return; + } + + if (n < 1) n = 1; + elemsize = template_findbyname(data->a_templatesym)->t_n * sizeof(t_word); + +#ifdef _POSIX_MEMLOCK + munlockall(); +#endif + if (was > n) + { + nvec = (char*)copybytes(data->a_vec, was * elemsize); + } + else + { + nvec = getbytes(n * elemsize); + memcpy (nvec, data->a_vec, was * elemsize); + + /* LATER should check t_resizebytes result */ + memset(nvec + was*elemsize, + 0, (n - was) * elemsize); + } + if (!nvec) + { + pd_error(x, "array resize failed: out of memory"); +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + return; + } + + + /* TB: we'll have to be sure that no one is accessing the array */ + sys_lock(); + + // no garray_locks in current pd +//#ifdef GARRAY_THREAD_LOCK + //garray_lock(x); +//#endif + + data->a_vec = nvec; + data->a_n = n; + + // no garray_locks in current pd +//#ifdef GARRAY_THREAD_LOCK + //garray_unlock(x); +//#endif + + if (x->x_usedindsp) canvas_update_dsp(); + sys_unlock(); + + + /* if this is the only array in the graph, + reset the graph's coordinates */ + gl = x->x_glist; + if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next) + { + vmess(&gl->gl_pd, gensym("bounds"), "ffff", + 0., gl->gl_y1, (double)(n > 1 ? n-1 : 1), gl->gl_y2); + /* close any dialogs that might have the wrong info now... */ + gfxstub_deleteforkey(gl); + } + else garray_redraw(x); + + freebytes (vec, was * elemsize); +#ifdef _POSIX_MEMLOCK + mlockall(MCL_FUTURE); +#endif + sys_lock(); + outlet_float(y->x_obj.ob_outlet, (float)atom_getintarg(1,argc,argv)); + sys_unlock(); + return; + + usage: + pd_error(x, "usage: resize tablename size"); +} + + +//static void threadedsf_t_const(t_threadedsf *x, t_symbol *s, +// int argc, t_atom *argv); +// +//static void threadedsf_t_const_addq(t_threadedsf *x, t_symbol *s, +// int argc, t_atom *argv) +//{ +// threadedsf_queue_add(threadedsf_t_const,(void *)x,argc, argv); +//} + + +/* TB: threadedsf_t_const ... + usage: const table value +*/ +//static void threadedsf_t_const(t_threadedsf *y, t_symbol *s, +// int argc, t_atom *argv) +//{ +// int size, elemsize; /* array contains was elements of size elemsize */ +// t_float * vec; /* old array */ +// t_glist *gl; +// int val; /* value */ +// char *nvec; /* new array */ +// int i; +// +// t_garray * x = (t_garray *)pd_findbyclass(argv[0].a_w.w_symbol, garray_class); +// t_array *data = garray_getarray(x); // TODO muss der im lock sein ? +// +// if (!(x)) +// { +// pd_error(y, "%s: no such table", argv[0].a_w.w_symbol->s_name); +// goto usage; +// } +// +// +// +// vec = (t_float*) data->a_vec; +// size = data->a_n; +// +// if ((argv+1)->a_type == A_FLOAT) +// { +// val = (int) (argv+1)->a_w.w_float; +// } +// else +// { +// goto usage; +// } +// +//#ifdef SFDEBUG +// post("array size: %d; new value: %d",size,val); +//#endif +// +// elemsize = template_findbyname(data->a_templatesym)->t_n * sizeof(t_word); +// +// +// /* allocating memory */ +//#ifdef _POSIX_MEMLOCK +// munlockall(); +//#endif +// nvec = getbytes(size * elemsize); +// +// if (!nvec) +// { +// pd_error(x, "array resize failed: out of memory"); +//#ifdef _POSIX_MEMLOCK +// mlockall(MCL_FUTURE); +//#endif +// return; +// } +// +// /* setting array */ +// for (i=0; i!=size; ++i) +// { +// nvec[i]=val; +// } +// +// /* TB: we'll have to be sure that no one is accessing the array */ +// sys_lock(); +//#ifdef GARRAY_THREAD_LOCK +// garray_lock(x); +//#endif +// data->a_vec = nvec; +//#ifdef GARRAY_THREAD_LOCK +// garray_unlock(x); +//#endif +// if (x->x_usedindsp) canvas_update_dsp(); +// sys_unlock(); +// +// +// /* if this is the only array in the graph, +// reset the graph's coordinates */ +// gl = x->x_glist; +// if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next) +// { +// vmess(&gl->gl_pd, gensym("bounds"), "ffff", +// 0., gl->gl_y1, (double)(size > 1 ? size-1 : 1), gl->gl_y2); +// /* close any dialogs that might have the wrong info now... */ +// gfxstub_deleteforkey(gl); +// } +// else garray_redraw(x); +// +// freebytes (vec, size * elemsize); +//#ifdef _POSIX_MEMLOCK +// mlockall(MCL_FUTURE); +//#endif +// sys_lock(); +// outlet_float(y->x_obj.ob_outlet, size); +// sys_unlock(); +// return; +// +// usage: +// pd_error(x, "usage: const tablename value"); +//} + + +static t_threadedsf *threadedsf_new(void) +{ + t_threadedsf *x = (t_threadedsf *)pd_new(threadedsf_class); + x->x_canvas = canvas_getcurrent(); + outlet_new(&x->x_obj, &s_float); + return (x); +} + + +void threadedsf_setup(void) +{ + threadedsf_class = class_new(gensym("threadedsf"), (t_newmethod)threadedsf_new, + 0, sizeof(t_threadedsf), 0, 0); + + class_addmethod(threadedsf_class, (t_method)threadedsf_t_read_addq, + gensym("read"), A_GIMME, 0); + class_addmethod(threadedsf_class, (t_method)threadedsf_t_write_addq, + gensym("write"), A_GIMME, 0); + class_addmethod(threadedsf_class, (t_method)threadedsf_t_resize_addq, + gensym("resize"), A_GIMME, 0); +// class_addmethod(threadedsf_class, (t_method)threadedsf_t_const_addq, +// gensym("const"), A_GIMME, 0); +} diff --git a/threadlib/src/threadlib.c b/threadlib/src/threadlib.c new file mode 100755 index 0000000..da17100 --- /dev/null +++ b/threadlib/src/threadlib.c @@ -0,0 +1,83 @@ +/* +* +* threadlib +* library for threaded patching in PureData +* Copyright (C) 2005 Georg Holzmann +* heavily based on code by Tim Blechmann +* (detach, join, pd_devel) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; see the file COPYING. If not, write to +* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#include "threadlib.h" + +typedef struct threadlib +{ + t_object x_obj; +} t_threadlib; + +t_class *threadlib_class; + +static void threadlib_help(void) +{ + post("\nthreadlib vers."VERSION", library for threaded patching\n" + "2005, by Georg Holzmann \n" + "heavily based on pd_devel code by Tim Blechmann\n" + + // help text: + "\tdetach run part of the patch in a helper thread\n" + "\tjoin synchronize messages to pd's main thread\n" + "\tsleep block system for specific time\n" + "\tthreadedsf threaded soundfiler from pd_devel_0.38\n" + + "WARNING: this is very experimental and can crash your patches !\n"); +} + +void *threadlib_new(void) +{ + t_threadlib *x = (t_threadlib *)pd_new(threadlib_class); + return (void *)x; +} + +void sleep_setup(); +void detach_setup(); +void join_setup(); +void threadedsf_setup(); +void sys_start_sfthread(); + +void threadlib_setup(void) +{ + // call all the setup functions: + sleep_setup(); + detach_setup(); + join_setup(); + threadedsf_setup(); + + // init callback system + h_init_callbacks(); + + // start global soundfiler helper thread + sys_start_sfthread(); + + post("\nthreadlib vers."VERSION", library for threaded patching\n" + "2005, by Georg Holzmann \n" + "heavily based on pd_devel code by Tim Blechmann\n" + "WARNING: this is very experimental and may crash your patches !\n"); + + threadlib_class = class_new(gensym("threadlib"), threadlib_new, 0, + sizeof(t_threadlib), 0, 0); + class_addmethod(threadlib_class, (t_method)threadlib_help, gensym("help"), 0); +} diff --git a/threadlib/src/threadlib.h b/threadlib/src/threadlib.h new file mode 100755 index 0000000..ead3a2f --- /dev/null +++ b/threadlib/src/threadlib.h @@ -0,0 +1,81 @@ +/* +* +* threadlib +* library for threaded patching in PureData +* Copyright (C) 2005 Georg Holzmann +* heavily based on code by Tim Blechmann +* (detach, join, pd_devel) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; see the file COPYING. If not, write to +* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#ifndef __PD_THREADLIB_H_ +#define __PD_THREADLIB_H_ + +#include "m_pd.h" +#include "pthread.h" + + +// threadlib version string +#define VERSION "0.1pre" + +// define it to use the lockfree fifo +#define THREADLIB_LOCKFREE + +// for debuging +//#define DEBUG + + +/* --------- lockfree FIFO of pd devel ----------- */ +// (implemted in fifo.c) + +/* used data structures */ +EXTERN_STRUCT _fifo; +#define t_fifo struct _fifo + +/* function prototypes */ +t_fifo * fifo_init(void); +void fifo_destroy(t_fifo*); + +/* fifo_put() and fifo_get are the only threadsafe functions!!! */ +void fifo_put(t_fifo*, void*); +void* fifo_get(t_fifo*); + + +/* --------- callback FIFO of pd devel ----------- */ +// (implemted in callbacks.c) + +/* NOTE: difference to pd_devel + * in pd_devel the callbacks are called in idle time + * (idle callbacks), because this is not possible + * in current pd, they are called here by the + * clock callbacks + */ + +/* set up callback fifo and start clock callback */ +void h_init_callbacks(); + +/* free fifo and clock callback */ +void h_free_callbacks(); + +/* tb: to be called at idle time */ +/* Holzmann: idle callbacks of current PD are not reliable, so + it will be called by the clock-callbacks for now */ +/* register a new callback in FIFO */ +void h_set_callback(t_int (*callback) (t_int* argv), t_int* argv, t_int argc); + + +#endif // __PD_THREADLIB_H_ -- cgit v1.2.1