├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── doc ├── BBD_Filters.png ├── HP_compare.png ├── Lanczos_compare.png ├── hpresampler.png ├── lanczosresampler.png ├── libsamplerate.png ├── speed_chart.png └── writeup.md ├── src ├── BaseSRC.h ├── HPResampler.h ├── LanczosSRC.h ├── LibSamplerRateSRC.h ├── src_sndfile.cpp ├── src_test.cpp ├── src_utils │ ├── FastMath.h │ ├── HPFilters.h │ ├── LanczosResampler.h │ └── SSEComplex.h └── utils.h ├── tests.py └── third_party └── matplotlibcpp.h /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.vscode 3 | .DS_Store 4 | *.wav 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/libsamplerate"] 2 | path = third_party/libsamplerate 3 | url = https://github.com/libsndfile/libsamplerate 4 | [submodule "third_party/libsndfile"] 5 | path = third_party/libsndfile 6 | url = https://github.com/libsndfile/libsndfile 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(NonIntegerSRC) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | add_executable(src_test src/src_test.cpp) 6 | add_executable(src_sndfile src/src_sndfile.cpp) 7 | 8 | # Link to libsamplerate 9 | set(BUILD_TESTING OFF CACHE BOOL "Don't build CMake tests!") 10 | add_subdirectory(third_party/libsamplerate) 11 | 12 | target_include_directories(src_test PRIVATE third_party/libsamplerate/include) 13 | target_link_libraries(src_test PUBLIC samplerate) 14 | 15 | target_include_directories(src_sndfile PRIVATE third_party/libsamplerate/include) 16 | target_link_libraries(src_sndfile PUBLIC samplerate) 17 | 18 | # Link to libsndfile 19 | set(BUILD_PROGRAMS OFF CACHE BOOL "Don't build libsndfile programs!") 20 | set(BUILD_EXAMPLES OFF CACHE BOOL "Don't build libsndfile examples!") 21 | set(BUILD_REGTEST OFF CACHE BOOL "Don't build libsndfile regtest!") 22 | add_subdirectory(third_party/libsndfile) 23 | 24 | target_include_directories(src_sndfile PRIVATE third_party/libsndfile/include) 25 | target_link_libraries(src_sndfile PUBLIC sndfile) 26 | 27 | # use matplotlibcpp (if needed) 28 | set(USEMATPLOTLIB OFF) 29 | if(USEMATPLOTLIB) 30 | find_package(Python3 COMPONENTS Development NumPy) 31 | 32 | target_include_directories(src_test PRIVATE 33 | ${Python3_INCLUDE_DIRS} 34 | ${Python3_NumPy_INCLUDE_DIRS} 35 | third_party 36 | ) 37 | 38 | target_link_libraries(src_test PUBLIC 39 | Python3::Python 40 | Python3::NumPy 41 | ) 42 | 43 | target_compile_definitions(src_test PUBLIC MAKE_PLOTS=1) 44 | endif() 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) 2018 {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 2021 Jatin Chowdhury 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Non-Integer Sample Rate Conversion 2 | 3 | This repository contains a comparison of sample-rate 4 | conversion (SRC) algorithms, with an emphasis on performance 5 | for non-integer SRC factors. Currently two oversampling 6 | algorithms are implemented: 7 | 8 | - libsamplerate (Sinc interpolation, using `SRC_SINC_FASTEST` mode) 9 | - Holters-Parker Resampler (using 4th-order Butterworth filters) 10 | - Lanczos Resampler (borrowed from the [Surge Synthesizer](https://github.com/surge-synthesizer/surge) project) 11 | 12 | ## Results: 13 | On my Linux machine, the HP resampler is faster than libsamplerate 14 | by the following amounts: 15 | 16 | ![](./doc/speed_chart.png) 17 | 18 | ## Building 19 | ```bash 20 | $ cmake -Bbuild 21 | $ cmake --build build --config Release 22 | ``` 23 | Then to run the testing tool, run 24 | `./build/src_test`. 25 | 26 | ## Credits 27 | 28 | - [libsamplerate](https://github.com/libsndfile/libsamplerate) 29 | - The Holters-Parker Resampler is based on the filters derived in their [2018 DAFx paper](https://www.dafx.de/paper-archive/2018/papers/DAFx2018_paper_12.pdf) 30 | - [matplotlibcpp](https://github.com/lava/matplotlib-cpp) 31 | - The HP-Resampler, and Lanczos Resampler use a ton of code borrowed from the [Surge Synthesizer project](https://github.com/surge-synthesizer/surge) (in particular, the `SSEComplex` and `LanczosResampler` classes written by Paul Walker) 32 | 33 | ## License 34 | 35 | The code in this repository is licensed under the GPLv3. Enjoy! 36 | -------------------------------------------------------------------------------- /doc/BBD_Filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/BBD_Filters.png -------------------------------------------------------------------------------- /doc/HP_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/HP_compare.png -------------------------------------------------------------------------------- /doc/Lanczos_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/Lanczos_compare.png -------------------------------------------------------------------------------- /doc/hpresampler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/hpresampler.png -------------------------------------------------------------------------------- /doc/lanczosresampler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/lanczosresampler.png -------------------------------------------------------------------------------- /doc/libsamplerate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/libsamplerate.png -------------------------------------------------------------------------------- /doc/speed_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinchowdhury18/NonIntegerSRC/a2034141116f67917ed6a8f1b306ab22e34b6318/doc/speed_chart.png -------------------------------------------------------------------------------- /doc/writeup.md: -------------------------------------------------------------------------------- 1 | # Faster Non-Integer Sample Rate Conversion 2 | 3 | A couple months ago, I was doing some research into emulating 4 | [bucket-brigade devices](https://en.wikipedia.org/wiki/Bucket-brigade_device) for creating analog-style 5 | delay-lines, when I ran into a rather interesting paper, 6 | written by [Martin Holters and Julian Parker](http://dafx2018.web.ua.pt/papers/DAFx2018_paper_12.pdf). 7 | 8 | Although bucket-brigade devices (BBDs) are made up of analog 9 | circuits, in concept they behave like a fixed-length digital 10 | delay-line with a variable sample rate which determines the 11 | length of the delay created by the device. One of the primary 12 | challenges of emulating BBDs is performing fast sample rate 13 | conversion (SRC) to match the effective sample rate of the BBD. 14 | 15 | ![](https://www.electrosmash.com/images/tech/mn3007/bbd-topology-basic.png) 16 | 17 | Holters and Parker found a rather brilliant solution to this 18 | problem. They noted that BBDs typically use filters at their 19 | input and output, to reduce imaging and aliasing artifacts, 20 | and figured out how to implement these filters in the digital 21 | domain as "multi-rate" filters, so that the filter can use the 22 | audio sample rate on one side of the filter, and the BBD sample 23 | rate on the other side. 24 | 25 | While I was eventually able to implement a BBD emulation using 26 | Holters and Parker's method, I came to realize that their 27 | algorithm can be used much more generally for any task that 28 | requires sample rate conversion. (If anyone is interested in 29 | the results of the BBD emulation, it is now impemented in 30 | the [Surge Synthesizer](https://surge-synthesizer.github.io) 31 | as part of their "Ensemble" effect.) 32 | 33 | ## Non-Integer Sample Rate Conversion 34 | 35 | There are two fundamental kinds of sample rate conversions: 36 | Integer Sample Rate Conversion and Non-Integer Sample Rate 37 | Conversion. 38 | 39 | With Integer SRC, the sample rate is being changed by an integer 40 | factor. In this case, the block of audio can simply be truncated 41 | or padded with zeros, and then filtered, again to avoid any 42 | imaging or aliasing artifacts in the reconstructed signal. 43 | 44 | It is possible to use two stages of Integer SRC to perform 45 | Non-Integer SRC. For example, to upsample a signal by a factor 46 | of 1.5, one option would be to upsample by a factor of 3, 47 | and then downsample by a factor of 2. However, this approach 48 | can become very costly in cases where a very large upsampling 49 | factor is required. 50 | 51 | ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.researchgate.net%2Fprofile%2FMarek_Blok%2Fpublication%2F261059052%2Ffigure%2Ffig1%2FAS%3A434104647852035%401480510021203%2FClassic-sample-rate-conversion-algorithm.png&f=1&nofb=1) 52 | 53 | A better option that works well for a broad range of SRC 54 | factors is to use interpolation, for example 55 | [Sinc interpolation](https://ccrma.stanford.edu/~jos/Interpolation/Ideal_Bandlimited_Sinc_Interpolation.html), or 56 | [Lanczos interpolation](https://en.wikipedia.org/wiki/Lanczos_resampling). 57 | One of the most commonly used sample rate conversion libraries, 58 | [`libsamplerate`](https://github.com/libsndfile/libsamplerate) 59 | (AKA "Secret Rabbit Code") uses Sinc interpolation, with a couple 60 | different options, to trade off quality for performance. 61 | 62 | Non-Integer sample rate conversion is a very common issue 63 | in audio signal processing. For example, two commonly used 64 | audio sample rates are 44.1 kHz and 48 kHz, which differ by 65 | a factor 0f 1.088. If an algorithm is designed to run at one 66 | sample rate, but a user wants to use the algorithm at a different 67 | sample rate, then real-time non-integer SRC is often required. 68 | In these cases, we want to be able to do the SRC as quickly as 69 | possible, so that the majority of the time spent in our 70 | processing code can be spent actually doing the signal 71 | processing that we're interested in, rather than getting 72 | to and from our target sample rate. 73 | 74 | ## Holters-Parker Resampling 75 | 76 | Here I'd like to introduce Holters-Parker resampling as a 77 | potential alternative to interpolation-based resampling. 78 | Holters and Parker start with a filter in the analog domain, 79 | to be used for anti-aliasing or anti-imaging similar to the 80 | filters used in integer sample rate conversion. Next, Holters 81 | and Parker mention the "impulse invariance" method for 82 | discretizing analog filters. The idea behind impulse invariance 83 | is to derive an expression for the impulse response of the 84 | analog system, and then essentially "sample" the impulse 85 | response at the sample rate of the digital system. 86 | 87 | However, Holters and Parker instead use what they refer to 88 | as a "modified impulse-invariant transform", so that the 89 | input and output signals for the transformed filters may be 90 | at different sample rates. When seen in the context of 91 | sample rate conversion, this is a pretty neat insight: 92 | rather than resampling and then filtering, the filtering and 93 | resampling happen in the same process! 94 | 95 | ![](./BBD_Filters.png) 96 | 97 | I won't delve too deeply into the implementation of the 98 | Holters-Parker resampler here, except to note that while 99 | it can be used with any choice of filters, I would 100 | recommend choosing a filter order that is a multiple 101 | of four. The reason for this is that my implementation 102 | uses SIMD registers for computing the filter stages in 103 | parallel. For CPUs with the SSE instruction set (most CPUs 104 | these days), a single SIMD register can contain 4 32-bit 105 | floating point numbers, enabling us to compute a fourth-order 106 | filter for the same computational cost as a first-order 107 | filter. In my implementation, I use a fourth-order Butterworth 108 | filter, but it should be possible to produce a higher-quality 109 | output using a filter with a steeper rolloff, or with a 110 | higher-order filter. 111 | 112 | ## Results 113 | Finally, let's see how the Holters-Parker resampler compares 114 | to the commonly used Sinc interpolation algorithm in 115 | `libsamplerate`. Since I'm primarily interested in 116 | speed rather than quality, I've set `libsamplerate` to 117 | use the `SRC_SINC_FASTEST` option. As it turns out, the 118 | results are not even close! On my Linux system running an 119 | Intel i7 CPU, the Holters-Parker resampler measures 10-40x 120 | faster than `libsamplerate` depending on the sample rate 121 | conversion factor! On a Mac machine, I found comparable 122 | results, while on my Windows machine, the improvements 123 | were even more significant, nearing 40x in almost every 124 | test case. Further, I don't think my implementation of 125 | the Holters-Parker resampler is fully optimal just yet. 126 | With a couple more rounds of optimisations, I bet 127 | I could have it running even faster! 128 | 129 | ![](./speed_chart.png) 130 | 131 | So does this mean that you should immediately abandon 132 | `libsamplerate` and interpolation-based resampling 133 | altogether? Certainly not! Sinc interpolation is 134 | still the most ideal sample rate conversion scheme 135 | in most cases, and `libsamplerate` has a thoroughly 136 | tested implementation, with well understood trade-offs 137 | between quality and speed. Further, there are other 138 | interpolation-based methods that give decent quality 139 | output and can also out-perform `libsamplerate`. 140 | 141 | My goal here is simply to introduce the Holters-Parker 142 | resampler as a potential option that may be useful for 143 | some cases. Eventually, I'll need to see how my implementation 144 | stacks up in both speed and quality to `libsamplerate` 145 | and other interpolation-based SRC implementations. In 146 | particular, I think choosing the right filter design 147 | for the Holters-Parker resampler will be an important 148 | part of getting the highest quality results. 149 | 150 | ## Conclusion 151 | I hope this discussion of non-integer sample rate conversion 152 | and Holters-Parker resampling has been interesting and useful! 153 | If you'd like to take a look at the source code for my 154 | implementation of the resampler, the code can be found on 155 | [GitHub](https://github.com/jatinchowdhury18/NonIntegerSRC). I would greatly appreciate 156 | any suggestions on how to improve the quality or performance 157 | of the implementation! 158 | 159 | Thanks to the Surge Synthesizer Team for piquing my interest 160 | about all this stuff in the first place, and especially to 161 | Paul Walker for showing me how to use SIMD to vectorize the 162 | filter computations! 163 | -------------------------------------------------------------------------------- /src/BaseSRC.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class BaseSRC 4 | { 5 | public: 6 | BaseSRC() = default; 7 | virtual ~BaseSRC() = default; 8 | 9 | virtual void prepare (double sample_rate, int block_size, double src_ratio) = 0; 10 | virtual int process (const float* input, float* output, int num_samples) = 0; 11 | }; 12 | -------------------------------------------------------------------------------- /src/HPResampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseSRC.h" 4 | #include "src_utils/HPFilters.h" 5 | 6 | class HPResampler : public BaseSRC 7 | { 8 | public: 9 | HPResampler() = default; 10 | 11 | void prepare (double sample_rate, int block_size, double src_ratio) override 12 | { 13 | ratio = (float) src_ratio; 14 | Ts = 1.0f / ((float) sample_rate); 15 | Ts_in = 1.0f / ((float) sample_rate * ratio); 16 | Ts_out = 1.0f / ((float) sample_rate / ratio); 17 | y_old = 0.0f; 18 | 19 | inFilter = std::make_unique (Ts); 20 | inFilter->set_freq ((float) sample_rate * 0.5f); 21 | inFilter->set_delta (Ts_in); 22 | 23 | outFilter = std::make_unique (Ts_in); 24 | outFilter->set_freq ((float) sample_rate * 0.5f); 25 | outFilter->set_delta (Ts_out); 26 | 27 | middle.resize (int (block_size * ratio) + 1, 0.0f); 28 | } 29 | 30 | #if 0 // use input filter (Dirac) 31 | int process (const float* input, float* output, int num_samples) override 32 | { 33 | inFilter->set_time (tn); 34 | int count = 0; 35 | for (int i = 0; i < num_samples; ++i) 36 | { 37 | while (tn < Ts) 38 | { 39 | inFilter->calcG(); 40 | auto sum = FastMath::vSum(SSEComplexMulReal(inFilter->Gcalc, inFilter->x)); 41 | output[count++] = -0.95097f * sum; 42 | 43 | tn += Ts_in; 44 | } 45 | tn -= Ts; 46 | 47 | inFilter->process(input[i]); 48 | } 49 | 50 | return count; 51 | } 52 | #else // use output filter (rectangular) 53 | int process (const float* input, float* output, int num_samples) override 54 | { 55 | outFilter->set_time (tn); 56 | int count = 0; 57 | for (int i = 0; i < num_samples; ) 58 | { 59 | SSEComplex xOutAccum; 60 | while (tn < Ts) 61 | { 62 | auto y = input[i++]; 63 | auto delta = (y - y_old); 64 | y_old = y; 65 | 66 | outFilter->calcG(); 67 | xOutAccum += outFilter->Gcalc * delta; 68 | tn += Ts_out; 69 | } 70 | tn -= Ts; 71 | 72 | outFilter->process(xOutAccum); 73 | float sum = FastMath::vSum(xOutAccum._r); 74 | output[count++] = y_old + sum; 75 | } 76 | 77 | return count; 78 | } 79 | #endif 80 | 81 | private: 82 | std::unique_ptr inFilter; 83 | std::unique_ptr outFilter; 84 | 85 | float Ts = 1.0f; 86 | float Ts_in = 1.0f; 87 | float Ts_out = 1.0f; 88 | float tn = 0.0; 89 | 90 | float y_old = 0.0f; 91 | float ratio = 1.0f; 92 | 93 | std::vector middle; 94 | }; 95 | -------------------------------------------------------------------------------- /src/LanczosSRC.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseSRC.h" 4 | #include "src_utils/LanczosResampler.h" 5 | #include 6 | 7 | class LanczosSRC : public BaseSRC 8 | { 9 | public: 10 | LanczosSRC() = default; 11 | 12 | void prepare (double sample_rate, int block_size, double src_ratio) override 13 | { 14 | ratio = src_ratio; 15 | lanczos = std::make_unique ((float) sample_rate, float (sample_rate * ratio)); 16 | } 17 | 18 | int process (const float* input, float* output, int num_samples) override 19 | { 20 | lanczos->renormalizePhases(); 21 | 22 | for (int i = 0; i < num_samples; ++i) 23 | lanczos->push(input[i]); 24 | 25 | return lanczos->populateNext (output, int (num_samples * ratio + 1)); 26 | } 27 | 28 | private: 29 | std::unique_ptr lanczos; 30 | double ratio = 1.0; 31 | }; 32 | -------------------------------------------------------------------------------- /src/LibSamplerRateSRC.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseSRC.h" 4 | #include 5 | #include 6 | #include 7 | 8 | class LibSampleRateSRC : public BaseSRC 9 | { 10 | public: 11 | LibSampleRateSRC() = default; 12 | 13 | void prepare (double /*sample_rate*/, int block_size, double src_ratio) override 14 | { 15 | int error; 16 | src_state.reset (src_new (SRC_SINC_FASTEST, 1, &error)); 17 | src_set_ratio (src_state.get(), src_ratio); 18 | 19 | ratio = src_ratio; 20 | // output_data.resize (block_size * src_ratio + 1, 0.0f); // allocate an extra sample for rounding error 21 | } 22 | 23 | int process (const float* input, float* output, int num_samples) override 24 | { 25 | SRC_DATA src_data { 26 | input, 27 | output, 28 | num_samples, 29 | int (num_samples * ratio + 1), 30 | 0, 31 | 0, 32 | 0, 33 | ratio 34 | }; 35 | 36 | src_process (src_state.get(), &src_data); 37 | 38 | return (int) src_data.output_frames_gen; 39 | } 40 | 41 | private: 42 | double ratio = 1.0; 43 | std::unique_ptr src_state {nullptr, &src_delete}; 44 | }; 45 | -------------------------------------------------------------------------------- /src/src_sndfile.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "LibSamplerRateSRC.h" 8 | #include "HPResampler.h" 9 | #include "LanczosSRC.h" 10 | 11 | void usage() 12 | { 13 | std::cout << "USAGE: src_sndfile in_file out_file target_sample_rate mode" << std::endl; 14 | } 15 | 16 | using Vec2d = std::vector>; 17 | 18 | std::pair load_file (char* file, SF_INFO& sf_info) 19 | { 20 | std::cout << "Loading file: " << file << std::endl; 21 | 22 | auto wavFile = sf_open(file, SFM_READ, &sf_info); 23 | 24 | if (sf_info.frames == 0) 25 | { 26 | std::cout << "File could not be opened!" << std::endl; 27 | exit (1); 28 | } 29 | 30 | const double fs = (double) sf_info.samplerate; 31 | std::cout << "Original sample rate: " << fs << std::endl; 32 | 33 | std::vector readInterleaved(sf_info.channels * sf_info.frames, 0.0); 34 | sf_readf_float(wavFile, readInterleaved.data(), sf_info.frames); 35 | sf_close(wavFile); 36 | 37 | Vec2d audio (sf_info.channels, std::vector (sf_info.frames, 0.0)); 38 | 39 | // de-interleave channels 40 | for (int i = 0; i < sf_info.frames; ++i) 41 | { 42 | int interleavedPtr = i * sf_info.channels; 43 | for(size_t ch = 0; ch < sf_info.channels; ++ch) 44 | audio[ch][i] = readInterleaved[interleavedPtr + ch]; 45 | } 46 | 47 | return std::make_pair(audio, fs); 48 | } 49 | 50 | void write_file (char* file, const Vec2d& audio, double fs, SF_INFO& sf_info) 51 | { 52 | std::cout << "Writing to file: " << file << std::endl; 53 | 54 | const auto channels = (int) audio.size(); 55 | const auto frames = (sf_count_t) audio[0].size(); 56 | sf_info.frames = frames; 57 | sf_info.samplerate = (int) fs; 58 | 59 | auto wavFile = sf_open(file, SFM_WRITE, &sf_info); 60 | std::vector writeInterleaved(channels * frames, 0.0); 61 | 62 | // de-interleave channels 63 | for (int i = 0; i < frames; ++i) 64 | { 65 | int interleavedPtr = i * channels; 66 | for(int ch = 0; ch < channels; ++ch) 67 | writeInterleaved[interleavedPtr + ch] = audio[ch][i]; 68 | } 69 | 70 | sf_writef_float(wavFile, writeInterleaved.data(), frames); 71 | sf_close(wavFile); 72 | } 73 | 74 | std::unique_ptr getSRC (int mode) 75 | { 76 | if (mode == 0) 77 | { 78 | std::cout << "Using mode: libsamplerate" << std::endl; 79 | return std::make_unique(); 80 | } 81 | 82 | if (mode == 1) 83 | { 84 | std::cout << "Using mode: HPResampler" << std::endl; 85 | return std::make_unique(); 86 | } 87 | 88 | if (mode == 2) 89 | { 90 | std::cout << "Using mode: LanczosResampler" << std::endl; 91 | return std::make_unique(); 92 | } 93 | 94 | std::cout << "Mode argument not recogized!" << std::endl; 95 | return std::make_unique(); 96 | } 97 | 98 | Vec2d process_data (const Vec2d& input, double ratio, BaseSRC* src, float fs) 99 | { 100 | constexpr int block_size = 2048; 101 | 102 | using clock_t = std::chrono::high_resolution_clock; 103 | using second_t = std::chrono::duration; 104 | 105 | auto start = clock_t::now(); 106 | 107 | Vec2d output; 108 | int ch = 0; 109 | for (auto& data : input) 110 | { 111 | std::cout << "Processing channel " << ch++ << "..." << std::endl; 112 | 113 | src->prepare ((double) fs, (int) input.size(), ratio); 114 | std::vector out (int (data.size() * ratio + 1), 0.0f); 115 | 116 | int out_ptr = 0; 117 | for (int i = 0; i + block_size < (int) data.size(); i += block_size) 118 | out_ptr += src->process (&data[i], &out[out_ptr], block_size); 119 | 120 | output.push_back(std::vector (out.begin(), out.begin() + out_ptr)); 121 | } 122 | 123 | auto dur = std::chrono::duration_cast(clock_t::now() - start).count(); 124 | std::cout << "Finished processing in " << dur << " seconds" << std::endl; 125 | 126 | return output; 127 | } 128 | 129 | int main (int argc, char* argv[]) 130 | { 131 | if (argc != 5) 132 | { 133 | usage(); 134 | return 0; 135 | } 136 | 137 | SF_INFO sf_info; 138 | auto [audio, fs] = load_file (argv[1], sf_info); 139 | 140 | const auto target_fs = std::atof (argv[3]); 141 | const auto ratio = target_fs / fs; 142 | 143 | auto src = getSRC (std::atoi (argv[4])); 144 | auto audio_out = process_data (audio, ratio, src.get(), (float) fs); 145 | 146 | write_file (argv[2], audio_out, target_fs, sf_info); 147 | 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /src/src_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "LibSamplerRateSRC.h" 6 | #include "HPResampler.h" 7 | #include "LanczosSRC.h" 8 | #include "utils.h" 9 | 10 | #if MAKE_PLOTS 11 | #include 12 | namespace plt = matplotlibcpp; 13 | #endif 14 | 15 | namespace 16 | { 17 | constexpr int n_samples = 1000000; 18 | constexpr float freq = 100.0f; 19 | } 20 | 21 | double process_data (const std::vector& data, std::vector& output, double ratio, BaseSRC* src, float fs) 22 | { 23 | constexpr int block_size = 2048; 24 | src->prepare ((double) fs, block_size, ratio); 25 | 26 | std::vector out (int ((data.size() + block_size) * ratio + 1), 0.0f); 27 | 28 | using clock_t = std::chrono::high_resolution_clock; 29 | using second_t = std::chrono::duration; 30 | 31 | auto start = clock_t::now(); 32 | 33 | int out_ptr = 0; 34 | for (int i = 0; i + block_size < (int) data.size(); i += block_size) 35 | out_ptr += src->process (&data[i], &out[out_ptr], block_size); 36 | 37 | auto dur = std::chrono::duration_cast(clock_t::now() - start).count(); 38 | 39 | output.resize (out_ptr, 0.0f); 40 | std::copy (out.begin(), out.begin() + out_ptr, output.begin()); 41 | 42 | return dur; 43 | } 44 | 45 | void test_libsamplerate (const std::vector& data, double ratio, float fs) 46 | { 47 | LibSampleRateSRC src; 48 | std::vector output; 49 | auto time = process_data (data, output, ratio, &src, fs); 50 | auto [latency, err] = calc_stats (freq, (float) ratio * fs, output); 51 | 52 | // std::cout << " Ratio: " << ratio << std::endl; 53 | std::cout << " libsamplerate Duration: " << time << std::endl; 54 | // std::cout << " Latency: " << latency << std::endl; 55 | // std::cout << " Error: " << err << std::endl; 56 | 57 | #if MAKE_PLOTS 58 | plt::figure(); 59 | plt::plot(gen_time (fs, (int) data.size()), data); 60 | plt::plot(gen_time (fs * (float) ratio, (int) output.size()), output); 61 | plt::xlim(0.0, 0.01); 62 | plt::save("./doc/libsamplerate.png"); 63 | #endif 64 | } 65 | 66 | void test_hpresampler (const std::vector& data, double ratio, float fs) 67 | { 68 | HPResampler src; 69 | std::vector output; 70 | auto time = process_data (data, output, ratio, &src, fs); 71 | auto [latency, err] = calc_stats (freq, (float) ratio * fs, output); 72 | 73 | // auto max_val = *std::max_element (output.begin(), output.end()); 74 | // std::cout << max_val << std::endl; 75 | // std::cout << 1.0f / max_val << std::endl; 76 | 77 | // std::cout << " Ratio: " << ratio << std::endl; 78 | std::cout << " HPResampler Duration: " << time << std::endl; 79 | // std::cout << " Latency: " << latency << std::endl; 80 | // std::cout << " Error: " << err << std::endl; 81 | 82 | #if MAKE_PLOTS 83 | plt::figure(); 84 | plt::plot(gen_time (fs, (int) data.size()), data); 85 | plt::plot(gen_time (fs * (float) ratio, (int) output.size()), output); 86 | plt::xlim(0.0, 0.01); 87 | plt::save("./doc/hpresampler.png"); 88 | #endif 89 | } 90 | 91 | void test_lanczos (const std::vector& data, double ratio, float fs) 92 | { 93 | LanczosSRC src; 94 | std::vector output; 95 | auto time = process_data (data, output, ratio, &src, fs); 96 | auto [latency, err] = calc_stats (freq, (float) ratio * fs, output); 97 | 98 | // auto max_val = *std::max_element (output.begin(), output.end()); 99 | // std::cout << max_val << std::endl; 100 | // std::cout << 1.0f / max_val << std::endl; 101 | 102 | // std::cout << " Ratio: " << ratio << std::endl; 103 | std::cout << " Lanczos Duration: " << time << std::endl; 104 | // std::cout << " Latency: " << latency << std::endl; 105 | // std::cout << " Error: " << err << std::endl; 106 | 107 | #if MAKE_PLOTS 108 | plt::figure(); 109 | plt::plot(gen_time (fs, (int) data.size()), data); 110 | plt::plot(gen_time (fs * (float) ratio, (int) output.size()), output); 111 | plt::xlim(0.0, 0.01); 112 | plt::save("./doc/lanczosresampler.png"); 113 | #endif 114 | } 115 | 116 | int main() 117 | { 118 | std::cout << "Generating input data..." << std::endl; 119 | auto sine_48 = gen_sine (freq, 48000.0f, n_samples); 120 | auto sine_96 = gen_sine (freq, 96000.0f, n_samples); 121 | auto sine_441 = gen_sine (freq, 44100.0f, n_samples); 122 | 123 | std::cout << "Testing 48k -> 96k..." << std::endl; 124 | test_libsamplerate (sine_48, 2.0, 48000.0f); 125 | test_hpresampler (sine_48, 2.0, 48000.0f); 126 | test_lanczos (sine_48, 2.0, 48000.0f); 127 | 128 | std::cout << "Testing 96k -> 48k..." << std::endl; 129 | test_libsamplerate (sine_96, 0.5, 96000.0f); 130 | test_hpresampler (sine_96, 0.5, 96000.0f); 131 | test_lanczos (sine_96, 0.5, 96000.0f); 132 | 133 | std::cout << "Testing 44.1k -> 48k..." << std::endl; 134 | test_libsamplerate (sine_441, 1.088, 44100.0f); 135 | test_hpresampler (sine_441, 1.088, 44100.0f); 136 | test_lanczos (sine_441, 1.088, 44100.0f); 137 | 138 | std::cout << "Testing 48k -> 44.1k..." << std::endl; 139 | test_libsamplerate (sine_48, 0.919, 48000.0f); 140 | test_hpresampler (sine_48, 0.919, 48000.0f); 141 | test_lanczos (sine_48, 0.919, 48000.0f); 142 | 143 | return 0; 144 | } 145 | -------------------------------------------------------------------------------- /src/src_utils/FastMath.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define _USE_MATH_DEFINES 4 | #include 5 | #include 6 | 7 | // Some SSE code borrowed from Surge 8 | namespace FastMath 9 | { 10 | 11 | inline float vSum(__m128 x) 12 | { 13 | __m128 a = _mm_add_ps(x, _mm_movehl_ps(x, x)); 14 | a = _mm_add_ss(a, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 1))); 15 | float f; 16 | _mm_store_ss(&f, a); 17 | 18 | return f; 19 | } 20 | 21 | inline __m128 fastsinSSE(__m128 x) noexcept 22 | { 23 | #define M(a, b) _mm_mul_ps(a, b) 24 | #define A(a, b) _mm_add_ps(a, b) 25 | #define S(a, b) _mm_sub_ps(a, b) 26 | #define F(a) _mm_set_ps1(a) 27 | #define C(x) __m128 m##x = F((float)x) 28 | 29 | /* 30 | auto numerator = -x * (-(float)11511339840 + 31 | x2 * ((float)1640635920 + x2 * (-(float)52785432 + x2 * (float)479249))); 32 | auto denominator = 33 | (float)11511339840 + x2 * ((float)277920720 + x2 * ((float)3177720 + x2 * (float)18361)); 34 | */ 35 | C(11511339840); 36 | C(1640635920); 37 | C(52785432); 38 | C(479249); 39 | C(277920720); 40 | C(3177720); 41 | C(18361); 42 | auto mnegone = F(-1); 43 | 44 | auto x2 = M(x, x); 45 | 46 | auto num = M(mnegone, 47 | M(x, S(M(x2, A(m1640635920, M(x2, S(M(x2, m479249), m52785432)))), m11511339840))); 48 | auto den = A(m11511339840, M(x2, A(m277920720, M(x2, A(m3177720, M(x2, m18361)))))); 49 | 50 | #undef C 51 | #undef M 52 | #undef A 53 | #undef S 54 | #undef F 55 | return _mm_div_ps(num, den); 56 | } 57 | 58 | inline __m128 fastcosSSE(__m128 x) noexcept 59 | { 60 | #define M(a, b) _mm_mul_ps(a, b) 61 | #define A(a, b) _mm_add_ps(a, b) 62 | #define S(a, b) _mm_sub_ps(a, b) 63 | #define F(a) _mm_set_ps1(a) 64 | #define C(x) __m128 m##x = F((float)x) 65 | 66 | // auto x2 = x * x; 67 | auto x2 = M(x, x); 68 | 69 | C(39251520); 70 | C(18471600); 71 | C(1075032); 72 | C(14615); 73 | C(1154160); 74 | C(16632); 75 | C(127); 76 | 77 | // auto numerator = -(-(float)39251520 + x2 * ((float)18471600 + x2 * (-1075032 + 14615 * x2))); 78 | auto num = S(m39251520, M(x2, A(m18471600, M(x2, S(M(m14615, x2), m1075032))))); 79 | 80 | // auto denominator = (float)39251520 + x2 * (1154160 + x2 * (16632 + x2 * 127)); 81 | auto den = A(m39251520, M(x2, A(m1154160, M(x2, A(m16632, M(x2, m127)))))); 82 | #undef C 83 | #undef M 84 | #undef A 85 | #undef S 86 | #undef F 87 | return _mm_div_ps(num, den); 88 | } 89 | 90 | inline __m128 clampToPiRangeSSE(__m128 x) 91 | { 92 | const auto mpi = _mm_set1_ps(M_PI); 93 | const auto m2pi = _mm_set1_ps(2.0 * M_PI); 94 | const auto oo2p = _mm_set1_ps(1.0 / (2.0 * M_PI)); 95 | const auto mz = _mm_setzero_ps(); 96 | 97 | auto y = _mm_add_ps(x, mpi); 98 | auto yip = _mm_cvtepi32_ps(_mm_cvttps_epi32(_mm_mul_ps(y, oo2p))); 99 | auto p = _mm_sub_ps(y, _mm_mul_ps(m2pi, yip)); 100 | auto off = _mm_and_ps(_mm_cmplt_ps(p, mz), m2pi); 101 | p = _mm_add_ps(p, off); 102 | 103 | return _mm_sub_ps(p, mpi); 104 | } 105 | 106 | } // namespace FastMath 107 | -------------------------------------------------------------------------------- /src/src_utils/HPFilters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SSEComplex.h" 4 | 5 | /** 6 | * Bucket-Bridage Device filters, as derived by 7 | * Martin Holters and Julian Parker: 8 | * http://dafx2018.web.ua.pt/papers/DAFx2018_paper_12.pdf 9 | */ 10 | 11 | /** 12 | * Anti-aliasing/reconstruction filters used by JUNO-60 chorus. 13 | * Root/Pole analysis borrowed from the above paper. 14 | */ 15 | namespace FilterSpec 16 | { 17 | constexpr size_t N_filt = 4; 18 | 19 | constexpr std::complex iFiltRoot[] = {{-10329.2715f, -329.848f}, 20 | {-10329.2715f, +329.848f}, 21 | {366.990557f, -1811.4318f}, 22 | {366.990557f, +1811.4318f}}; 23 | constexpr std::complex iFiltPole[] = { 24 | {-55482.0f, -25082.0f}, {-55482.0f, +25082.0f}, {-26292.0f, -59437.0f}, {-26292.0f, +59437.0f}}; 25 | 26 | constexpr std::complex oFiltRoot[] = { 27 | {-11256.0f, -99566.0f}, {-11256.0f, +99566.0f}, {-13802.0f, -24606.0f}, {-13802.0f, +24606.0f}}; 28 | constexpr std::complex oFiltPole[] = { 29 | {-51468.0f, -21437.0f}, {-51468.0f, +21437.0f}, {-26276.0f, -59699.0f}, {-26276.0f, +59699.0f}}; 30 | } // namespace FilterSpec 31 | 32 | inline SSEComplex fast_complex_pow(__m128 angle, float b) 33 | { 34 | const __m128 scalar = _mm_set1_ps(b); 35 | auto angle_pow = _mm_mul_ps(angle, scalar); 36 | return SSEComplex::fastExp(angle_pow); 37 | } 38 | 39 | struct InputFilterBank 40 | { 41 | public: 42 | InputFilterBank(float sampleTime) : Ts(sampleTime) 43 | { 44 | float root_real alignas(16)[4]; 45 | float root_imag alignas(16)[4]; 46 | float pole_real alignas(16)[4]; 47 | float pole_imag alignas(16)[4]; 48 | for (size_t i = 0; i < FilterSpec::N_filt; ++i) 49 | { 50 | root_real[i] = FilterSpec::iFiltRoot[i].real(); 51 | root_imag[i] = FilterSpec::iFiltRoot[i].imag(); 52 | 53 | pole_real[i] = FilterSpec::iFiltPole[i].real(); 54 | pole_imag[i] = FilterSpec::iFiltPole[i].imag(); 55 | } 56 | roots = SSEComplex(root_real, root_imag); 57 | poles = SSEComplex(pole_real, pole_imag); 58 | } 59 | 60 | inline void set_freq(float freq) 61 | { 62 | constexpr float originalCutoff = 9900.0f; 63 | const float freqFactor = freq / originalCutoff; 64 | root_corr = roots * freqFactor; 65 | pole_corr = poles.map([&freqFactor, this](const std::complex &f) { 66 | return std::exp(f * freqFactor * Ts); 67 | }); 68 | 69 | pole_corr_angle = 70 | pole_corr.map_float([](const std::complex &f) { return std::arg(f); }); 71 | 72 | gCoef = root_corr * Ts; 73 | } 74 | 75 | inline void set_time(float tn) 76 | { 77 | Gcalc = 78 | gCoef * pole_corr.map([&tn](const std::complex &f) { return std::pow(f, tn); }); 79 | } 80 | 81 | inline void set_delta(float delta) { Aplus = fast_complex_pow(pole_corr_angle, delta); } 82 | 83 | inline void calcG() noexcept { Gcalc = Aplus * Gcalc; } 84 | 85 | inline void process(float u) 86 | { 87 | x = pole_corr * x + SSEComplex(_mm_set1_ps(u), _mm_set1_ps(0.0f)); 88 | } 89 | 90 | SSEComplex x; 91 | SSEComplex Gcalc{{1.0f, 1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}}; 92 | 93 | private: 94 | SSEComplex roots; 95 | SSEComplex poles; 96 | SSEComplex root_corr; 97 | SSEComplex pole_corr; 98 | __m128 pole_corr_angle; 99 | 100 | SSEComplex Aplus; 101 | 102 | const float Ts; 103 | SSEComplex gCoef; 104 | }; 105 | 106 | struct OutputFilterBank 107 | { 108 | public: 109 | OutputFilterBank(float sampleTime) : Ts(sampleTime) 110 | { 111 | float gcoefs_real alignas(16)[4]; 112 | float gcoefs_imag alignas(16)[4]; 113 | float pole_real alignas(16)[4]; 114 | float pole_imag alignas(16)[4]; 115 | for (size_t i = 0; i < FilterSpec::N_filt; ++i) 116 | { 117 | auto gVal = FilterSpec::oFiltRoot[i] / FilterSpec::oFiltPole[i]; 118 | gcoefs_real[i] = gVal.real(); 119 | gcoefs_imag[i] = gVal.imag(); 120 | 121 | pole_real[i] = FilterSpec::oFiltPole[i].real(); 122 | pole_imag[i] = FilterSpec::oFiltPole[i].imag(); 123 | } 124 | gCoef = SSEComplex(gcoefs_real, gcoefs_imag); 125 | poles = SSEComplex(pole_real, pole_imag); 126 | } 127 | 128 | inline float calcH0() const { return -1.0f * FastMath::vSum(gCoef.real()); } 129 | 130 | inline void set_freq(float freq) 131 | { 132 | constexpr float originalCutoff = 9500.0f; 133 | const float freqFactor = freq / originalCutoff; 134 | pole_corr = poles.map([&freqFactor, this](const std::complex &f) { 135 | return std::exp(f * freqFactor * Ts); 136 | }); 137 | 138 | pole_corr_angle = 139 | pole_corr.map_float([](const std::complex &f) { return std::arg(f); }); 140 | 141 | Amult = gCoef * pole_corr; 142 | } 143 | 144 | inline void set_time(float tn) 145 | { 146 | Gcalc = Amult * pole_corr.map( 147 | [&tn](const std::complex &f) { return std::pow(f, 1.0f - tn); }); 148 | } 149 | 150 | inline void set_delta(float delta) { Aplus = fast_complex_pow(pole_corr_angle, -delta); } 151 | 152 | inline void calcG() noexcept { Gcalc = Aplus * Gcalc; } 153 | 154 | inline void process(SSEComplex u) { x = pole_corr * x + u; } 155 | 156 | SSEComplex x; 157 | SSEComplex Gcalc{{1.0f, 1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}}; 158 | 159 | private: 160 | SSEComplex gCoef; 161 | SSEComplex poles; 162 | SSEComplex root_corr; 163 | SSEComplex pole_corr; 164 | __m128 pole_corr_angle; 165 | 166 | SSEComplex Aplus; 167 | 168 | const float Ts; 169 | SSEComplex Amult; 170 | }; 171 | -------------------------------------------------------------------------------- /src/src_utils/LanczosResampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Surge Synthesizer is Free and Open Source Software 3 | ** 4 | ** Surge is made available under the Gnu General Public License, v3.0 5 | ** https://www.gnu.org/licenses/gpl-3.0.en.html 6 | ** 7 | ** Copyright 2004-2021 by various individuals as described by the Git transaction log 8 | ** 9 | ** All source at: https://github.com/surge-synthesizer/surge.git 10 | ** 11 | ** Surge was a commercial product from 2004-2018, with Copyright and ownership 12 | ** in that period held by Claes Johanson at Vember Audio. Claes made Surge 13 | ** open source in September 2018. 14 | */ 15 | 16 | #ifndef SURGE_LANCZOSRESAMPLER_H 17 | #define SURGE_LANCZOSRESAMPLER_H 18 | 19 | #include "FastMath.h" 20 | #include 21 | #include 22 | 23 | /* 24 | * See https://en.wikipedia.org/wiki/Lanczos_resampling 25 | */ 26 | 27 | struct LanczosResampler 28 | { 29 | static constexpr size_t A = 4; 30 | static constexpr size_t BUFFER_SZ = 4096; 31 | static constexpr size_t filterWidth = A * 2; 32 | static constexpr size_t tableObs = 8192; 33 | static constexpr double dx = 1.0 / (tableObs); 34 | 35 | // Fixme: Make this static and shared 36 | static float lanczosTable alignas(16)[tableObs][filterWidth], lanczosTableDX 37 | alignas(16)[tableObs][filterWidth]; 38 | static bool tablesInitialized; 39 | 40 | float input[BUFFER_SZ * 2]; 41 | int wp = 0; 42 | float sri, sro; 43 | double phaseI, phaseO, dPhaseI, dPhaseO; 44 | 45 | inline double kernel(double x) 46 | { 47 | if (fabs(x) < 1e-7) 48 | return 1; 49 | return A * std::sin(M_PI * x) * std::sin(M_PI * x / A) / (M_PI * M_PI * x * x); 50 | } 51 | 52 | LanczosResampler(float inputRate, float outputRate) : sri(inputRate), sro(outputRate) 53 | { 54 | phaseI = 0; 55 | phaseO = 0; 56 | 57 | dPhaseI = 1.0; 58 | dPhaseO = sri / sro; 59 | 60 | memset(input, 0, 2 * BUFFER_SZ * sizeof(float)); 61 | if (!tablesInitialized) 62 | { 63 | for (int t = 0; t < tableObs; ++t) 64 | { 65 | double x0 = dx * t; 66 | for (int i = 0; i < filterWidth; ++i) 67 | { 68 | double x = x0 + i - A; 69 | lanczosTable[t][i] = kernel(x); 70 | } 71 | } 72 | for (int t = 0; t < tableObs; ++t) 73 | { 74 | for (int i = 0; i < filterWidth; ++i) 75 | { 76 | lanczosTableDX[t][i] = 77 | lanczosTable[(t + 1) & (tableObs - 1)][i] - lanczosTable[t][i]; 78 | } 79 | } 80 | tablesInitialized = true; 81 | } 82 | } 83 | 84 | inline void push(float f) 85 | { 86 | input[wp] = f; 87 | input[wp + BUFFER_SZ] = f; // this way we can always wrap 88 | wp = (wp + 1) & (BUFFER_SZ - 1); 89 | phaseI += dPhaseI; 90 | } 91 | 92 | inline float readZOH(double xBack) const 93 | { 94 | double p0 = wp - xBack; 95 | int idx0 = (int)p0; 96 | idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); 97 | if (idx0 <= A) 98 | idx0 += BUFFER_SZ; 99 | 100 | return input[idx0]; 101 | } 102 | 103 | inline float readLin(double xBack) const 104 | { 105 | double p0 = wp - xBack; 106 | int idx0 = (int)p0; 107 | float frac = p0 - idx0; 108 | idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); 109 | if (idx0 <= A) 110 | idx0 += BUFFER_SZ; 111 | 112 | return (1.0 - frac) * input[idx0] + frac * input[idx0 + 1]; 113 | } 114 | 115 | inline float read(double xBack) const 116 | { 117 | double p0 = wp - xBack; 118 | int idx0 = floor(p0); 119 | double off0 = 1.0 - (p0 - idx0); 120 | 121 | idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); 122 | idx0 += (idx0 <= A) * BUFFER_SZ; 123 | 124 | double off0byto = off0 * tableObs; 125 | int tidx = (int)(off0byto); 126 | double fidx = (off0byto - tidx); 127 | 128 | auto fl = _mm_set1_ps((float)fidx); 129 | auto f0 = _mm_load_ps(&lanczosTable[tidx][0]); 130 | auto df0 = _mm_load_ps(&lanczosTableDX[tidx][0]); 131 | 132 | f0 = _mm_add_ps(f0, _mm_mul_ps(df0, fl)); 133 | 134 | auto f1 = _mm_load_ps(&lanczosTable[tidx][4]); 135 | auto df1 = _mm_load_ps(&lanczosTableDX[tidx][4]); 136 | f1 = _mm_add_ps(f1, _mm_mul_ps(df1, fl)); 137 | 138 | auto d0 = _mm_loadu_ps(&input[idx0 - A]); 139 | auto d1 = _mm_loadu_ps(&input[idx0]); 140 | auto rv = _mm_add_ps(_mm_mul_ps(f0, d0), _mm_mul_ps(f1, d1)); 141 | return FastMath::vSum(rv); 142 | } 143 | 144 | inline size_t inputsRequiredToGenerateOutputs(size_t desiredOutputs) const 145 | { 146 | /* 147 | * So (phaseI + dPhaseI * res - phaseO - dPhaseO * desiredOutputs) * sri > A + 1 148 | * 149 | * Use the fact that dPhaseI = sri and find 150 | * res > (A+1) - (phaseI - phaseO + dPhaseO * desiredOutputs) * sri 151 | */ 152 | double res = A + 1 - (phaseI - phaseO - dPhaseO * desiredOutputs); 153 | 154 | return (size_t)std::max(res + 1, 0.0); // Check this calculation 155 | } 156 | 157 | size_t populateNext(float *f, size_t max); 158 | 159 | // /* 160 | // * This is a dangerous but efficient function which more quickly 161 | // * populates BLOCK_SIZE_OS worth of items, but assumes you have 162 | // * checked the range. 163 | // */ 164 | // void populateNextBlockSizeOS(float *f); 165 | 166 | inline void advanceReadPointer(size_t n) { phaseO += n * dPhaseO; } 167 | inline void snapOutToIn() 168 | { 169 | phaseO = 0; 170 | phaseI = 0; 171 | } 172 | 173 | inline void renormalizePhases() 174 | { 175 | phaseI -= phaseO; 176 | phaseO = 0; 177 | } 178 | }; 179 | 180 | float LanczosResampler::lanczosTable alignas( 181 | 16)[LanczosResampler::tableObs][LanczosResampler::filterWidth]; 182 | float LanczosResampler::lanczosTableDX alignas( 183 | 16)[LanczosResampler::tableObs][LanczosResampler::filterWidth]; 184 | 185 | bool LanczosResampler::tablesInitialized = false; 186 | 187 | size_t LanczosResampler::populateNext(float *f, size_t max) 188 | { 189 | int populated = 0; 190 | while (populated < max && (phaseI - phaseO) > A + 1) 191 | { 192 | f[populated] = read(phaseI - phaseO); 193 | phaseO += dPhaseO; 194 | populated++; 195 | } 196 | return populated; 197 | } 198 | 199 | #endif // SURGE_LANCZOSRESAMPLER_H 200 | -------------------------------------------------------------------------------- /src/src_utils/SSEComplex.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Surge Synthesizer is Free and Open Source Software 3 | ** 4 | ** Surge is made available under the Gnu General Public License, v3.0 5 | ** https://www.gnu.org/licenses/gpl-3.0.en.html 6 | ** 7 | ** Copyright 2004-2021 by various individuals as described by the Git transaction log 8 | ** 9 | ** All source at: https://github.com/surge-synthesizer/surge.git 10 | ** 11 | ** Surge was a commercial product from 2004-2018, with Copyright and ownership 12 | ** in that period held by Claes Johanson at Vember Audio. Claes made Surge 13 | ** open source in September 2018. 14 | */ 15 | 16 | /* 17 | * You would hope you could specialize std::complex for SSE, but alas, you cannot 18 | * because it assumes (in the spec) that you only do float double or longdouble and 19 | * that shows up in how it is coded. So instead write a little SSEComplex class 20 | * with limited capabilities 21 | */ 22 | 23 | #ifndef SURGE_SSECOMPLEX_H 24 | #define SURGE_SSECOMPLEX_H 25 | 26 | #include "FastMath.h" 27 | #include 28 | #include 29 | 30 | struct SSEComplex 31 | { 32 | typedef __m128 T; 33 | T _r, _i; 34 | constexpr SSEComplex(const T &r = _mm_setzero_ps(), const T &i = _mm_setzero_ps()) 35 | : _r(r), _i(i) 36 | { 37 | } 38 | 39 | SSEComplex(float r[4], float i[4]) 40 | { 41 | _r = _mm_loadu_ps(r); 42 | _i = _mm_loadu_ps(i); 43 | } 44 | 45 | inline __m128 real() const { return _r; } 46 | inline __m128 imag() const { return _i; } 47 | 48 | SSEComplex(std::initializer_list r, std::initializer_list i) 49 | { 50 | if (r.size() != 4 && i.size() != 4) 51 | { 52 | throw std::invalid_argument("Initialize lists must be of size 4"); 53 | } 54 | float rfl alignas(16)[4], ifl alignas(16)[4]; 55 | for (int q = 0; q < 4; ++q) 56 | { 57 | rfl[q] = *(r.begin() + q); 58 | ifl[q] = *(i.begin() + q); 59 | } 60 | 61 | _r = _mm_load_ps(rfl); 62 | _i = _mm_load_ps(ifl); 63 | } 64 | inline SSEComplex &operator+=(const SSEComplex &o) 65 | { 66 | _r = _mm_add_ps(_r, o._r); 67 | _i = _mm_add_ps(_i, o._i); 68 | return *this; 69 | } 70 | 71 | std::complex atIndex(int i) const 72 | { 73 | float rfl alignas(16)[4], ifl alignas(16)[4]; 74 | _mm_store_ps(rfl, _r); 75 | _mm_store_ps(ifl, _i); 76 | return std::complex{rfl[i], ifl[i]}; 77 | } 78 | 79 | inline static SSEComplex fastExp(__m128 angle) 80 | { 81 | angle = FastMath::clampToPiRangeSSE(angle); 82 | return {FastMath::fastcosSSE(angle), FastMath::fastsinSSE(angle)}; 83 | } 84 | 85 | inline SSEComplex map(std::function(const std::complex &)> f) 86 | { 87 | float rfl alignas(16)[4], ifl alignas(16)[4]; 88 | _mm_store_ps(rfl, _r); 89 | _mm_store_ps(ifl, _i); 90 | 91 | float rflR alignas(16)[4], iflR alignas(16)[4]; 92 | for (int i = 0; i < 4; ++i) 93 | { 94 | auto a = std::complex{rfl[i], ifl[i]}; 95 | auto b = f(a); 96 | rflR[i] = b.real(); 97 | iflR[i] = b.imag(); 98 | } 99 | return {_mm_load_ps(rflR), _mm_load_ps(iflR)}; 100 | } 101 | 102 | inline __m128 map_float(std::function &)> f) 103 | { 104 | float rfl alignas(16)[4], ifl alignas(16)[4]; 105 | _mm_store_ps(rfl, _r); 106 | _mm_store_ps(ifl, _i); 107 | 108 | float out alignas(16)[4]; 109 | for (int i = 0; i < 4; ++i) 110 | { 111 | auto a = std::complex{rfl[i], ifl[i]}; 112 | out[i] = f(a); 113 | } 114 | return _mm_load_ps(out); 115 | } 116 | }; 117 | 118 | inline SSEComplex operator+(const SSEComplex &a, const SSEComplex &b) 119 | { 120 | return {_mm_add_ps(a._r, b._r), _mm_add_ps(a._i, b._i)}; 121 | } 122 | 123 | inline __m128 SSEComplexMulReal(const SSEComplex &a, const SSEComplex &b) 124 | { 125 | return _mm_sub_ps(_mm_mul_ps(a._r, b._r), _mm_mul_ps(a._i, b._i)); 126 | } 127 | 128 | inline __m128 SSEComplexMulImag(const SSEComplex &a, const SSEComplex &b) 129 | { 130 | return _mm_add_ps(_mm_mul_ps(a._r, b._i), _mm_mul_ps(a._i, b._r)); 131 | } 132 | 133 | inline SSEComplex operator*(const SSEComplex &a, const SSEComplex &b) 134 | { 135 | return {SSEComplexMulReal(a, b), SSEComplexMulImag(a, b)}; 136 | } 137 | 138 | inline SSEComplex operator*(const SSEComplex &a, const float &b) 139 | { 140 | const __m128 scalar = _mm_set1_ps(b); 141 | return {_mm_mul_ps(a._r, scalar), _mm_mul_ps(a._i, scalar)}; 142 | } 143 | 144 | #endif // SURGE_SSECOMPLEX_H 145 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::vector gen_time (float fs, int num_samples) 6 | { 7 | std::vector time (num_samples); 8 | std::generate (time.begin(), time.end(), [=, n = 0.0f] () mutable { return n++ / fs; }); 9 | 10 | return std::move (time); 11 | } 12 | 13 | std::vector gen_sine (float freq, float fs, int num_samples) 14 | { 15 | std::vector data (num_samples); 16 | std::generate (data.begin(), data.end(), [=, n = 0.0f] () mutable { return std::sin (2.0f * M_PI * n++ * freq / fs); }); 17 | 18 | return std::move (data); 19 | } 20 | 21 | int calc_latency (const std::vector& data, const std::vector& ref_data) 22 | { 23 | auto find_first_point5 = [] (const std::vector& x) -> int { 24 | for (int i = 0; i < (int) x.size(); ++i) 25 | { 26 | if (x[i] >= 0.5f) 27 | return i; 28 | } 29 | return 0; 30 | }; 31 | 32 | auto ref_one = find_first_point5 (ref_data); 33 | auto actual_one = find_first_point5 (data); 34 | 35 | return actual_one - ref_one; 36 | } 37 | 38 | std::pair calc_stats (float freq, float fs, const std::vector& data) 39 | { 40 | const int num_samples = (int) data.size(); 41 | auto compare_data = gen_sine (freq, fs, num_samples); 42 | auto latency_samp = calc_latency (data, compare_data); 43 | 44 | float error_sum = 0.0f; 45 | for (int i = 0; i < num_samples - latency_samp; ++i) 46 | error_sum += std::abs (compare_data[i] - data[i + latency_samp]); 47 | 48 | // for (int i = 0; i < 50; i += 5) 49 | // std::cout << compare_data[i] << ", " << data[i] << std::endl; 50 | 51 | auto avg_error = error_sum / (float) num_samples; 52 | return std::make_pair (latency_samp, avg_error); 53 | } 54 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.io import wavfile 3 | import matplotlib.pyplot as plt 4 | import os 5 | 6 | FS = 44100 7 | N = int(FS) 8 | freq = 100.0 9 | x = np.sin(2 * np.pi * np.arange(N) * freq / FS) 10 | wavfile.write('audio/sine_441.wav', FS, x) 11 | 12 | def plot_fft(x, fs, *args, **kwargs): 13 | freqs = np.fft.rfftfreq(len(x), 1.0 / fs) 14 | X = np.fft.rfft(x) / len(x) 15 | plt.semilogx(freqs, 20 * np.log10(np.abs(X)), *args, **kwargs) 16 | 17 | # plot_fft(x, FS) 18 | 19 | target_fs = 96 # kHz 20 | 21 | os.system(f'./build/src_sndfile audio/sine_441.wav audio/sine_{target_fs}_SRC.wav {1000 * target_fs} 0') 22 | os.system(f'./build/src_sndfile audio/sine_441.wav audio/sine_{target_fs}_HPR.wav {1000 * target_fs} 1') 23 | os.system(f'./build/src_sndfile audio/sine_441.wav audio/sine_{target_fs}_LCZ.wav {1000 * target_fs} 2') 24 | 25 | fs, x1 = wavfile.read('audio/sine_96_SRC.wav') 26 | plot_fft(x1, fs, label='SRC') 27 | 28 | # fs, x2 = wavfile.read('audio/sine_96_HPR.wav') 29 | # plot_fft(x2, fs, '--', label='HP') 30 | 31 | fs, x3 = wavfile.read('audio/sine_96_LCZ.wav') 32 | plot_fft(x3, fs, '--', label='Lanczos') 33 | 34 | plt.grid() 35 | plt.ylim(-150) 36 | plt.legend() 37 | 38 | plt.title('SRC Comparison') 39 | plt.xlabel('Frequency [Hz]') 40 | plt.ylabel('Amplitude [dB]') 41 | 42 | plt.show() 43 | -------------------------------------------------------------------------------- /third_party/matplotlibcpp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Python headers must be included before any system headers, since 4 | // they define _POSIX_C_SOURCE 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include // requires c++11 support 15 | #include 16 | #include 17 | 18 | #ifndef WITHOUT_NUMPY 19 | # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 20 | # include 21 | 22 | # ifdef WITH_OPENCV 23 | # include 24 | # endif // WITH_OPENCV 25 | 26 | /* 27 | * A bunch of constants were removed in OpenCV 4 in favour of enum classes, so 28 | * define the ones we need here. 29 | */ 30 | # if CV_MAJOR_VERSION > 3 31 | # define CV_BGR2RGB cv::COLOR_BGR2RGB 32 | # define CV_BGRA2RGBA cv::COLOR_BGRA2RGBA 33 | # endif 34 | #endif // WITHOUT_NUMPY 35 | 36 | #if PY_MAJOR_VERSION >= 3 37 | # define PyString_FromString PyUnicode_FromString 38 | # define PyInt_FromLong PyLong_FromLong 39 | # define PyString_FromString PyUnicode_FromString 40 | #endif 41 | 42 | 43 | namespace matplotlibcpp { 44 | namespace detail { 45 | 46 | static std::string s_backend; 47 | 48 | struct _interpreter { 49 | PyObject* s_python_function_arrow; 50 | PyObject *s_python_function_show; 51 | PyObject *s_python_function_close; 52 | PyObject *s_python_function_draw; 53 | PyObject *s_python_function_pause; 54 | PyObject *s_python_function_save; 55 | PyObject *s_python_function_figure; 56 | PyObject *s_python_function_fignum_exists; 57 | PyObject *s_python_function_plot; 58 | PyObject *s_python_function_quiver; 59 | PyObject* s_python_function_contour; 60 | PyObject *s_python_function_semilogx; 61 | PyObject *s_python_function_semilogy; 62 | PyObject *s_python_function_loglog; 63 | PyObject *s_python_function_fill; 64 | PyObject *s_python_function_fill_between; 65 | PyObject *s_python_function_hist; 66 | PyObject *s_python_function_imshow; 67 | PyObject *s_python_function_scatter; 68 | PyObject *s_python_function_boxplot; 69 | PyObject *s_python_function_subplot; 70 | PyObject *s_python_function_subplot2grid; 71 | PyObject *s_python_function_legend; 72 | PyObject *s_python_function_xlim; 73 | PyObject *s_python_function_ion; 74 | PyObject *s_python_function_ginput; 75 | PyObject *s_python_function_ylim; 76 | PyObject *s_python_function_title; 77 | PyObject *s_python_function_axis; 78 | PyObject *s_python_function_axvline; 79 | PyObject *s_python_function_axvspan; 80 | PyObject *s_python_function_xlabel; 81 | PyObject *s_python_function_ylabel; 82 | PyObject *s_python_function_gca; 83 | PyObject *s_python_function_xticks; 84 | PyObject *s_python_function_yticks; 85 | PyObject* s_python_function_margins; 86 | PyObject *s_python_function_tick_params; 87 | PyObject *s_python_function_grid; 88 | PyObject* s_python_function_cla; 89 | PyObject *s_python_function_clf; 90 | PyObject *s_python_function_errorbar; 91 | PyObject *s_python_function_annotate; 92 | PyObject *s_python_function_tight_layout; 93 | PyObject *s_python_colormap; 94 | PyObject *s_python_empty_tuple; 95 | PyObject *s_python_function_stem; 96 | PyObject *s_python_function_xkcd; 97 | PyObject *s_python_function_text; 98 | PyObject *s_python_function_suptitle; 99 | PyObject *s_python_function_bar; 100 | PyObject *s_python_function_barh; 101 | PyObject *s_python_function_colorbar; 102 | PyObject *s_python_function_subplots_adjust; 103 | 104 | 105 | /* For now, _interpreter is implemented as a singleton since its currently not possible to have 106 | multiple independent embedded python interpreters without patching the python source code 107 | or starting a separate process for each. [1] 108 | Furthermore, many python objects expect that they are destructed in the same thread as they 109 | were constructed. [2] So for advanced usage, a `kill()` function is provided so that library 110 | users can manually ensure that the interpreter is constructed and destroyed within the 111 | same thread. 112 | 113 | 1: http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program 114 | 2: https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256 115 | */ 116 | 117 | static _interpreter& get() { 118 | return interkeeper(false); 119 | } 120 | 121 | static _interpreter& kill() { 122 | return interkeeper(true); 123 | } 124 | 125 | // Stores the actual singleton object referenced by `get()` and `kill()`. 126 | static _interpreter& interkeeper(bool should_kill) { 127 | static _interpreter ctx; 128 | if (should_kill) 129 | ctx.~_interpreter(); 130 | return ctx; 131 | } 132 | 133 | PyObject* safe_import(PyObject* module, std::string fname) { 134 | PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); 135 | 136 | if (!fn) 137 | throw std::runtime_error(std::string("Couldn't find required function: ") + fname); 138 | 139 | if (!PyFunction_Check(fn)) 140 | throw std::runtime_error(fname + std::string(" is unexpectedly not a PyFunction.")); 141 | 142 | return fn; 143 | } 144 | 145 | private: 146 | 147 | #ifndef WITHOUT_NUMPY 148 | # if PY_MAJOR_VERSION >= 3 149 | 150 | void *import_numpy() { 151 | import_array(); // initialize C-API 152 | return NULL; 153 | } 154 | 155 | # else 156 | 157 | void import_numpy() { 158 | import_array(); // initialize C-API 159 | } 160 | 161 | # endif 162 | #endif 163 | 164 | _interpreter() { 165 | 166 | // optional but recommended 167 | #if PY_MAJOR_VERSION >= 3 168 | wchar_t name[] = L"plotting"; 169 | #else 170 | char name[] = "plotting"; 171 | #endif 172 | Py_SetProgramName(name); 173 | Py_Initialize(); 174 | 175 | wchar_t const *dummy_args[] = {L"Python", NULL}; // const is needed because literals must not be modified 176 | wchar_t const **argv = dummy_args; 177 | int argc = sizeof(dummy_args)/sizeof(dummy_args[0])-1; 178 | PySys_SetArgv(argc, const_cast(argv)); 179 | 180 | #ifndef WITHOUT_NUMPY 181 | import_numpy(); // initialize numpy C-API 182 | #endif 183 | 184 | PyObject* matplotlibname = PyString_FromString("matplotlib"); 185 | PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); 186 | PyObject* cmname = PyString_FromString("matplotlib.cm"); 187 | PyObject* pylabname = PyString_FromString("pylab"); 188 | if (!pyplotname || !pylabname || !matplotlibname || !cmname) { 189 | throw std::runtime_error("couldnt create string"); 190 | } 191 | 192 | PyObject* matplotlib = PyImport_Import(matplotlibname); 193 | Py_DECREF(matplotlibname); 194 | if (!matplotlib) { 195 | PyErr_Print(); 196 | throw std::runtime_error("Error loading module matplotlib!"); 197 | } 198 | 199 | // matplotlib.use() must be called *before* pylab, matplotlib.pyplot, 200 | // or matplotlib.backends is imported for the first time 201 | if (!s_backend.empty()) { 202 | PyObject_CallMethod(matplotlib, const_cast("use"), const_cast("s"), s_backend.c_str()); 203 | } 204 | 205 | PyObject* pymod = PyImport_Import(pyplotname); 206 | Py_DECREF(pyplotname); 207 | if (!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); } 208 | 209 | s_python_colormap = PyImport_Import(cmname); 210 | Py_DECREF(cmname); 211 | if (!s_python_colormap) { throw std::runtime_error("Error loading module matplotlib.cm!"); } 212 | 213 | PyObject* pylabmod = PyImport_Import(pylabname); 214 | Py_DECREF(pylabname); 215 | if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } 216 | 217 | s_python_function_arrow = safe_import(pymod, "arrow"); 218 | s_python_function_show = safe_import(pymod, "show"); 219 | s_python_function_close = safe_import(pymod, "close"); 220 | s_python_function_draw = safe_import(pymod, "draw"); 221 | s_python_function_pause = safe_import(pymod, "pause"); 222 | s_python_function_figure = safe_import(pymod, "figure"); 223 | s_python_function_fignum_exists = safe_import(pymod, "fignum_exists"); 224 | s_python_function_plot = safe_import(pymod, "plot"); 225 | s_python_function_quiver = safe_import(pymod, "quiver"); 226 | s_python_function_contour = safe_import(pymod, "contour"); 227 | s_python_function_semilogx = safe_import(pymod, "semilogx"); 228 | s_python_function_semilogy = safe_import(pymod, "semilogy"); 229 | s_python_function_loglog = safe_import(pymod, "loglog"); 230 | s_python_function_fill = safe_import(pymod, "fill"); 231 | s_python_function_fill_between = safe_import(pymod, "fill_between"); 232 | s_python_function_hist = safe_import(pymod,"hist"); 233 | s_python_function_scatter = safe_import(pymod,"scatter"); 234 | s_python_function_boxplot = safe_import(pymod,"boxplot"); 235 | s_python_function_subplot = safe_import(pymod, "subplot"); 236 | s_python_function_subplot2grid = safe_import(pymod, "subplot2grid"); 237 | s_python_function_legend = safe_import(pymod, "legend"); 238 | s_python_function_ylim = safe_import(pymod, "ylim"); 239 | s_python_function_title = safe_import(pymod, "title"); 240 | s_python_function_axis = safe_import(pymod, "axis"); 241 | s_python_function_axvline = safe_import(pymod, "axvline"); 242 | s_python_function_axvspan = safe_import(pymod, "axvspan"); 243 | s_python_function_xlabel = safe_import(pymod, "xlabel"); 244 | s_python_function_ylabel = safe_import(pymod, "ylabel"); 245 | s_python_function_gca = safe_import(pymod, "gca"); 246 | s_python_function_xticks = safe_import(pymod, "xticks"); 247 | s_python_function_yticks = safe_import(pymod, "yticks"); 248 | s_python_function_margins = safe_import(pymod, "margins"); 249 | s_python_function_tick_params = safe_import(pymod, "tick_params"); 250 | s_python_function_grid = safe_import(pymod, "grid"); 251 | s_python_function_xlim = safe_import(pymod, "xlim"); 252 | s_python_function_ion = safe_import(pymod, "ion"); 253 | s_python_function_ginput = safe_import(pymod, "ginput"); 254 | s_python_function_save = safe_import(pylabmod, "savefig"); 255 | s_python_function_annotate = safe_import(pymod,"annotate"); 256 | s_python_function_cla = safe_import(pymod, "cla"); 257 | s_python_function_clf = safe_import(pymod, "clf"); 258 | s_python_function_errorbar = safe_import(pymod, "errorbar"); 259 | s_python_function_tight_layout = safe_import(pymod, "tight_layout"); 260 | s_python_function_stem = safe_import(pymod, "stem"); 261 | s_python_function_xkcd = safe_import(pymod, "xkcd"); 262 | s_python_function_text = safe_import(pymod, "text"); 263 | s_python_function_suptitle = safe_import(pymod, "suptitle"); 264 | s_python_function_bar = safe_import(pymod,"bar"); 265 | s_python_function_barh = safe_import(pymod, "barh"); 266 | s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); 267 | s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); 268 | #ifndef WITHOUT_NUMPY 269 | s_python_function_imshow = safe_import(pymod, "imshow"); 270 | #endif 271 | s_python_empty_tuple = PyTuple_New(0); 272 | } 273 | 274 | ~_interpreter() { 275 | Py_Finalize(); 276 | } 277 | }; 278 | 279 | } // end namespace detail 280 | 281 | /// Select the backend 282 | /// 283 | /// **NOTE:** This must be called before the first plot command to have 284 | /// any effect. 285 | /// 286 | /// Mainly useful to select the non-interactive 'Agg' backend when running 287 | /// matplotlibcpp in headless mode, for example on a machine with no display. 288 | /// 289 | /// See also: https://matplotlib.org/2.0.2/api/matplotlib_configuration_api.html#matplotlib.use 290 | inline void backend(const std::string& name) 291 | { 292 | detail::s_backend = name; 293 | } 294 | 295 | inline bool annotate(std::string annotation, double x, double y) 296 | { 297 | detail::_interpreter::get(); 298 | 299 | PyObject * xy = PyTuple_New(2); 300 | PyObject * str = PyString_FromString(annotation.c_str()); 301 | 302 | PyTuple_SetItem(xy,0,PyFloat_FromDouble(x)); 303 | PyTuple_SetItem(xy,1,PyFloat_FromDouble(y)); 304 | 305 | PyObject* kwargs = PyDict_New(); 306 | PyDict_SetItemString(kwargs, "xy", xy); 307 | 308 | PyObject* args = PyTuple_New(1); 309 | PyTuple_SetItem(args, 0, str); 310 | 311 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_annotate, args, kwargs); 312 | 313 | Py_DECREF(args); 314 | Py_DECREF(kwargs); 315 | 316 | if(res) Py_DECREF(res); 317 | 318 | return res; 319 | } 320 | 321 | namespace detail { 322 | 323 | #ifndef WITHOUT_NUMPY 324 | // Type selector for numpy array conversion 325 | template struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; //Default 326 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_DOUBLE; }; 327 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_FLOAT; }; 328 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_BOOL; }; 329 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT8; }; 330 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_SHORT; }; 331 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT; }; 332 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; 333 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT8; }; 334 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_USHORT; }; 335 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_ULONG; }; 336 | template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; 337 | 338 | // Sanity checks; comment them out or change the numpy type below if you're compiling on 339 | // a platform where they don't apply 340 | // static_assert(sizeof(long long) == 8); 341 | // template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; 342 | // static_assert(sizeof(unsigned long long) == 8); 343 | // template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; 344 | // TODO: add int, long, etc. 345 | 346 | template 347 | PyObject* get_array(const std::vector& v) 348 | { 349 | npy_intp vsize = v.size(); 350 | NPY_TYPES type = select_npy_type::type; 351 | if (type == NPY_NOTYPE) { 352 | size_t memsize = v.size()*sizeof(double); 353 | double* dp = static_cast(::malloc(memsize)); 354 | for (size_t i=0; i(varray), NPY_ARRAY_OWNDATA); 358 | return varray; 359 | } 360 | 361 | PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); 362 | return varray; 363 | } 364 | 365 | 366 | template 367 | PyObject* get_2darray(const std::vector<::std::vector>& v) 368 | { 369 | if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); 370 | 371 | npy_intp vsize[2] = {static_cast(v.size()), 372 | static_cast(v[0].size())}; 373 | 374 | PyArrayObject *varray = 375 | (PyArrayObject *)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); 376 | 377 | double *vd_begin = static_cast(PyArray_DATA(varray)); 378 | 379 | for (const ::std::vector &v_row : v) { 380 | if (v_row.size() != static_cast(vsize[1])) 381 | throw std::runtime_error("Missmatched array size"); 382 | std::copy(v_row.begin(), v_row.end(), vd_begin); 383 | vd_begin += vsize[1]; 384 | } 385 | 386 | return reinterpret_cast(varray); 387 | } 388 | 389 | #else // fallback if we don't have numpy: copy every element of the given vector 390 | 391 | template 392 | PyObject* get_array(const std::vector& v) 393 | { 394 | PyObject* list = PyList_New(v.size()); 395 | for(size_t i = 0; i < v.size(); ++i) { 396 | PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i))); 397 | } 398 | return list; 399 | } 400 | 401 | #endif // WITHOUT_NUMPY 402 | 403 | // sometimes, for labels and such, we need string arrays 404 | inline PyObject * get_array(const std::vector& strings) 405 | { 406 | PyObject* list = PyList_New(strings.size()); 407 | for (std::size_t i = 0; i < strings.size(); ++i) { 408 | PyList_SetItem(list, i, PyString_FromString(strings[i].c_str())); 409 | } 410 | return list; 411 | } 412 | 413 | // not all matplotlib need 2d arrays, some prefer lists of lists 414 | template 415 | PyObject* get_listlist(const std::vector>& ll) 416 | { 417 | PyObject* listlist = PyList_New(ll.size()); 418 | for (std::size_t i = 0; i < ll.size(); ++i) { 419 | PyList_SetItem(listlist, i, get_array(ll[i])); 420 | } 421 | return listlist; 422 | } 423 | 424 | } // namespace detail 425 | 426 | /// Plot a line through the given x and y data points.. 427 | /// 428 | /// See: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html 429 | template 430 | bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) 431 | { 432 | assert(x.size() == y.size()); 433 | 434 | detail::_interpreter::get(); 435 | 436 | // using numpy arrays 437 | PyObject* xarray = detail::get_array(x); 438 | PyObject* yarray = detail::get_array(y); 439 | 440 | // construct positional args 441 | PyObject* args = PyTuple_New(2); 442 | PyTuple_SetItem(args, 0, xarray); 443 | PyTuple_SetItem(args, 1, yarray); 444 | 445 | // construct keyword args 446 | PyObject* kwargs = PyDict_New(); 447 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 448 | { 449 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 450 | } 451 | 452 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); 453 | 454 | Py_DECREF(args); 455 | Py_DECREF(kwargs); 456 | if(res) Py_DECREF(res); 457 | 458 | return res; 459 | } 460 | 461 | // TODO - it should be possible to make this work by implementing 462 | // a non-numpy alternative for `detail::get_2darray()`. 463 | #ifndef WITHOUT_NUMPY 464 | template 465 | void plot_surface(const std::vector<::std::vector> &x, 466 | const std::vector<::std::vector> &y, 467 | const std::vector<::std::vector> &z, 468 | const std::map &keywords = 469 | std::map()) 470 | { 471 | detail::_interpreter::get(); 472 | 473 | // We lazily load the modules here the first time this function is called 474 | // because I'm not sure that we can assume "matplotlib installed" implies 475 | // "mpl_toolkits installed" on all platforms, and we don't want to require 476 | // it for people who don't need 3d plots. 477 | static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; 478 | if (!mpl_toolkitsmod) { 479 | detail::_interpreter::get(); 480 | 481 | PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); 482 | PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); 483 | if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } 484 | 485 | mpl_toolkitsmod = PyImport_Import(mpl_toolkits); 486 | Py_DECREF(mpl_toolkits); 487 | if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } 488 | 489 | axis3dmod = PyImport_Import(axis3d); 490 | Py_DECREF(axis3d); 491 | if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } 492 | } 493 | 494 | assert(x.size() == y.size()); 495 | assert(y.size() == z.size()); 496 | 497 | // using numpy arrays 498 | PyObject *xarray = detail::get_2darray(x); 499 | PyObject *yarray = detail::get_2darray(y); 500 | PyObject *zarray = detail::get_2darray(z); 501 | 502 | // construct positional args 503 | PyObject *args = PyTuple_New(3); 504 | PyTuple_SetItem(args, 0, xarray); 505 | PyTuple_SetItem(args, 1, yarray); 506 | PyTuple_SetItem(args, 2, zarray); 507 | 508 | // Build up the kw args. 509 | PyObject *kwargs = PyDict_New(); 510 | PyDict_SetItemString(kwargs, "rstride", PyInt_FromLong(1)); 511 | PyDict_SetItemString(kwargs, "cstride", PyInt_FromLong(1)); 512 | 513 | PyObject *python_colormap_coolwarm = PyObject_GetAttrString( 514 | detail::_interpreter::get().s_python_colormap, "coolwarm"); 515 | 516 | PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); 517 | 518 | for (std::map::const_iterator it = keywords.begin(); 519 | it != keywords.end(); ++it) { 520 | PyDict_SetItemString(kwargs, it->first.c_str(), 521 | PyString_FromString(it->second.c_str())); 522 | } 523 | 524 | 525 | PyObject *fig = 526 | PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, 527 | detail::_interpreter::get().s_python_empty_tuple); 528 | if (!fig) throw std::runtime_error("Call to figure() failed."); 529 | 530 | PyObject *gca_kwargs = PyDict_New(); 531 | PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); 532 | 533 | PyObject *gca = PyObject_GetAttrString(fig, "gca"); 534 | if (!gca) throw std::runtime_error("No gca"); 535 | Py_INCREF(gca); 536 | PyObject *axis = PyObject_Call( 537 | gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); 538 | 539 | if (!axis) throw std::runtime_error("No axis"); 540 | Py_INCREF(axis); 541 | 542 | Py_DECREF(gca); 543 | Py_DECREF(gca_kwargs); 544 | 545 | PyObject *plot_surface = PyObject_GetAttrString(axis, "plot_surface"); 546 | if (!plot_surface) throw std::runtime_error("No surface"); 547 | Py_INCREF(plot_surface); 548 | PyObject *res = PyObject_Call(plot_surface, args, kwargs); 549 | if (!res) throw std::runtime_error("failed surface"); 550 | Py_DECREF(plot_surface); 551 | 552 | Py_DECREF(axis); 553 | Py_DECREF(args); 554 | Py_DECREF(kwargs); 555 | if (res) Py_DECREF(res); 556 | } 557 | #endif // WITHOUT_NUMPY 558 | 559 | template 560 | void plot3(const std::vector &x, 561 | const std::vector &y, 562 | const std::vector &z, 563 | const std::map &keywords = 564 | std::map()) 565 | { 566 | detail::_interpreter::get(); 567 | 568 | // Same as with plot_surface: We lazily load the modules here the first time 569 | // this function is called because I'm not sure that we can assume "matplotlib 570 | // installed" implies "mpl_toolkits installed" on all platforms, and we don't 571 | // want to require it for people who don't need 3d plots. 572 | static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; 573 | if (!mpl_toolkitsmod) { 574 | detail::_interpreter::get(); 575 | 576 | PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); 577 | PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); 578 | if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } 579 | 580 | mpl_toolkitsmod = PyImport_Import(mpl_toolkits); 581 | Py_DECREF(mpl_toolkits); 582 | if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } 583 | 584 | axis3dmod = PyImport_Import(axis3d); 585 | Py_DECREF(axis3d); 586 | if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } 587 | } 588 | 589 | assert(x.size() == y.size()); 590 | assert(y.size() == z.size()); 591 | 592 | PyObject *xarray = detail::get_array(x); 593 | PyObject *yarray = detail::get_array(y); 594 | PyObject *zarray = detail::get_array(z); 595 | 596 | // construct positional args 597 | PyObject *args = PyTuple_New(3); 598 | PyTuple_SetItem(args, 0, xarray); 599 | PyTuple_SetItem(args, 1, yarray); 600 | PyTuple_SetItem(args, 2, zarray); 601 | 602 | // Build up the kw args. 603 | PyObject *kwargs = PyDict_New(); 604 | 605 | for (std::map::const_iterator it = keywords.begin(); 606 | it != keywords.end(); ++it) { 607 | PyDict_SetItemString(kwargs, it->first.c_str(), 608 | PyString_FromString(it->second.c_str())); 609 | } 610 | 611 | PyObject *fig = 612 | PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, 613 | detail::_interpreter::get().s_python_empty_tuple); 614 | if (!fig) throw std::runtime_error("Call to figure() failed."); 615 | 616 | PyObject *gca_kwargs = PyDict_New(); 617 | PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); 618 | 619 | PyObject *gca = PyObject_GetAttrString(fig, "gca"); 620 | if (!gca) throw std::runtime_error("No gca"); 621 | Py_INCREF(gca); 622 | PyObject *axis = PyObject_Call( 623 | gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); 624 | 625 | if (!axis) throw std::runtime_error("No axis"); 626 | Py_INCREF(axis); 627 | 628 | Py_DECREF(gca); 629 | Py_DECREF(gca_kwargs); 630 | 631 | PyObject *plot3 = PyObject_GetAttrString(axis, "plot"); 632 | if (!plot3) throw std::runtime_error("No 3D line plot"); 633 | Py_INCREF(plot3); 634 | PyObject *res = PyObject_Call(plot3, args, kwargs); 635 | if (!res) throw std::runtime_error("Failed 3D line plot"); 636 | Py_DECREF(plot3); 637 | 638 | Py_DECREF(axis); 639 | Py_DECREF(args); 640 | Py_DECREF(kwargs); 641 | if (res) Py_DECREF(res); 642 | } 643 | 644 | template 645 | bool stem(const std::vector &x, const std::vector &y, const std::map& keywords) 646 | { 647 | assert(x.size() == y.size()); 648 | 649 | detail::_interpreter::get(); 650 | 651 | // using numpy arrays 652 | PyObject* xarray = detail::get_array(x); 653 | PyObject* yarray = detail::get_array(y); 654 | 655 | // construct positional args 656 | PyObject* args = PyTuple_New(2); 657 | PyTuple_SetItem(args, 0, xarray); 658 | PyTuple_SetItem(args, 1, yarray); 659 | 660 | // construct keyword args 661 | PyObject* kwargs = PyDict_New(); 662 | for (std::map::const_iterator it = 663 | keywords.begin(); it != keywords.end(); ++it) { 664 | PyDict_SetItemString(kwargs, it->first.c_str(), 665 | PyString_FromString(it->second.c_str())); 666 | } 667 | 668 | PyObject* res = PyObject_Call( 669 | detail::_interpreter::get().s_python_function_stem, args, kwargs); 670 | 671 | Py_DECREF(args); 672 | Py_DECREF(kwargs); 673 | if (res) 674 | Py_DECREF(res); 675 | 676 | return res; 677 | } 678 | 679 | template< typename Numeric > 680 | bool fill(const std::vector& x, const std::vector& y, const std::map& keywords) 681 | { 682 | assert(x.size() == y.size()); 683 | 684 | detail::_interpreter::get(); 685 | 686 | // using numpy arrays 687 | PyObject* xarray = detail::get_array(x); 688 | PyObject* yarray = detail::get_array(y); 689 | 690 | // construct positional args 691 | PyObject* args = PyTuple_New(2); 692 | PyTuple_SetItem(args, 0, xarray); 693 | PyTuple_SetItem(args, 1, yarray); 694 | 695 | // construct keyword args 696 | PyObject* kwargs = PyDict_New(); 697 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 698 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 699 | } 700 | 701 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill, args, kwargs); 702 | 703 | Py_DECREF(args); 704 | Py_DECREF(kwargs); 705 | 706 | if (res) Py_DECREF(res); 707 | 708 | return res; 709 | } 710 | 711 | template< typename Numeric > 712 | bool fill_between(const std::vector& x, const std::vector& y1, const std::vector& y2, const std::map& keywords) 713 | { 714 | assert(x.size() == y1.size()); 715 | assert(x.size() == y2.size()); 716 | 717 | detail::_interpreter::get(); 718 | 719 | // using numpy arrays 720 | PyObject* xarray = detail::get_array(x); 721 | PyObject* y1array = detail::get_array(y1); 722 | PyObject* y2array = detail::get_array(y2); 723 | 724 | // construct positional args 725 | PyObject* args = PyTuple_New(3); 726 | PyTuple_SetItem(args, 0, xarray); 727 | PyTuple_SetItem(args, 1, y1array); 728 | PyTuple_SetItem(args, 2, y2array); 729 | 730 | // construct keyword args 731 | PyObject* kwargs = PyDict_New(); 732 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { 733 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 734 | } 735 | 736 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill_between, args, kwargs); 737 | 738 | Py_DECREF(args); 739 | Py_DECREF(kwargs); 740 | if(res) Py_DECREF(res); 741 | 742 | return res; 743 | } 744 | 745 | template 746 | bool arrow(Numeric x, Numeric y, Numeric end_x, Numeric end_y, const std::string& fc = "r", 747 | const std::string ec = "k", Numeric head_length = 0.25, Numeric head_width = 0.1625) { 748 | PyObject* obj_x = PyFloat_FromDouble(x); 749 | PyObject* obj_y = PyFloat_FromDouble(y); 750 | PyObject* obj_end_x = PyFloat_FromDouble(end_x); 751 | PyObject* obj_end_y = PyFloat_FromDouble(end_y); 752 | 753 | PyObject* kwargs = PyDict_New(); 754 | PyDict_SetItemString(kwargs, "fc", PyString_FromString(fc.c_str())); 755 | PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); 756 | PyDict_SetItemString(kwargs, "head_width", PyFloat_FromDouble(head_width)); 757 | PyDict_SetItemString(kwargs, "head_length", PyFloat_FromDouble(head_length)); 758 | 759 | PyObject* plot_args = PyTuple_New(4); 760 | PyTuple_SetItem(plot_args, 0, obj_x); 761 | PyTuple_SetItem(plot_args, 1, obj_y); 762 | PyTuple_SetItem(plot_args, 2, obj_end_x); 763 | PyTuple_SetItem(plot_args, 3, obj_end_y); 764 | 765 | PyObject* res = 766 | PyObject_Call(detail::_interpreter::get().s_python_function_arrow, plot_args, kwargs); 767 | 768 | Py_DECREF(plot_args); 769 | Py_DECREF(kwargs); 770 | if (res) 771 | Py_DECREF(res); 772 | 773 | return res; 774 | } 775 | 776 | template< typename Numeric> 777 | bool hist(const std::vector& y, long bins=10,std::string color="b", 778 | double alpha=1.0, bool cumulative=false) 779 | { 780 | detail::_interpreter::get(); 781 | 782 | PyObject* yarray = detail::get_array(y); 783 | 784 | PyObject* kwargs = PyDict_New(); 785 | PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); 786 | PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); 787 | PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); 788 | PyDict_SetItemString(kwargs, "cumulative", cumulative ? Py_True : Py_False); 789 | 790 | PyObject* plot_args = PyTuple_New(1); 791 | 792 | PyTuple_SetItem(plot_args, 0, yarray); 793 | 794 | 795 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); 796 | 797 | 798 | Py_DECREF(plot_args); 799 | Py_DECREF(kwargs); 800 | if(res) Py_DECREF(res); 801 | 802 | return res; 803 | } 804 | 805 | #ifndef WITHOUT_NUMPY 806 | namespace detail { 807 | 808 | inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map &keywords, PyObject** out) 809 | { 810 | assert(type == NPY_UINT8 || type == NPY_FLOAT); 811 | assert(colors == 1 || colors == 3 || colors == 4); 812 | 813 | detail::_interpreter::get(); 814 | 815 | // construct args 816 | npy_intp dims[3] = { rows, columns, colors }; 817 | PyObject *args = PyTuple_New(1); 818 | PyTuple_SetItem(args, 0, PyArray_SimpleNewFromData(colors == 1 ? 2 : 3, dims, type, ptr)); 819 | 820 | // construct keyword args 821 | PyObject* kwargs = PyDict_New(); 822 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 823 | { 824 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 825 | } 826 | 827 | PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_imshow, args, kwargs); 828 | Py_DECREF(args); 829 | Py_DECREF(kwargs); 830 | if (!res) 831 | throw std::runtime_error("Call to imshow() failed"); 832 | if (out) 833 | *out = res; 834 | else 835 | Py_DECREF(res); 836 | } 837 | 838 | } // namespace detail 839 | 840 | inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) 841 | { 842 | detail::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); 843 | } 844 | 845 | inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) 846 | { 847 | detail::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); 848 | } 849 | 850 | #ifdef WITH_OPENCV 851 | void imshow(const cv::Mat &image, const std::map &keywords = {}) 852 | { 853 | // Convert underlying type of matrix, if needed 854 | cv::Mat image2; 855 | NPY_TYPES npy_type = NPY_UINT8; 856 | switch (image.type() & CV_MAT_DEPTH_MASK) { 857 | case CV_8U: 858 | image2 = image; 859 | break; 860 | case CV_32F: 861 | image2 = image; 862 | npy_type = NPY_FLOAT; 863 | break; 864 | default: 865 | image.convertTo(image2, CV_MAKETYPE(CV_8U, image.channels())); 866 | } 867 | 868 | // If color image, convert from BGR to RGB 869 | switch (image2.channels()) { 870 | case 3: 871 | cv::cvtColor(image2, image2, CV_BGR2RGB); 872 | break; 873 | case 4: 874 | cv::cvtColor(image2, image2, CV_BGRA2RGBA); 875 | } 876 | 877 | detail::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); 878 | } 879 | #endif // WITH_OPENCV 880 | #endif // WITHOUT_NUMPY 881 | 882 | template 883 | bool scatter(const std::vector& x, 884 | const std::vector& y, 885 | const double s=1.0, // The marker size in points**2 886 | const std::map & keywords = {}) 887 | { 888 | detail::_interpreter::get(); 889 | 890 | assert(x.size() == y.size()); 891 | 892 | PyObject* xarray = detail::get_array(x); 893 | PyObject* yarray = detail::get_array(y); 894 | 895 | PyObject* kwargs = PyDict_New(); 896 | PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); 897 | for (const auto& it : keywords) 898 | { 899 | PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); 900 | } 901 | 902 | PyObject* plot_args = PyTuple_New(2); 903 | PyTuple_SetItem(plot_args, 0, xarray); 904 | PyTuple_SetItem(plot_args, 1, yarray); 905 | 906 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); 907 | 908 | Py_DECREF(plot_args); 909 | Py_DECREF(kwargs); 910 | if(res) Py_DECREF(res); 911 | 912 | return res; 913 | } 914 | 915 | template 916 | bool boxplot(const std::vector>& data, 917 | const std::vector& labels = {}, 918 | const std::map & keywords = {}) 919 | { 920 | detail::_interpreter::get(); 921 | 922 | PyObject* listlist = detail::get_listlist(data); 923 | PyObject* args = PyTuple_New(1); 924 | PyTuple_SetItem(args, 0, listlist); 925 | 926 | PyObject* kwargs = PyDict_New(); 927 | 928 | // kwargs needs the labels, if there are (the correct number of) labels 929 | if (!labels.empty() && labels.size() == data.size()) { 930 | PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); 931 | } 932 | 933 | // take care of the remaining keywords 934 | for (const auto& it : keywords) 935 | { 936 | PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); 937 | } 938 | 939 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); 940 | 941 | Py_DECREF(args); 942 | Py_DECREF(kwargs); 943 | 944 | if(res) Py_DECREF(res); 945 | 946 | return res; 947 | } 948 | 949 | template 950 | bool boxplot(const std::vector& data, 951 | const std::map & keywords = {}) 952 | { 953 | detail::_interpreter::get(); 954 | 955 | PyObject* vector = detail::get_array(data); 956 | PyObject* args = PyTuple_New(1); 957 | PyTuple_SetItem(args, 0, vector); 958 | 959 | PyObject* kwargs = PyDict_New(); 960 | for (const auto& it : keywords) 961 | { 962 | PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); 963 | } 964 | 965 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); 966 | 967 | Py_DECREF(args); 968 | Py_DECREF(kwargs); 969 | 970 | if(res) Py_DECREF(res); 971 | 972 | return res; 973 | } 974 | 975 | template 976 | bool bar(const std::vector & x, 977 | const std::vector & y, 978 | std::string ec = "black", 979 | std::string ls = "-", 980 | double lw = 1.0, 981 | const std::map & keywords = {}) 982 | { 983 | detail::_interpreter::get(); 984 | 985 | PyObject * xarray = detail::get_array(x); 986 | PyObject * yarray = detail::get_array(y); 987 | 988 | PyObject * kwargs = PyDict_New(); 989 | 990 | PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); 991 | PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); 992 | PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); 993 | 994 | for (std::map::const_iterator it = 995 | keywords.begin(); 996 | it != keywords.end(); 997 | ++it) { 998 | PyDict_SetItemString( 999 | kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1000 | } 1001 | 1002 | PyObject * plot_args = PyTuple_New(2); 1003 | PyTuple_SetItem(plot_args, 0, xarray); 1004 | PyTuple_SetItem(plot_args, 1, yarray); 1005 | 1006 | PyObject * res = PyObject_Call( 1007 | detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); 1008 | 1009 | Py_DECREF(plot_args); 1010 | Py_DECREF(kwargs); 1011 | if (res) Py_DECREF(res); 1012 | 1013 | return res; 1014 | } 1015 | 1016 | template 1017 | bool bar(const std::vector & y, 1018 | std::string ec = "black", 1019 | std::string ls = "-", 1020 | double lw = 1.0, 1021 | const std::map & keywords = {}) 1022 | { 1023 | using T = typename std::remove_reference::type::value_type; 1024 | 1025 | detail::_interpreter::get(); 1026 | 1027 | std::vector x; 1028 | for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } 1029 | 1030 | return bar(x, y, ec, ls, lw, keywords); 1031 | } 1032 | 1033 | 1034 | template 1035 | bool barh(const std::vector &x, const std::vector &y, std::string ec = "black", std::string ls = "-", double lw = 1.0, const std::map &keywords = { }) { 1036 | PyObject *xarray = detail::get_array(x); 1037 | PyObject *yarray = detail::get_array(y); 1038 | 1039 | PyObject *kwargs = PyDict_New(); 1040 | 1041 | PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); 1042 | PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); 1043 | PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); 1044 | 1045 | for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { 1046 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1047 | } 1048 | 1049 | PyObject *plot_args = PyTuple_New(2); 1050 | PyTuple_SetItem(plot_args, 0, xarray); 1051 | PyTuple_SetItem(plot_args, 1, yarray); 1052 | 1053 | PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_barh, plot_args, kwargs); 1054 | 1055 | Py_DECREF(plot_args); 1056 | Py_DECREF(kwargs); 1057 | if (res) Py_DECREF(res); 1058 | 1059 | return res; 1060 | } 1061 | 1062 | 1063 | inline bool subplots_adjust(const std::map& keywords = {}) 1064 | { 1065 | detail::_interpreter::get(); 1066 | 1067 | PyObject* kwargs = PyDict_New(); 1068 | for (std::map::const_iterator it = 1069 | keywords.begin(); it != keywords.end(); ++it) { 1070 | PyDict_SetItemString(kwargs, it->first.c_str(), 1071 | PyFloat_FromDouble(it->second)); 1072 | } 1073 | 1074 | 1075 | PyObject* plot_args = PyTuple_New(0); 1076 | 1077 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_subplots_adjust, plot_args, kwargs); 1078 | 1079 | Py_DECREF(plot_args); 1080 | Py_DECREF(kwargs); 1081 | if(res) Py_DECREF(res); 1082 | 1083 | return res; 1084 | } 1085 | 1086 | template< typename Numeric> 1087 | bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) 1088 | { 1089 | detail::_interpreter::get(); 1090 | 1091 | PyObject* yarray = detail::get_array(y); 1092 | 1093 | PyObject* kwargs = PyDict_New(); 1094 | PyDict_SetItemString(kwargs, "label", PyString_FromString(label.c_str())); 1095 | PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); 1096 | PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); 1097 | PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); 1098 | 1099 | 1100 | PyObject* plot_args = PyTuple_New(1); 1101 | PyTuple_SetItem(plot_args, 0, yarray); 1102 | 1103 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); 1104 | 1105 | Py_DECREF(plot_args); 1106 | Py_DECREF(kwargs); 1107 | if(res) Py_DECREF(res); 1108 | 1109 | return res; 1110 | } 1111 | 1112 | template 1113 | bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") 1114 | { 1115 | assert(x.size() == y.size()); 1116 | 1117 | detail::_interpreter::get(); 1118 | 1119 | PyObject* xarray = detail::get_array(x); 1120 | PyObject* yarray = detail::get_array(y); 1121 | 1122 | PyObject* pystring = PyString_FromString(s.c_str()); 1123 | 1124 | PyObject* plot_args = PyTuple_New(3); 1125 | PyTuple_SetItem(plot_args, 0, xarray); 1126 | PyTuple_SetItem(plot_args, 1, yarray); 1127 | PyTuple_SetItem(plot_args, 2, pystring); 1128 | 1129 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); 1130 | 1131 | Py_DECREF(plot_args); 1132 | if(res) Py_DECREF(res); 1133 | 1134 | return res; 1135 | } 1136 | 1137 | template 1138 | bool contour(const std::vector& x, const std::vector& y, 1139 | const std::vector& z, 1140 | const std::map& keywords = {}) { 1141 | assert(x.size() == y.size() && x.size() == z.size()); 1142 | 1143 | PyObject* xarray = get_array(x); 1144 | PyObject* yarray = get_array(y); 1145 | PyObject* zarray = get_array(z); 1146 | 1147 | PyObject* plot_args = PyTuple_New(3); 1148 | PyTuple_SetItem(plot_args, 0, xarray); 1149 | PyTuple_SetItem(plot_args, 1, yarray); 1150 | PyTuple_SetItem(plot_args, 2, zarray); 1151 | 1152 | // construct keyword args 1153 | PyObject* kwargs = PyDict_New(); 1154 | for (std::map::const_iterator it = keywords.begin(); 1155 | it != keywords.end(); ++it) { 1156 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1157 | } 1158 | 1159 | PyObject* res = 1160 | PyObject_Call(detail::_interpreter::get().s_python_function_contour, plot_args, kwargs); 1161 | 1162 | Py_DECREF(kwargs); 1163 | Py_DECREF(plot_args); 1164 | if (res) 1165 | Py_DECREF(res); 1166 | 1167 | return res; 1168 | } 1169 | 1170 | template 1171 | bool quiver(const std::vector& x, const std::vector& y, const std::vector& u, const std::vector& w, const std::map& keywords = {}) 1172 | { 1173 | assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); 1174 | 1175 | detail::_interpreter::get(); 1176 | 1177 | PyObject* xarray = detail::get_array(x); 1178 | PyObject* yarray = detail::get_array(y); 1179 | PyObject* uarray = detail::get_array(u); 1180 | PyObject* warray = detail::get_array(w); 1181 | 1182 | PyObject* plot_args = PyTuple_New(4); 1183 | PyTuple_SetItem(plot_args, 0, xarray); 1184 | PyTuple_SetItem(plot_args, 1, yarray); 1185 | PyTuple_SetItem(plot_args, 2, uarray); 1186 | PyTuple_SetItem(plot_args, 3, warray); 1187 | 1188 | // construct keyword args 1189 | PyObject* kwargs = PyDict_New(); 1190 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1191 | { 1192 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1193 | } 1194 | 1195 | PyObject* res = PyObject_Call( 1196 | detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); 1197 | 1198 | Py_DECREF(kwargs); 1199 | Py_DECREF(plot_args); 1200 | if (res) 1201 | Py_DECREF(res); 1202 | 1203 | return res; 1204 | } 1205 | 1206 | template 1207 | bool stem(const std::vector& x, const std::vector& y, const std::string& s = "") 1208 | { 1209 | assert(x.size() == y.size()); 1210 | 1211 | detail::_interpreter::get(); 1212 | 1213 | PyObject* xarray = detail::get_array(x); 1214 | PyObject* yarray = detail::get_array(y); 1215 | 1216 | PyObject* pystring = PyString_FromString(s.c_str()); 1217 | 1218 | PyObject* plot_args = PyTuple_New(3); 1219 | PyTuple_SetItem(plot_args, 0, xarray); 1220 | PyTuple_SetItem(plot_args, 1, yarray); 1221 | PyTuple_SetItem(plot_args, 2, pystring); 1222 | 1223 | PyObject* res = PyObject_CallObject( 1224 | detail::_interpreter::get().s_python_function_stem, plot_args); 1225 | 1226 | Py_DECREF(plot_args); 1227 | if (res) 1228 | Py_DECREF(res); 1229 | 1230 | return res; 1231 | } 1232 | 1233 | template 1234 | bool semilogx(const std::vector& x, const std::vector& y, const std::string& s = "") 1235 | { 1236 | assert(x.size() == y.size()); 1237 | 1238 | detail::_interpreter::get(); 1239 | 1240 | PyObject* xarray = detail::get_array(x); 1241 | PyObject* yarray = detail::get_array(y); 1242 | 1243 | PyObject* pystring = PyString_FromString(s.c_str()); 1244 | 1245 | PyObject* plot_args = PyTuple_New(3); 1246 | PyTuple_SetItem(plot_args, 0, xarray); 1247 | PyTuple_SetItem(plot_args, 1, yarray); 1248 | PyTuple_SetItem(plot_args, 2, pystring); 1249 | 1250 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogx, plot_args); 1251 | 1252 | Py_DECREF(plot_args); 1253 | if(res) Py_DECREF(res); 1254 | 1255 | return res; 1256 | } 1257 | 1258 | template 1259 | bool semilogy(const std::vector& x, const std::vector& y, const std::string& s = "") 1260 | { 1261 | assert(x.size() == y.size()); 1262 | 1263 | detail::_interpreter::get(); 1264 | 1265 | PyObject* xarray = detail::get_array(x); 1266 | PyObject* yarray = detail::get_array(y); 1267 | 1268 | PyObject* pystring = PyString_FromString(s.c_str()); 1269 | 1270 | PyObject* plot_args = PyTuple_New(3); 1271 | PyTuple_SetItem(plot_args, 0, xarray); 1272 | PyTuple_SetItem(plot_args, 1, yarray); 1273 | PyTuple_SetItem(plot_args, 2, pystring); 1274 | 1275 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogy, plot_args); 1276 | 1277 | Py_DECREF(plot_args); 1278 | if(res) Py_DECREF(res); 1279 | 1280 | return res; 1281 | } 1282 | 1283 | template 1284 | bool loglog(const std::vector& x, const std::vector& y, const std::string& s = "") 1285 | { 1286 | assert(x.size() == y.size()); 1287 | 1288 | detail::_interpreter::get(); 1289 | 1290 | PyObject* xarray = detail::get_array(x); 1291 | PyObject* yarray = detail::get_array(y); 1292 | 1293 | PyObject* pystring = PyString_FromString(s.c_str()); 1294 | 1295 | PyObject* plot_args = PyTuple_New(3); 1296 | PyTuple_SetItem(plot_args, 0, xarray); 1297 | PyTuple_SetItem(plot_args, 1, yarray); 1298 | PyTuple_SetItem(plot_args, 2, pystring); 1299 | 1300 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_loglog, plot_args); 1301 | 1302 | Py_DECREF(plot_args); 1303 | if(res) Py_DECREF(res); 1304 | 1305 | return res; 1306 | } 1307 | 1308 | template 1309 | bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::map &keywords = {}) 1310 | { 1311 | assert(x.size() == y.size()); 1312 | 1313 | detail::_interpreter::get(); 1314 | 1315 | PyObject* xarray = detail::get_array(x); 1316 | PyObject* yarray = detail::get_array(y); 1317 | PyObject* yerrarray = detail::get_array(yerr); 1318 | 1319 | // construct keyword args 1320 | PyObject* kwargs = PyDict_New(); 1321 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1322 | { 1323 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1324 | } 1325 | 1326 | PyDict_SetItemString(kwargs, "yerr", yerrarray); 1327 | 1328 | PyObject *plot_args = PyTuple_New(2); 1329 | PyTuple_SetItem(plot_args, 0, xarray); 1330 | PyTuple_SetItem(plot_args, 1, yarray); 1331 | 1332 | PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_errorbar, plot_args, kwargs); 1333 | 1334 | Py_DECREF(kwargs); 1335 | Py_DECREF(plot_args); 1336 | 1337 | if (res) 1338 | Py_DECREF(res); 1339 | else 1340 | throw std::runtime_error("Call to errorbar() failed."); 1341 | 1342 | return res; 1343 | } 1344 | 1345 | template 1346 | bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") 1347 | { 1348 | detail::_interpreter::get(); 1349 | 1350 | PyObject* kwargs = PyDict_New(); 1351 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 1352 | 1353 | PyObject* yarray = detail::get_array(y); 1354 | 1355 | PyObject* pystring = PyString_FromString(format.c_str()); 1356 | 1357 | PyObject* plot_args = PyTuple_New(2); 1358 | 1359 | PyTuple_SetItem(plot_args, 0, yarray); 1360 | PyTuple_SetItem(plot_args, 1, pystring); 1361 | 1362 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); 1363 | 1364 | Py_DECREF(kwargs); 1365 | Py_DECREF(plot_args); 1366 | if (res) Py_DECREF(res); 1367 | 1368 | return res; 1369 | } 1370 | 1371 | template 1372 | bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") 1373 | { 1374 | detail::_interpreter::get(); 1375 | 1376 | PyObject* kwargs = PyDict_New(); 1377 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 1378 | 1379 | PyObject* xarray = detail::get_array(x); 1380 | PyObject* yarray = detail::get_array(y); 1381 | 1382 | PyObject* pystring = PyString_FromString(format.c_str()); 1383 | 1384 | PyObject* plot_args = PyTuple_New(3); 1385 | PyTuple_SetItem(plot_args, 0, xarray); 1386 | PyTuple_SetItem(plot_args, 1, yarray); 1387 | PyTuple_SetItem(plot_args, 2, pystring); 1388 | 1389 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); 1390 | 1391 | Py_DECREF(kwargs); 1392 | Py_DECREF(plot_args); 1393 | if (res) Py_DECREF(res); 1394 | 1395 | return res; 1396 | } 1397 | 1398 | template 1399 | bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") 1400 | { 1401 | detail::_interpreter::get(); 1402 | 1403 | PyObject* kwargs = PyDict_New(); 1404 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 1405 | 1406 | PyObject* xarray = detail::get_array(x); 1407 | PyObject* yarray = detail::get_array(y); 1408 | 1409 | PyObject* pystring = PyString_FromString(format.c_str()); 1410 | 1411 | PyObject* plot_args = PyTuple_New(3); 1412 | PyTuple_SetItem(plot_args, 0, xarray); 1413 | PyTuple_SetItem(plot_args, 1, yarray); 1414 | PyTuple_SetItem(plot_args, 2, pystring); 1415 | 1416 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogx, plot_args, kwargs); 1417 | 1418 | Py_DECREF(kwargs); 1419 | Py_DECREF(plot_args); 1420 | if (res) Py_DECREF(res); 1421 | 1422 | return res; 1423 | } 1424 | 1425 | template 1426 | bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") 1427 | { 1428 | detail::_interpreter::get(); 1429 | 1430 | PyObject* kwargs = PyDict_New(); 1431 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 1432 | 1433 | PyObject* xarray = detail::get_array(x); 1434 | PyObject* yarray = detail::get_array(y); 1435 | 1436 | PyObject* pystring = PyString_FromString(format.c_str()); 1437 | 1438 | PyObject* plot_args = PyTuple_New(3); 1439 | PyTuple_SetItem(plot_args, 0, xarray); 1440 | PyTuple_SetItem(plot_args, 1, yarray); 1441 | PyTuple_SetItem(plot_args, 2, pystring); 1442 | 1443 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogy, plot_args, kwargs); 1444 | 1445 | Py_DECREF(kwargs); 1446 | Py_DECREF(plot_args); 1447 | if (res) Py_DECREF(res); 1448 | 1449 | return res; 1450 | } 1451 | 1452 | template 1453 | bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") 1454 | { 1455 | detail::_interpreter::get(); 1456 | 1457 | PyObject* kwargs = PyDict_New(); 1458 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 1459 | 1460 | PyObject* xarray = detail::get_array(x); 1461 | PyObject* yarray = detail::get_array(y); 1462 | 1463 | PyObject* pystring = PyString_FromString(format.c_str()); 1464 | 1465 | PyObject* plot_args = PyTuple_New(3); 1466 | PyTuple_SetItem(plot_args, 0, xarray); 1467 | PyTuple_SetItem(plot_args, 1, yarray); 1468 | PyTuple_SetItem(plot_args, 2, pystring); 1469 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_loglog, plot_args, kwargs); 1470 | 1471 | Py_DECREF(kwargs); 1472 | Py_DECREF(plot_args); 1473 | if (res) Py_DECREF(res); 1474 | 1475 | return res; 1476 | } 1477 | 1478 | template 1479 | bool plot(const std::vector& y, const std::string& format = "") 1480 | { 1481 | std::vector x(y.size()); 1482 | for(size_t i=0; i 1487 | bool plot(const std::vector& y, const std::map& keywords) 1488 | { 1489 | std::vector x(y.size()); 1490 | for(size_t i=0; i 1495 | bool stem(const std::vector& y, const std::string& format = "") 1496 | { 1497 | std::vector x(y.size()); 1498 | for (size_t i = 0; i < x.size(); ++i) x.at(i) = i; 1499 | return stem(x, y, format); 1500 | } 1501 | 1502 | template 1503 | void text(Numeric x, Numeric y, const std::string& s = "") 1504 | { 1505 | detail::_interpreter::get(); 1506 | 1507 | PyObject* args = PyTuple_New(3); 1508 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); 1509 | PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); 1510 | PyTuple_SetItem(args, 2, PyString_FromString(s.c_str())); 1511 | 1512 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); 1513 | if(!res) throw std::runtime_error("Call to text() failed."); 1514 | 1515 | Py_DECREF(args); 1516 | Py_DECREF(res); 1517 | } 1518 | 1519 | inline void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) 1520 | { 1521 | if (mappable == NULL) 1522 | throw std::runtime_error("Must call colorbar with PyObject* returned from an image, contour, surface, etc."); 1523 | 1524 | detail::_interpreter::get(); 1525 | 1526 | PyObject* args = PyTuple_New(1); 1527 | PyTuple_SetItem(args, 0, mappable); 1528 | 1529 | PyObject* kwargs = PyDict_New(); 1530 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1531 | { 1532 | PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(it->second)); 1533 | } 1534 | 1535 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_colorbar, args, kwargs); 1536 | if(!res) throw std::runtime_error("Call to colorbar() failed."); 1537 | 1538 | Py_DECREF(args); 1539 | Py_DECREF(kwargs); 1540 | Py_DECREF(res); 1541 | } 1542 | 1543 | 1544 | inline long figure(long number = -1) 1545 | { 1546 | detail::_interpreter::get(); 1547 | 1548 | PyObject *res; 1549 | if (number == -1) 1550 | res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); 1551 | else { 1552 | assert(number > 0); 1553 | 1554 | // Make sure interpreter is initialised 1555 | detail::_interpreter::get(); 1556 | 1557 | PyObject *args = PyTuple_New(1); 1558 | PyTuple_SetItem(args, 0, PyLong_FromLong(number)); 1559 | res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, args); 1560 | Py_DECREF(args); 1561 | } 1562 | 1563 | if(!res) throw std::runtime_error("Call to figure() failed."); 1564 | 1565 | PyObject* num = PyObject_GetAttrString(res, "number"); 1566 | if (!num) throw std::runtime_error("Could not get number attribute of figure object"); 1567 | const long figureNumber = PyLong_AsLong(num); 1568 | 1569 | Py_DECREF(num); 1570 | Py_DECREF(res); 1571 | 1572 | return figureNumber; 1573 | } 1574 | 1575 | inline bool fignum_exists(long number) 1576 | { 1577 | detail::_interpreter::get(); 1578 | 1579 | PyObject *args = PyTuple_New(1); 1580 | PyTuple_SetItem(args, 0, PyLong_FromLong(number)); 1581 | PyObject *res = PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, args); 1582 | if(!res) throw std::runtime_error("Call to fignum_exists() failed."); 1583 | 1584 | bool ret = PyObject_IsTrue(res); 1585 | Py_DECREF(res); 1586 | Py_DECREF(args); 1587 | 1588 | return ret; 1589 | } 1590 | 1591 | inline void figure_size(size_t w, size_t h) 1592 | { 1593 | detail::_interpreter::get(); 1594 | 1595 | const size_t dpi = 100; 1596 | PyObject* size = PyTuple_New(2); 1597 | PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); 1598 | PyTuple_SetItem(size, 1, PyFloat_FromDouble((double)h / dpi)); 1599 | 1600 | PyObject* kwargs = PyDict_New(); 1601 | PyDict_SetItemString(kwargs, "figsize", size); 1602 | PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); 1603 | 1604 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, 1605 | detail::_interpreter::get().s_python_empty_tuple, kwargs); 1606 | 1607 | Py_DECREF(kwargs); 1608 | 1609 | if(!res) throw std::runtime_error("Call to figure_size() failed."); 1610 | Py_DECREF(res); 1611 | } 1612 | 1613 | inline void legend() 1614 | { 1615 | detail::_interpreter::get(); 1616 | 1617 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple); 1618 | if(!res) throw std::runtime_error("Call to legend() failed."); 1619 | 1620 | Py_DECREF(res); 1621 | } 1622 | 1623 | inline void legend(const std::map& keywords) 1624 | { 1625 | detail::_interpreter::get(); 1626 | 1627 | // construct keyword args 1628 | PyObject* kwargs = PyDict_New(); 1629 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1630 | { 1631 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1632 | } 1633 | 1634 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple, kwargs); 1635 | if(!res) throw std::runtime_error("Call to legend() failed."); 1636 | 1637 | Py_DECREF(kwargs); 1638 | Py_DECREF(res); 1639 | } 1640 | 1641 | template 1642 | void ylim(Numeric left, Numeric right) 1643 | { 1644 | detail::_interpreter::get(); 1645 | 1646 | PyObject* list = PyList_New(2); 1647 | PyList_SetItem(list, 0, PyFloat_FromDouble(left)); 1648 | PyList_SetItem(list, 1, PyFloat_FromDouble(right)); 1649 | 1650 | PyObject* args = PyTuple_New(1); 1651 | PyTuple_SetItem(args, 0, list); 1652 | 1653 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); 1654 | if(!res) throw std::runtime_error("Call to ylim() failed."); 1655 | 1656 | Py_DECREF(args); 1657 | Py_DECREF(res); 1658 | } 1659 | 1660 | template 1661 | void xlim(Numeric left, Numeric right) 1662 | { 1663 | detail::_interpreter::get(); 1664 | 1665 | PyObject* list = PyList_New(2); 1666 | PyList_SetItem(list, 0, PyFloat_FromDouble(left)); 1667 | PyList_SetItem(list, 1, PyFloat_FromDouble(right)); 1668 | 1669 | PyObject* args = PyTuple_New(1); 1670 | PyTuple_SetItem(args, 0, list); 1671 | 1672 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); 1673 | if(!res) throw std::runtime_error("Call to xlim() failed."); 1674 | 1675 | Py_DECREF(args); 1676 | Py_DECREF(res); 1677 | } 1678 | 1679 | 1680 | inline double* xlim() 1681 | { 1682 | detail::_interpreter::get(); 1683 | 1684 | PyObject* args = PyTuple_New(0); 1685 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); 1686 | PyObject* left = PyTuple_GetItem(res,0); 1687 | PyObject* right = PyTuple_GetItem(res,1); 1688 | 1689 | double* arr = new double[2]; 1690 | arr[0] = PyFloat_AsDouble(left); 1691 | arr[1] = PyFloat_AsDouble(right); 1692 | 1693 | if(!res) throw std::runtime_error("Call to xlim() failed."); 1694 | 1695 | Py_DECREF(res); 1696 | return arr; 1697 | } 1698 | 1699 | 1700 | inline double* ylim() 1701 | { 1702 | detail::_interpreter::get(); 1703 | 1704 | PyObject* args = PyTuple_New(0); 1705 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); 1706 | PyObject* left = PyTuple_GetItem(res,0); 1707 | PyObject* right = PyTuple_GetItem(res,1); 1708 | 1709 | double* arr = new double[2]; 1710 | arr[0] = PyFloat_AsDouble(left); 1711 | arr[1] = PyFloat_AsDouble(right); 1712 | 1713 | if(!res) throw std::runtime_error("Call to ylim() failed."); 1714 | 1715 | Py_DECREF(res); 1716 | return arr; 1717 | } 1718 | 1719 | template 1720 | inline void xticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) 1721 | { 1722 | assert(labels.size() == 0 || ticks.size() == labels.size()); 1723 | 1724 | detail::_interpreter::get(); 1725 | 1726 | // using numpy array 1727 | PyObject* ticksarray = detail::get_array(ticks); 1728 | 1729 | PyObject* args; 1730 | if(labels.size() == 0) { 1731 | // construct positional args 1732 | args = PyTuple_New(1); 1733 | PyTuple_SetItem(args, 0, ticksarray); 1734 | } else { 1735 | // make tuple of tick labels 1736 | PyObject* labelstuple = PyTuple_New(labels.size()); 1737 | for (size_t i = 0; i < labels.size(); i++) 1738 | PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); 1739 | 1740 | // construct positional args 1741 | args = PyTuple_New(2); 1742 | PyTuple_SetItem(args, 0, ticksarray); 1743 | PyTuple_SetItem(args, 1, labelstuple); 1744 | } 1745 | 1746 | // construct keyword args 1747 | PyObject* kwargs = PyDict_New(); 1748 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1749 | { 1750 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1751 | } 1752 | 1753 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xticks, args, kwargs); 1754 | 1755 | Py_DECREF(args); 1756 | Py_DECREF(kwargs); 1757 | if(!res) throw std::runtime_error("Call to xticks() failed"); 1758 | 1759 | Py_DECREF(res); 1760 | } 1761 | 1762 | template 1763 | inline void xticks(const std::vector &ticks, const std::map& keywords) 1764 | { 1765 | xticks(ticks, {}, keywords); 1766 | } 1767 | 1768 | template 1769 | inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) 1770 | { 1771 | assert(labels.size() == 0 || ticks.size() == labels.size()); 1772 | 1773 | detail::_interpreter::get(); 1774 | 1775 | // using numpy array 1776 | PyObject* ticksarray = detail::get_array(ticks); 1777 | 1778 | PyObject* args; 1779 | if(labels.size() == 0) { 1780 | // construct positional args 1781 | args = PyTuple_New(1); 1782 | PyTuple_SetItem(args, 0, ticksarray); 1783 | } else { 1784 | // make tuple of tick labels 1785 | PyObject* labelstuple = PyTuple_New(labels.size()); 1786 | for (size_t i = 0; i < labels.size(); i++) 1787 | PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); 1788 | 1789 | // construct positional args 1790 | args = PyTuple_New(2); 1791 | PyTuple_SetItem(args, 0, ticksarray); 1792 | PyTuple_SetItem(args, 1, labelstuple); 1793 | } 1794 | 1795 | // construct keyword args 1796 | PyObject* kwargs = PyDict_New(); 1797 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1798 | { 1799 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1800 | } 1801 | 1802 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_yticks, args, kwargs); 1803 | 1804 | Py_DECREF(args); 1805 | Py_DECREF(kwargs); 1806 | if(!res) throw std::runtime_error("Call to yticks() failed"); 1807 | 1808 | Py_DECREF(res); 1809 | } 1810 | 1811 | template 1812 | inline void yticks(const std::vector &ticks, const std::map& keywords) 1813 | { 1814 | yticks(ticks, {}, keywords); 1815 | } 1816 | 1817 | template inline void margins(Numeric margin) 1818 | { 1819 | // construct positional args 1820 | PyObject* args = PyTuple_New(1); 1821 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); 1822 | 1823 | PyObject* res = 1824 | PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); 1825 | if (!res) 1826 | throw std::runtime_error("Call to margins() failed."); 1827 | 1828 | Py_DECREF(args); 1829 | Py_DECREF(res); 1830 | } 1831 | 1832 | template inline void margins(Numeric margin_x, Numeric margin_y) 1833 | { 1834 | // construct positional args 1835 | PyObject* args = PyTuple_New(2); 1836 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin_x)); 1837 | PyTuple_SetItem(args, 1, PyFloat_FromDouble(margin_y)); 1838 | 1839 | PyObject* res = 1840 | PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); 1841 | if (!res) 1842 | throw std::runtime_error("Call to margins() failed."); 1843 | 1844 | Py_DECREF(args); 1845 | Py_DECREF(res); 1846 | } 1847 | 1848 | 1849 | inline void tick_params(const std::map& keywords, const std::string axis = "both") 1850 | { 1851 | detail::_interpreter::get(); 1852 | 1853 | // construct positional args 1854 | PyObject* args; 1855 | args = PyTuple_New(1); 1856 | PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); 1857 | 1858 | // construct keyword args 1859 | PyObject* kwargs = PyDict_New(); 1860 | for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1861 | { 1862 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1863 | } 1864 | 1865 | 1866 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_tick_params, args, kwargs); 1867 | 1868 | Py_DECREF(args); 1869 | Py_DECREF(kwargs); 1870 | if (!res) throw std::runtime_error("Call to tick_params() failed"); 1871 | 1872 | Py_DECREF(res); 1873 | } 1874 | 1875 | inline void subplot(long nrows, long ncols, long plot_number) 1876 | { 1877 | detail::_interpreter::get(); 1878 | 1879 | // construct positional args 1880 | PyObject* args = PyTuple_New(3); 1881 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); 1882 | PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); 1883 | PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); 1884 | 1885 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot, args); 1886 | if(!res) throw std::runtime_error("Call to subplot() failed."); 1887 | 1888 | Py_DECREF(args); 1889 | Py_DECREF(res); 1890 | } 1891 | 1892 | inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) 1893 | { 1894 | detail::_interpreter::get(); 1895 | 1896 | PyObject* shape = PyTuple_New(2); 1897 | PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); 1898 | PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); 1899 | 1900 | PyObject* loc = PyTuple_New(2); 1901 | PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); 1902 | PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); 1903 | 1904 | PyObject* args = PyTuple_New(4); 1905 | PyTuple_SetItem(args, 0, shape); 1906 | PyTuple_SetItem(args, 1, loc); 1907 | PyTuple_SetItem(args, 2, PyLong_FromLong(rowspan)); 1908 | PyTuple_SetItem(args, 3, PyLong_FromLong(colspan)); 1909 | 1910 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot2grid, args); 1911 | if(!res) throw std::runtime_error("Call to subplot2grid() failed."); 1912 | 1913 | Py_DECREF(shape); 1914 | Py_DECREF(loc); 1915 | Py_DECREF(args); 1916 | Py_DECREF(res); 1917 | } 1918 | 1919 | inline void title(const std::string &titlestr, const std::map &keywords = {}) 1920 | { 1921 | detail::_interpreter::get(); 1922 | 1923 | PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); 1924 | PyObject* args = PyTuple_New(1); 1925 | PyTuple_SetItem(args, 0, pytitlestr); 1926 | 1927 | PyObject* kwargs = PyDict_New(); 1928 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 1929 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1930 | } 1931 | 1932 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_title, args, kwargs); 1933 | if(!res) throw std::runtime_error("Call to title() failed."); 1934 | 1935 | Py_DECREF(args); 1936 | Py_DECREF(kwargs); 1937 | Py_DECREF(res); 1938 | } 1939 | 1940 | inline void suptitle(const std::string &suptitlestr, const std::map &keywords = {}) 1941 | { 1942 | detail::_interpreter::get(); 1943 | 1944 | PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); 1945 | PyObject* args = PyTuple_New(1); 1946 | PyTuple_SetItem(args, 0, pysuptitlestr); 1947 | 1948 | PyObject* kwargs = PyDict_New(); 1949 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 1950 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 1951 | } 1952 | 1953 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_suptitle, args, kwargs); 1954 | if(!res) throw std::runtime_error("Call to suptitle() failed."); 1955 | 1956 | Py_DECREF(args); 1957 | Py_DECREF(kwargs); 1958 | Py_DECREF(res); 1959 | } 1960 | 1961 | inline void axis(const std::string &axisstr) 1962 | { 1963 | detail::_interpreter::get(); 1964 | 1965 | PyObject* str = PyString_FromString(axisstr.c_str()); 1966 | PyObject* args = PyTuple_New(1); 1967 | PyTuple_SetItem(args, 0, str); 1968 | 1969 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_axis, args); 1970 | if(!res) throw std::runtime_error("Call to title() failed."); 1971 | 1972 | Py_DECREF(args); 1973 | Py_DECREF(res); 1974 | } 1975 | 1976 | inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) 1977 | { 1978 | detail::_interpreter::get(); 1979 | 1980 | // construct positional args 1981 | PyObject* args = PyTuple_New(3); 1982 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); 1983 | PyTuple_SetItem(args, 1, PyFloat_FromDouble(ymin)); 1984 | PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymax)); 1985 | 1986 | // construct keyword args 1987 | PyObject* kwargs = PyDict_New(); 1988 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 1989 | { 1990 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 1991 | } 1992 | 1993 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvline, args, kwargs); 1994 | 1995 | Py_DECREF(args); 1996 | Py_DECREF(kwargs); 1997 | 1998 | if(res) Py_DECREF(res); 1999 | } 2000 | 2001 | inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) 2002 | { 2003 | // construct positional args 2004 | PyObject* args = PyTuple_New(4); 2005 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(xmin)); 2006 | PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmax)); 2007 | PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymin)); 2008 | PyTuple_SetItem(args, 3, PyFloat_FromDouble(ymax)); 2009 | 2010 | // construct keyword args 2011 | PyObject* kwargs = PyDict_New(); 2012 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 2013 | { 2014 | if (it->first == "linewidth" || it->first == "alpha") 2015 | PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(std::stod(it->second))); 2016 | else 2017 | PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); 2018 | } 2019 | 2020 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvspan, args, kwargs); 2021 | Py_DECREF(args); 2022 | Py_DECREF(kwargs); 2023 | 2024 | if(res) Py_DECREF(res); 2025 | } 2026 | 2027 | inline void xlabel(const std::string &str, const std::map &keywords = {}) 2028 | { 2029 | detail::_interpreter::get(); 2030 | 2031 | PyObject* pystr = PyString_FromString(str.c_str()); 2032 | PyObject* args = PyTuple_New(1); 2033 | PyTuple_SetItem(args, 0, pystr); 2034 | 2035 | PyObject* kwargs = PyDict_New(); 2036 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 2037 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 2038 | } 2039 | 2040 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xlabel, args, kwargs); 2041 | if(!res) throw std::runtime_error("Call to xlabel() failed."); 2042 | 2043 | Py_DECREF(args); 2044 | Py_DECREF(kwargs); 2045 | Py_DECREF(res); 2046 | } 2047 | 2048 | inline void ylabel(const std::string &str, const std::map& keywords = {}) 2049 | { 2050 | detail::_interpreter::get(); 2051 | 2052 | PyObject* pystr = PyString_FromString(str.c_str()); 2053 | PyObject* args = PyTuple_New(1); 2054 | PyTuple_SetItem(args, 0, pystr); 2055 | 2056 | PyObject* kwargs = PyDict_New(); 2057 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 2058 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 2059 | } 2060 | 2061 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_ylabel, args, kwargs); 2062 | if(!res) throw std::runtime_error("Call to ylabel() failed."); 2063 | 2064 | Py_DECREF(args); 2065 | Py_DECREF(kwargs); 2066 | Py_DECREF(res); 2067 | } 2068 | 2069 | inline void set_zlabel(const std::string &str, const std::map& keywords = {}) 2070 | { 2071 | detail::_interpreter::get(); 2072 | 2073 | // Same as with plot_surface: We lazily load the modules here the first time 2074 | // this function is called because I'm not sure that we can assume "matplotlib 2075 | // installed" implies "mpl_toolkits installed" on all platforms, and we don't 2076 | // want to require it for people who don't need 3d plots. 2077 | static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; 2078 | if (!mpl_toolkitsmod) { 2079 | PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); 2080 | PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); 2081 | if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } 2082 | 2083 | mpl_toolkitsmod = PyImport_Import(mpl_toolkits); 2084 | Py_DECREF(mpl_toolkits); 2085 | if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } 2086 | 2087 | axis3dmod = PyImport_Import(axis3d); 2088 | Py_DECREF(axis3d); 2089 | if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } 2090 | } 2091 | 2092 | PyObject* pystr = PyString_FromString(str.c_str()); 2093 | PyObject* args = PyTuple_New(1); 2094 | PyTuple_SetItem(args, 0, pystr); 2095 | 2096 | PyObject* kwargs = PyDict_New(); 2097 | for (auto it = keywords.begin(); it != keywords.end(); ++it) { 2098 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 2099 | } 2100 | 2101 | PyObject *ax = 2102 | PyObject_CallObject(detail::_interpreter::get().s_python_function_gca, 2103 | detail::_interpreter::get().s_python_empty_tuple); 2104 | if (!ax) throw std::runtime_error("Call to gca() failed."); 2105 | Py_INCREF(ax); 2106 | 2107 | PyObject *zlabel = PyObject_GetAttrString(ax, "set_zlabel"); 2108 | if (!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); 2109 | Py_INCREF(zlabel); 2110 | 2111 | PyObject *res = PyObject_Call(zlabel, args, kwargs); 2112 | if (!res) throw std::runtime_error("Call to set_zlabel() failed."); 2113 | Py_DECREF(zlabel); 2114 | 2115 | Py_DECREF(ax); 2116 | Py_DECREF(args); 2117 | Py_DECREF(kwargs); 2118 | if (res) Py_DECREF(res); 2119 | } 2120 | 2121 | inline void grid(bool flag) 2122 | { 2123 | detail::_interpreter::get(); 2124 | 2125 | PyObject* pyflag = flag ? Py_True : Py_False; 2126 | Py_INCREF(pyflag); 2127 | 2128 | PyObject* args = PyTuple_New(1); 2129 | PyTuple_SetItem(args, 0, pyflag); 2130 | 2131 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_grid, args); 2132 | if(!res) throw std::runtime_error("Call to grid() failed."); 2133 | 2134 | Py_DECREF(args); 2135 | Py_DECREF(res); 2136 | } 2137 | 2138 | inline void show(const bool block = true) 2139 | { 2140 | detail::_interpreter::get(); 2141 | 2142 | PyObject* res; 2143 | if(block) 2144 | { 2145 | res = PyObject_CallObject( 2146 | detail::_interpreter::get().s_python_function_show, 2147 | detail::_interpreter::get().s_python_empty_tuple); 2148 | } 2149 | else 2150 | { 2151 | PyObject *kwargs = PyDict_New(); 2152 | PyDict_SetItemString(kwargs, "block", Py_False); 2153 | res = PyObject_Call( detail::_interpreter::get().s_python_function_show, detail::_interpreter::get().s_python_empty_tuple, kwargs); 2154 | Py_DECREF(kwargs); 2155 | } 2156 | 2157 | 2158 | if (!res) throw std::runtime_error("Call to show() failed."); 2159 | 2160 | Py_DECREF(res); 2161 | } 2162 | 2163 | inline void close() 2164 | { 2165 | detail::_interpreter::get(); 2166 | 2167 | PyObject* res = PyObject_CallObject( 2168 | detail::_interpreter::get().s_python_function_close, 2169 | detail::_interpreter::get().s_python_empty_tuple); 2170 | 2171 | if (!res) throw std::runtime_error("Call to close() failed."); 2172 | 2173 | Py_DECREF(res); 2174 | } 2175 | 2176 | inline void xkcd() { 2177 | detail::_interpreter::get(); 2178 | 2179 | PyObject* res; 2180 | PyObject *kwargs = PyDict_New(); 2181 | 2182 | res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, 2183 | detail::_interpreter::get().s_python_empty_tuple, kwargs); 2184 | 2185 | Py_DECREF(kwargs); 2186 | 2187 | if (!res) 2188 | throw std::runtime_error("Call to show() failed."); 2189 | 2190 | Py_DECREF(res); 2191 | } 2192 | 2193 | inline void draw() 2194 | { 2195 | detail::_interpreter::get(); 2196 | 2197 | PyObject* res = PyObject_CallObject( 2198 | detail::_interpreter::get().s_python_function_draw, 2199 | detail::_interpreter::get().s_python_empty_tuple); 2200 | 2201 | if (!res) throw std::runtime_error("Call to draw() failed."); 2202 | 2203 | Py_DECREF(res); 2204 | } 2205 | 2206 | template 2207 | inline void pause(Numeric interval) 2208 | { 2209 | detail::_interpreter::get(); 2210 | 2211 | PyObject* args = PyTuple_New(1); 2212 | PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); 2213 | 2214 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_pause, args); 2215 | if(!res) throw std::runtime_error("Call to pause() failed."); 2216 | 2217 | Py_DECREF(args); 2218 | Py_DECREF(res); 2219 | } 2220 | 2221 | inline void save(const std::string& filename) 2222 | { 2223 | detail::_interpreter::get(); 2224 | 2225 | PyObject* pyfilename = PyString_FromString(filename.c_str()); 2226 | 2227 | PyObject* args = PyTuple_New(1); 2228 | PyTuple_SetItem(args, 0, pyfilename); 2229 | 2230 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_save, args); 2231 | if (!res) throw std::runtime_error("Call to save() failed."); 2232 | 2233 | Py_DECREF(args); 2234 | Py_DECREF(res); 2235 | } 2236 | 2237 | inline void clf() { 2238 | detail::_interpreter::get(); 2239 | 2240 | PyObject *res = PyObject_CallObject( 2241 | detail::_interpreter::get().s_python_function_clf, 2242 | detail::_interpreter::get().s_python_empty_tuple); 2243 | 2244 | if (!res) throw std::runtime_error("Call to clf() failed."); 2245 | 2246 | Py_DECREF(res); 2247 | } 2248 | 2249 | inline void cla() { 2250 | detail::_interpreter::get(); 2251 | 2252 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_cla, 2253 | detail::_interpreter::get().s_python_empty_tuple); 2254 | 2255 | if (!res) 2256 | throw std::runtime_error("Call to cla() failed."); 2257 | 2258 | Py_DECREF(res); 2259 | } 2260 | 2261 | inline void ion() { 2262 | detail::_interpreter::get(); 2263 | 2264 | PyObject *res = PyObject_CallObject( 2265 | detail::_interpreter::get().s_python_function_ion, 2266 | detail::_interpreter::get().s_python_empty_tuple); 2267 | 2268 | if (!res) throw std::runtime_error("Call to ion() failed."); 2269 | 2270 | Py_DECREF(res); 2271 | } 2272 | 2273 | inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) 2274 | { 2275 | detail::_interpreter::get(); 2276 | 2277 | PyObject *args = PyTuple_New(1); 2278 | PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); 2279 | 2280 | // construct keyword args 2281 | PyObject* kwargs = PyDict_New(); 2282 | for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) 2283 | { 2284 | PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); 2285 | } 2286 | 2287 | PyObject* res = PyObject_Call( 2288 | detail::_interpreter::get().s_python_function_ginput, args, kwargs); 2289 | 2290 | Py_DECREF(kwargs); 2291 | Py_DECREF(args); 2292 | if (!res) throw std::runtime_error("Call to ginput() failed."); 2293 | 2294 | const size_t len = PyList_Size(res); 2295 | std::vector> out; 2296 | out.reserve(len); 2297 | for (size_t i = 0; i < len; i++) { 2298 | PyObject *current = PyList_GetItem(res, i); 2299 | std::array position; 2300 | position[0] = PyFloat_AsDouble(PyTuple_GetItem(current, 0)); 2301 | position[1] = PyFloat_AsDouble(PyTuple_GetItem(current, 1)); 2302 | out.push_back(position); 2303 | } 2304 | Py_DECREF(res); 2305 | 2306 | return out; 2307 | } 2308 | 2309 | // Actually, is there any reason not to call this automatically for every plot? 2310 | inline void tight_layout() { 2311 | detail::_interpreter::get(); 2312 | 2313 | PyObject *res = PyObject_CallObject( 2314 | detail::_interpreter::get().s_python_function_tight_layout, 2315 | detail::_interpreter::get().s_python_empty_tuple); 2316 | 2317 | if (!res) throw std::runtime_error("Call to tight_layout() failed."); 2318 | 2319 | Py_DECREF(res); 2320 | } 2321 | 2322 | // Support for variadic plot() and initializer lists: 2323 | 2324 | namespace detail { 2325 | 2326 | template 2327 | using is_function = typename std::is_function>>::type; 2328 | 2329 | template 2330 | struct is_callable_impl; 2331 | 2332 | template 2333 | struct is_callable_impl 2334 | { 2335 | typedef is_function type; 2336 | }; // a non-object is callable iff it is a function 2337 | 2338 | template 2339 | struct is_callable_impl 2340 | { 2341 | struct Fallback { void operator()(); }; 2342 | struct Derived : T, Fallback { }; 2343 | 2344 | template struct Check; 2345 | 2346 | template 2347 | static std::true_type test( ... ); // use a variadic function to make sure (1) it accepts everything and (2) its always the worst match 2348 | 2349 | template 2350 | static std::false_type test( Check* ); 2351 | 2352 | public: 2353 | typedef decltype(test(nullptr)) type; 2354 | typedef decltype(&Fallback::operator()) dtype; 2355 | static constexpr bool value = type::value; 2356 | }; // an object is callable iff it defines operator() 2357 | 2358 | template 2359 | struct is_callable 2360 | { 2361 | // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not 2362 | typedef typename is_callable_impl::value, T>::type type; 2363 | }; 2364 | 2365 | template 2366 | struct plot_impl { }; 2367 | 2368 | template<> 2369 | struct plot_impl 2370 | { 2371 | template 2372 | bool operator()(const IterableX& x, const IterableY& y, const std::string& format) 2373 | { 2374 | detail::_interpreter::get(); 2375 | 2376 | // 2-phase lookup for distance, begin, end 2377 | using std::distance; 2378 | using std::begin; 2379 | using std::end; 2380 | 2381 | auto xs = distance(begin(x), end(x)); 2382 | auto ys = distance(begin(y), end(y)); 2383 | assert(xs == ys && "x and y data must have the same number of elements!"); 2384 | 2385 | PyObject* xlist = PyList_New(xs); 2386 | PyObject* ylist = PyList_New(ys); 2387 | PyObject* pystring = PyString_FromString(format.c_str()); 2388 | 2389 | auto itx = begin(x), ity = begin(y); 2390 | for(size_t i = 0; i < xs; ++i) { 2391 | PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); 2392 | PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); 2393 | } 2394 | 2395 | PyObject* plot_args = PyTuple_New(3); 2396 | PyTuple_SetItem(plot_args, 0, xlist); 2397 | PyTuple_SetItem(plot_args, 1, ylist); 2398 | PyTuple_SetItem(plot_args, 2, pystring); 2399 | 2400 | PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); 2401 | 2402 | Py_DECREF(plot_args); 2403 | if(res) Py_DECREF(res); 2404 | 2405 | return res; 2406 | } 2407 | }; 2408 | 2409 | template<> 2410 | struct plot_impl 2411 | { 2412 | template 2413 | bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) 2414 | { 2415 | if(begin(ticks) == end(ticks)) return true; 2416 | 2417 | // We could use additional meta-programming to deduce the correct element type of y, 2418 | // but all values have to be convertible to double anyways 2419 | std::vector y; 2420 | for(auto x : ticks) y.push_back(f(x)); 2421 | return plot_impl()(ticks,y,format); 2422 | } 2423 | }; 2424 | 2425 | } // end namespace detail 2426 | 2427 | // recursion stop for the above 2428 | template 2429 | bool plot() { return true; } 2430 | 2431 | template 2432 | bool plot(const A& a, const B& b, const std::string& format, Args... args) 2433 | { 2434 | return detail::plot_impl::type>()(a,b,format) && plot(args...); 2435 | } 2436 | 2437 | /* 2438 | * This group of plot() functions is needed to support initializer lists, i.e. calling 2439 | * plot( {1,2,3,4} ) 2440 | */ 2441 | inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") { 2442 | return plot(x,y,format); 2443 | } 2444 | 2445 | inline bool plot(const std::vector& y, const std::string& format = "") { 2446 | return plot(y,format); 2447 | } 2448 | 2449 | inline bool plot(const std::vector& x, const std::vector& y, const std::map& keywords) { 2450 | return plot(x,y,keywords); 2451 | } 2452 | 2453 | /* 2454 | * This class allows dynamic plots, ie changing the plotted data without clearing and re-plotting 2455 | */ 2456 | class Plot 2457 | { 2458 | public: 2459 | // default initialization with plot label, some data and format 2460 | template 2461 | Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { 2462 | detail::_interpreter::get(); 2463 | 2464 | assert(x.size() == y.size()); 2465 | 2466 | PyObject* kwargs = PyDict_New(); 2467 | if(name != "") 2468 | PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); 2469 | 2470 | PyObject* xarray = detail::get_array(x); 2471 | PyObject* yarray = detail::get_array(y); 2472 | 2473 | PyObject* pystring = PyString_FromString(format.c_str()); 2474 | 2475 | PyObject* plot_args = PyTuple_New(3); 2476 | PyTuple_SetItem(plot_args, 0, xarray); 2477 | PyTuple_SetItem(plot_args, 1, yarray); 2478 | PyTuple_SetItem(plot_args, 2, pystring); 2479 | 2480 | PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); 2481 | 2482 | Py_DECREF(kwargs); 2483 | Py_DECREF(plot_args); 2484 | 2485 | if(res) 2486 | { 2487 | line= PyList_GetItem(res, 0); 2488 | 2489 | if(line) 2490 | set_data_fct = PyObject_GetAttrString(line,"set_data"); 2491 | else 2492 | Py_DECREF(line); 2493 | Py_DECREF(res); 2494 | } 2495 | } 2496 | 2497 | // shorter initialization with name or format only 2498 | // basically calls line, = plot([], []) 2499 | Plot(const std::string& name = "", const std::string& format = "") 2500 | : Plot(name, std::vector(), std::vector(), format) {} 2501 | 2502 | template 2503 | bool update(const std::vector& x, const std::vector& y) { 2504 | assert(x.size() == y.size()); 2505 | if(set_data_fct) 2506 | { 2507 | PyObject* xarray = detail::get_array(x); 2508 | PyObject* yarray = detail::get_array(y); 2509 | 2510 | PyObject* plot_args = PyTuple_New(2); 2511 | PyTuple_SetItem(plot_args, 0, xarray); 2512 | PyTuple_SetItem(plot_args, 1, yarray); 2513 | 2514 | PyObject* res = PyObject_CallObject(set_data_fct, plot_args); 2515 | if (res) Py_DECREF(res); 2516 | return res; 2517 | } 2518 | return false; 2519 | } 2520 | 2521 | // clears the plot but keep it available 2522 | bool clear() { 2523 | return update(std::vector(), std::vector()); 2524 | } 2525 | 2526 | // definitely remove this line 2527 | void remove() { 2528 | if(line) 2529 | { 2530 | auto remove_fct = PyObject_GetAttrString(line,"remove"); 2531 | PyObject* args = PyTuple_New(0); 2532 | PyObject* res = PyObject_CallObject(remove_fct, args); 2533 | if (res) Py_DECREF(res); 2534 | } 2535 | decref(); 2536 | } 2537 | 2538 | ~Plot() { 2539 | decref(); 2540 | } 2541 | private: 2542 | 2543 | void decref() { 2544 | if(line) 2545 | Py_DECREF(line); 2546 | if(set_data_fct) 2547 | Py_DECREF(set_data_fct); 2548 | } 2549 | 2550 | 2551 | PyObject* line = nullptr; 2552 | PyObject* set_data_fct = nullptr; 2553 | }; 2554 | 2555 | } // end namespace matplotlibcpp 2556 | --------------------------------------------------------------------------------