├── .github └── workflows │ ├── gradle.yml │ └── maven.yml ├── .gitignore ├── .space.kts ├── LICENSE ├── MetaData.xsd ├── Project.xsd ├── README.md ├── Reference.html ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml ├── settings.gradle ├── src ├── main │ └── java │ │ ├── com │ │ └── bitwig │ │ │ └── dawproject │ │ │ ├── Application.java │ │ │ ├── Arrangement.java │ │ │ ├── BoolParameter.java │ │ │ ├── Channel.java │ │ │ ├── ContentType.java │ │ │ ├── DawProject.java │ │ │ ├── DoubleAdapter.java │ │ │ ├── EnumParameter.java │ │ │ ├── ExpressionType.java │ │ │ ├── FileReference.java │ │ │ ├── IntegerParameter.java │ │ │ ├── Interpolation.java │ │ │ ├── Lane.java │ │ │ ├── MetaData.java │ │ │ ├── MixerRole.java │ │ │ ├── Nameable.java │ │ │ ├── Parameter.java │ │ │ ├── Project.java │ │ │ ├── RealParameter.java │ │ │ ├── Referenceable.java │ │ │ ├── Scene.java │ │ │ ├── Send.java │ │ │ ├── SendType.java │ │ │ ├── TimeSignatureParameter.java │ │ │ ├── Track.java │ │ │ ├── Transport.java │ │ │ ├── Unit.java │ │ │ ├── Utility.java │ │ │ ├── device │ │ │ ├── AuPlugin.java │ │ │ ├── BuiltinDevice.java │ │ │ ├── ClapPlugin.java │ │ │ ├── Compressor.java │ │ │ ├── Device.java │ │ │ ├── DeviceRole.java │ │ │ ├── EqBand.java │ │ │ ├── EqBandType.java │ │ │ ├── Equalizer.java │ │ │ ├── Limiter.java │ │ │ ├── NoiseGate.java │ │ │ ├── Plugin.java │ │ │ ├── Vst2Plugin.java │ │ │ └── Vst3Plugin.java │ │ │ └── timeline │ │ │ ├── Audio.java │ │ │ ├── AutomationTarget.java │ │ │ ├── BoolPoint.java │ │ │ ├── Clip.java │ │ │ ├── ClipSlot.java │ │ │ ├── Clips.java │ │ │ ├── EnumPoint.java │ │ │ ├── IntegerPoint.java │ │ │ ├── Lanes.java │ │ │ ├── Marker.java │ │ │ ├── Markers.java │ │ │ ├── MediaFile.java │ │ │ ├── Note.java │ │ │ ├── Notes.java │ │ │ ├── Point.java │ │ │ ├── Points.java │ │ │ ├── RealPoint.java │ │ │ ├── TimeSignaturePoint.java │ │ │ ├── TimeUnit.java │ │ │ ├── Timeline.java │ │ │ ├── Video.java │ │ │ ├── Warp.java │ │ │ └── Warps.java │ │ └── module-info.java └── test │ └── java │ └── com │ └── bitwig │ └── dawproject │ ├── DawProjectTest.java │ ├── GenerateDocumentationTest.java │ └── LoadDawProjectTest.java ├── style.css └── test-data └── white-glasses.wav /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK 21 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: '21' 24 | - name: Grant execute permission for gradlew 25 | run: chmod +x gradlew 26 | - name: Build with Gradle 27 | run: ./gradlew build 28 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK 21 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | - name: Build with Maven 25 | run: mvn -B package --file pom.xml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | /target/ 24 | .gradle/ 25 | /build/ 26 | .DS_Store 27 | /.classpath 28 | /.project 29 | /.settings 30 | /out/ 31 | -------------------------------------------------------------------------------- /.space.kts: -------------------------------------------------------------------------------- 1 | job("Build and run tests") { 2 | container(displayName = "Gradle build", image = "openjdk:15") { 3 | kotlinScript { api -> 4 | // here goes complex logic 5 | api.gradlew("build") 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bitwig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetaData.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Project.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DAWproject 2 | 3 | Open exchange format for user data between Digital Audio Workstations (DAWs) 4 | 5 | ## Motivation 6 | 7 | The DAWproject format provides a (vendor-agnostic) way of transferring user data between different music applications (DAWs). 8 | 9 | Currently, there is no file-format which is purpose-built for this task. 10 | Standard MIDI files can represent note data, but it is often a lower-level representation (no ramps) of data than what the DAW uses internally, which forces consolidation on export. AAF only covers audio and doesn't have any concept of musical-time, which limits it to post-audio workflows . Most plug-ins do allow you to save presets to a shared location, but this has to be done for each instance. What most users end up doing is just exporting audio as stems. 11 | 12 | The aim of this project is to export all translatable project data (audio/note/automation/plug-in) along with the structure surrounding it into a single DAWproject file. 13 | 14 | The table below aims to explain the scope format from a music-production perspective and how it compares to other methods of data transfer. 15 | 16 | | | DAWproject | Standard MIDI Files | Advanced Authoring Format (AAF) | 17 | |---------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------:|:---------------------------------------------------------------------:| 18 | | Intended Use | Music Production | MIDI Sequencing | Video Post-Production | 19 | | Time Format
(seconds/beats) | Beats and seconds can be combined | Beats | Seconds | 20 | | Audio | Audio
Events/Clips
Fades
Crossfades
Amplitude
Pan
Time Warping
Transpose | - | Audio
Events/Clips
Fades
Crossfades
Amplitude
Pan | 21 | | Notes | Notes
Note Expressions | Notes | - | 22 | | Automation | Tempo
Time Signature
MIDI Messages
Volume
Pan
Mute
Sends
Plug-in Parameters
Built-in Device Parameters | Tempo
Time Signature
MIDI Messages
SysEx Messages | Volume
Pan
Video Related Parameters | 23 | | Plug-ins | Stores full plug-in state
and automation of parameters | - | - | 24 | | Built-in Devices | Generic EQ
Generic Compressor
Generic Gate
Generic Limiter | - | - | 25 | | Clip Launcher | Clips
Scenes | - | - | 26 | 27 | ## Status 28 | 29 | The format is version 1.0 and is stable. 30 | 31 | ## Goals 32 | 33 | * Package all user data of a project/song into a single file. 34 | * Audio timeline data 35 | * Note timeline data 36 | * Note expression data 37 | * Automation timeline data 38 | * Audio data (embedded or referenced) 39 | * Plug-in states (always embedded) 40 | * The format should be able to preserve as much user created data as feasible. 41 | * The format should be able to express the track and timeline structures of the exporting DAW as is, leaving it up to the importer to use this data and flatten it as needed. 42 | * Simple to implement 43 | * Built upon established open standards 44 | * Language agnostic, no special dependencies 45 | * Open & free 46 | 47 | ## Non-goals 48 | 49 | * Being the native file-format for a DAW 50 | * Optimal performance (like a binary format could provide) 51 | * Storing low-level MIDI events directly (but rather relying on higher level abstractions) 52 | * Storing non-session data (view settings, preferences) 53 | 54 | ## Format Specification 55 | 56 | * File Extension: .dawproject 57 | * Container: ZIP 58 | * Format: XML (project.xml, metadata.xml) 59 | * Text encoding: UTF-8 60 | * The exporting DAW is free to choose the directory structure it wants for media and plug-in files. 61 | 62 | * [DAWproject XML Reference](https://htmlpreview.github.io/?https://github.com/bitwig/dawproject/blob/main/Reference.html) 63 | * [Project XML Schema](Project.xsd) 64 | * [MetaData XML Schema](MetaData.xsd) 65 | 66 | ## Language Support 67 | 68 | DAWproject is based on plain XML/ZIP and can be used with any programming language that can parse those. 69 | 70 | The DOM of DAWproject is defined by a set of Java classes which have XML-related annotations and HTML-induced Javadoc comments. 71 | Those are used (via reflection) to generate XML Documentation and Schemas. Potentially, the same approach could be used to generate code for other languages (contributions welcome). 72 | 73 | ## Building the Library, Documentation and Tests 74 | 75 | Requires Java Runtime version 16 or later. 76 | 77 | To build (using Gradle): 78 | 79 | ``` 80 | ./gradlew build 81 | ``` 82 | 83 | ## Example project 84 | 85 | The exporting application is free to structure tracks and timelines in a way that fits its internal model. 86 | The choice is left to the importing application to either use the level of structure provided (if applicable) or to flatten/convert it to match its model. 87 | 88 | As an example, here's the project.xml of a simple file saved in Bitwig Studio 5.0 with one instrument track and one audio track. As the audio clips in Bitwig Studio are themselves a timeline of audio events, you will notice that there are two levels of elements, id25 representing the clip timeline on the arrangement, and id26 representing the audio events inside the clip. 89 | 90 | ```xml 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ``` 172 | 173 | ## DAW Support 174 | 175 | DAWproject 1.0 is currently supported by the following DAWs 176 | 177 | * Bitwig Studio 5.0.9 178 | * PreSonus Studio One 6.5 179 | * Steinberg Cubase 14 180 | * Steinberg Cubasis 3.7.1 181 | * Steinberg VST Live 2.2 182 | 183 | ## Converters 184 | 185 | There are various tools that can convert from and to DAWproject files 186 | 187 | * https://github.com/SatyrDiamond/DawVert: various formats 188 | * https://github.com/git-moss/ProjectConverter: Cockos Reaper 189 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'eclipse' 4 | id 'idea' 5 | id 'com.github.ben-manes.versions' version '0.51.0' 6 | id "com.diffplug.spotless" version "6.25.0" 7 | } 8 | 9 | java { 10 | sourceCompatibility = JavaVersion.VERSION_21 11 | targetCompatibility = JavaVersion.VERSION_21 12 | } 13 | 14 | // Ignore Alpha and Beta versions when checking for version updates 15 | dependencyUpdates.resolutionStrategy = { 16 | componentSelection { rules -> 17 | rules.all { ComponentSelection selection -> 18 | boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm'].any { qualifier -> 19 | selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ 20 | } 21 | if (rejected) { 22 | selection.reject('Release candidate') 23 | } 24 | } 25 | } 26 | } 27 | 28 | spotless { 29 | java { 30 | target 'src/**/*.java' 31 | targetExclude 'src/**/module-info.java' 32 | importOrder('java', 'javax', 'jakarta', '') 33 | removeUnusedImports() 34 | eclipse() 35 | } 36 | } 37 | 38 | version = '1-SNAPSHOT' 39 | group = 'com.bitwig.open' 40 | description = 'dawproject file format' 41 | buildDir = new File(project.projectDir, "target") 42 | 43 | repositories { 44 | mavenLocal() 45 | maven { 46 | url = uri('https://repo.maven.apache.org/maven2/') 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '4.0.2' 52 | runtimeOnly group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '4.0.5' 53 | implementation group: 'commons-io', name: 'commons-io', version: '2.18.0' 54 | testImplementation group: 'junit', name: 'junit', version: '4.13.2' 55 | 56 | annotationProcessor group: 'com.github.therapi', name: 'therapi-runtime-javadoc-scribe', version: '0.15.0' 57 | implementation group: 'com.github.therapi', name: 'therapi-runtime-javadoc', version: '0.15.0' 58 | testImplementation group: 'com.j2html', name: 'j2html', version: '1.6.0' 59 | testImplementation group: 'org.reflections', name: 'reflections', version: '0.10.2' 60 | } 61 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwig/dawproject/f016fb017292eb481d9afbfbafc0afa49ac6b89a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.bitwig.open 6 | dawproject 7 | jar 8 | dawproject file format 9 | 1-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | 17 | central 18 | https://repo.maven.apache.org/maven2/ 19 | 20 | 21 | 22 | 23 | 24 | junit 25 | junit 26 | 4.13.2 27 | test 28 | 29 | 30 | jakarta.xml.bind 31 | jakarta.xml.bind-api 32 | 4.0.2 33 | 34 | 35 | com.sun.xml.bind 36 | jaxb-impl 37 | 4.0.5 38 | runtime 39 | 40 | 41 | commons-io 42 | commons-io 43 | 2.18.0 44 | 45 | 46 | com.github.therapi 47 | therapi-runtime-javadoc-scribe 48 | 0.15.0 49 | 50 | 51 | com.github.therapi 52 | therapi-runtime-javadoc 53 | 0.15.0 54 | 55 | 56 | com.j2html 57 | j2html 58 | 1.6.0 59 | 60 | 61 | org.reflections 62 | reflections 63 | 0.10.2 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-enforcer-plugin 74 | 3.5.0 75 | 76 | 77 | enforce-maven 78 | 79 | enforce 80 | 81 | 82 | 83 | 84 | 3.8.1 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.codehaus.mojo 95 | versions-maven-plugin 96 | 2.18.0 97 | 98 | .*-M.*,.*-alpha.*,.*-beta.*,.*-ea.* 99 | false 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-compiler-plugin 106 | 3.13.0 107 | 108 | true 109 | 21 110 | 21 111 | UTF-8 112 | 1024m 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-source-plugin 119 | 3.3.1 120 | 121 | 122 | attach-sources 123 | 124 | jar 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | */ 4 | 5 | rootProject.name = 'dawproject' 6 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Application.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | 5 | /** Metadata about the application which saved the DAWPROJECT file. */ 6 | public class Application { 7 | /** Name of the application. */ 8 | @XmlAttribute(required = true) 9 | public String name; 10 | 11 | /** Version number of the application. */ 12 | @XmlAttribute(required = true) 13 | public String version; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Arrangement.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.timeline.Lanes; 7 | import com.bitwig.dawproject.timeline.Markers; 8 | import com.bitwig.dawproject.timeline.Points; 9 | 10 | /** Represents the main Arrangement timeline of a DAW. */ 11 | @XmlRootElement(name = "Arrangement") 12 | public class Arrangement extends Referenceable { 13 | /** 14 | * Automation data for time-signature inside this Arrangement. 15 | * 16 | *
{@code
17 | 	 * 
18 | 	 *   
19 | 	 *     
20 | 	 *     
21 | 	 *        ...
22 | 	 *   
23 | 	 * 
24 | 	 * }
25 | */ 26 | @XmlElement(required = false, name = "TimeSignatureAutomation", type = Points.class) 27 | public Points timeSignatureAutomation; 28 | 29 | /** 30 | * Automation data for tempo inside this Arrangement, which will define the 31 | * conversion between seconds and beats at the root level. 32 | */ 33 | @XmlElement(required = false, name = "TempoAutomation", type = Points.class) 34 | public Points tempoAutomation; 35 | 36 | /** Cue markers inside this arrangement */ 37 | @XmlElement(required = false, name = "Markers", type = Markers.class) 38 | public Markers markers; 39 | 40 | /** 41 | * The lanes of this arrangement. Generally this would contain another Lanes 42 | * timeline for (and scoped to) each track which would then contain all Note, 43 | * Audio, and Automation timelines. 44 | */ 45 | @XmlElement(name = "Lanes", type = Lanes.class) 46 | public Lanes lanes; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/BoolParameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Represents a parameter which can provide a boolean (true/false) value and be 8 | * used as an automation target. 9 | */ 10 | @XmlRootElement(name = "BoolParameter") 11 | public class BoolParameter extends Parameter { 12 | /** Boolean value for this parameter. */ 13 | @XmlAttribute(required = false) 14 | public Boolean value; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Channel.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.xml.bind.annotation.XmlAttribute; 6 | import jakarta.xml.bind.annotation.XmlElement; 7 | import jakarta.xml.bind.annotation.XmlElementRef; 8 | import jakarta.xml.bind.annotation.XmlElementWrapper; 9 | import jakarta.xml.bind.annotation.XmlIDREF; 10 | import jakarta.xml.bind.annotation.XmlRootElement; 11 | 12 | import com.bitwig.dawproject.device.Device; 13 | 14 | /** 15 | * Represents a mixer channel. It provides the ability to route signals to other 16 | * channels and can contain Device/Plug-in for processing. 17 | */ 18 | @XmlRootElement(name = "Channel") 19 | public class Channel extends Lane { 20 | /** Role of this channel in the mixer. */ 21 | @XmlAttribute(required = false) 22 | public MixerRole role; 23 | 24 | /** Number of audio-channels of this mixer channel. (1=mono, 2=stereo…) */ 25 | @XmlAttribute(required = false) 26 | public Integer audioChannels = Integer.valueOf(2); 27 | 28 | /** Channel volume */ 29 | @XmlElement(name = "Volume", required = false) 30 | public RealParameter volume; 31 | 32 | /** Channel pan/balance */ 33 | @XmlElement(name = "Pan", required = false) 34 | public RealParameter pan; 35 | 36 | /** Channel mute */ 37 | @XmlElement(name = "Mute", required = false) 38 | public BoolParameter mute; 39 | 40 | /** Channel solo */ 41 | @XmlAttribute(required = false) 42 | public Boolean solo; 43 | 44 | /** Output channel routing */ 45 | @XmlIDREF 46 | @XmlAttribute() 47 | public Channel destination; 48 | 49 | /** Send levels & destination */ 50 | @XmlElementWrapper(name = "Sends") 51 | @XmlElement(name = "Send", type = Send.class) 52 | public List sends; 53 | 54 | /** Devices & plug-ins of this channel */ 55 | @XmlElementWrapper(name = "Devices") 56 | @XmlElementRef 57 | public List devices; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/ContentType.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** The type of the content. */ 7 | @XmlEnum 8 | public enum ContentType { 9 | /** Audio content. */ 10 | @XmlEnumValue("audio") 11 | AUDIO, 12 | 13 | /** Automation content. */ 14 | @XmlEnumValue("automation") 15 | AUTOMATION, 16 | 17 | /** Notes content. */ 18 | @XmlEnumValue("notes") 19 | NOTES, 20 | 21 | /** Video content. */ 22 | @XmlEnumValue("video") 23 | VIDEO, 24 | 25 | /** Markers content. */ 26 | @XmlEnumValue("markers") 27 | MARKERS, 28 | 29 | /** Tracks content. */ 30 | @XmlEnumValue("tracks") 31 | TRACKS 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/DawProject.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStreamWriter; 10 | import java.io.PrintWriter; 11 | import java.io.StringReader; 12 | import java.io.StringWriter; 13 | import java.io.Writer; 14 | import java.nio.charset.Charset; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.Map; 17 | import java.util.zip.ZipEntry; 18 | import java.util.zip.ZipFile; 19 | import java.util.zip.ZipOutputStream; 20 | 21 | import javax.xml.XMLConstants; 22 | import javax.xml.transform.Result; 23 | import javax.xml.transform.stream.StreamResult; 24 | import javax.xml.validation.Schema; 25 | import javax.xml.validation.SchemaFactory; 26 | 27 | import jakarta.xml.bind.JAXBContext; 28 | import jakarta.xml.bind.JAXBException; 29 | import jakarta.xml.bind.Marshaller; 30 | import jakarta.xml.bind.SchemaOutputResolver; 31 | 32 | import org.apache.commons.io.ByteOrderMark; 33 | import org.apache.commons.io.input.BOMInputStream; 34 | import org.xml.sax.SAXException; 35 | 36 | /** Several helper functions to deal with DAWproject files. */ 37 | public class DawProject { 38 | /** The (English) description text of the format. */ 39 | public static final String FORMAT_NAME = "DAWproject exchange format"; 40 | 41 | /** The file extension for DAWproject files. */ 42 | public static final String FILE_EXTENSION = "dawproject"; 43 | 44 | private static final String PROJECT_FILE = "project.xml"; 45 | private static final String METADATA_FILE = "metadata.xml"; 46 | 47 | /** Private constructor since this is a helper class. */ 48 | private DawProject() { 49 | // Intentionally empty 50 | } 51 | 52 | /** 53 | * Exports the XML schema of the given Java class. 54 | * 55 | * @param file 56 | * The file into which to store the XML schema 57 | * @param cls 58 | * The class from which to create the schema 59 | * @throws IOException 60 | * Could not write the file 61 | */ 62 | public static void exportSchema(final File file, final Class cls) throws IOException { 63 | try (final FileOutputStream fileOutputStream = new FileOutputStream(file)) { 64 | final var resolver = new SchemaOutputResolver() { 65 | @Override 66 | public Result createOutput(final String namespaceUri, final String suggestedFileName) 67 | throws IOException { 68 | final StreamResult result = new StreamResult(new Writer() { 69 | // Ugly workaround to ensure the same newline on all platforms 70 | 71 | private final PrintWriter pw = new PrintWriter( 72 | new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) { 73 | @Override 74 | public void println() { 75 | this.write('\n'); 76 | } 77 | }; 78 | 79 | @Override 80 | public void write(final String str) throws IOException { 81 | this.pw.write(str); 82 | } 83 | 84 | @Override 85 | public void write(final int c) throws IOException { 86 | this.pw.write(c); 87 | } 88 | 89 | @Override 90 | public void write(final char[] cbuf, final int off, final int len) throws IOException { 91 | this.pw.write(cbuf, off, len); 92 | } 93 | 94 | @Override 95 | public void flush() throws IOException { 96 | this.pw.flush(); 97 | } 98 | 99 | @Override 100 | public void close() throws IOException { 101 | this.pw.close(); 102 | } 103 | }); 104 | 105 | result.setSystemId(file.getName()); 106 | return result; 107 | } 108 | }; 109 | 110 | createContext(cls).generateSchema(resolver); 111 | } catch (final JAXBException ex) { 112 | throw new IOException(ex); 113 | } 114 | } 115 | 116 | /** 117 | * Stores the given project to the given file. 118 | * 119 | * @param project 120 | * The project to store 121 | * @param file 122 | * The file into which to store the project 123 | * @throws IOException 124 | * Could not store the project 125 | */ 126 | public static void saveXML(final Project project, final File file) throws IOException { 127 | final String projectXML = toXML(project); 128 | try (final FileOutputStream fileOutputStream = new FileOutputStream(file)) { 129 | fileOutputStream.write(projectXML.getBytes(StandardCharsets.UTF_8)); 130 | } 131 | } 132 | 133 | /** 134 | * Stores the given project, metadata and embedded files to the given (ZIP) 135 | * file. 136 | * 137 | * @param project 138 | * The project to store 139 | * @param metadata 140 | * The metadata to store 141 | * @param embeddedFiles 142 | * The embedded files 143 | * @param file 144 | * The file into which to store the project 145 | * @throws IOException 146 | * Could not store the files 147 | */ 148 | public static void save(final Project project, final MetaData metadata, final Map embeddedFiles, 149 | final File file) throws IOException { 150 | final String metadataXML = toXML(metadata); 151 | final String projectXML = toXML(project); 152 | 153 | try (final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file))) { 154 | addToZip(zos, METADATA_FILE, metadataXML.getBytes(StandardCharsets.UTF_8)); 155 | addToZip(zos, PROJECT_FILE, projectXML.getBytes(StandardCharsets.UTF_8)); 156 | 157 | for (final Map.Entry entry : embeddedFiles.entrySet()) 158 | addToZip(zos, entry.getValue(), entry.getKey()); 159 | } 160 | } 161 | 162 | /** 163 | * Loads a project from the given file. 164 | * 165 | * @param file 166 | * The file which contains the project in XML format 167 | * @return The loaded project 168 | * @throws IOException 169 | * Could not load the project 170 | */ 171 | public static Project loadProject(final File file) throws IOException { 172 | try (final ZipFile zipFile = new ZipFile(file)) { 173 | final ZipEntry projectEntry = zipFile.getEntry(PROJECT_FILE); 174 | try (final InputStreamReader reader = stripBom(zipFile.getInputStream(projectEntry))) { 175 | return fromXML(reader, Project.class); 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * Loads metadata from the given file. 182 | * 183 | * @param file 184 | * The file which contains the metadata in XML format 185 | * @return The loaded metadata 186 | * @throws IOException 187 | * Could not load the metadata 188 | */ 189 | public static MetaData loadMetadata(final File file) throws IOException { 190 | try (final ZipFile zipFile = new ZipFile(file)) { 191 | final ZipEntry entry = zipFile.getEntry(METADATA_FILE); 192 | try (final InputStreamReader reader = stripBom(zipFile.getInputStream(entry))) { 193 | return fromXML(reader, MetaData.class); 194 | } 195 | } 196 | } 197 | 198 | /** 199 | * Loads the data of an embedded file. 200 | * 201 | * @param file 202 | * The ZIP archive which contains the embedded file 203 | * @param embeddedPath 204 | * The path of the embedded file in the ZIP archive 205 | * @return The loaded data 206 | * @throws IOException 207 | * Could not load the metadata 208 | */ 209 | public static byte[] loadEmbedded(final File file, final String embeddedPath) throws IOException { 210 | try (final ZipFile zipFile = new ZipFile(file)) { 211 | final ZipEntry entry = zipFile.getEntry(embeddedPath); 212 | try (final InputStream zipInputStream = zipFile.getInputStream(entry)) { 213 | return zipInputStream.readAllBytes(); 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * Validates the given project by serializing it to XML and validating the XML 220 | * code to the project XML schema. 221 | * 222 | * @param project 223 | * The project to validate 224 | * @throws IOException 225 | * Error reading/writing 226 | */ 227 | public static void validate(final Project project) throws IOException { 228 | final String projectXML = toXML(project); 229 | try { 230 | final var context = createContext(Project.class); 231 | 232 | final var schemaFile = File.createTempFile("schema", ".xml"); 233 | exportSchema(schemaFile, Project.class); 234 | 235 | final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 236 | final Schema schema = sf.newSchema(schemaFile); 237 | 238 | final var unmarshaller = context.createUnmarshaller(); 239 | unmarshaller.setSchema(schema); 240 | unmarshaller.unmarshal(new StringReader(projectXML)); 241 | } catch (final JAXBException | SAXException ex) { 242 | throw new IOException(ex); 243 | } 244 | } 245 | 246 | private static String toXML(final Object object) throws IOException { 247 | try { 248 | final var context = createContext(object.getClass()); 249 | 250 | final var marshaller = context.createMarshaller(); 251 | marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 252 | 253 | final var sw = new StringWriter(); 254 | marshaller.marshal(object, sw); 255 | 256 | return sw.toString(); 257 | } catch (final Exception e) { 258 | throw new IOException(e); 259 | } 260 | } 261 | 262 | private static JAXBContext createContext(final Class cls) throws JAXBException { 263 | return JAXBContext.newInstance(cls); 264 | } 265 | 266 | private static T fromXML(final InputStreamReader reader, final Class cls) throws IOException { 267 | try { 268 | final var jaxbContext = JAXBContext.newInstance(cls); 269 | final var unmarshaller = jaxbContext.createUnmarshaller(); 270 | return cls.cast(unmarshaller.unmarshal(reader)); 271 | } catch (final JAXBException e) { 272 | throw new IOException(e); 273 | } 274 | } 275 | 276 | private static void addToZip(final ZipOutputStream zos, final String path, final byte[] data) throws IOException { 277 | final ZipEntry entry = new ZipEntry(path); 278 | zos.putNextEntry(entry); 279 | zos.write(data); 280 | zos.closeEntry(); 281 | } 282 | 283 | private static void addToZip(final ZipOutputStream zos, final String path, final File file) throws IOException { 284 | final ZipEntry entry = new ZipEntry(path); 285 | zos.putNextEntry(entry); 286 | 287 | try (final FileInputStream fileInputStream = new FileInputStream(file)) { 288 | final byte[] data = new byte[65536]; 289 | int size = 0; 290 | while ((size = fileInputStream.read(data)) != -1) 291 | zos.write(data, 0, size); 292 | zos.flush(); 293 | } 294 | 295 | zos.closeEntry(); 296 | } 297 | 298 | private static InputStreamReader stripBom(final InputStream inputStream) throws IOException { 299 | final BOMInputStream bomInputStream = BOMInputStream.builder().setInputStream(inputStream) 300 | .setByteOrderMarks(ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, 301 | ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE) 302 | .get(); 303 | Charset charset; 304 | if (!bomInputStream.hasBOM()) 305 | charset = StandardCharsets.UTF_8; 306 | else if (bomInputStream.hasBOM(ByteOrderMark.UTF_8)) 307 | charset = StandardCharsets.UTF_8; 308 | else if (bomInputStream.hasBOM(ByteOrderMark.UTF_16LE)) 309 | charset = StandardCharsets.UTF_16LE; 310 | else if (bomInputStream.hasBOM(ByteOrderMark.UTF_16BE)) 311 | charset = StandardCharsets.UTF_16BE; 312 | else 313 | throw new IOException("The charset is not supported."); 314 | 315 | return new InputStreamReader(bomInputStream, charset); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/DoubleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.util.Locale; 4 | 5 | import jakarta.xml.bind.annotation.adapters.XmlAdapter; 6 | 7 | /** Adapter for a double value which can handle Infinity constants. */ 8 | public class DoubleAdapter extends XmlAdapter { 9 | /** {@inheritDoc} */ 10 | @Override 11 | public Double unmarshal(final String v) throws Exception { 12 | if (v == null || v.isEmpty() || v.equals("null")) 13 | return null; 14 | return Double.valueOf(v.replace("inf", "Infinity")); 15 | } 16 | 17 | /** {@inheritDoc} */ 18 | @Override 19 | public String marshal(final Double v) throws Exception { 20 | if (v == null) 21 | return null; 22 | return String.format(Locale.US, "%.6f", v).replace("Infinity", "inf"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/EnumParameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Represents an enumerated parameter which can provide a value and be used as 8 | * an automation target. 9 | */ 10 | @XmlRootElement(name = "EnumParameter") 11 | public class EnumParameter extends Parameter { 12 | /** Index of the enumeration value. */ 13 | @XmlAttribute 14 | public Integer value; 15 | 16 | /** 17 | * Number of entries in enumeration value. value will be in the range [0 .. 18 | * count-1]. 19 | */ 20 | @XmlAttribute(required = true) 21 | public Integer count; 22 | 23 | /** Labels of the individual enumeration values. */ 24 | @XmlAttribute(required = false) 25 | public String[] labels; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/ExpressionType.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** An expression type. */ 7 | @XmlEnum 8 | public enum ExpressionType { 9 | /** The gain expression. */ 10 | @XmlEnumValue("gain") 11 | GAIN, 12 | 13 | /** The panorama expression. */ 14 | @XmlEnumValue("pan") 15 | PAN, 16 | 17 | /** The transpose expression. */ 18 | @XmlEnumValue("transpose") 19 | TRANSPOSE, 20 | 21 | /** The timbre expression. */ 22 | @XmlEnumValue("timbre") 23 | TIMBRE, 24 | 25 | /** The formant expression. */ 26 | @XmlEnumValue("formant") 27 | FORMANT, 28 | 29 | /** The pressure expression. */ 30 | @XmlEnumValue("pressure") 31 | PRESSURE, 32 | 33 | /** The MIDI channel controller expression. */ 34 | @XmlEnumValue("channelController") 35 | CHANNEL_CONTROLLER, 36 | 37 | /** The MIDI channel pressure expression. */ 38 | @XmlEnumValue("channelPressure") 39 | CHANNEL_PRESSURE, 40 | 41 | /** The MIDI poly-pressure expression. */ 42 | @XmlEnumValue("polyPressure") 43 | POLY_PRESSURE, 44 | 45 | /** The MIDI pitch-bend expression. */ 46 | @XmlEnumValue("pitchBend") 47 | PITCH_BEND, 48 | 49 | /** The MIDI program change expression. */ 50 | @XmlEnumValue("programChange") 51 | PROGRAM_CHANGE 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/FileReference.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | 5 | /** References a file either within a DAWproject container or on disk. */ 6 | public class FileReference { 7 | /** 8 | * File path. either 9 | *
  • path within the container 10 | *
  • relative to .dawproject file (when external = "true") 11 | *
  • absolute path (when external = "true" and path starts with a slash or 12 | * windows drive letter) 13 | */ 14 | @XmlAttribute(required = true) 15 | public String path; 16 | 17 | /** 18 | * When true, the path is relative to the .dawproject file. Default value is 19 | * false. 20 | */ 21 | @XmlAttribute(required = false) 22 | public Boolean external; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/IntegerParameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Represents an enumerated parameter which can provide a value and be used as 8 | * an automation target. 9 | */ 10 | @XmlRootElement(name = "IntegerParameter") 11 | public class IntegerParameter extends Parameter { 12 | /** Integer value for this parameter. */ 13 | @XmlAttribute 14 | public Integer value; 15 | 16 | /** Minimum value this parameter can have (inclusive). */ 17 | @XmlAttribute 18 | public Integer min; 19 | 20 | /** Maximum value this parameter can have (inclusive). */ 21 | @XmlAttribute 22 | public Integer max; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Interpolation.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnumValue; 4 | 5 | /** Interpolation variations. */ 6 | public enum Interpolation { 7 | /** Hold interpolation. */ 8 | @XmlEnumValue("hold") 9 | HOLD, 10 | 11 | /** Linear interpolation. */ 12 | @XmlEnumValue("linear") 13 | LINEAR 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Lane.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | /** Abstract base class for lanes like channels and tracks. */ 4 | public abstract class Lane extends Referenceable { 5 | // Intentionally empty 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/MetaData.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Metadata root element of the DAWPROJECT format. This is stored in the file 8 | * metadata.xml file inside the container. 9 | */ 10 | @XmlRootElement(name = "MetaData") 11 | public class MetaData { 12 | /** Title of the song/project. */ 13 | @XmlElement(name = "Title") 14 | public String title; 15 | 16 | /** Recording Artist. */ 17 | @XmlElement(name = "Artist") 18 | public String artist; 19 | 20 | /** Album. */ 21 | @XmlElement(name = "Album") 22 | public String album; 23 | 24 | /** Original Artist. */ 25 | @XmlElement(name = "OriginalArtist") 26 | public String originalArtist; 27 | 28 | /** Composer. */ 29 | @XmlElement(name = "Composer") 30 | public String composer; 31 | 32 | /** Songwriter. */ 33 | @XmlElement(name = "Songwriter") 34 | public String songwriter; 35 | 36 | /** Producer. */ 37 | @XmlElement(name = "Producer") 38 | public String producer; 39 | 40 | /** Arranger. */ 41 | @XmlElement(name = "Arranger") 42 | public String arranger; 43 | 44 | /** Year this project/song was recorded. */ 45 | @XmlElement(name = "Year") 46 | public String year; 47 | 48 | /** Genre/style */ 49 | @XmlElement(name = "Genre") 50 | public String genre; 51 | 52 | /** Copyright notice. */ 53 | @XmlElement(name = "Copyright") 54 | public String copyright; 55 | 56 | /** URL to website related to this project. */ 57 | @XmlElement(name = "Website") 58 | public String website; 59 | 60 | /** General comment or description. */ 61 | @XmlElement(name = "Comment") 62 | public String comment; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/MixerRole.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** The role of a track or channel in the mixer. */ 7 | @XmlEnum 8 | public enum MixerRole { 9 | /** The 'default' role. */ 10 | @XmlEnumValue("regular") 11 | REGULAR, 12 | /** E.g. the role of a master track. */ 13 | @XmlEnumValue("master") 14 | MASTER, 15 | /** E.g. the role of an effect track. */ 16 | @XmlEnumValue("effect") 17 | EFFECT_TRACK, 18 | /** E.g. the role of a group track. */ 19 | @XmlEnumValue("submix") 20 | SUBMIX, 21 | /** The role of a VCA track. */ 22 | @XmlEnumValue("vca") 23 | VCA 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Nameable.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | 5 | /** Base class for everything with a name. */ 6 | public abstract class Nameable { 7 | /** Name/label of this object. */ 8 | @XmlAttribute 9 | public String name; 10 | 11 | /** Color of this object in HTML-style format. (#rrggbb) */ 12 | @XmlAttribute 13 | public String color; 14 | 15 | /** Comment/description of this object. */ 16 | @XmlAttribute 17 | public String comment; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Parameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | import jakarta.xml.bind.annotation.XmlSeeAlso; 6 | 7 | /** 8 | * Represents a parameter which can provide a value and be used as an automation 9 | * target. 10 | */ 11 | @XmlRootElement 12 | @XmlSeeAlso({RealParameter.class, BoolParameter.class, IntegerParameter.class, EnumParameter.class, 13 | TimeSignatureParameter.class}) 14 | public abstract class Parameter extends Referenceable { 15 | /** Parameter ID as used by VST2 (index), VST3(ParamID). */ 16 | @XmlAttribute(required = false) 17 | public Integer parameterID; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Project.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlAttribute; 7 | import jakarta.xml.bind.annotation.XmlElement; 8 | import jakarta.xml.bind.annotation.XmlElementRef; 9 | import jakarta.xml.bind.annotation.XmlElementWrapper; 10 | import jakarta.xml.bind.annotation.XmlRootElement; 11 | import jakarta.xml.bind.annotation.XmlSeeAlso; 12 | 13 | import com.bitwig.dawproject.device.Device; 14 | import com.bitwig.dawproject.timeline.Timeline; 15 | 16 | /** 17 | * The main root element of the DAWPROJECT format. This is stored in the file 18 | * project.xml file inside the container. 19 | */ 20 | @XmlRootElement(name = "Project") 21 | @XmlSeeAlso({Device.class, Timeline.class}) 22 | public class Project { 23 | /** The version of the format. */ 24 | public static final String CURRENT_VERSION = "1.0"; 25 | 26 | /** Version of DAWPROJECT format this file was saved as. */ 27 | @XmlAttribute(required = true) 28 | public String version = CURRENT_VERSION; 29 | 30 | /** Metadata (name/version) about the application that saved this file. */ 31 | @XmlElement(name = "Application", required = true) 32 | public Application application = new Application(); 33 | 34 | /** 35 | * Transport element containing playback parameters such as Tempo and 36 | * Time-signature. 37 | */ 38 | @XmlElement(name = "Transport") 39 | public Transport transport; 40 | 41 | /** Track/Channel structure of this file. */ 42 | @XmlElementWrapper(name = "Structure") 43 | @XmlElementRef 44 | public List structure = new ArrayList<>(); 45 | 46 | /** The main Arrangement timeline of this file. */ 47 | @XmlElement(name = "Arrangement", type = Arrangement.class, required = false) 48 | public Arrangement arrangement; 49 | 50 | /** Clip Launcher scenes of this file. */ 51 | @XmlElementWrapper(name = "Scenes") 52 | @XmlElement(name = "Scene") 53 | public List scenes = new ArrayList<>(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/RealParameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | import jakarta.xml.bind.annotation.XmlSeeAlso; 6 | import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 7 | 8 | /** 9 | * Represents a real valued (double) parameter which can provide a value and be 10 | * used as an automation target. 11 | */ 12 | @XmlRootElement(name = "RealParameter") 13 | @XmlSeeAlso({Unit.class}) 14 | public class RealParameter extends Parameter { 15 | /** 16 | * Real (double) value for this parameter. 17 | * 18 | *

    19 | * When serializing value to text for XML, infinite values are allowed and 20 | * should be represented as inf and -inf. 21 | */ 22 | @XmlAttribute 23 | @XmlJavaTypeAdapter(DoubleAdapter.class) 24 | public Double value; 25 | 26 | /** 27 | * Unit in which value, minimum and maximum are defined. 28 | * 29 | *

    30 | * Using this rather than normalized value ranges allows transfer of parameter 31 | * values and automation data. 32 | */ 33 | @XmlAttribute(required = true) 34 | public Unit unit; 35 | 36 | /** Minimum value this parameter can have (inclusive). */ 37 | @XmlAttribute 38 | @XmlJavaTypeAdapter(DoubleAdapter.class) 39 | public Double min; 40 | 41 | /** Maximum value this parameter can have (inclusive). */ 42 | @XmlAttribute 43 | @XmlJavaTypeAdapter(DoubleAdapter.class) 44 | public Double max; 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Referenceable.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAccessOrder; 4 | import jakarta.xml.bind.annotation.XmlAccessorOrder; 5 | import jakarta.xml.bind.annotation.XmlAttribute; 6 | import jakarta.xml.bind.annotation.XmlID; 7 | 8 | /** Base class for everything which can be referenced. */ 9 | @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) 10 | public abstract class Referenceable extends Nameable { 11 | private static int idCounter = 0; 12 | private static boolean enableAutoID = false; 13 | 14 | /** 15 | * Unique string identifier of this element. This is used for referencing this 16 | * instance from other elements. 17 | */ 18 | @XmlAttribute 19 | @XmlID() 20 | public final String id; 21 | 22 | /** Constructor. */ 23 | protected Referenceable() { 24 | this.id = enableAutoID ? "id" + (idCounter++) : null; 25 | } 26 | 27 | /** 28 | * Enables automatic creation of XML IDs. Resets the IDs as well to 0. 29 | * 30 | * @param enable 31 | * True to enable automatic ID creation for all instances of 32 | * {@link #Referenceable()} 33 | */ 34 | public static void setAutoID(final boolean enable) { 35 | enableAutoID = enable; 36 | idCounter = 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Scene.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlElementRef; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.timeline.Timeline; 7 | 8 | /** Represents a clip launcher Scene of a DAW. */ 9 | @XmlRootElement(name = "Scene") 10 | public class Scene extends Referenceable { 11 | /** 12 | * Content timeline of this scene, will typically be structured like this: 13 | * 14 | *

    {@code
    15 | 	 * 
    16 | 	 *   
    17 | 	 *     
    18 | 	 *        
    19 | 	 *           ...
    20 | 	 *        
    21 | 	 *     
    22 | 	 *      ...
    23 | 	 *   
    24 | 	 * 
    25 | 	 * }
    26 | */ 27 | @XmlElementRef(name = "Timeline") 28 | public Timeline content; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Send.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElement; 5 | import jakarta.xml.bind.annotation.XmlIDREF; 6 | 7 | /** A single send of a mixer channel. */ 8 | public class Send extends Referenceable { 9 | /** Send level. */ 10 | @XmlElement(required = true, name = "Volume") 11 | public RealParameter volume; 12 | 13 | /** Send pan/balance. */ 14 | @XmlElement(required = false, name = "Pan") 15 | public RealParameter pan; 16 | 17 | /** Send enable. */ 18 | @XmlElement(required = false, name = "Enable") 19 | public BoolParameter enable; 20 | 21 | /** Send type. */ 22 | @XmlAttribute 23 | public SendType type = SendType.POST; 24 | 25 | /** Send destination. */ 26 | @XmlAttribute 27 | @XmlIDREF 28 | public Channel destination; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/SendType.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** The type of a send. */ 7 | @XmlEnum 8 | public enum SendType { 9 | /** A pre-fader send. */ 10 | @XmlEnumValue("pre") 11 | PRE, 12 | 13 | /** A post-fader send. */ 14 | @XmlEnumValue("post") 15 | POST 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/TimeSignatureParameter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Represents a (the) time-signature parameter which can provide a value and be 8 | * used as an automation target. 9 | */ 10 | @XmlRootElement(name = "TimeSignatureParameter") 11 | public class TimeSignatureParameter extends Parameter { 12 | /** Numerator of the time-signature (3/4 → 3, 4/4 → 4). */ 13 | @XmlAttribute(required = true) 14 | public Integer numerator; 15 | 16 | /** Denominator of the time-signature (3/4 → 4, 7/8 → 8). */ 17 | @XmlAttribute(required = true) 18 | public Integer denominator; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Track.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlAttribute; 7 | import jakarta.xml.bind.annotation.XmlElement; 8 | import jakarta.xml.bind.annotation.XmlList; 9 | import jakarta.xml.bind.annotation.XmlRootElement; 10 | 11 | /** Represents a sequencer track. */ 12 | @XmlRootElement(name = "Track") 13 | public class Track extends Lane { 14 | /** 15 | * Role of this track in timelines & arranger. Can be multiple 16 | * (comma-separated). 17 | */ 18 | @XmlAttribute(required = false) 19 | @XmlList() 20 | public ContentType[] contentType; 21 | 22 | /** If this track is loaded/active of not. */ 23 | @XmlAttribute(required = false) 24 | public Boolean loaded; 25 | 26 | /** Mixer channel used for the output of this track. */ 27 | @XmlElement(name = "Channel", required = false) 28 | public Channel channel; 29 | 30 | /** 31 | * Child tracks, typically used to represent group/folder tracks with 32 | * contentType="tracks". 33 | */ 34 | @XmlElement(name = "Track") 35 | public List tracks = new ArrayList<>(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Transport.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | 5 | /** 6 | * Transport element containing playback parameters such as Tempo and 7 | * Time-signature. 8 | */ 9 | public class Transport { 10 | /** Tempo parameter for setting and/or automating the tempo. */ 11 | @XmlElement(name = "Tempo") 12 | public RealParameter tempo; 13 | 14 | /** Time-signature parameter. */ 15 | @XmlElement(name = "TimeSignature") 16 | public TimeSignatureParameter timeSignature; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Unit.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** Units for parameter values. */ 7 | @XmlEnum 8 | public enum Unit { 9 | /** Linear. */ 10 | @XmlEnumValue("linear") 11 | LINEAR, 12 | 13 | /** A normalized value (0-1). */ 14 | @XmlEnumValue("normalized") 15 | NORMALIZED, 16 | 17 | /** A percentage value (0-100). */ 18 | @XmlEnumValue("percent") 19 | PERCENT, 20 | 21 | /** A decibel value (dB). */ 22 | @XmlEnumValue("decibel") 23 | DECIBEL, 24 | 25 | /** A frequency value in Hertz. */ 26 | @XmlEnumValue("hertz") 27 | HERTZ, 28 | 29 | /** A semi-tone value. */ 30 | @XmlEnumValue("semitones") 31 | SEMITONES, 32 | 33 | /** A time value in seconds. */ 34 | @XmlEnumValue("seconds") 35 | SECONDS, 36 | 37 | /** A time value in beats (quarter notes). */ 38 | @XmlEnumValue("beats") 39 | BEATS, 40 | 41 | /** A beats-per-minute value. */ 42 | @XmlEnumValue("bpm") 43 | BPM 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/Utility.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | 6 | import com.bitwig.dawproject.timeline.Audio; 7 | import com.bitwig.dawproject.timeline.Clip; 8 | import com.bitwig.dawproject.timeline.Clips; 9 | import com.bitwig.dawproject.timeline.Marker; 10 | import com.bitwig.dawproject.timeline.RealPoint; 11 | import com.bitwig.dawproject.timeline.TimeUnit; 12 | import com.bitwig.dawproject.timeline.Timeline; 13 | import com.bitwig.dawproject.timeline.Warp; 14 | 15 | /** Helper class to create different DAWproject objects. */ 16 | public class Utility { 17 | /** Private constructor due to utility class. */ 18 | private Utility() { 19 | // Intentionally empty 20 | } 21 | 22 | /** 23 | * Creates a track. 24 | * 25 | * @param name 26 | * The name of the track 27 | * @param contentTypes 28 | * The content types that can be placed on the track 29 | * @param mixerRole 30 | * The mixer role of the track 31 | * @param volume 32 | * The volume setting of the track 33 | * @param pan 34 | * The panorama setting of the track 35 | * @return The track instance 36 | */ 37 | public static Track createTrack(final String name, final Set contentTypes, final MixerRole mixerRole, 38 | final double volume, final double pan) { 39 | final Track track = new Track(); 40 | track.channel = new Channel(); 41 | track.name = name; 42 | track.channel.volume = createRealParameter(Unit.LINEAR, volume); 43 | track.channel.pan = createRealParameter(Unit.NORMALIZED, pan); 44 | track.contentType = contentTypes.toArray(new ContentType[]{}); 45 | track.channel.role = mixerRole; 46 | return track; 47 | } 48 | 49 | /** 50 | * Creates an audio media file. 51 | * 52 | * @param relativePath 53 | * The relative path to the audio file 54 | * @param sampleRate 55 | * The sample rate 56 | * @param channels 57 | * The number of channels 58 | * @param duration 59 | * The duration in seconds 60 | * @return The audio instance 61 | */ 62 | public static Audio createAudio(final String relativePath, final int sampleRate, final int channels, 63 | final double duration) { 64 | final var audio = new Audio(); 65 | audio.timeUnit = TimeUnit.SECONDS; 66 | audio.file = new FileReference(); 67 | audio.file.path = relativePath; 68 | audio.file.external = Boolean.FALSE; 69 | audio.sampleRate = sampleRate; 70 | audio.channels = channels; 71 | audio.duration = duration; 72 | return audio; 73 | } 74 | 75 | /** 76 | * Creates a warp. 77 | * 78 | * @param time 79 | * The warped time 80 | * @param contentTime 81 | * The time of the content 82 | * @return The warp instance 83 | */ 84 | public static Warp createWarp(final double time, final double contentTime) { 85 | final var warp = new Warp(); 86 | warp.time = time; 87 | warp.contentTime = contentTime; 88 | return warp; 89 | } 90 | 91 | /** 92 | * Creates a clip. 93 | * 94 | * @param content 95 | * The content of the clip 96 | * @param time 97 | * The time position of the clip 98 | * @param duration 99 | * The duration of the clip 100 | * @return The clip instance 101 | */ 102 | public static Clip createClip(final Timeline content, final double time, final double duration) { 103 | final var clip = new Clip(); 104 | clip.content = content; 105 | clip.time = time; 106 | clip.duration = Double.valueOf(duration); 107 | return clip; 108 | } 109 | 110 | /** 111 | * Creates a Clips object from several clips. 112 | * 113 | * @param clips 114 | * The clips to wrap 115 | * @return The Clips instance 116 | */ 117 | public static Clips createClips(final Clip... clips) { 118 | final var timeline = new Clips(); 119 | Collections.addAll(timeline.clips, clips); 120 | return timeline; 121 | } 122 | 123 | /** 124 | * Creates a marker. 125 | * 126 | * @param time 127 | * The time position of the marker 128 | * @param name 129 | * The name of the marker 130 | * @return The marker instance 131 | */ 132 | public static Marker createMarker(final double time, final String name) { 133 | final var markerEvent = new Marker(); 134 | markerEvent.time = time; 135 | markerEvent.name = name; 136 | return markerEvent; 137 | } 138 | 139 | /** 140 | * Creates a point for an automation envelope. 141 | * 142 | * @param time 143 | * The time position of the point 144 | * @param value 145 | * The value of the point 146 | * @param interpolation 147 | * The interpolation to use 148 | * @return The point instance 149 | */ 150 | public static RealPoint createPoint(final double time, final double value, final Interpolation interpolation) { 151 | final var point = new RealPoint(); 152 | point.time = Double.valueOf(time); 153 | point.value = Double.valueOf(value); 154 | point.interpolation = interpolation; 155 | return point; 156 | } 157 | 158 | /** 159 | * Create a real parameter instance. 160 | * 161 | * @param unit 162 | * The unit of the parameter 163 | * @param value 164 | * The value of the parameter 165 | * @return The parameter 166 | */ 167 | public static RealParameter createRealParameter(final Unit unit, final double value) { 168 | final RealParameter param = new RealParameter(); 169 | param.unit = unit; 170 | param.value = Double.valueOf(value); 171 | return param; 172 | } 173 | 174 | /** 175 | * Create a real parameter instance. 176 | * 177 | * @param unit 178 | * The unit of the parameter 179 | * @param min 180 | * The minimum value of the parameter 181 | * @param max 182 | * The maximum value of the parameter 183 | * @param value 184 | * The value of the parameter 185 | * @return The parameter 186 | */ 187 | public static RealParameter createRealParameter(final Unit unit, final double min, final double max, 188 | final double value) { 189 | final RealParameter param = createRealParameter(unit, value); 190 | param.min = Double.valueOf(min); 191 | param.max = Double.valueOf(max); 192 | return param; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/AuPlugin.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlRootElement; 4 | 5 | /** The Apple AU plug-in format. */ 6 | @XmlRootElement(name = "AuPlugin") 7 | public class AuPlugin extends Plugin { 8 | // Intentionally empty 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/BuiltinDevice.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlRootElement; 4 | import jakarta.xml.bind.annotation.XmlSeeAlso; 5 | 6 | /** The built-in plug-in format. */ 7 | @XmlRootElement(name = "BuiltinDevice") 8 | @XmlSeeAlso({Equalizer.class, Compressor.class, NoiseGate.class, Limiter.class}) 9 | public class BuiltinDevice extends Device { 10 | // Intentionally empty 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/ClapPlugin.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * A CLAP Plug-in instance. 7 | * 8 | *

    9 | * The CLAP plug-in state should be stored in .clap-preset format. 10 | */ 11 | @XmlRootElement(name = "ClapPlugin") 12 | public class ClapPlugin extends Plugin { 13 | // Intentionally empty 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Compressor.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.BoolParameter; 7 | import com.bitwig.dawproject.RealParameter; 8 | 9 | /** A generic 'built-in' compressor. */ 10 | @XmlRootElement(name = "Compressor") 11 | public class Compressor extends BuiltinDevice { 12 | /** The threshold setting of the compressor in dB. */ 13 | @XmlElement(name = "Threshold") 14 | public RealParameter threshold; 15 | 16 | /** The ratio setting of the compressor in percent (0-100). */ 17 | @XmlElement(name = "Ratio") 18 | public RealParameter ratio; 19 | 20 | /** The attack setting of the compressor in seconds. */ 21 | @XmlElement(name = "Attack") 22 | public RealParameter attack; 23 | 24 | /** The release setting of the compressor in seconds. */ 25 | @XmlElement(name = "Release") 26 | public RealParameter release; 27 | 28 | /** Pre-compression gain stage. (input/gain/drive) in dB. */ 29 | @XmlElement(name = "InputGain") 30 | public RealParameter inputGain; 31 | 32 | /** Post-compression gain stage. (output/makeup gain) in dB. */ 33 | @XmlElement(name = "OutputGain") 34 | public RealParameter outputGain; 35 | 36 | /** Should auto makeup be applied? */ 37 | @XmlElement(name = "AutoMakeup") 38 | public BoolParameter autoMakeup; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Device.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlAttribute; 7 | import jakarta.xml.bind.annotation.XmlElement; 8 | import jakarta.xml.bind.annotation.XmlElementRef; 9 | import jakarta.xml.bind.annotation.XmlElementWrapper; 10 | import jakarta.xml.bind.annotation.XmlRootElement; 11 | import jakarta.xml.bind.annotation.XmlSeeAlso; 12 | 13 | import com.bitwig.dawproject.BoolParameter; 14 | import com.bitwig.dawproject.FileReference; 15 | import com.bitwig.dawproject.Parameter; 16 | import com.bitwig.dawproject.Referenceable; 17 | 18 | /** Either a Plug-in or native Device with in a DAW. */ 19 | @XmlRootElement(name = "Device") 20 | @XmlSeeAlso({Vst2Plugin.class, Vst3Plugin.class, ClapPlugin.class, BuiltinDevice.class, AuPlugin.class, 21 | Parameter.class}) 22 | public class Device extends Referenceable { 23 | /** This device is enabled (as in not bypassed). */ 24 | @XmlElement(name = "Enabled") 25 | public BoolParameter enabled; 26 | 27 | /** Role of this device/plug-in. */ 28 | @XmlAttribute(required = true) 29 | public DeviceRole deviceRole; 30 | 31 | /** If this device/plug-in is loaded/active of not. */ 32 | @XmlAttribute 33 | public Boolean loaded = Boolean.TRUE; 34 | 35 | /** Name of the device/plugin */ 36 | @XmlAttribute(required = true) 37 | public String deviceName; 38 | 39 | /** 40 | * Unique identifier of device/plug-in.
    41 | * Standards which use UUID as an identifier use the canonical textual 42 | * representation of the UUID (8-4-4-4-12 with no braces) (VST3)
    43 | * Standards which use an integer as an identifier use the value in decimal 44 | * form. (base-10 unsigned) (VST2)
    45 | * Text-based identifiers are used as-is. (CLAP) 46 | */ 47 | @XmlAttribute 48 | public String deviceID; 49 | 50 | /** Vendor name of the device/plugin */ 51 | @XmlAttribute 52 | public String deviceVendor; 53 | 54 | /** 55 | * Path to a file representing the device / plug-in state in its native format. 56 | * 57 | *

    58 | * This file must be embedded inside the container ZIP and have the 59 | * FileReference configured with (external=false). 60 | */ 61 | @XmlElement(name = "State", required = false) 62 | public FileReference state; 63 | 64 | /** 65 | * Parameters for this device, which is required for automated parameters in 66 | * order to provide an ID.
    67 | * Note: If the automated parameter is already present like the BuiltinDevice 68 | * parameters, it should not be included here as well. 69 | */ 70 | @XmlElementWrapper(name = "Parameters", required = false) 71 | @XmlElementRef 72 | public List automatedParameters = new ArrayList<>(); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/DeviceRole.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** The role of a device. */ 7 | @XmlEnum 8 | public enum DeviceRole { 9 | /** An instrument device. */ 10 | @XmlEnumValue("instrument") 11 | INSTRUMENT, 12 | 13 | /** A note/MIDI effect device. */ 14 | @XmlEnumValue("noteFX") 15 | NOTE_FX, 16 | 17 | /** An audio effect device. */ 18 | @XmlEnumValue("audioFX") 19 | AUDIO_FX, 20 | 21 | /** An analyzer device. */ 22 | @XmlEnumValue("analyzer") 23 | ANALYZER 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/EqBand.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElement; 5 | 6 | import com.bitwig.dawproject.BoolParameter; 7 | import com.bitwig.dawproject.RealParameter; 8 | 9 | /** The parameters of a band of a generic 'built-in' equalizer. */ 10 | public class EqBand { 11 | /** The frequency setting of the band. */ 12 | @XmlElement(name = "Freq", required = true) 13 | public RealParameter freq; 14 | 15 | /** The gain setting of the band. */ 16 | @XmlElement(name = "Gain") 17 | public RealParameter gain; 18 | 19 | /** The Q setting of the band. */ 20 | @XmlElement(name = "Q") 21 | public RealParameter Q; 22 | 23 | /** The enabled state of the band. */ 24 | @XmlElement(name = "Enabled") 25 | public BoolParameter enabled; 26 | 27 | /** The filter type of the band. */ 28 | @XmlAttribute(required = true) 29 | public EqBandType type; 30 | 31 | /** The index of the band. */ 32 | @XmlAttribute 33 | public Integer order; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/EqBandType.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** A type of a band of a generic 'built-in' equalizer. */ 7 | @XmlEnum 8 | public enum EqBandType { 9 | /** A high pass filter. */ 10 | @XmlEnumValue("highPass") 11 | HIGH_PASS, 12 | /** A low pass filter. */ 13 | @XmlEnumValue("lowPass") 14 | LOW_PASS, 15 | /** A band pass filter. */ 16 | @XmlEnumValue("bandPass") 17 | BAND_PASS, 18 | /** A high shelf filter. */ 19 | @XmlEnumValue("highShelf") 20 | HIGH_SHELF, 21 | /** A low shelf filter. */ 22 | @XmlEnumValue("lowShelf") 23 | LOW_SHELF, 24 | /** A bell filter. */ 25 | @XmlEnumValue("bell") 26 | BELL, 27 | /** A notch filter. */ 28 | @XmlEnumValue("notch") 29 | NOTCH 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Equalizer.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlElement; 7 | import jakarta.xml.bind.annotation.XmlRootElement; 8 | 9 | import com.bitwig.dawproject.RealParameter; 10 | 11 | /** A generic 'built-in' equalizer. */ 12 | @XmlRootElement(name = "Equalizer") 13 | public class Equalizer extends BuiltinDevice { 14 | /** The bands of the equalizer. */ 15 | @XmlElement(name = "Band") 16 | public List bands = new ArrayList<>(); 17 | 18 | /** The input gain of the equalizer in dB. */ 19 | @XmlElement(name = "InputGain") 20 | public RealParameter inputGain; 21 | 22 | /** The output gain of the equalizer in dB. */ 23 | @XmlElement(name = "OutputGain") 24 | public RealParameter outputGain; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Limiter.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.RealParameter; 7 | 8 | /** A generic 'built-in' limiter. */ 9 | @XmlRootElement(name = "Limiter") 10 | public class Limiter extends BuiltinDevice { 11 | /** The threshold setting of the limiter in dB. */ 12 | @XmlElement(name = "Threshold") 13 | public RealParameter threshold; 14 | 15 | /** The input gain of the limiter in dB. */ 16 | @XmlElement(name = "InputGain") 17 | public RealParameter inputGain; 18 | 19 | /** The output gain of the limiter in dB. */ 20 | @XmlElement(name = "OutputGain") 21 | public RealParameter outputGain; 22 | 23 | /** The attack setting of the limiter in seconds. */ 24 | @XmlElement(name = "Attack") 25 | public RealParameter attack; 26 | 27 | /** The release setting of the limiter in seconds. */ 28 | @XmlElement(name = "Release") 29 | public RealParameter release; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/NoiseGate.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlElement; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.RealParameter; 7 | 8 | /** A generic 'built-in' noise gate. */ 9 | @XmlRootElement(name = "NoiseGate") 10 | public class NoiseGate extends BuiltinDevice { 11 | /** The threshold of the noise gate in dB. */ 12 | @XmlElement(name = "Threshold") 13 | public RealParameter threshold; 14 | 15 | /** The ratio of the noise gate in percent (0-100). */ 16 | @XmlElement(name = "Ratio") 17 | public RealParameter ratio; 18 | 19 | /** The attack of the noise gate in seconds. */ 20 | @XmlElement(name = "Attack") 21 | public RealParameter attack; 22 | 23 | /** The release of the noise gate in seconds. */ 24 | @XmlElement(name = "Release") 25 | public RealParameter release; 26 | 27 | /** Range or amount of maximum gain reduction. Possible range [-inf to 0] */ 28 | @XmlElement(name = "Range") 29 | public RealParameter range; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Plugin.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | 5 | /** Abstract base class for all plug-in formats. */ 6 | public abstract class Plugin extends Device { 7 | /** Version of the plug-in */ 8 | @XmlAttribute 9 | public String pluginVersion; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Vst2Plugin.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * A VST2 Plug-in instance. 7 | * 8 | *

    9 | * The VST2 plug-in state should be stored in FXB or FXP format. 10 | */ 11 | @XmlRootElement(name = "Vst2Plugin") 12 | public class Vst2Plugin extends Plugin { 13 | // Intentionally empty 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/device/Vst3Plugin.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.device; 2 | 3 | import jakarta.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * A VST3 Plug-in instance. 7 | * 8 | *

    9 | * The VST3 plug-in state should be stored in .vstpreset format. 10 | */ 11 | @XmlRootElement(name = "Vst3Plugin") 12 | public class Vst3Plugin extends Plugin { 13 | // Intentionally empty 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Audio.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Representation of an audio file as a timeline. Duration should be the entire 8 | * length of the file, any clipping should be done by placing the Audio element 9 | * within a Clip element. The timeUnit attribute should always be set to 10 | * seconds. 11 | */ 12 | @XmlRootElement(name = "Audio") 13 | public class Audio extends MediaFile { 14 | /** Sample-rate of audio-file. */ 15 | @XmlAttribute(required = true) 16 | public int sampleRate; 17 | 18 | /** Number of channels of audio-file (1=mono, 2=stereo...). */ 19 | @XmlAttribute(required = true) 20 | public int channels; 21 | 22 | /** Playback algorithm used to warp audio (vendor-specific). */ 23 | @XmlAttribute(required = false) 24 | public String algorithm; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/AutomationTarget.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlIDREF; 5 | 6 | import com.bitwig.dawproject.ExpressionType; 7 | import com.bitwig.dawproject.Parameter; 8 | 9 | /** 10 | * Defines the target of automation or expression, usually used within a Points 11 | * element. 12 | * 13 | *

    14 | * Either it points directly ot a parameter or an expression, and in the 15 | * expression case it can either be monophonic (such as MIDI CCs) or 16 | * per-note/polyphonic (such as poly pressure) 17 | */ 18 | public class AutomationTarget { 19 | /** Parameter to automate. */ 20 | @XmlIDREF 21 | @XmlAttribute(required = false) 22 | public Parameter parameter; 23 | 24 | /** Expression type to control. */ 25 | @XmlAttribute(required = false) 26 | public ExpressionType expression; 27 | 28 | /** MIDI channel */ 29 | @XmlAttribute(required = false) 30 | public Integer channel; 31 | 32 | /** 33 | * MIDI key. 34 | * 35 | *

    36 | * Used when expression="polyPressure". 37 | */ 38 | @XmlAttribute(required = false) 39 | public Integer key; 40 | 41 | /** 42 | * MIDI Channel Controller Number (0 based index). 43 | * 44 | *

    45 | * Used when expression="channelController". 46 | */ 47 | @XmlAttribute(required = false) 48 | public Integer controller; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/BoolPoint.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** A single automation point for a boolean value. */ 7 | @XmlRootElement(name = "BoolPoint") 8 | public class BoolPoint extends Point { 9 | /** Boolean value of this point (true/false). */ 10 | @XmlAttribute(required = true) 11 | public Boolean value; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Clip.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElementRef; 5 | import jakarta.xml.bind.annotation.XmlIDREF; 6 | import jakarta.xml.bind.annotation.XmlRootElement; 7 | 8 | import com.bitwig.dawproject.Nameable; 9 | 10 | /** 11 | * A Clip provides a clipped view on to a Timeline, and is used either on a 12 | * Clips timeline (typically for arrangements) or inside a ClipSlot element (for 13 | * clip launcher Scenes). A Clip must either have a child-element inheriting 14 | * from Timeline or provide a ID reference to a timeline somewhere else (for 15 | * linked/alias clips). 16 | */ 17 | @XmlRootElement(name = "Clip") 18 | public class Clip extends Nameable { 19 | /** Time on the parent timeline where this clips starts playing. */ 20 | @XmlAttribute(required = true) 21 | public double time; 22 | 23 | /** 24 | * Duration on the parent timeline of this clip.
    25 | * If duration is omitted, it should be inferred from the playStop - playStart 26 | * instead.
    27 | * This is particularity useful when timeUnit and contentTimeUnit are different, 28 | * like when placing an audio clip with content length defined in seconds onto 29 | * an arrangement defined in beats. 30 | */ 31 | @XmlAttribute(required = false) 32 | public Double duration; 33 | 34 | /** 35 | * The TimeUnit used by the scope inside this timeline. This affects the 36 | * content/reference, playStart, playStop, loopStart, loopEnd but not time and 37 | * duration which are using the TimeUnit of the parent scope. 38 | */ 39 | @XmlAttribute(required = false) 40 | public TimeUnit contentTimeUnit; 41 | 42 | /** 43 | * Time inside the content timeline (or reference) where the clip starts 44 | * playing. 45 | */ 46 | @XmlAttribute(required = false) 47 | public Double playStart; 48 | 49 | /** 50 | * Time inside the content timeline (or reference) where the clip stops playing. 51 | */ 52 | @XmlAttribute(required = false) 53 | public Double playStop; 54 | 55 | /** 56 | * Time inside the content timeline (or reference) where the clip loop starts. 57 | */ 58 | @XmlAttribute(required = false) 59 | public Double loopStart; 60 | 61 | /** Time inside the content timeline (or reference) where the clip loop ends. */ 62 | @XmlAttribute(required = false) 63 | public Double loopEnd; 64 | 65 | /** The TimeUnit used by the fadeInTime and fadeOutTime. */ 66 | @XmlAttribute(required = false) 67 | public TimeUnit fadeTimeUnit; 68 | 69 | /** 70 | * Duration of fade-in. 71 | * 72 | *

    73 | * To create cross-fade, use a negative value which will make this Clip start at 74 | * t = time - abs(fadeInTime) 75 | */ 76 | @XmlAttribute(required = false) 77 | public Double fadeInTime; 78 | 79 | /** Duration of fade-out. */ 80 | @XmlAttribute(required = false) 81 | public Double fadeOutTime; 82 | 83 | /** Whether this clip should be played back. Default value is true. */ 84 | @XmlAttribute(required = false) 85 | public Boolean enable; 86 | 87 | /** Content Timeline this clip is playing. */ 88 | @XmlElementRef(required = false) 89 | public Timeline content; 90 | 91 | /** 92 | * Reference to a Content Timeline this clip is playing, in case of linked/alias 93 | * clips. You can use either content or reference for one clip, but not both. 94 | */ 95 | @XmlAttribute(required = false) 96 | @XmlIDREF 97 | public Timeline reference; 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/ClipSlot.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElement; 5 | import jakarta.xml.bind.annotation.XmlRootElement; 6 | 7 | /** 8 | * Represent a clip launcher slot within a Scene which can contain a Clip. It is 9 | * generally set to a specific track. 10 | */ 11 | @XmlRootElement(name = "ClipSlot") 12 | public class ClipSlot extends Timeline { 13 | /** 14 | * Whether launching this slot should stop the track playback when this slot is 15 | * empty. 16 | */ 17 | @XmlAttribute(required = false) 18 | public Boolean hasStop; 19 | 20 | /** Contained clip. */ 21 | @XmlElement(name = "Clip", required = false) 22 | public Clip clip; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Clips.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlElement; 7 | import jakarta.xml.bind.annotation.XmlRootElement; 8 | 9 | /** 10 | * Represents a timeline of clips. Each contained Clip have its time and 11 | * duration that defines its location on this timeline (defined by timeUnit of 12 | * the Clips element). 13 | */ 14 | @XmlRootElement(name = "Clips") 15 | public class Clips extends Timeline { 16 | /** Clips of this timeline. */ 17 | @XmlElement(name = "Clip") 18 | public List clips = new ArrayList<>(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/EnumPoint.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** A single automation point for an enumerated value. */ 7 | @XmlRootElement(name = "EnumPoint") 8 | public class EnumPoint extends Point { 9 | /** Integer value of the Enum index for this point. */ 10 | @XmlAttribute(required = true) 11 | public Integer value; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/IntegerPoint.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** A single automation point for an integer value. */ 7 | @XmlRootElement(name = "IntegerPoint") 8 | public class IntegerPoint extends Point { 9 | /** Integer value of this point. */ 10 | @XmlAttribute(required = true) 11 | public Integer value; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Lanes.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlElementRef; 7 | import jakarta.xml.bind.annotation.XmlRootElement; 8 | 9 | /** 10 | * The Lanes element provides the ability to contain multiple parallel timelines 11 | * inside it, and is the main layering element of the format. It is also a 12 | * natural fit for defining the scope of contained timelines to a specific 13 | * track. 14 | */ 15 | @XmlRootElement(name = "Lanes") 16 | public class Lanes extends Timeline { 17 | /** Lanes representing nested content. */ 18 | @XmlElementRef 19 | public List lanes = new ArrayList<>(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Marker.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | import com.bitwig.dawproject.Nameable; 7 | 8 | /** A single cue-marker. */ 9 | @XmlRootElement(name = "Marker") 10 | public class Marker extends Nameable { 11 | /** Time on the parent timeline of this marker. */ 12 | @XmlAttribute(required = true) 13 | public double time; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Markers.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlElement; 7 | import jakarta.xml.bind.annotation.XmlRootElement; 8 | 9 | /** Represents a timeline of cue-markers. */ 10 | @XmlRootElement 11 | public class Markers extends Timeline { 12 | /** Markers of this timeline. */ 13 | @XmlElement(required = true, name = "Marker") 14 | public List markers = new ArrayList<>(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/MediaFile.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElement; 5 | 6 | import com.bitwig.dawproject.FileReference; 7 | 8 | /** 9 | * A media file. (audio or video). 10 | * 11 | *

    12 | * The duration attribute is intended to be provide the file length (and not be 13 | * interpreted as a playback parameter, use a Clip or Warps element for that). 14 | */ 15 | public class MediaFile extends Timeline { 16 | /** The media file. */ 17 | @XmlElement(name = "File", required = true) 18 | public FileReference file = new FileReference(); 19 | 20 | /** Duration in seconds of the media file (as stored). */ 21 | @XmlAttribute(required = true) 22 | public double duration; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Note.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlElementRef; 5 | import jakarta.xml.bind.annotation.XmlRootElement; 6 | import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 7 | 8 | import com.bitwig.dawproject.DoubleAdapter; 9 | 10 | /** 11 | * A single Note (MIDI-style). It can additionally contain child timelines to 12 | * hold per-note expression. 13 | */ 14 | @XmlRootElement(name = "Note") 15 | public final class Note { 16 | /** Time on the parent timeline where this note starts playing. */ 17 | @XmlAttribute(required = true) 18 | @XmlJavaTypeAdapter(DoubleAdapter.class) 19 | public Double time; 20 | 21 | /** Duration on the parent timeline of this note. */ 22 | @XmlAttribute(required = true) 23 | @XmlJavaTypeAdapter(DoubleAdapter.class) 24 | public Double duration; 25 | 26 | /** MIDI channel of this note. */ 27 | @XmlAttribute(required = false) 28 | public int channel; 29 | 30 | /** MIDI key of this note. */ 31 | @XmlAttribute(required = true) 32 | public int key; 33 | 34 | /** Note On Velocity of this note. (normalized) */ 35 | @XmlAttribute(name = "vel") 36 | @XmlJavaTypeAdapter(DoubleAdapter.class) 37 | public Double velocity; 38 | 39 | /** Note Off Velocity of this note. (normalized) */ 40 | @XmlAttribute(name = "rel") 41 | @XmlJavaTypeAdapter(DoubleAdapter.class) 42 | public Double releaseVelocity; 43 | 44 | /** Per-note expressions can be stored within the note object as timelines. */ 45 | @XmlElementRef(name = "Content", required = false) 46 | public Timeline content; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Notes.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlElement; 7 | import jakarta.xml.bind.annotation.XmlRootElement; 8 | 9 | /** Timeline containing Notes (MIDI-style) */ 10 | @XmlRootElement(name = "Notes") 11 | public class Notes extends Timeline { 12 | /** Contained notes. */ 13 | @XmlElement(name = "Note") 14 | public List notes = new ArrayList<>(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Point.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | import jakarta.xml.bind.annotation.XmlSeeAlso; 6 | import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 7 | 8 | import com.bitwig.dawproject.DoubleAdapter; 9 | 10 | /** A single automation point (abstract class). */ 11 | @XmlRootElement(name = "Point") 12 | @XmlSeeAlso({RealPoint.class, EnumPoint.class, BoolPoint.class, IntegerPoint.class, TimeSignaturePoint.class}) 13 | public abstract class Point { 14 | /** Time (within enclosing Points timeline) of this event */ 15 | @XmlJavaTypeAdapter(DoubleAdapter.class) 16 | @XmlAttribute(required = true) 17 | public Double time; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Points.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlAttribute; 7 | import jakarta.xml.bind.annotation.XmlElement; 8 | import jakarta.xml.bind.annotation.XmlElementRef; 9 | import jakarta.xml.bind.annotation.XmlRootElement; 10 | import jakarta.xml.bind.annotation.XmlType; 11 | 12 | import com.bitwig.dawproject.Unit; 13 | 14 | /** 15 | * A timeline of points for automation or expression. 16 | * 17 | *

    18 | * All the points should be of the same element-type and match the target. 19 | */ 20 | @XmlRootElement(name = "Points") 21 | @XmlType(propOrder = {"target", "points", "unit"}) 22 | public class Points extends Timeline { 23 | /** The parameter or expression this timeline should target. */ 24 | @XmlElement(name = "Target", required = true) 25 | public AutomationTarget target = new AutomationTarget(); 26 | 27 | /** 28 | * The contained points. They should all be of the same type and match the 29 | * target parameter. 30 | */ 31 | @XmlElementRef(required = true) 32 | public List points = new ArrayList<>(); 33 | 34 | /** A unit should be provided for when used with RealPoint elements. */ 35 | @XmlAttribute(required = false) 36 | public Unit unit; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/RealPoint.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 6 | 7 | import com.bitwig.dawproject.DoubleAdapter; 8 | import com.bitwig.dawproject.Interpolation; 9 | 10 | /** 11 | * A point with a double resolution and additional interpolation information. 12 | */ 13 | @XmlRootElement(name = "RealPoint") 14 | public class RealPoint extends Point { 15 | /** The value of the point. */ 16 | @XmlJavaTypeAdapter(DoubleAdapter.class) 17 | @XmlAttribute(required = true) 18 | public Double value; 19 | 20 | /** 21 | * Interpolation mode used for the segment starting at this point. Default to 22 | * 'hold' when unspecified. 23 | */ 24 | @XmlAttribute(required = false) 25 | public Interpolation interpolation; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/TimeSignaturePoint.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** A single automation point for a time-signature value. */ 7 | @XmlRootElement(name = "TimeSignaturePoint") 8 | public class TimeSignaturePoint extends Point { 9 | /** Numerator of the time-signature. (3/4 → 3, 4/4 → 4) */ 10 | @XmlAttribute(required = true) 11 | public Integer numerator; 12 | 13 | /** Denominator of the time-signature. (3/4 → 4, 7/8 → 8) */ 14 | @XmlAttribute(required = true) 15 | public Integer denominator; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/TimeUnit.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlEnum; 4 | import jakarta.xml.bind.annotation.XmlEnumValue; 5 | 6 | /** The unit of time. */ 7 | @XmlEnum 8 | public enum TimeUnit { 9 | /** Time is represented in beats (quarter-notes). */ 10 | @XmlEnumValue("beats") 11 | BEATS, 12 | /** Time is represented in seconds. */ 13 | @XmlEnumValue("seconds") 14 | SECONDS; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Timeline.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlIDREF; 5 | import jakarta.xml.bind.annotation.XmlRootElement; 6 | import jakarta.xml.bind.annotation.XmlSeeAlso; 7 | 8 | import com.bitwig.dawproject.Referenceable; 9 | import com.bitwig.dawproject.Track; 10 | 11 | /** Abstract base class for all timeline structures. */ 12 | @XmlRootElement(name = "Timeline") 13 | @XmlSeeAlso({Note.class, Notes.class, Lanes.class, Clip.class, Clips.class, ClipSlot.class, Marker.class, Markers.class, 14 | Warps.class, Audio.class, Video.class, Point.class, Points.class}) 15 | public abstract class Timeline extends Referenceable { 16 | /** When present, the timeline is local to this track. */ 17 | @XmlAttribute(required = false) 18 | @XmlIDREF 19 | public Track track; 20 | 21 | /** 22 | * The TimeUnit used by this and nested timelines. If no TimeUnit is provided by 23 | * this or the parent scope then 'beats' will be used. 24 | */ 25 | @XmlAttribute(required = false) 26 | public TimeUnit timeUnit; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Video.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * Representation of a video file as a timeline. Duration should be the entire 8 | * length of the file, any clipping should be done by placing the Audio element 9 | * within a Clip element. The timeUnit attribute should always be set to 10 | * seconds. 11 | */ 12 | @XmlRootElement(name = "Video") 13 | public class Video extends MediaFile { 14 | /** Sample-rate of audio (if present). */ 15 | @XmlAttribute(required = false) 16 | public int sampleRate; 17 | 18 | /** Number of channels of audio (1=mono..., if present). */ 19 | @XmlAttribute(required = false) 20 | public int channels; 21 | 22 | /** Playback algorithm used to warp audio (vendor-specific, if present). */ 23 | @XmlAttribute(required = false) 24 | public String algorithm; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Warp.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import jakarta.xml.bind.annotation.XmlAttribute; 4 | import jakarta.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * A single warp event, which defines the time both on the outer scope (time) 8 | * and the inner scope (contentTime). The time range between the Warp events are 9 | * assumed to be linearly interpolated. 10 | */ 11 | @XmlRootElement(name = "Warp") 12 | public class Warp { 13 | /** 14 | * The time this point represent to the 'outside' of the Warps element. The 15 | * TimeUnit is defined by the parent Warps element timeUnit attribute or 16 | * inherited from the parent element of the Warps container 17 | */ 18 | @XmlAttribute(required = true) 19 | public double time; 20 | 21 | /** 22 | * The time this point represent to the 'inside' of the Warps element. The 23 | * TimeUnit is defined by the parent Warps element contentTimeUnit attribute 24 | */ 25 | @XmlAttribute(required = true) 26 | public double contentTime; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/bitwig/dawproject/timeline/Warps.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject.timeline; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import jakarta.xml.bind.annotation.XmlAttribute; 7 | import jakarta.xml.bind.annotation.XmlElement; 8 | import jakarta.xml.bind.annotation.XmlElementRef; 9 | import jakarta.xml.bind.annotation.XmlRootElement; 10 | 11 | /** 12 | * Warps the time of nested content as defined by a list of Warp points. 13 | * 14 | *

    15 | * A typical use case would be to warp an audio-file (contentTimeUnit = seconds) 16 | * onto a timeline defined in beats (timeUnit = beats) as defined by a set of 17 | * Warp events. 18 | * 19 | *

    20 | * At least two Warp events need to present in order to define a usable 21 | * beats/seconds conversion. For a plain fixed-speed mapping, provide two event: 22 | * One at (0,0) and a second event with the desired beat-time length along with 23 | * the length of the contained Audio file in seconds. 24 | * 25 | *

    {@code
    26 |  * 
    27 |  * 
    28 |  *   
    29 |  *     
    32 |  *     
    33 |  *     
    34 |  *   
    35 |  * 
    36 |  * }
    37 | */ 38 | @XmlRootElement(name = "Warps") 39 | public class Warps extends Timeline { 40 | /** Warp events defining the transformation (minimum 2). */ 41 | @XmlElement(required = true, name = "Warp") 42 | public List events = new ArrayList<>(); 43 | 44 | /** Content timeline to be warped. */ 45 | @XmlElementRef(name = "Content", required = true) 46 | public Timeline content; 47 | 48 | /** 49 | * The TimeUnit used by the content (nested) timeline and the contentTime 50 | * attribute of the Warp events. 51 | */ 52 | @XmlAttribute(required = true) 53 | public TimeUnit contentTimeUnit; 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** Module definition for dawproject. */ 2 | module com.bitwig.dawproject 3 | { 4 | requires jakarta.xml.bind; 5 | requires org.apache.commons.io; 6 | 7 | 8 | opens com.bitwig.dawproject to jakarta.xml.bind; 9 | opens com.bitwig.dawproject.device to jakarta.xml.bind; 10 | opens com.bitwig.dawproject.timeline to jakarta.xml.bind; 11 | 12 | 13 | exports com.bitwig.dawproject; 14 | exports com.bitwig.dawproject.device; 15 | exports com.bitwig.dawproject.timeline; 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/bitwig/dawproject/DawProjectTest.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.EnumSet; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.function.BiConsumer; 11 | 12 | import com.bitwig.dawproject.device.Device; 13 | import com.bitwig.dawproject.device.DeviceRole; 14 | import com.bitwig.dawproject.device.Vst3Plugin; 15 | import com.bitwig.dawproject.timeline.Clip; 16 | import com.bitwig.dawproject.timeline.Clips; 17 | import com.bitwig.dawproject.timeline.Lanes; 18 | import com.bitwig.dawproject.timeline.Markers; 19 | import com.bitwig.dawproject.timeline.Note; 20 | import com.bitwig.dawproject.timeline.Notes; 21 | import com.bitwig.dawproject.timeline.Points; 22 | import com.bitwig.dawproject.timeline.TimeUnit; 23 | import com.bitwig.dawproject.timeline.Warps; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | 27 | /** Several tests for the reading/writing DAWproject files. */ 28 | public class DawProjectTest { 29 | private enum Features { 30 | CUE_MARKERS, CLIPS, AUDIO, NOTES, AUTOMATION, ALIAS_CLIPS, PLUGINS 31 | } 32 | 33 | private enum AudioScenario { 34 | WARPED, RAW_BEATS, RAW_SECONDS, FILE_WITH_ABSOLUTE_PATH, FILE_WITH_RELATIVE_PATH 35 | } 36 | 37 | private final Set simpleFeatures = EnumSet.of(Features.CLIPS, Features.NOTES, Features.AUDIO); 38 | 39 | /** 40 | * Validate a project. 41 | * 42 | * @throws IOException 43 | * Could not validate the project 44 | */ 45 | @Test 46 | public void validateDawProject() throws IOException { 47 | final Project project = createDummyProject(3, this.simpleFeatures); 48 | DawProject.validate(project); 49 | } 50 | 51 | /** 52 | * Validate a complex project. 53 | * 54 | * @throws IOException 55 | * Could not validate the project 56 | */ 57 | @Test 58 | public void validateComplexDawProject() throws IOException { 59 | final Project project = createDummyProject(3, EnumSet.allOf(Features.class)); 60 | DawProject.validate(project); 61 | } 62 | 63 | /** 64 | * Test storing a project. 65 | * 66 | * @throws IOException 67 | * Could not store the project 68 | */ 69 | @Test 70 | public void saveDawProject() throws IOException { 71 | final Project project = createDummyProject(3, this.simpleFeatures); 72 | final MetaData metadata = new MetaData(); 73 | 74 | final Map embeddedFiles = new HashMap<>(); 75 | final File file = new File("target/test.dawproject"); 76 | DawProject.save(project, metadata, embeddedFiles, file); 77 | DawProject.saveXML(project, new File("target/test.dawproject.xml")); 78 | Assert.assertTrue(file.exists()); 79 | } 80 | 81 | /** 82 | * Test storing and loading a project. 83 | * 84 | * @throws IOException 85 | * Could not load the project 86 | */ 87 | @Test 88 | public void saveAndLoadDawProject() throws IOException { 89 | final Project project = createDummyProject(5, this.simpleFeatures); 90 | final MetaData metadata = new MetaData(); 91 | 92 | final var file = File.createTempFile("testfile", ".dawproject"); 93 | final Map embeddedFiles = new HashMap<>(); 94 | DawProject.save(project, metadata, embeddedFiles, file); 95 | 96 | final var loadedProject = DawProject.loadProject(file); 97 | 98 | Assert.assertEquals(project.structure.size(), loadedProject.structure.size()); 99 | Assert.assertEquals(project.scenes.size(), loadedProject.scenes.size()); 100 | } 101 | 102 | /** 103 | * Test storing a complex project. 104 | * 105 | * @throws IOException 106 | * Could not store the project 107 | */ 108 | @Test 109 | public void saveComplexDawProject() throws IOException { 110 | final Project project = createDummyProject(3, EnumSet.allOf(Features.class)); 111 | final MetaData metadata = new MetaData(); 112 | 113 | final Map embeddedFiles = new HashMap<>(); 114 | final File file = new File("target/test-complex.dawproject"); 115 | DawProject.save(project, metadata, embeddedFiles, file); 116 | DawProject.saveXML(project, new File("target/test-complex.dawproject.xml")); 117 | Assert.assertTrue(file.exists()); 118 | } 119 | 120 | /** 121 | * Test storing and loading a complex project. 122 | * 123 | * @throws IOException 124 | * Could not store or load the project 125 | */ 126 | @Test 127 | public void saveAndLoadComplexDawProject() throws IOException { 128 | final Project project = createDummyProject(5, EnumSet.allOf(Features.class)); 129 | final MetaData metadata = new MetaData(); 130 | 131 | final Map embeddedFiles = new HashMap<>(); 132 | final var file = File.createTempFile("testfile2", ".dawproject"); 133 | DawProject.save(project, metadata, embeddedFiles, file); 134 | 135 | final var loadedProject = DawProject.loadProject(file); 136 | 137 | Assert.assertEquals(project.structure.size(), loadedProject.structure.size()); 138 | Assert.assertEquals(project.scenes.size(), loadedProject.scenes.size()); 139 | Assert.assertEquals(project.arrangement.lanes.getClass(), loadedProject.arrangement.lanes.getClass()); 140 | Assert.assertEquals(project.arrangement.markers.getClass(), loadedProject.arrangement.markers.getClass()); 141 | } 142 | 143 | /** 144 | * Test creating the metadata XML schema. 145 | * 146 | * @throws IOException 147 | * Could not create the XML schema 148 | */ 149 | @Test 150 | public void writeMetadataSchema() throws IOException { 151 | final File file = new File("MetaData.xsd"); 152 | DawProject.exportSchema(file, MetaData.class); 153 | Assert.assertTrue(file.exists()); 154 | } 155 | 156 | /** 157 | * Test creating the project XML schema. 158 | * 159 | * @throws IOException 160 | * Could not create the XML schema 161 | */ 162 | @Test 163 | public void writeProjectSchema() throws IOException { 164 | final File file = new File("Project.xsd"); 165 | DawProject.exportSchema(file, Project.class); 166 | Assert.assertTrue(file.exists()); 167 | } 168 | 169 | /** 170 | * Test audio clips with offsets and fades. 171 | * 172 | * @throws IOException 173 | * Could not create 174 | */ 175 | @Test 176 | public void createAudioExample() throws IOException { 177 | for (AudioScenario scenario : AudioScenario.values()) { 178 | createAudioExample(0, 0, scenario, false); 179 | if (shouldTestOffsetAndFades(scenario)) { 180 | createAudioExample(0, 0, scenario, true); 181 | createAudioExample(1, 0, scenario, false); 182 | createAudioExample(0, 1, scenario, false); 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * Test MIDI automation envelopes. 189 | * 190 | * @throws IOException 191 | * Could not create 192 | */ 193 | @Test 194 | public void createMIDIAutomationInClipsExample() throws IOException { 195 | createMIDIAutomationExample("MIDI-CC1-AutomationOnTrack", false, false); 196 | createMIDIAutomationExample("MIDI-CC1-AutomationInClips", true, false); 197 | createMIDIAutomationExample("MIDI-PitchBend-AutomationOnTrack", false, true); 198 | createMIDIAutomationExample("MIDI-PitchBend-AutomationInClips", true, true); 199 | } 200 | 201 | /** 202 | * Test the double adapter for infinity constants. 203 | * 204 | * @throws Exception 205 | * Could not parse the infinity constants 206 | */ 207 | @Test 208 | public void testDoubleAdapter() throws Exception { 209 | final var adapter = new DoubleAdapter(); 210 | Assert.assertEquals(adapter.unmarshal("-inf").doubleValue(), Double.NEGATIVE_INFINITY, 0); 211 | Assert.assertEquals(adapter.unmarshal("inf").doubleValue(), Double.POSITIVE_INFINITY, 0); 212 | Assert.assertEquals("inf", adapter.marshal(Double.valueOf(Double.POSITIVE_INFINITY))); 213 | Assert.assertEquals("-inf", adapter.marshal(Double.valueOf(Double.NEGATIVE_INFINITY))); 214 | } 215 | 216 | private static boolean shouldTestOffsetAndFades(final AudioScenario scenario) { 217 | return switch (scenario) { 218 | case WARPED -> true; 219 | case RAW_BEATS -> true; 220 | case RAW_SECONDS -> true; 221 | default -> false; 222 | }; 223 | } 224 | 225 | private static void createAudioExample(final double playStartOffset, final double clipTime, 226 | final AudioScenario scenario, final boolean withFades) throws IOException { 227 | String name = "Audio" + scenario.name(); 228 | if (withFades) 229 | name += "WithFades"; 230 | if (playStartOffset != 0) 231 | name += "Offset"; 232 | if (clipTime != 0) 233 | name += "Clipstart"; 234 | 235 | final Project project = createEmptyProject(); 236 | final Track masterTrack = Utility.createTrack("Master", EnumSet.noneOf(ContentType.class), MixerRole.MASTER, 1, 237 | 0.5); 238 | final var audioTrack = Utility.createTrack("Audio", EnumSet.of(ContentType.AUDIO), MixerRole.REGULAR, 1, 0.5); 239 | audioTrack.channel.destination = masterTrack.channel; 240 | 241 | project.structure.add(masterTrack); 242 | project.structure.add(audioTrack); 243 | 244 | project.arrangement = new Arrangement(); 245 | project.transport = new Transport(); 246 | project.transport.tempo = Utility.createRealParameter(Unit.BPM, 155.0); 247 | final var arrangementLanes = new Lanes(); 248 | project.arrangement.lanes = arrangementLanes; 249 | final var arrangementIsInSeconds = scenario == AudioScenario.RAW_SECONDS; 250 | project.arrangement.lanes.timeUnit = arrangementIsInSeconds ? TimeUnit.SECONDS : TimeUnit.BEATS; 251 | 252 | final var sample = "white-glasses.wav"; 253 | Clip audioClip; 254 | final var sampleDuration = 3.097; 255 | final var audio = Utility.createAudio(sample, 44100, 2, sampleDuration); 256 | 257 | if (scenario == AudioScenario.FILE_WITH_ABSOLUTE_PATH) { 258 | audio.file.external = Boolean.TRUE; 259 | audio.file.path = new File("test-data", sample).getAbsolutePath(); 260 | } else if (scenario == AudioScenario.FILE_WITH_RELATIVE_PATH) { 261 | audio.file.external = Boolean.TRUE; 262 | audio.file.path = "../test-data/" + sample; 263 | } 264 | 265 | if (scenario == AudioScenario.WARPED) { 266 | final var warps = new Warps(); 267 | warps.content = audio; 268 | warps.contentTimeUnit = TimeUnit.SECONDS; 269 | warps.events.add(Utility.createWarp(0, 0)); 270 | warps.events.add(Utility.createWarp(8, sampleDuration)); 271 | audioClip = Utility.createClip(warps, clipTime, 8); 272 | audioClip.contentTimeUnit = TimeUnit.BEATS; 273 | audioClip.playStart = Double.valueOf(playStartOffset); 274 | if (withFades) { 275 | audioClip.fadeTimeUnit = TimeUnit.BEATS; 276 | audioClip.fadeInTime = Double.valueOf(0.25); 277 | audioClip.fadeOutTime = Double.valueOf(0.25); 278 | } 279 | } else { 280 | audioClip = Utility.createClip(audio, clipTime, arrangementIsInSeconds ? sampleDuration : 8); 281 | audioClip.contentTimeUnit = TimeUnit.SECONDS; 282 | audioClip.playStart = Double.valueOf(playStartOffset); 283 | audioClip.playStop = Double.valueOf(sampleDuration); 284 | if (withFades) { 285 | audioClip.fadeTimeUnit = TimeUnit.SECONDS; 286 | audioClip.fadeInTime = Double.valueOf(0.1); 287 | audioClip.fadeOutTime = Double.valueOf(0.1); 288 | } 289 | } 290 | 291 | final var clips = Utility.createClips(audioClip); 292 | clips.track = audioTrack; 293 | arrangementLanes.lanes.add(clips); 294 | 295 | saveTestProject(project, name, (meta, files) -> files.put(new File("test-data/" + sample), sample)); 296 | } 297 | 298 | private static void createMIDIAutomationExample(final String name, final boolean inClips, final boolean isPitchBend) 299 | throws IOException { 300 | final Project project = createEmptyProject(); 301 | final Track masterTrack = Utility.createTrack("Master", EnumSet.noneOf(ContentType.class), MixerRole.MASTER, 1, 302 | 0.5); 303 | final var instrumentTrack = Utility.createTrack("Notes", EnumSet.of(ContentType.NOTES), MixerRole.REGULAR, 1, 304 | 0.5); 305 | instrumentTrack.channel.destination = masterTrack.channel; 306 | 307 | project.structure.add(masterTrack); 308 | project.structure.add(instrumentTrack); 309 | 310 | project.arrangement = new Arrangement(); 311 | project.transport = new Transport(); 312 | project.transport.tempo = Utility.createRealParameter(Unit.BPM, 123.0); 313 | final var arrangementLanes = new Lanes(); 314 | project.arrangement.lanes = arrangementLanes; 315 | project.arrangement.lanes.timeUnit = TimeUnit.BEATS; 316 | 317 | // Create some mod-wheel or pitch-bend automation 318 | final var automation = new Points(); 319 | automation.unit = Unit.NORMALIZED; 320 | if (isPitchBend) { 321 | automation.target.expression = ExpressionType.PITCH_BEND; 322 | automation.target.channel = Integer.valueOf(0); 323 | } else { 324 | automation.target.expression = ExpressionType.CHANNEL_CONTROLLER; 325 | automation.target.channel = Integer.valueOf(0); 326 | automation.target.controller = Integer.valueOf(1); 327 | } 328 | automation.points.add(Utility.createPoint(0, 0.0, Interpolation.LINEAR)); 329 | automation.points.add(Utility.createPoint(1, 0.0, Interpolation.LINEAR)); 330 | automation.points.add(Utility.createPoint(2, 0.5, Interpolation.LINEAR)); 331 | automation.points.add(Utility.createPoint(3, 0.5, Interpolation.LINEAR)); 332 | automation.points.add(Utility.createPoint(4, 1.0, Interpolation.LINEAR)); 333 | automation.points.add(Utility.createPoint(5, 1.0, Interpolation.LINEAR)); 334 | automation.points.add(Utility.createPoint(6, 0.5, Interpolation.LINEAR)); 335 | automation.points.add(Utility.createPoint(7, 1, Interpolation.HOLD)); 336 | automation.points.add(Utility.createPoint(8, 0.5, Interpolation.HOLD)); 337 | 338 | if (inClips) { 339 | final var noteClip = Utility.createClip(automation, 0, 8); 340 | final var clips = Utility.createClips(noteClip); 341 | clips.track = instrumentTrack; 342 | arrangementLanes.lanes.add(clips); 343 | } else { 344 | automation.track = instrumentTrack; 345 | arrangementLanes.lanes.add(automation); 346 | } 347 | 348 | saveTestProject(project, name, null); 349 | } 350 | 351 | private static void saveTestProject(final Project project, final String name, 352 | final BiConsumer> configurer) throws IOException { 353 | final MetaData metadata = new MetaData(); 354 | final Map embeddedFiles = new HashMap<>(); 355 | 356 | if (configurer != null) 357 | configurer.accept(metadata, embeddedFiles); 358 | 359 | DawProject.save(project, metadata, embeddedFiles, new File("target/" + name + ".dawproject")); 360 | DawProject.saveXML(project, new File("target/" + name + ".xml")); 361 | DawProject.validate(project); 362 | } 363 | 364 | private static Project createDummyProject(final int numTracks, final Set features) { 365 | final Project project = createEmptyProject(); 366 | 367 | final Track masterTrack = Utility.createTrack("Master", EnumSet.noneOf(ContentType.class), MixerRole.MASTER, 1, 368 | 0.5); 369 | project.structure.add(masterTrack); 370 | 371 | if (features.contains(Features.PLUGINS)) { 372 | final Device device = new Vst3Plugin(); 373 | device.deviceName = "Limiter"; 374 | device.deviceRole = DeviceRole.AUDIO_FX; 375 | device.state = new FileReference(); 376 | device.state.path = "plugin-states/12323545.vstpreset"; 377 | 378 | if (masterTrack.channel.devices == null) 379 | masterTrack.channel.devices = new ArrayList<>(); 380 | 381 | masterTrack.channel.devices.add(device); 382 | } 383 | 384 | project.arrangement = new Arrangement(); 385 | final var arrangementLanes = new Lanes(); 386 | arrangementLanes.timeUnit = TimeUnit.BEATS; 387 | project.arrangement.lanes = arrangementLanes; 388 | 389 | if (features.contains(Features.CUE_MARKERS)) { 390 | final var cueMarkers = new Markers(); 391 | project.arrangement.markers = cueMarkers; 392 | cueMarkers.markers.add(Utility.createMarker(0, "Verse")); 393 | cueMarkers.markers.add(Utility.createMarker(24, "Chorus")); 394 | } 395 | 396 | for (int i = 0; i < numTracks; i++) { 397 | final var track = Utility.createTrack("Track " + (i + 1), EnumSet.of(ContentType.NOTES), MixerRole.REGULAR, 398 | 1, 0.5); 399 | project.structure.add(track); 400 | track.color = "#" + i + i + i + i + i + i; 401 | track.channel.destination = masterTrack.channel; 402 | 403 | final var trackLanes = new Lanes(); 404 | trackLanes.track = track; 405 | arrangementLanes.lanes.add(trackLanes); 406 | 407 | if (features.contains(Features.CLIPS)) { 408 | final var clips = new Clips(); 409 | 410 | trackLanes.lanes.add(clips); 411 | 412 | final var clip = new Clip(); 413 | clip.name = "Clip " + i; 414 | clip.time = 8 * i; 415 | clip.duration = Double.valueOf(4.0); 416 | clips.clips.add(clip); 417 | 418 | final var notes = new Notes(); 419 | clip.content = notes; 420 | 421 | for (int j = 0; j < 8; j++) { 422 | final var note = new Note(); 423 | note.key = 36 + 12 * (j % (1 + i)); 424 | note.velocity = Double.valueOf(0.8); 425 | note.releaseVelocity = Double.valueOf(0.5); 426 | note.time = Double.valueOf(0.5 * j); 427 | note.duration = Double.valueOf(0.5); 428 | notes.notes.add(note); 429 | } 430 | 431 | if (features.contains(Features.ALIAS_CLIPS)) { 432 | final var clip2 = new Clip(); 433 | clip2.name = "Alias Clip " + i; 434 | clip2.time = 32 + 8 * i; 435 | clip2.duration = Double.valueOf(4.0); 436 | clips.clips.add(clip2); 437 | clip2.reference = notes; 438 | } 439 | 440 | if (i == 0 && features.contains(Features.AUTOMATION)) { 441 | final var points = new Points(); 442 | points.target.parameter = track.channel.volume; 443 | trackLanes.lanes.add(points); 444 | 445 | // fade-in over 8 quarter notes 446 | points.points.add(Utility.createPoint(0.0, 0.0, Interpolation.LINEAR)); 447 | points.points.add(Utility.createPoint(8.0, 1.0, Interpolation.LINEAR)); 448 | } 449 | } 450 | } 451 | 452 | return project; 453 | } 454 | 455 | private static Project createEmptyProject() { 456 | Referenceable.setAutoID(true); 457 | 458 | final Project project = new Project(); 459 | project.application.name = "Test"; 460 | project.application.version = "1.0"; 461 | return project; 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/test/java/com/bitwig/dawproject/GenerateDocumentationTest.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import static j2html.TagCreator.a; 4 | import static j2html.TagCreator.b; 5 | import static j2html.TagCreator.body; 6 | import static j2html.TagCreator.br; 7 | import static j2html.TagCreator.div; 8 | import static j2html.TagCreator.h1; 9 | import static j2html.TagCreator.h2; 10 | import static j2html.TagCreator.h3; 11 | import static j2html.TagCreator.head; 12 | import static j2html.TagCreator.html; 13 | import static j2html.TagCreator.link; 14 | import static j2html.TagCreator.p; 15 | import static j2html.TagCreator.rawHtml; 16 | import static j2html.TagCreator.span; 17 | import static j2html.TagCreator.table; 18 | import static j2html.TagCreator.td; 19 | import static j2html.TagCreator.text; 20 | import static j2html.TagCreator.th; 21 | import static j2html.TagCreator.title; 22 | import static j2html.TagCreator.tr; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.lang.annotation.Annotation; 27 | import java.lang.reflect.Field; 28 | import java.lang.reflect.Modifier; 29 | import java.lang.reflect.ParameterizedType; 30 | import java.nio.charset.StandardCharsets; 31 | import java.nio.file.Files; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.Collections; 35 | import java.util.Comparator; 36 | import java.util.List; 37 | import java.util.Optional; 38 | import java.util.stream.Collectors; 39 | 40 | import jakarta.xml.bind.annotation.XmlAttribute; 41 | import jakarta.xml.bind.annotation.XmlElement; 42 | import jakarta.xml.bind.annotation.XmlElementRef; 43 | import jakarta.xml.bind.annotation.XmlElementWrapper; 44 | import jakarta.xml.bind.annotation.XmlEnumValue; 45 | import jakarta.xml.bind.annotation.XmlIDREF; 46 | import jakarta.xml.bind.annotation.XmlRootElement; 47 | 48 | import com.bitwig.dawproject.device.AuPlugin; 49 | import com.bitwig.dawproject.device.BuiltinDevice; 50 | import com.bitwig.dawproject.device.ClapPlugin; 51 | import com.bitwig.dawproject.device.Compressor; 52 | import com.bitwig.dawproject.device.Device; 53 | import com.bitwig.dawproject.device.EqBand; 54 | import com.bitwig.dawproject.device.Equalizer; 55 | import com.bitwig.dawproject.device.Limiter; 56 | import com.bitwig.dawproject.device.NoiseGate; 57 | import com.bitwig.dawproject.device.Plugin; 58 | import com.bitwig.dawproject.device.Vst2Plugin; 59 | import com.bitwig.dawproject.device.Vst3Plugin; 60 | import com.bitwig.dawproject.timeline.Audio; 61 | import com.bitwig.dawproject.timeline.AutomationTarget; 62 | import com.bitwig.dawproject.timeline.BoolPoint; 63 | import com.bitwig.dawproject.timeline.Clip; 64 | import com.bitwig.dawproject.timeline.ClipSlot; 65 | import com.bitwig.dawproject.timeline.Clips; 66 | import com.bitwig.dawproject.timeline.EnumPoint; 67 | import com.bitwig.dawproject.timeline.IntegerPoint; 68 | import com.bitwig.dawproject.timeline.Lanes; 69 | import com.bitwig.dawproject.timeline.Marker; 70 | import com.bitwig.dawproject.timeline.Markers; 71 | import com.bitwig.dawproject.timeline.MediaFile; 72 | import com.bitwig.dawproject.timeline.Note; 73 | import com.bitwig.dawproject.timeline.Notes; 74 | import com.bitwig.dawproject.timeline.Point; 75 | import com.bitwig.dawproject.timeline.Points; 76 | import com.bitwig.dawproject.timeline.RealPoint; 77 | import com.bitwig.dawproject.timeline.TimeSignaturePoint; 78 | import com.bitwig.dawproject.timeline.Timeline; 79 | import com.bitwig.dawproject.timeline.Video; 80 | import com.bitwig.dawproject.timeline.Warp; 81 | import com.bitwig.dawproject.timeline.Warps; 82 | import com.github.therapi.runtimejavadoc.ClassJavadoc; 83 | import com.github.therapi.runtimejavadoc.Comment; 84 | import com.github.therapi.runtimejavadoc.CommentFormatter; 85 | import com.github.therapi.runtimejavadoc.FieldJavadoc; 86 | import com.github.therapi.runtimejavadoc.RuntimeJavadoc; 87 | import j2html.rendering.IndentedHtml; 88 | import j2html.tags.DomContent; 89 | import j2html.tags.specialized.DivTag; 90 | import j2html.tags.specialized.HtmlTag; 91 | import j2html.tags.specialized.SpanTag; 92 | import j2html.tags.specialized.TableTag; 93 | import j2html.tags.specialized.TdTag; 94 | import j2html.tags.specialized.TrTag; 95 | import org.junit.Assert; 96 | import org.junit.Test; 97 | import org.reflections.Reflections; 98 | 99 | /** Not really a test but generates the HTML documentation. */ 100 | public class GenerateDocumentationTest { 101 | private static final CommentFormatter formatter = new CommentFormatter(); 102 | 103 | private Reflections mReflections = new Reflections("com.bitwig.dawproject"); 104 | 105 | /** 106 | * Create a HTML class summary. 107 | * 108 | * @throws IOException 109 | * Could not write the HTML file 110 | */ 111 | @Test 112 | public void createClassSummary() throws IOException { 113 | final var htmlFile = new File("Reference.html"); 114 | 115 | final var title = "DAWPROJECT XML Reference"; 116 | final var html = createDocument(title); 117 | 118 | Files.write(htmlFile.toPath(), Collections.singleton(html.render(IndentedHtml.inMemory())), 119 | StandardCharsets.UTF_8); 120 | 121 | Assert.assertTrue(htmlFile.exists()); 122 | } 123 | 124 | private HtmlTag createDocument(final String title) { 125 | final var toc = div().withClass("toc"); 126 | toc.with(b("Table of Contents")); 127 | return html(head(title(title), link().withRel("stylesheet").withHref("style.css")), body(h1(title), 128 | div(a(rawHtml("↑")).withHref("#top")).withClass("goto-toc"), toc, 129 | createClassesSummary(toc, "Root", new Class[]{Project.class, MetaData.class,}), 130 | createClassesSummary(toc, "Other", 131 | new Class[]{Application.class, FileReference.class, Transport.class,}), 132 | createClassesSummary(toc, "Mixer", new Class[]{Track.class, Channel.class, Send.class,}), 133 | createClassesSummary(toc, "Timeline", 134 | new Class[]{Arrangement.class, Scene.class, ClipSlot.class, Timeline.class, Lanes.class, 135 | Clips.class, Clip.class, Notes.class, Note.class, Audio.class, Video.class, Warps.class, 136 | Warp.class, Markers.class, Marker.class,}), 137 | createClassesSummary(toc, "Parameters", 138 | new Class[]{Parameter.class, BoolParameter.class, EnumParameter.class, IntegerParameter.class, 139 | RealParameter.class, TimeSignatureParameter.class,}), 140 | createClassesSummary(toc, "Automation", 141 | new Class[]{Points.class, AutomationTarget.class, Point.class, RealPoint.class, BoolPoint.class, 142 | EnumPoint.class, IntegerPoint.class, TimeSignaturePoint.class,}), 143 | createClassesSummary(toc, "Device", 144 | new Class[]{Device.class, AuPlugin.class, ClapPlugin.class, Plugin.class, Vst2Plugin.class, 145 | Vst3Plugin.class, BuiltinDevice.class, Compressor.class, Equalizer.class, EqBand.class, 146 | Limiter.class, NoiseGate.class,}), 147 | createClassesSummary(toc, "Abstract", 148 | new Class[]{Nameable.class, Referenceable.class, MediaFile.class,}))); 149 | } 150 | 151 | private DomContent createClassesSummary(final DivTag toc, final String label, final Class[] classes) { 152 | final var content = new ArrayList(); 153 | 154 | content.add(h2(label + " Elements")); 155 | 156 | final var tocP = p(label + " Elements"); 157 | final var tocDiv = div(); 158 | toc.with(tocP, tocDiv); 159 | 160 | for (final var cls : classes) { 161 | content.add(createClassSummary(cls)); 162 | tocDiv.with(createElementLink(cls)); 163 | } 164 | 165 | return new SpanTag().with(content).withClass("elements-block"); 166 | } 167 | 168 | private DomContent createClassSummary(final Class cls) { 169 | final var content = span().withClass("element-block"); 170 | final var elementName = getElementNameForClass(cls); 171 | content.with(h3(bracketize(elementName)).withId(elementName).withClass("element-title")); 172 | 173 | ClassJavadoc classDoc = RuntimeJavadoc.getJavadoc(cls); 174 | 175 | if (!classDoc.isEmpty()) { 176 | content.with(p(formatJavadocComment(classDoc.getComment()))); 177 | } 178 | 179 | var superClass = cls.getSuperclass(); 180 | 181 | if (superClass != Object.class) { 182 | final var p = p("Inherits from").withClass("bubble"); 183 | 184 | while (superClass != Object.class) { 185 | p.with(createElementLink(superClass)); 186 | superClass = superClass.getSuperclass(); 187 | } 188 | content.with(p); 189 | } 190 | 191 | final var subTypes = this.mReflections.getSubTypesOf(cls); 192 | 193 | if (!subTypes.isEmpty()) { 194 | final var p = p("Implementations").withClass("bubble"); 195 | 196 | subTypes.stream().sorted(Comparator.comparing(Class::getName)).forEach(c -> p.with(createElementLink(c))); 197 | 198 | content.with(p); 199 | } 200 | 201 | if (Modifier.isAbstract(cls.getModifiers())) { 202 | content.with(p("\nThis element is abstract in the DOM and cannot be used as an XML element directly.")); 203 | } 204 | 205 | final var table = table().withClass("detail-table"); 206 | 207 | content.with(table); 208 | createAttributeTable(table, cls); 209 | createElementsTable(table, cls); 210 | 211 | return content; 212 | } 213 | 214 | private static void createAttributeTable(final TableTag table, final Class cls) { 215 | final var content = new ArrayList(); 216 | 217 | content.add(tr(th("Attribute name").withStyle("text-align:center;"), th("Description"), 218 | th("Type").withStyle("text-align:center;"), th("Required").withStyle("text-align:center;"))); 219 | 220 | for (final var field : cls.getFields()) { 221 | final var fieldJavadoc = RuntimeJavadoc.getJavadoc(field); 222 | createAttributeTableRow(field, fieldJavadoc, field.getDeclaringClass() == cls).ifPresent(content::add); 223 | } 224 | 225 | if (content.size() > 1) 226 | table.with(content); 227 | } 228 | 229 | private static Optional createAttributeTableRow(final Field field, final FieldJavadoc javadoc, 230 | final boolean isDeclaredInThisClass) { 231 | for (Annotation annotation : field.getAnnotations()) { 232 | if (annotation instanceof XmlAttribute attribute) { 233 | final var comment = getComment(field, javadoc); 234 | var name = attribute.name(); 235 | if (name.startsWith("#")) 236 | name = field.getName(); 237 | 238 | var tr = tr(td(name).withStyle("text-align:center;"), td(comment), 239 | td(getType(field)).withStyle("text-align:center;"), 240 | td(attribute.required() ? "yes" : "no").withStyle("text-align:center;")); 241 | 242 | if (!isDeclaredInThisClass) 243 | tr = tr.withClass("inherited"); 244 | 245 | return Optional.of(tr); 246 | } 247 | } 248 | return Optional.empty(); 249 | } 250 | 251 | private void createElementsTable(final TableTag table, final Class cls) { 252 | final var content = new ArrayList(); 253 | 254 | content.add(tr(th("Element name").withStyle("text-align:center;"), th("Description"), 255 | th("Element Type").withStyle("text-align:center;"), th("Required").withStyle("text-align:center;"))); 256 | 257 | for (final var field : cls.getFields()) { 258 | final var fieldJavadoc = RuntimeJavadoc.getJavadoc(field); 259 | createElementTableRow(field, fieldJavadoc, field.getDeclaringClass() == cls).ifPresent(content::add); 260 | } 261 | 262 | if (content.size() > 1) 263 | table.with(content); 264 | } 265 | 266 | private static boolean isDynamicType(final Field field) { 267 | return field.getAnnotation(XmlElementRef.class) != null; 268 | } 269 | 270 | private static boolean isRequired(final Field field) { 271 | for (Annotation annotation : field.getAnnotations()) { 272 | if (annotation instanceof XmlElementWrapper e && e.required()) 273 | return true; 274 | if (annotation instanceof XmlElement e && e.required()) 275 | return true; 276 | if (annotation instanceof XmlElementRef e && e.required()) 277 | return true; 278 | } 279 | return false; 280 | } 281 | 282 | private static boolean isElement(final Field field) { 283 | for (Annotation annotation : field.getAnnotations()) { 284 | if (annotation instanceof XmlElementWrapper) 285 | return true; 286 | if (annotation instanceof XmlElement) 287 | return true; 288 | if (annotation instanceof XmlElementRef) 289 | return true; 290 | } 291 | return false; 292 | } 293 | 294 | boolean isFieldList(final Field field) { 295 | final Class type = field.getType(); 296 | return type == List.class; 297 | } 298 | 299 | Class getListGenericType(final Field field) { 300 | if (field.getGenericType() instanceof ParameterizedType pt && pt.getActualTypeArguments().length == 1) { 301 | final var listType = pt.getActualTypeArguments()[0]; 302 | if (listType instanceof Class cls) 303 | return cls; 304 | } 305 | return null; 306 | } 307 | 308 | private static DomContent formatJavadocComment(Comment comment) { 309 | return rawHtml(formatter.format(comment)); 310 | } 311 | 312 | private Optional createElementTableRow(final Field field, final FieldJavadoc javadoc, 313 | final boolean isDeclaredInThisClass) { 314 | if (!isElement(field)) 315 | return Optional.empty(); 316 | 317 | final var comment = getComment(field, javadoc); 318 | var name = getFieldName(field); 319 | 320 | final var isRequired = isRequired(field); 321 | final boolean isList = isFieldList(field); 322 | 323 | if (isDynamicType(field)) { 324 | final var elementName = td(text("from Type")); 325 | if (isList) 326 | elementName.with(br(), text("(multiple)")); 327 | TdTag typeCell = createElementLinksToSubclasses(field); 328 | 329 | var tr = tr(elementName.withStyle("text-align:center;"), td(comment), 330 | typeCell.withStyle("text-align:center;"), 331 | td(isRequired ? "yes" : "no").withStyle("text-align:center;")); 332 | if (!isDeclaredInThisClass) 333 | tr = tr.withClass("inherited"); 334 | 335 | return Optional.of(tr); 336 | } 337 | 338 | var typeCell = td(createElementLink(isList ? getListGenericType(field) : field.getType())); 339 | final var elementName = td("<" + name + ">"); 340 | if (isList) 341 | elementName.with(br(), text("(multiple)")); 342 | 343 | var tr = tr(elementName.withStyle("text-align:center;"), td(comment), typeCell.withStyle("text-align:center;"), 344 | td(isRequired ? "yes" : "no").withStyle("text-align:center;")); 345 | if (!isDeclaredInThisClass) 346 | tr = tr.withClass("inherited"); 347 | 348 | return Optional.of(tr); 349 | } 350 | 351 | private TdTag createElementLinksToSubclasses(final Field field) { 352 | final var td = td(); 353 | final boolean isList = isFieldList(field); 354 | 355 | final var isCollection = isList && field.getGenericType() instanceof final ParameterizedType pt 356 | && pt.getActualTypeArguments().length == 1; 357 | 358 | final Class type = isCollection ? getListGenericType(field) : field.getType(); 359 | this.mReflections.getSubTypesOf(type).stream().sorted(Comparator.comparing(Class::getName)).forEach(t -> { 360 | td.with(createElementLink(t)); 361 | td.with(br()); 362 | }); 363 | 364 | return td; 365 | } 366 | 367 | private static DomContent getComment(final Field field, final FieldJavadoc javadoc) { 368 | final var comment = javadoc != null ? formatJavadocComment(javadoc.getComment()) : text(""); 369 | 370 | final Class type = field.getType(); 371 | 372 | final var enumClass = type.isEnum() 373 | ? type 374 | : type.isArray() ? type.getComponentType().isEnum() ? type.getComponentType() : null : null; 375 | 376 | if (enumClass != null) { 377 | final var choices = "Possible values: " 378 | + Arrays.stream(enumClass.getFields()).map(f -> getFieldName(f)).collect(Collectors.joining(", ")); 379 | return span(comment, p(choices)); 380 | } 381 | 382 | return comment; 383 | } 384 | 385 | private static String getFieldName(final Field field) { 386 | for (Annotation annotation : field.getAnnotations()) { 387 | if (annotation instanceof XmlElementWrapper wrapper) { 388 | if (!wrapper.name().startsWith("#")) 389 | return wrapper.name(); 390 | } else if (annotation instanceof XmlElement element) { 391 | if (!element.name().startsWith("#")) 392 | return element.name(); 393 | } else if (annotation instanceof XmlElementRef element) { 394 | if (!element.name().startsWith("#")) 395 | return element.name(); 396 | } else if (annotation instanceof XmlEnumValue element) { 397 | if (!element.value().startsWith("#")) 398 | return element.value(); 399 | } 400 | } 401 | 402 | return field.getName(); 403 | } 404 | 405 | private static String getType(final Field field) { 406 | final var xmlIDREF = field.getAnnotation(XmlIDREF.class); 407 | 408 | if (xmlIDREF != null) 409 | return "ID"; 410 | 411 | final Class type = field.getType(); 412 | 413 | if (type.isEnum()) 414 | return "Enum"; 415 | 416 | if (type.isArray() && type.getComponentType().isEnum()) 417 | return "Enum,..."; 418 | 419 | return type.getSimpleName(); 420 | } 421 | 422 | private static String getElementNameForClass(Class cls) { 423 | final var rootElement = cls.getDeclaredAnnotation(XmlRootElement.class); 424 | if (rootElement instanceof XmlRootElement re && !re.name().startsWith("#")) 425 | return re.name(); 426 | return cls.getSimpleName(); 427 | } 428 | 429 | private static DomContent createElementLink(Class cls) { 430 | if (cls == String.class) 431 | return text("text"); 432 | 433 | if (cls.isPrimitive()) 434 | return text(cls.getSimpleName()); 435 | 436 | final var name = getElementNameForClass(cls); 437 | return a("<" + name + ">").withHref("#" + name).withClass("element-link"); 438 | } 439 | 440 | private static String bracketize(final String s) { 441 | return "<" + s + ">"; 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/test/java/com/bitwig/dawproject/LoadDawProjectTest.java: -------------------------------------------------------------------------------- 1 | package com.bitwig.dawproject; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.FileVisitResult; 6 | import java.nio.file.FileVisitor; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.attribute.BasicFileAttributes; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | import org.junit.Assert; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | import org.junit.runners.Parameterized; 18 | 19 | /** Parameterized class to test loading a metadata and project files. */ 20 | @RunWith(Parameterized.class) 21 | public class LoadDawProjectTest { 22 | private final File mFile; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param file 28 | * The file to load 29 | * @param name 30 | * Unused 31 | */ 32 | public LoadDawProjectTest(final File file, @SuppressWarnings("unused") final Object name) { 33 | this.mFile = file; 34 | } 35 | 36 | /** 37 | * Get all files. 38 | * 39 | * @return The files 40 | * @throws IOException 41 | * Could not load the files 42 | */ 43 | @Parameterized.Parameters(name = "{1}") 44 | public static Collection getFiles() throws IOException { 45 | final List result = new ArrayList<>(); 46 | 47 | final File testDataDir = new File("src/test-data"); 48 | 49 | if (testDataDir.exists() && testDataDir.isDirectory()) { 50 | final Path rootPath = testDataDir.toPath(); 51 | Files.walkFileTree(rootPath, new FileVisitor<>() { 52 | @Override 53 | public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) { 54 | return FileVisitResult.CONTINUE; 55 | } 56 | 57 | @Override 58 | public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) { 59 | final File file = path.toFile(); 60 | 61 | if (file.getAbsolutePath().toLowerCase().endsWith("." + DawProject.FILE_EXTENSION)) { 62 | final Object[] args = {file, rootPath.relativize(path).toString()}; 63 | result.add(args); 64 | } 65 | return FileVisitResult.CONTINUE; 66 | } 67 | 68 | @Override 69 | public FileVisitResult visitFileFailed(final Path file, final IOException exc) { 70 | return FileVisitResult.CONTINUE; 71 | } 72 | 73 | @Override 74 | public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) { 75 | return FileVisitResult.CONTINUE; 76 | } 77 | }); 78 | } 79 | 80 | return result; 81 | } 82 | 83 | /** 84 | * Load the project. 85 | * 86 | * @throws IOException 87 | * Could not load the project 88 | */ 89 | @Test 90 | public void loadProject() throws IOException { 91 | final Project project = DawProject.loadProject(this.mFile); 92 | Assert.assertNotNull(project); 93 | } 94 | 95 | /** 96 | * Load the metadata. 97 | * 98 | * @throws IOException 99 | * Could not load the metadata 100 | */ 101 | @Test 102 | public void loadMetadata() throws IOException { 103 | final MetaData metadata = DawProject.loadMetadata(this.mFile); 104 | Assert.assertNotNull(metadata); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,200;8..144,300;8..144,400&display=swap'); 2 | 3 | body { 4 | margin: 24px; 5 | font-family: "Roboto Flex", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 6 | font-size: 1rem; 7 | font-weight: 400; 8 | line-height: 1.25; 9 | color: #212529; 10 | text-align: left; 11 | background-color: #fff; 12 | margin-bottom: 80px; 13 | } 14 | 15 | th, td { 16 | padding: 8px; 17 | padding-inline: 12px; 18 | line-height: 1.25; 19 | } 20 | 21 | table { 22 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 23 | margin-top: 16px; 24 | margin-bottom: 16px; 25 | border-collapse:collapse; 26 | } 27 | 28 | th { 29 | background-color: #999; 30 | color: #fff; 31 | font-weight: 500; 32 | font-size: 0.8em; 33 | text-align: left; 34 | } 35 | 36 | h1 { 37 | font-weight: 100; 38 | font-size: 2.6em; 39 | } 40 | 41 | h2 { 42 | font-synthesis: none; 43 | color: #777; 44 | font-weight: 300; 45 | font-size: 2.1em; 46 | font-style: italic; 47 | padding-top: 16px; 48 | border-top: 1px solid #cccccc; 49 | } 50 | 51 | .element-title { 52 | font-weight: 200; 53 | font-size: 1.8em; 54 | letter-spacing: 2px; 55 | } 56 | 57 | tbody tr { 58 | border-bottom: 1px solid #dddddd; 59 | } 60 | 61 | tbody tr:nth-of-type(even) { 62 | background-color: #f3f3f3; 63 | } 64 | 65 | .detail-table { 66 | width: 100%; 67 | } 68 | 69 | .inherited { 70 | font-style: italic; 71 | font-weight: 250; 72 | color: #555; 73 | } 74 | 75 | .element-link { 76 | color: #FF5A00; 77 | font-size: 1em; 78 | font-weight: 300; 79 | margin-inline-start: 8px; 80 | text-decoration: none; 81 | letter-spacing: 1px; 82 | transition: color 0.25s; 83 | margin-bottom: 8px; 84 | } 85 | 86 | .element-link:hover { 87 | color: #2E9CE6; 88 | } 89 | 90 | .bubble { 91 | /*outline: 2px solid #bbb; 92 | border-radius: 8px; 93 | padding: 4px 12px 4px 12px;*/ 94 | } 95 | 96 | .toc { 97 | /*outline: 2px solid #bbb;*/ 98 | border-radius: 8px; 99 | padding: 20px; 100 | background-color: #eee; 101 | } 102 | 103 | .goto-toc { 104 | border-radius: 8px; 105 | padding: 4px 12px 4px 12px; 106 | background-color: #eee; 107 | position:fixed; 108 | bottom: 24px; 109 | right: 24px 110 | } 111 | -------------------------------------------------------------------------------- /test-data/white-glasses.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwig/dawproject/f016fb017292eb481d9afbfbafc0afa49ac6b89a/test-data/white-glasses.wav --------------------------------------------------------------------------------